|
|
@@ -28,11 +28,25 @@ vi.mock("@/models/user", () => {
|
|
|
};
|
|
|
});
|
|
|
|
|
|
+vi.mock("bcryptjs", () => {
|
|
|
+ const hash = vi.fn();
|
|
|
+ return {
|
|
|
+ default: { hash },
|
|
|
+ hash,
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
+vi.mock("@/lib/auth/adminTempPassword", () => ({
|
|
|
+ generateAdminTemporaryPassword: vi.fn(),
|
|
|
+}));
|
|
|
+
|
|
|
import { getSession } from "@/lib/auth/session";
|
|
|
import { getDb } from "@/lib/db";
|
|
|
import User from "@/models/user";
|
|
|
+import { hash as bcryptHash } from "bcryptjs";
|
|
|
+import { generateAdminTemporaryPassword } from "@/lib/auth/adminTempPassword";
|
|
|
|
|
|
-import { PATCH, DELETE, dynamic } from "./route.js";
|
|
|
+import { PATCH, POST, DELETE, dynamic } from "./route.js";
|
|
|
|
|
|
function createRequestStub(body) {
|
|
|
return {
|
|
|
@@ -335,6 +349,170 @@ describe("PATCH /api/admin/users/[userId]", () => {
|
|
|
});
|
|
|
});
|
|
|
|
|
|
+describe("POST /api/admin/users/[userId]", () => {
|
|
|
+ beforeEach(() => {
|
|
|
+ vi.clearAllMocks();
|
|
|
+ getDb.mockResolvedValue({});
|
|
|
+ });
|
|
|
+
|
|
|
+ it("returns 401 when unauthenticated", async () => {
|
|
|
+ getSession.mockResolvedValue(null);
|
|
|
+
|
|
|
+ const res = await POST(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 POST(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",
|
|
|
+ },
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it("returns 400 for invalid userId", async () => {
|
|
|
+ getSession.mockResolvedValue({
|
|
|
+ userId: "u2",
|
|
|
+ role: "dev",
|
|
|
+ branchId: null,
|
|
|
+ email: "dev@example.com",
|
|
|
+ });
|
|
|
+
|
|
|
+ const res = await POST(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 reset the current user password", async () => {
|
|
|
+ getSession.mockResolvedValue({
|
|
|
+ userId: "507f1f77bcf86cd799439011",
|
|
|
+ role: "superadmin",
|
|
|
+ branchId: null,
|
|
|
+ email: "superadmin@example.com",
|
|
|
+ });
|
|
|
+
|
|
|
+ const res = await POST(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 reset current user password",
|
|
|
+ code: "VALIDATION_INVALID_FIELD",
|
|
|
+ details: { field: "userId", reason: "SELF_PASSWORD_RESET_FORBIDDEN" },
|
|
|
+ },
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it("returns 404 when user does not exist", async () => {
|
|
|
+ getSession.mockResolvedValue({
|
|
|
+ userId: "u2",
|
|
|
+ role: "dev",
|
|
|
+ branchId: null,
|
|
|
+ email: "dev@example.com",
|
|
|
+ });
|
|
|
+
|
|
|
+ User.findById.mockReturnValue({
|
|
|
+ exec: vi.fn().mockResolvedValue(null),
|
|
|
+ });
|
|
|
+
|
|
|
+ const res = await POST(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 resets password with mustChangePassword=true", async () => {
|
|
|
+ getSession.mockResolvedValue({
|
|
|
+ userId: "u2",
|
|
|
+ role: "superadmin",
|
|
|
+ branchId: null,
|
|
|
+ email: "superadmin@example.com",
|
|
|
+ });
|
|
|
+
|
|
|
+ const user = {
|
|
|
+ _id: "507f1f77bcf86cd799439099",
|
|
|
+ username: "branch2",
|
|
|
+ email: "branch2@example.com",
|
|
|
+ role: "branch",
|
|
|
+ branchId: "NL02",
|
|
|
+ passwordHash: "old-hash",
|
|
|
+ mustChangePassword: false,
|
|
|
+ passwordResetToken: "token",
|
|
|
+ passwordResetExpiresAt: new Date("2030-01-01"),
|
|
|
+ createdAt: new Date("2026-02-01T10:00:00.000Z"),
|
|
|
+ updatedAt: new Date("2026-02-02T10:00:00.000Z"),
|
|
|
+ save: vi.fn().mockResolvedValue(true),
|
|
|
+ };
|
|
|
+
|
|
|
+ User.findById.mockReturnValue({
|
|
|
+ exec: vi.fn().mockResolvedValue(user),
|
|
|
+ });
|
|
|
+
|
|
|
+ generateAdminTemporaryPassword.mockReturnValue("TempPass123!");
|
|
|
+ bcryptHash.mockResolvedValue("hashed-temp");
|
|
|
+
|
|
|
+ const res = await POST(new Request("http://localhost/api/admin/users/x"), {
|
|
|
+ params: Promise.resolve({ userId: "507f1f77bcf86cd799439099" }),
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(res.status).toBe(200);
|
|
|
+ expect(generateAdminTemporaryPassword).toHaveBeenCalledTimes(1);
|
|
|
+ expect(bcryptHash).toHaveBeenCalledWith("TempPass123!", 12);
|
|
|
+ expect(user.passwordHash).toBe("hashed-temp");
|
|
|
+ expect(user.mustChangePassword).toBe(true);
|
|
|
+ expect(user.passwordResetToken).toBe(null);
|
|
|
+ expect(user.passwordResetExpiresAt).toBe(null);
|
|
|
+ expect(user.save).toHaveBeenCalledTimes(1);
|
|
|
+
|
|
|
+ expect(await res.json()).toMatchObject({
|
|
|
+ ok: true,
|
|
|
+ temporaryPassword: "TempPass123!",
|
|
|
+ user: {
|
|
|
+ id: "507f1f77bcf86cd799439099",
|
|
|
+ username: "branch2",
|
|
|
+ email: "branch2@example.com",
|
|
|
+ role: "branch",
|
|
|
+ branchId: "NL02",
|
|
|
+ mustChangePassword: true,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
describe("DELETE /api/admin/users/[userId]", () => {
|
|
|
beforeEach(() => {
|
|
|
vi.clearAllMocks();
|