import { beforeEach, describe, expect, it, vi } from "vitest"; import type { Mock } from "vitest"; import type { Session } from "next-auth"; // Mirrors tests/employees.test.ts's @/auth mocking pattern. vi.mock("@/auth", () => ({ auth: vi.fn() })); import { GET, POST } from "@/app/api/assets/route"; import { prisma } from "@/lib/prisma"; import { auth } from "@/auth"; const mockedAuth = auth as unknown as Mock<() => Promise>; const testSession: Session = { user: { id: "test-user-id" }, expires: new Date(Date.now() + 60 * 60 * 1000).toISOString(), }; function postRequest(body: unknown): Request { return new Request("http://localhost/api/assets", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), }); } beforeEach(async () => { // Each test starts from an empty table so listing/order assertions are // deterministic. await prisma.assetItem.deleteMany(); // Default to a logged-in session — the tests below exercise business // logic, not auth gating (that's covered by the "auth gating" describe // block further down). mockedAuth.mockResolvedValue(testSession); }); describe("POST /api/assets", () => { it("creates an asset item in in_stock status on valid input, with no warrantyEndDate", async () => { const res = await POST( postRequest({ serialNumber: "SN-001", model: "Dell Latitude 5440", purchaseDate: "2026-01-15" }) ); expect(res.status).toBe(201); const body = await res.json(); expect(body).toMatchObject({ serial_number: "SN-001", model: "Dell Latitude 5440", status: "in_stock", warranty_end_date: null, }); expect(typeof body.id).toBe("string"); expect(body.id.length).toBeGreaterThan(0); expect(Number.isNaN(Date.parse(body.purchase_date))).toBe(false); expect(Number.isNaN(Date.parse(body.created_at))).toBe(false); }); it("creates an asset item with a warrantyEndDate", async () => { const res = await POST( postRequest({ serialNumber: "SN-002", model: "Dell Latitude 5440", purchaseDate: "2026-01-15", warrantyEndDate: "2029-01-15", }) ); expect(res.status).toBe(201); const body = await res.json(); expect(Number.isNaN(Date.parse(body.warranty_end_date))).toBe(false); }); it("rejects an empty serialNumber", async () => { const res = await POST(postRequest({ serialNumber: "", model: "Dell", purchaseDate: "2026-01-15" })); expect(res.status).toBe(400); const body = await res.json(); expect(body.error).toMatch(/serialNumber/i); }); it("rejects an empty model", async () => { const res = await POST(postRequest({ serialNumber: "SN-003", model: "", purchaseDate: "2026-01-15" })); expect(res.status).toBe(400); const body = await res.json(); expect(body.error).toMatch(/model/i); }); it("rejects an invalid purchaseDate", async () => { const res = await POST( postRequest({ serialNumber: "SN-004", model: "Dell", purchaseDate: "not-a-date" }) ); expect(res.status).toBe(400); const body = await res.json(); expect(body.error).toMatch(/purchaseDate/i); }); it("rejects an invalid warrantyEndDate", async () => { const res = await POST( postRequest({ serialNumber: "SN-005", model: "Dell", purchaseDate: "2026-01-15", warrantyEndDate: "not-a-date", }) ); expect(res.status).toBe(400); const body = await res.json(); expect(body.error).toMatch(/warrantyEndDate/i); }); it("rejects a duplicate serialNumber", async () => { await POST(postRequest({ serialNumber: "SN-006", model: "Dell", purchaseDate: "2026-01-15" })); const res = await POST(postRequest({ serialNumber: "SN-006", model: "HP", purchaseDate: "2026-02-01" })); expect(res.status).toBe(400); const body = await res.json(); expect(body.error).toMatch(/serialNumber already exists/i); // And it must not have created a second row. const remaining = await prisma.assetItem.findMany(); expect(remaining).toHaveLength(1); }); }); describe("GET /api/assets", () => { it("lists created asset items, newest first", async () => { await POST(postRequest({ serialNumber: "SN-010", model: "Dell", purchaseDate: "2026-01-15" })); // Ensure a distinct created_at ordering even on fast filesystems/clocks. await new Promise((resolve) => setTimeout(resolve, 5)); await POST(postRequest({ serialNumber: "SN-011", model: "HP", purchaseDate: "2026-02-01" })); const res = await GET(); expect(res.status).toBe(200); const body = await res.json(); expect(Array.isArray(body)).toBe(true); expect(body).toHaveLength(2); // newest first expect(body[0]).toMatchObject({ serial_number: "SN-011", model: "HP" }); expect(body[1]).toMatchObject({ serial_number: "SN-010", model: "Dell" }); }); it("returns an empty list when there are no asset items", async () => { const res = await GET(); expect(res.status).toBe(200); const body = await res.json(); expect(body).toEqual([]); }); }); describe("auth gating on /api/assets", () => { // Mirrors tests/employees.test.ts's "auth gating" block — only checks // session presence, per the flat permission model (no role/permission // logic). it("POST /api/assets returns 401 without a session", async () => { mockedAuth.mockResolvedValue(null); const res = await POST( postRequest({ serialNumber: "SN-020", model: "Dell", purchaseDate: "2026-01-15" }) ); expect(res.status).toBe(401); const body = await res.json(); expect(body).toEqual({ error: "Unauthorized" }); // And it must not have created anything. const remaining = await prisma.assetItem.findMany(); expect(remaining).toHaveLength(0); }); it("GET /api/assets returns 401 without a session", async () => { mockedAuth.mockResolvedValue(null); const res = await GET(); expect(res.status).toBe(401); const body = await res.json(); expect(body).toEqual({ error: "Unauthorized" }); }); });