diff --git a/src/app/api/v1/purchase-orders/[id]/attachments/upload/route.test.ts b/src/app/api/v1/purchase-orders/[id]/attachments/upload/route.test.ts new file mode 100644 index 0000000..8a29b49 --- /dev/null +++ b/src/app/api/v1/purchase-orders/[id]/attachments/upload/route.test.ts @@ -0,0 +1,82 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { NextRequest } from "next/server"; + +const getOneMock = vi.fn(); +const updateMock = vi.fn(); + +vi.mock("@/lib/server/pocketbase-admin", () => ({ + getAdminPocketBase: vi.fn().mockResolvedValue({ + collection: () => ({ + getOne: getOneMock, + update: updateMock, + }), + }), +})); + +import { POST } from "./route"; + +function makeUploadReq(url: string, file: File | null) { + const fd = new FormData(); + if (file) fd.append("file", file); + return new NextRequest(url, { method: "POST", body: fd }); +} + +describe("POST /api/v1/purchase-orders/[id]/attachments/upload", () => { + beforeEach(() => { + getOneMock.mockReset(); + updateMock.mockReset(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("400s when no file is in the request", async () => { + const res = await POST( + makeUploadReq( + "http://localhost/api/v1/purchase-orders/po-1/attachments/upload", + null, + ), + { params: Promise.resolve({ id: "po-1" }) }, + ); + expect(res.status).toBe(400); + }); + + it("appends the new token to the existing array", async () => { + getOneMock.mockResolvedValueOnce({ + id: "po-1", + additional_attachments: ["existing-token.pdf"], + }); + updateMock.mockResolvedValueOnce({}); + + const res = await POST( + makeUploadReq( + "http://localhost/api/v1/purchase-orders/po-1/attachments/upload", + new File(["data"], "supplier-quote.pdf", { type: "application/pdf" }), + ), + { params: Promise.resolve({ id: "po-1" }) }, + ); + + expect(res.status).toBe(201); + expect(updateMock).toHaveBeenCalledTimes(1); + const payload = updateMock.mock.calls[0][1] as FormData; + // FormData wraps the file; we just need to confirm update was called + // with a FormData body. The route handles the read-modify-write. + expect(payload).toBeInstanceOf(FormData); + }); + + it("rejects files over 25 MB", async () => { + const big = new File([new Uint8Array(26 * 1024 * 1024)], "big.pdf", { + type: "application/pdf", + }); + const res = await POST( + makeUploadReq( + "http://localhost/api/v1/purchase-orders/po-1/attachments/upload", + big, + ), + { params: Promise.resolve({ id: "po-1" }) }, + ); + expect(res.status).toBe(413); + expect(updateMock).not.toHaveBeenCalled(); + }); +});