From 00fb9a38f0e5a78af4eb8da75949f7463f16a283 Mon Sep 17 00:00:00 2001 From: SJCoetz Date: Fri, 10 Jul 2026 00:05:54 +0200 Subject: [PATCH] test(collection-map): cover ready_notes required-on-ready --- .../items/[id]/assign/route.test.ts | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/app/api/v1/collection-map/items/[id]/assign/route.test.ts diff --git a/src/app/api/v1/collection-map/items/[id]/assign/route.test.ts b/src/app/api/v1/collection-map/items/[id]/assign/route.test.ts new file mode 100644 index 0000000..f3ada73 --- /dev/null +++ b/src/app/api/v1/collection-map/items/[id]/assign/route.test.ts @@ -0,0 +1,99 @@ +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 { PATCH } from "./route"; + +function makeReq(body: unknown) { + return new NextRequest( + "http://localhost/api/v1/collection-map/items/item-1/assign", + { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }, + ); +} + +describe("PATCH /api/v1/collection-map/items/[id]/assign", () => { + beforeEach(() => { + getOneMock.mockReset(); + updateMock.mockReset(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it("writes trimmed notes to the item when ready_notes is provided", async () => { + getOneMock.mockResolvedValueOnce({ id: "item-1" }); + updateMock.mockResolvedValueOnce({ id: "item-1" }); + + const res = await PATCH( + makeReq({ + ready_for_collection: true, + ready_notes: " spoke to David, ETA tomorrow ", + }), + { params: Promise.resolve({ id: "item-1" }) }, + ); + + expect(res.status).toBe(200); + const payload = updateMock.mock.calls[0][1] as Record; + expect(payload.notes).toBe("spoke to David, ETA tomorrow"); + expect(payload.ready_for_collection).toBe(true); + }); + + it("returns 400 when ready_for_collection is true and ready_notes is empty", async () => { + const res = await PATCH( + makeReq({ ready_for_collection: true, ready_notes: "" }), + { params: Promise.resolve({ id: "item-1" }) }, + ); + expect(res.status).toBe(400); + expect(updateMock).not.toHaveBeenCalled(); + }); + + it("returns 400 when ready_for_collection is true and ready_notes is whitespace only", async () => { + const res = await PATCH( + makeReq({ ready_for_collection: true, ready_notes: " \n\t " }), + { params: Promise.resolve({ id: "item-1" }) }, + ); + expect(res.status).toBe(400); + expect(updateMock).not.toHaveBeenCalled(); + }); + + it("returns 400 when ready_for_collection is true and ready_notes is missing", async () => { + const res = await PATCH( + makeReq({ ready_for_collection: true }), + { params: Promise.resolve({ id: "item-1" }) }, + ); + expect(res.status).toBe(400); + expect(updateMock).not.toHaveBeenCalled(); + }); + + it("accepts the unmark path without a note (ready_for_collection: false)", async () => { + getOneMock.mockResolvedValueOnce({ id: "item-1", notes: "existing" }); + updateMock.mockResolvedValueOnce({ id: "item-1" }); + + const res = await PATCH( + makeReq({ ready_for_collection: false }), + { params: Promise.resolve({ id: "item-1" }) }, + ); + + expect(res.status).toBe(200); + const payload = updateMock.mock.calls[0][1] as Record; + expect(payload.ready_for_collection).toBe(false); + // The unmark path must not silently overwrite the existing note. + expect(payload.notes).toBeUndefined(); + }); +}); \ No newline at end of file