Skip to content

Commit

Permalink
Merge branch 'main' of github.com:theopensystemslab/planx-new into je…
Browse files Browse the repository at this point in the history
…ss/map-n-label-back-tab
  • Loading branch information
jessicamcinchak committed Oct 1, 2024
2 parents 13efcf3 + 1c3e77f commit c77835d
Show file tree
Hide file tree
Showing 64 changed files with 9,972 additions and 15,196 deletions.
63 changes: 0 additions & 63 deletions api.planx.uk/modules/user/controller.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,9 @@
import { z } from "zod";
import type { ValidatedRequestHandler } from "../../shared/middleware/validate.js";
import { getClient } from "../../client/index.js";
import { ServerError } from "../../errors/index.js";
import type { RequestHandler } from "express";
import type { User } from "@opensystemslab/planx-core/types";
import { userContext } from "../auth/middleware.js";

interface UserResponse {
message: string;
}

export const createUserSchema = z.object({
body: z.object({
firstName: z.string(),
lastName: z.string(),
email: z.string().email(),
isPlatformAdmin: z.boolean().optional().default(false),
}),
});

export type CreateUser = ValidatedRequestHandler<
typeof createUserSchema,
UserResponse
>;

export const createUser: CreateUser = async (_req, res, next) => {
try {
const newUser = res.locals.parsedReq.body;
const $client = getClient();
await $client.user.create(newUser);
return res.send({ message: "Successfully created user" });
} catch (error) {
return next(
new ServerError({ message: "Failed to create user", cause: error }),
);
}
};

export const deleteUserSchema = z.object({
params: z.object({
email: z.string().trim().email().toLowerCase(),
}),
});

export type DeleteUser = ValidatedRequestHandler<
typeof deleteUserSchema,
UserResponse
>;

export const deleteUser: DeleteUser = async (_req, res, next) => {
try {
const { email } = res.locals.parsedReq.params;
const $client = getClient();

const user = await $client.user.getByEmail(email);
if (!user) throw Error(`No user matching email ${email} found`);

const isSuccessful = await $client.user.delete(user.id);
if (!isSuccessful) throw Error("Request to delete user failed");

return res.send({ message: "Successfully deleted user" });
} catch (error) {
return next(
new ServerError({ message: "Failed to delete user", cause: error }),
);
}
};

export const getLoggedInUserDetails: RequestHandler<
Record<string, never>,
User & { jwt: string | undefined }
Expand Down
54 changes: 0 additions & 54 deletions api.planx.uk/modules/user/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,7 @@ info:
tags:
name: user
description: User management
components:
schemas:
CreateUserSchema:
type: object
properties:
firstName:
type: string
example: Bilbo
lastName:
type: string
example: Baggins
email:
type: email
example: [email protected]
isPlatformAdmin:
type: bool
example: false
paths:
/user:
put:
summary: Create a new user
description: "Requires authentication via a Cloudflare WARP client
\n\n
Please login at [https://api.editor.planx.uk/user](https://api.editor.planx.uk/user)"
tags: ["user"]
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUserSchema"
responses:
"200":
$ref: "#/components/responses/SuccessMessage"
"500":
$ref: "#/components/responses/ErrorMessage"
/user/{email}:
delete:
summary: Delete a user
description: "Requires authentication via a Cloudflare WARP client
\n\n
Please login at [https://api.editor.planx.uk/user](https://api.editor.planx.uk/user)"
tags: ["user"]
parameters:
- in: path
name: email
type: string
format: email
example: [email protected]
description: Email address of the user to be deleted
responses:
"200":
$ref: "#/components/responses/SuccessMessage"
"500":
$ref: "#/components/responses/ErrorMessage"
/user/me:
get:
summary: Get information about currently logged in user
Expand Down
116 changes: 0 additions & 116 deletions api.planx.uk/modules/user/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,6 @@ import { userContext } from "../auth/middleware.js";

const getStoreMock = vi.spyOn(userContext, "getStore");

const mockUser = {
firstName: "Bilbo",
lastName: "Baggins",
email: "[email protected]",
isPlatformAdmin: false,
};

const mockCreateUser = vi.fn();
const mockDeleteUser = vi.fn();
const mockGetByEmail = vi.fn().mockResolvedValue(mockUser);
const mockGetById = vi.fn().mockResolvedValue({
id: 123,
firstName: "Albert",
Expand All @@ -37,118 +27,12 @@ vi.mock("@opensystemslab/planx-core", () => {
return {
CoreDomainClient: vi.fn().mockImplementation(() => ({
user: {
create: () => mockCreateUser(),
delete: () => mockDeleteUser(),
getByEmail: () => mockGetByEmail(),
getById: () => mockGetById(),
},
})),
};
});

const auth = authHeader({ role: "platformAdmin" });

describe("Create user endpoint", () => {
it("requires authentication", async () => {
await supertest(app).put("/user").send(mockUser).expect(401);
});

it("requires the 'platformAdmin' role", async () => {
await supertest(app)
.put("/user")
.set(authHeader({ role: "teamEditor" }))
.send(mockUser)
.expect(403);
});

it("handles Hasura / DB errors", async () => {
mockCreateUser.mockRejectedValue(new Error("Something went wrong"));

await supertest(app)
.put("/user")
.set(auth)
.send(mockUser)
.expect(500)
.then((res) => {
expect(mockCreateUser).toHaveBeenCalled();
expect(res.body).toHaveProperty("error");
expect(res.body.error).toMatch(/Failed to create user/);
});
});

it("can successfully create a user", async () => {
mockCreateUser.mockResolvedValue(123);

await supertest(app)
.put("/user")
.set(auth)
.send(mockUser)
.expect(200)
.then((res) => {
expect(mockCreateUser).toHaveBeenCalled();
expect(res.body).toHaveProperty("message");
expect(res.body.message).toMatch(/Successfully created user/);
});
});
});

describe("Delete user endpoint", () => {
it("requires authentication", async () => {
await supertest(app).delete("/user/[email protected]").expect(401);
});

it("requires the 'platformAdmin' role", async () => {
await supertest(app)
.delete("/user/[email protected]")
.set(authHeader({ role: "teamEditor" }))
.expect(403);
});

it("handles an invalid email", async () => {
mockGetByEmail.mockResolvedValueOnce(null);

await supertest(app)
.delete("/user/[email protected]")
.set(auth)
.expect(500)
.then((res) => {
expect(mockGetByEmail).toHaveBeenCalled();
expect(res.body).toHaveProperty("error");
expect(res.body.error).toMatch(/Failed to delete user/);
});
});

it("handles a failure to delete the user", async () => {
mockDeleteUser.mockResolvedValueOnce(false);

await supertest(app)
.delete("/user/[email protected]")
.set(auth)
.expect(500)
.then((res) => {
expect(mockGetByEmail).toHaveBeenCalled();
expect(mockDeleteUser).toHaveBeenCalled();
expect(res.body).toHaveProperty("error");
expect(res.body.error).toMatch(/Failed to delete user/);
});
});

it("can successfully delete a user", async () => {
mockDeleteUser.mockResolvedValue(true);

await supertest(app)
.delete("/user/[email protected]")
.set(auth)
.expect(200)
.then((res) => {
expect(mockGetByEmail).toHaveBeenCalled();
expect(mockDeleteUser).toHaveBeenCalled();
expect(res.body).toHaveProperty("message");
expect(res.body.message).toMatch(/Successfully deleted user/);
});
});
});

describe("/me endpoint", () => {
beforeEach(() => {
getStoreMock.mockReturnValue({
Expand Down
23 changes: 2 additions & 21 deletions api.planx.uk/modules/user/routes.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,9 @@
import { Router } from "express";
import { useLoginAuth, usePlatformAdminAuth } from "../auth/middleware.js";
import { validate } from "../../shared/middleware/validate.js";
import {
createUserSchema,
createUser,
deleteUserSchema,
deleteUser,
getLoggedInUserDetails,
} from "./controller.js";
import { useLoginAuth } from "../auth/middleware.js";
import { getLoggedInUserDetails } from "./controller.js";

const router = Router();

router.put(
"/user",
usePlatformAdminAuth,
validate(createUserSchema),
createUser,
);
router.delete(
"/user/:email",
usePlatformAdminAuth,
validate(deleteUserSchema),
deleteUser,
);
router.get("/user/me", useLoginAuth, getLoggedInUserDetails);

export default router;
2 changes: 1 addition & 1 deletion api.planx.uk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"dependencies": {
"@airbrake/node": "^2.1.8",
"@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#c4a725f",
"@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#79b6409",
"@types/isomorphic-fetch": "^0.0.36",
"adm-zip": "^0.5.10",
"aws-sdk": "^2.1467.0",
Expand Down
Loading

0 comments on commit c77835d

Please sign in to comment.