add isAdmin to secure specific endpoints
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, type Mocked } from "vitest";
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { isAdmin } from "../../../src/rest/middleware/isAdmin";
|
||||
import { createMockUserService } from "../../helpers/testSetup";
|
||||
import { UserService } from "../../../src/db/services/db/UserService";
|
||||
import { notFound } from "../../../src/rest/utils/responses";
|
||||
|
||||
vi.mock("../../../src/db/services/db/UserService", () => ({
|
||||
UserService: {
|
||||
create: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/rest/utils/responses", () => ({
|
||||
notFound: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("isAdmin middleware", () => {
|
||||
let mockedUserService: any;
|
||||
let req: Partial<Request>;
|
||||
let res: Mocked<Response>;
|
||||
let next: Mocked<NextFunction>;
|
||||
|
||||
const uuid = "abcd-coffe";
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockedUserService = createMockUserService();
|
||||
vi.mocked(UserService.create).mockResolvedValue(mockedUserService);
|
||||
|
||||
req = {
|
||||
payload: { uuid, username: "username", id: ""}
|
||||
};
|
||||
|
||||
res = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
send: vi.fn().mockReturnThis(),
|
||||
} as unknown as Mocked<Response>;
|
||||
|
||||
next = vi.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("Success Scenarios", () => {
|
||||
it("should call next() if user is an admin", async () => {
|
||||
const mockUser = { uuid, config: { isAdmin: true } };
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
|
||||
await isAdmin(req as Request, res, next);
|
||||
|
||||
expect(UserService.create).toHaveBeenCalledOnce();
|
||||
expect(mockedUserService.getUserByUUID).toHaveBeenCalledWith(uuid);
|
||||
expect(next).toHaveBeenCalledOnce();
|
||||
expect(res.status).not.toHaveBeenCalled();
|
||||
expect(res.send).not.toHaveBeenCalled();
|
||||
expect(notFound).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Failure Scenarios", () => {
|
||||
it("should call notFound if user is not an admin", async () => {
|
||||
const mockUser = { uuid, config: { isAdmin: false } };
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
|
||||
await isAdmin(req as Request, res, next);
|
||||
|
||||
expect(mockedUserService.getUserByUUID).toHaveBeenCalledWith(uuid);
|
||||
expect(notFound).toHaveBeenCalledWith(res);
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call notFound if user does not exist", async () => {
|
||||
mockedUserService.getUserByUUID.mockResolvedValue(null);
|
||||
|
||||
await isAdmin(req as Request, res, next);
|
||||
|
||||
expect(mockedUserService.getUserByUUID).toHaveBeenCalledWith(uuid);
|
||||
expect(notFound).toHaveBeenCalledWith(res);
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle errors during user fetching", async () => {
|
||||
const dbError = new Error("Database error");
|
||||
mockedUserService.getUserByUUID.mockRejectedValue(dbError);
|
||||
|
||||
await isAdmin(req as Request, res, next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith(expect.objectContaining({ message: 'Database error' }));
|
||||
});
|
||||
});
|
||||
});
|
||||
+107
-60
@@ -1,8 +1,8 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import {describe, it, expect, vi, beforeEach, afterEach} from "vitest";
|
||||
import request from "supertest";
|
||||
|
||||
import { RestUser } from "../../src/rest/restUser";
|
||||
import { setupTestEnvironment, type TestEnvironment } from "../helpers/testSetup";
|
||||
import {RestUser} from "../../src/rest/restUser";
|
||||
import {setupTestEnvironment, type TestEnvironment} from "../helpers/testSetup";
|
||||
|
||||
vi.mock("../../src/db/services/db/UserService", () => ({
|
||||
UserService: {
|
||||
@@ -21,6 +21,10 @@ vi.mock("../../src/utils/passwordUtils", () => ({
|
||||
describe("RestUser", () => {
|
||||
let testEnv: TestEnvironment;
|
||||
|
||||
const requestingUserUUID = "test-user-uuid";
|
||||
const adminUser = { uuid: requestingUserUUID, config: { isAdmin: true } };
|
||||
const nonAdminUser = { uuid: requestingUserUUID, config: { isAdmin: false } };
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -32,35 +36,6 @@ describe("RestUser", () => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
|
||||
describe("GET /", () => {
|
||||
it("should return all users", async () => {
|
||||
const mockUsers = [
|
||||
{id: "1", name: "user1", uuid: "uuid1"},
|
||||
{id: "2", name: "user2", uuid: "uuid2"}
|
||||
];
|
||||
|
||||
testEnv.mockUserService.getAllUsers.mockResolvedValue(mockUsers);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.get("/user/")
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data.users).toEqual(mockUsers);
|
||||
expect(testEnv.mockUserService.getAllUsers).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle empty user list", async () => {
|
||||
testEnv. mockUserService.getAllUsers.mockResolvedValue([]);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.get("/user/")
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data.users).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /me", () => {
|
||||
it("should return current user", async () => {
|
||||
const mockUser = {
|
||||
@@ -118,7 +93,7 @@ describe("RestUser", () => {
|
||||
});
|
||||
|
||||
it("should return bad request when user not found", async () => {
|
||||
testEnv. mockUserService.getUserByUUID.mockResolvedValue(null);
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(null);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.put("/user/me/spotify")
|
||||
@@ -240,7 +215,7 @@ describe("RestUser", () => {
|
||||
password: "old-hashed-password"
|
||||
};
|
||||
|
||||
testEnv. mockUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(mockUser);
|
||||
vi.mocked(PasswordUtils.validatePassword).mockReturnValue({valid: true});
|
||||
vi.mocked(PasswordUtils.hashPassword).mockResolvedValue("new-hashed-password");
|
||||
testEnv.mockUserService.updateUser.mockResolvedValue(mockUser);
|
||||
@@ -352,40 +327,112 @@ describe("RestUser", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /:id", () => {
|
||||
it("should return user by id", async () => {
|
||||
const mockUser = {
|
||||
id: "specific-user-id",
|
||||
name: "specificuser",
|
||||
uuid: "specific-uuid"
|
||||
};
|
||||
describe("GET / (Admin only)", () => {
|
||||
|
||||
testEnv.mockUserService.getUserById.mockResolvedValue(mockUser);
|
||||
describe("when user is an admin", () => {
|
||||
beforeEach(() => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(adminUser);
|
||||
});
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.get("/user/specific-user-id")
|
||||
.expect(200);
|
||||
it("should return all users", async () => {
|
||||
const mockUsers = [
|
||||
{id: "1", name: "user1", uuid: "uuid1"},
|
||||
{id: "2", name: "user2", uuid: "uuid2"}
|
||||
];
|
||||
testEnv.mockUserService.getAllUsers.mockResolvedValue(mockUsers);
|
||||
|
||||
expect(response.body.data).toEqual(mockUser);
|
||||
expect(testEnv.mockUserService.getUserById).toHaveBeenCalledWith("specific-user-id");
|
||||
const response = await request(testEnv.app)
|
||||
.get("/user/")
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data.users).toEqual(mockUsers);
|
||||
expect(testEnv.mockUserService.getUserByUUID).toHaveBeenCalledWith(requestingUserUUID);
|
||||
expect(testEnv.mockUserService.getAllUsers).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle empty user list", async () => {
|
||||
testEnv.mockUserService.getAllUsers.mockResolvedValue([]);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.get("/user/")
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data.users).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
it("should return bad request when user not found", async () => {
|
||||
testEnv.mockUserService.getUserById.mockResolvedValue(null);
|
||||
describe("when user is not an admin", () => {
|
||||
it("should return 404 Not Found if user is not an admin", async () => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(nonAdminUser);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.get("/user/nonexistent-id")
|
||||
.expect(400);
|
||||
await request(testEnv.app)
|
||||
.get("/user/")
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
expect(response.body.data.message).toBe("Unable to find matching document with id: nonexistent-id");
|
||||
});
|
||||
it("should return 404 Not Found if user does not exist", async () => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(null);
|
||||
|
||||
it("should return all users when id is empty", async () => {
|
||||
const response = await request(testEnv.app)
|
||||
.get("/user/")
|
||||
.expect(200);
|
||||
|
||||
expect(testEnv.mockUserService.getAllUsers).toHaveBeenCalled();
|
||||
await request(testEnv.app)
|
||||
.get("/user/")
|
||||
.expect(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /:id (Admin only)", () => {
|
||||
const specificUserId = "specific-user-id";
|
||||
const mockUser = {
|
||||
id: specificUserId,
|
||||
name: "specificuser",
|
||||
uuid: "specific-uuid"
|
||||
};
|
||||
|
||||
describe("when user is an admin", () => {
|
||||
beforeEach(() => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(adminUser);
|
||||
});
|
||||
|
||||
it("should return user by id", async () => {
|
||||
testEnv.mockUserService.getUserById.mockResolvedValue(mockUser);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.get(`/user/${specificUserId}`)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data).toEqual(mockUser);
|
||||
expect(testEnv.mockUserService.getUserByUUID).toHaveBeenCalledWith(requestingUserUUID);
|
||||
expect(testEnv.mockUserService.getUserById).toHaveBeenCalledWith(specificUserId);
|
||||
});
|
||||
|
||||
it("should return bad request when target user is not found", async () => {
|
||||
testEnv.mockUserService.getUserById.mockResolvedValue(null);
|
||||
|
||||
const response = await request(testEnv.app)
|
||||
.get(`/user/nonexistent-id`)
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.data.message).toBe("Unable to find matching document with id: nonexistent-id");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when user is not an admin", () => {
|
||||
it("should return 404 Not Found if user is not an admin", async () => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(nonAdminUser);
|
||||
|
||||
await request(testEnv.app)
|
||||
.get(`/user/${specificUserId}`)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it("should return 404 Not Found if user does not exist", async () => {
|
||||
testEnv.mockUserService.getUserByUUID.mockResolvedValue(null);
|
||||
|
||||
await request(testEnv.app)
|
||||
.get(`/user/${specificUserId}`)
|
||||
.expect(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user