diff --git a/src/app/api/dn-batches/[id]/notes/[noteId]/post/route.test.ts b/src/app/api/dn-batches/[id]/notes/[noteId]/post/route.test.ts index 1c49af1..f3c1b22 100644 --- a/src/app/api/dn-batches/[id]/notes/[noteId]/post/route.test.ts +++ b/src/app/api/dn-batches/[id]/notes/[noteId]/post/route.test.ts @@ -22,7 +22,7 @@ const state = vi.hoisted(() => ({ vi.mock("@/lib/pb", () => ({ pbAdmin: vi.fn(async () => ({ authStore: { token: "test" }, - files: { getURL: (_record: unknown, filename: string) => `https://pb.test/${filename}` }, + files: { getURL: (_record: unknown, filename: string) => `https://pb.test/${filename}`, getToken: vi.fn(async () => "file-token") }, collection(name: string) { if (name === "dn_batches") return { getOne: vi.fn(async () => state.batch) }; if (name === "dn_batch_notes") return { getOne: vi.fn(async () => state.note) }; @@ -95,6 +95,18 @@ describe("post delivery note route", () => { expect(state.events[0]).toMatchObject({ action: "post", note: NOTE }); }); + it("uses a PocketBase file token to fetch a protected candidate page", async () => { + const png = Uint8Array.from(Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=", "base64")); + vi.stubGlobal("fetch", vi.fn(async (input: string | URL | Request) => + String(input).includes("token=file-token") + ? new Response(png, { status: 200 }) + : new Response(null, { status: 404 }))); + const { POST } = await import("./route"); + const response = await POST(request(), context()); + expect(response.status).toBe(200); + expect(vi.mocked(fetch).mock.calls[0]?.[0]).toBe("https://pb.test/page.png?token=file-token"); + }); + it("refuses to post when another delivery note with the same number was posted after this candidate's last rerun", async () => { // Two scans of one physical delivery can each pass not_duplicate and // reach ready_to_post before either is posted, because neither rerun @@ -143,7 +155,7 @@ describe("post delivery note route", () => { const response = await POST(request(), context()); expect(response.status).toBe(200); expect(vi.mocked(fetch)).toHaveBeenCalledTimes(1); - expect(vi.mocked(fetch).mock.calls[0]?.[0]).toBe("https://pb.test/second.png"); + expect(vi.mocked(fetch).mock.calls[0]?.[0]).toBe("https://pb.test/second.png?token=file-token"); }); it("selects an explicit global page after a missing middle page", async () => { @@ -156,7 +168,7 @@ describe("post delivery note route", () => { const { POST } = await import("./route"); const response = await POST(request(), context()); expect(response.status).toBe(200); - expect(vi.mocked(fetch).mock.calls[0]?.[0]).toBe("https://pb.test/third.png"); + expect(vi.mocked(fetch).mock.calls[0]?.[0]).toBe("https://pb.test/third.png?token=file-token"); }); it("retries a real promotion instead of reporting success when a blocked candidate carries a dangling delivery_note pointer", async () => { diff --git a/src/app/api/dn-batches/[id]/notes/[noteId]/post/route.ts b/src/app/api/dn-batches/[id]/notes/[noteId]/post/route.ts index 7f711dc..7cad371 100644 --- a/src/app/api/dn-batches/[id]/notes/[noteId]/post/route.ts +++ b/src/app/api/dn-batches/[id]/notes/[noteId]/post/route.ts @@ -104,9 +104,11 @@ function asItems(value: unknown): PageItem[] { async function buildPagePdf(pb: Awaited>, pages: Record[]): Promise { const pdf = await PDFDocument.create(); + const fileToken = await pb.files.getToken(); for (const page of pages) { if (typeof page.image !== "string" || !page.image) throw new Error("candidate page image missing"); - const response = await fetch(pocketBaseFileUrl(pb, page as { id: string }, "dn_batch_pages", page.image), { + const imageUrl = `${pocketBaseFileUrl(pb, page as { id: string }, "dn_batch_pages", page.image)}?token=${encodeURIComponent(fileToken)}`; + const response = await fetch(imageUrl, { headers: { Authorization: pb.authStore.token }, }); if (!response.ok) throw new Error(`candidate page fetch failed: ${response.status}`);