import { describe, it, expect, vi, beforeEach } from "vitest"; // 1) Mocks vi.mock("@/lib/db", () => ({ getDb: vi.fn(), })); vi.mock("@/models/user", () => ({ default: { findOne: vi.fn(), }, })); vi.mock("@/lib/auth/session", () => ({ createSession: vi.fn(), })); vi.mock("bcryptjs", () => { const compare = vi.fn(); return { default: { compare }, compare, }; }); // 2) Imports AFTER the mocks import { getDb } from "@/lib/db"; import User from "@/models/user"; import { createSession } from "@/lib/auth/session"; import { compare as bcryptCompare } from "bcryptjs"; import { POST, dynamic } from "./route.js"; function createRequestStub(body) { return { async json() { return body; }, }; } describe("POST /api/auth/login", () => { beforeEach(() => { vi.clearAllMocks(); getDb.mockResolvedValue({}); // we only need it to "connect" }); it('exports dynamic="force-dynamic" (RHL-006)', () => { expect(dynamic).toBe("force-dynamic"); }); it("logs in successfully with correct credentials", async () => { const user = { _id: "507f1f77bcf86cd799439011", username: "branchuser", passwordHash: "hashed-password", role: "branch", branchId: "NL01", }; User.findOne.mockReturnValue({ exec: vi.fn().mockResolvedValue(user), }); bcryptCompare.mockResolvedValue(true); const request = createRequestStub({ username: "BranchUser", // mixed case, should be normalized password: "secret-password", }); const response = await POST(request); const json = await response.json(); expect(response.status).toBe(200); expect(json).toEqual({ ok: true }); expect(getDb).toHaveBeenCalledTimes(1); expect(User.findOne).toHaveBeenCalledWith({ username: "branchuser" }); expect(bcryptCompare).toHaveBeenCalledWith( "secret-password", "hashed-password" ); expect(createSession).toHaveBeenCalledWith({ userId: "507f1f77bcf86cd799439011", role: "branch", branchId: "NL01", }); }); it("returns 400 when JSON parsing fails", async () => { // Simulate request.json() throwing (invalid JSON body). const request = { json: vi.fn().mockRejectedValue(new Error("invalid json")), }; const response = await POST(request); const body = await response.json(); expect(response.status).toBe(400); expect(body).toEqual({ error: { message: "Invalid request body", code: "VALIDATION_INVALID_JSON", }, }); expect(createSession).not.toHaveBeenCalled(); }); it("returns 401 when user does not exist", async () => { User.findOne.mockReturnValue({ exec: vi.fn().mockResolvedValue(null), }); const request = createRequestStub({ username: "unknownuser", password: "some-password", }); const response = await POST(request); const body = await response.json(); expect(response.status).toBe(401); expect(body).toEqual({ error: { message: "Invalid credentials", code: "AUTH_INVALID_CREDENTIALS", }, }); expect(createSession).not.toHaveBeenCalled(); }); it("returns 401 when passwordHash is missing (defensive)", async () => { User.findOne.mockReturnValue({ exec: vi.fn().mockResolvedValue({ _id: "507f1f77bcf86cd799439099", username: "branchuser", // passwordHash missing on purpose role: "branch", branchId: "NL01", }), }); const request = createRequestStub({ username: "branchuser", password: "secret-password", }); const response = await POST(request); const body = await response.json(); expect(response.status).toBe(401); expect(body).toEqual({ error: { message: "Invalid credentials", code: "AUTH_INVALID_CREDENTIALS", }, }); expect(bcryptCompare).not.toHaveBeenCalled(); expect(createSession).not.toHaveBeenCalled(); }); it("returns 401 when password is incorrect", async () => { const user = { _id: "507f1f77bcf86cd799439012", username: "branchuser", passwordHash: "hashed-password", role: "branch", branchId: "NL02", }; User.findOne.mockReturnValue({ exec: vi.fn().mockResolvedValue(user), }); bcryptCompare.mockResolvedValue(false); const request = createRequestStub({ username: "branchuser", password: "wrong-password", }); const response = await POST(request); const body = await response.json(); expect(response.status).toBe(401); expect(body).toEqual({ error: { message: "Invalid credentials", code: "AUTH_INVALID_CREDENTIALS", }, }); expect(createSession).not.toHaveBeenCalled(); }); it("returns 400 when username or password is missing", async () => { const request = createRequestStub({ username: "only-username", }); const response = await POST(request); const body = await response.json(); expect(response.status).toBe(400); expect(body).toEqual({ error: { message: "Missing username or password", code: "VALIDATION_MISSING_FIELD", details: { fields: ["username", "password"] }, }, }); expect(User.findOne).not.toHaveBeenCalled(); expect(createSession).not.toHaveBeenCalled(); }); it("returns 500 when an unexpected error occurs", async () => { User.findOne.mockImplementation(() => { throw new Error("DB failure"); }); const request = createRequestStub({ username: "branchuser", password: "secret-password", }); const response = await POST(request); const body = await response.json(); expect(response.status).toBe(500); expect(body).toEqual({ error: { message: "Internal server error", code: "INTERNAL_SERVER_ERROR", }, }); expect(createSession).not.toHaveBeenCalled(); }); });