|
|
@@ -22,6 +22,7 @@ vi.mock("@/models/user", () => {
|
|
|
default: {
|
|
|
findById: vi.fn(),
|
|
|
findOne: vi.fn(),
|
|
|
+ findByIdAndDelete: vi.fn(),
|
|
|
},
|
|
|
USER_ROLES,
|
|
|
};
|
|
|
@@ -31,7 +32,7 @@ import { getSession } from "@/lib/auth/session";
|
|
|
import { getDb } from "@/lib/db";
|
|
|
import User from "@/models/user";
|
|
|
|
|
|
-import { PATCH, dynamic } from "./route.js";
|
|
|
+import { PATCH, DELETE, dynamic } from "./route.js";
|
|
|
|
|
|
function createRequestStub(body) {
|
|
|
return {
|
|
|
@@ -257,7 +258,6 @@ describe("PATCH /api/admin/users/[userId]", () => {
|
|
|
exec: vi.fn().mockResolvedValue(user),
|
|
|
});
|
|
|
|
|
|
- // No uniqueness checks needed here (we only change role + mustChangePassword)
|
|
|
const res = await PATCH(
|
|
|
createRequestStub({
|
|
|
role: "admin",
|
|
|
@@ -270,7 +270,6 @@ describe("PATCH /api/admin/users/[userId]", () => {
|
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
|
- // Role changed => branchId must be cleared
|
|
|
expect(user.role).toBe("admin");
|
|
|
expect(user.branchId).toBe(null);
|
|
|
expect(user.mustChangePassword).toBe(false);
|
|
|
@@ -335,3 +334,175 @@ describe("PATCH /api/admin/users/[userId]", () => {
|
|
|
});
|
|
|
});
|
|
|
});
|
|
|
+
|
|
|
+describe("DELETE /api/admin/users/[userId]", () => {
|
|
|
+ beforeEach(() => {
|
|
|
+ vi.clearAllMocks();
|
|
|
+ getDb.mockResolvedValue({});
|
|
|
+ });
|
|
|
+
|
|
|
+ it("returns 401 when unauthenticated", async () => {
|
|
|
+ getSession.mockResolvedValue(null);
|
|
|
+
|
|
|
+ const res = await DELETE(
|
|
|
+ new Request("http://localhost/api/admin/users/x"),
|
|
|
+ {
|
|
|
+ params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(res.status).toBe(401);
|
|
|
+ expect(await res.json()).toEqual({
|
|
|
+ error: { message: "Unauthorized", code: "AUTH_UNAUTHENTICATED" },
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it("returns 403 when authenticated but not allowed (admin)", async () => {
|
|
|
+ getSession.mockResolvedValue({
|
|
|
+ userId: "u1",
|
|
|
+ role: "admin",
|
|
|
+ branchId: null,
|
|
|
+ email: "admin@example.com",
|
|
|
+ });
|
|
|
+
|
|
|
+ const res = await DELETE(
|
|
|
+ new Request("http://localhost/api/admin/users/x"),
|
|
|
+ {
|
|
|
+ params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(res.status).toBe(403);
|
|
|
+ expect(await res.json()).toEqual({
|
|
|
+ error: {
|
|
|
+ message: "Forbidden",
|
|
|
+ code: "AUTH_FORBIDDEN_USER_MANAGEMENT",
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(User.findByIdAndDelete).not.toHaveBeenCalled();
|
|
|
+ });
|
|
|
+
|
|
|
+ it("returns 400 for invalid userId", async () => {
|
|
|
+ getSession.mockResolvedValue({
|
|
|
+ userId: "u2",
|
|
|
+ role: "dev",
|
|
|
+ branchId: null,
|
|
|
+ email: "dev@example.com",
|
|
|
+ });
|
|
|
+
|
|
|
+ const res = await DELETE(
|
|
|
+ new Request("http://localhost/api/admin/users/x"),
|
|
|
+ {
|
|
|
+ params: Promise.resolve({ userId: "nope" }),
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(res.status).toBe(400);
|
|
|
+ expect(await res.json()).toMatchObject({
|
|
|
+ error: { code: "VALIDATION_INVALID_FIELD" },
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it("returns 400 when trying to delete the current user (self delete)", async () => {
|
|
|
+ getSession.mockResolvedValue({
|
|
|
+ userId: "507f1f77bcf86cd799439011",
|
|
|
+ role: "superadmin",
|
|
|
+ branchId: null,
|
|
|
+ email: "superadmin@example.com",
|
|
|
+ });
|
|
|
+
|
|
|
+ const res = await DELETE(
|
|
|
+ new Request("http://localhost/api/admin/users/x"),
|
|
|
+ {
|
|
|
+ params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(res.status).toBe(400);
|
|
|
+ expect(await res.json()).toEqual({
|
|
|
+ error: {
|
|
|
+ message: "Cannot delete current user",
|
|
|
+ code: "VALIDATION_INVALID_FIELD",
|
|
|
+ details: { field: "userId", reason: "SELF_DELETE_FORBIDDEN" },
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(User.findByIdAndDelete).not.toHaveBeenCalled();
|
|
|
+ });
|
|
|
+
|
|
|
+ it("returns 404 when user does not exist", async () => {
|
|
|
+ getSession.mockResolvedValue({
|
|
|
+ userId: "u2",
|
|
|
+ role: "dev",
|
|
|
+ branchId: null,
|
|
|
+ email: "dev@example.com",
|
|
|
+ });
|
|
|
+
|
|
|
+ User.findByIdAndDelete.mockReturnValue({
|
|
|
+ select: vi.fn().mockReturnThis(),
|
|
|
+ exec: vi.fn().mockResolvedValue(null),
|
|
|
+ });
|
|
|
+
|
|
|
+ const res = await DELETE(
|
|
|
+ new Request("http://localhost/api/admin/users/x"),
|
|
|
+ {
|
|
|
+ params: Promise.resolve({ userId: "507f1f77bcf86cd799439099" }),
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(res.status).toBe(404);
|
|
|
+ expect(await res.json()).toEqual({
|
|
|
+ error: {
|
|
|
+ message: "Not found",
|
|
|
+ code: "USER_NOT_FOUND",
|
|
|
+ details: { userId: "507f1f77bcf86cd799439099" },
|
|
|
+ },
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it("returns 200 and deleted user payload on success", async () => {
|
|
|
+ getSession.mockResolvedValue({
|
|
|
+ userId: "u2",
|
|
|
+ role: "superadmin",
|
|
|
+ branchId: null,
|
|
|
+ email: "superadmin@example.com",
|
|
|
+ });
|
|
|
+
|
|
|
+ User.findByIdAndDelete.mockReturnValue({
|
|
|
+ select: vi.fn().mockReturnThis(),
|
|
|
+ exec: vi.fn().mockResolvedValue({
|
|
|
+ _id: "507f1f77bcf86cd799439099",
|
|
|
+ username: "todelete",
|
|
|
+ email: "todelete@example.com",
|
|
|
+ role: "branch",
|
|
|
+ branchId: "NL01",
|
|
|
+ mustChangePassword: true,
|
|
|
+ createdAt: new Date("2026-02-01T10:00:00.000Z"),
|
|
|
+ updatedAt: new Date("2026-02-02T10:00:00.000Z"),
|
|
|
+ }),
|
|
|
+ });
|
|
|
+
|
|
|
+ const res = await DELETE(
|
|
|
+ new Request("http://localhost/api/admin/users/x"),
|
|
|
+ {
|
|
|
+ params: Promise.resolve({ userId: "507f1f77bcf86cd799439099" }),
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(res.status).toBe(200);
|
|
|
+
|
|
|
+ const body = await res.json();
|
|
|
+ expect(body).toMatchObject({
|
|
|
+ ok: true,
|
|
|
+ user: {
|
|
|
+ id: "507f1f77bcf86cd799439099",
|
|
|
+ username: "todelete",
|
|
|
+ email: "todelete@example.com",
|
|
|
+ role: "branch",
|
|
|
+ branchId: "NL01",
|
|
|
+ mustChangePassword: true,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|