test(po-pdf): cover token capture + proxy routing

This commit is contained in:
SJCoetz 2026-07-09 22:23:49 +02:00
parent 6a7dcc0af2
commit f146e42ed0

View file

@ -0,0 +1,243 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { NextRequest } from "next/server";
// Mocks
const getOneMock = vi.fn();
const updateMock = vi.fn();
const fetchMock = vi.fn();
vi.mock("@/lib/server/pocketbase-admin", () => ({
getAdminPocketBase: vi.fn().mockResolvedValue({
collection: (name: string) => {
if (name === "order_items") {
return {
getFullList: vi
.fn()
.mockResolvedValue([
{ id: "item-1", quantity: 1, cost_price: 100, purchase_order: "po-1" },
]),
};
}
return {
getOne: getOneMock,
update: updateMock,
};
},
files: { getURL: vi.fn() },
authStore: { token: "test-token" },
}),
}));
vi.mock("@/lib/letterhead", () => ({
fetchLetterhead: vi.fn().mockResolvedValue({ id: "cnl", name: "CNL" }),
loadLetterheadLogoDataUri: vi.fn().mockResolvedValue("data:image/svg+xml;base64,AAA"),
}));
vi.mock("@/lib/pdf/render", () => ({
renderPurchaseOrderPdf: vi.fn().mockResolvedValue(Buffer.from("PDF")),
}));
import { GET, POST } from "./route";
function makeReq(url: string, init: { method?: string; body?: unknown } = {}) {
return new NextRequest(url, {
method: init.method ?? "GET",
headers: { cookie: "test-cookie" },
body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
});
}
describe("/api/v1/purchase-orders/[id]/pdf", () => {
beforeEach(() => {
getOneMock.mockReset();
updateMock.mockReset();
fetchMock.mockReset();
vi.stubGlobal("fetch", fetchMock);
});
afterEach(() => {
vi.unstubAllGlobals();
});
describe("POST — token capture", () => {
it("stores the PB token, not the human-readable filename, in documents_history", async () => {
updateMock
.mockResolvedValueOnce({
documents: ["slug-token-v3.pdf", "slug-token-v4.pdf"],
})
.mockResolvedValueOnce({});
getOneMock.mockResolvedValueOnce({
id: "po-1",
po_number: "PO-2026-07-009",
order: "order-1",
supplier: "supplier-1",
expand: { order: { id: "order-1" }, supplier: { id: "supplier-1" } },
documents: ["slug-token-v3.pdf"],
documents_history: [
{
version: 3,
file: "PO-CNL-PO-2026-07-009-v3.pdf",
token: "slug-token-v3.pdf",
generatedAt: "2026-07-09T00:00:00Z",
generatedBy: "",
reason: "",
status: "current",
},
],
});
const req = makeReq("http://localhost/api/v1/purchase-orders/po-1/pdf", {
method: "POST",
body: {},
});
const res = await POST(req, { params: Promise.resolve({ id: "po-1" }) });
expect(res.status).toBe(200);
const historyCall = updateMock.mock.calls[1];
expect(historyCall[1]).toEqual({
documents_history: expect.arrayContaining([
expect.objectContaining({
version: 4,
file: "PO-CNL-PO-2026-07-009-v4.pdf",
token: "slug-token-v4.pdf",
status: "current",
}),
]),
});
});
});
describe("GET — proxy routing", () => {
it("streams the PDF through the file proxy using entry.token", async () => {
getOneMock.mockResolvedValueOnce({
id: "po-1",
po_number: "PO-2026-07-009",
documents: ["slug-v4.pdf"],
documents_history: [
{
version: 4,
file: "PO-CNL-PO-2026-07-009-v4.pdf",
token: "slug-v4.pdf",
generatedAt: "2026-07-09T00:00:00Z",
generatedBy: "",
reason: "",
status: "current",
},
],
});
fetchMock.mockResolvedValueOnce(
new Response(Buffer.from("PDF bytes"), {
status: 200,
headers: { "Content-Type": "application/pdf" },
}),
);
const req = makeReq("http://localhost/api/v1/purchase-orders/po-1/pdf?version=4");
const res = await GET(req, { params: Promise.resolve({ id: "po-1" }) });
expect(res.status).toBe(200);
expect(fetchMock).toHaveBeenCalledTimes(1);
const [url] = fetchMock.mock.calls[0];
expect(String(url)).toContain("/api/v1/files/purchase_orders/po-1/slug-v4.pdf");
});
it("falls back to documents[i] when entry.token is missing", async () => {
getOneMock.mockResolvedValueOnce({
id: "po-1",
po_number: "PO-2026-07-009",
documents: ["legacy-token.pdf"],
documents_history: [
{
version: 2,
file: "PO-CNL-PO-2026-07-009-v2.pdf",
generatedAt: "2026-07-09T00:00:00Z",
generatedBy: "",
reason: "",
status: "superseded",
},
],
});
fetchMock.mockResolvedValueOnce(
new Response(Buffer.from("PDF bytes"), { status: 200 }),
);
const req = makeReq("http://localhost/api/v1/purchase-orders/po-1/pdf?version=2");
const res = await GET(req, { params: Promise.resolve({ id: "po-1" }) });
expect(res.status).toBe(200);
const [url] = fetchMock.mock.calls[0];
expect(String(url)).toContain("/api/v1/files/purchase_orders/po-1/legacy-token.pdf");
});
it("404s with a clear message when no token is resolvable", async () => {
getOneMock.mockResolvedValueOnce({
id: "po-1",
po_number: "PO-2026-07-009",
documents: [],
documents_history: [
{
version: 1,
file: "PO-CNL-PO-2026-07-009-v1.pdf",
generatedAt: "2026-07-09T00:00:00Z",
generatedBy: "",
reason: "",
status: "current",
},
],
});
const req = makeReq("http://localhost/api/v1/purchase-orders/po-1/pdf");
const res = await GET(req, { params: Promise.resolve({ id: "po-1" }) });
expect(res.status).toBe(404);
const body = await res.json();
expect(body.error).toMatch(/token missing/i);
});
it("respects ?version=N and ?download=1", async () => {
getOneMock.mockResolvedValueOnce({
id: "po-1",
po_number: "PO-2026-07-009",
documents: ["slug-v2.pdf"],
documents_history: [
{
version: 1,
file: "PO-CNL-PO-2026-07-009-v1.pdf",
token: "slug-v1.pdf",
generatedAt: "2026-07-09T00:00:00Z",
generatedBy: "",
reason: "",
status: "superseded",
},
{
version: 2,
file: "PO-CNL-PO-2026-07-009-v2.pdf",
token: "slug-v2.pdf",
generatedAt: "2026-07-09T00:00:00Z",
generatedBy: "",
reason: "",
status: "current",
},
],
});
fetchMock.mockResolvedValueOnce(
new Response(Buffer.from("PDF"), { status: 200 }),
);
const req = makeReq(
"http://localhost/api/v1/purchase-orders/po-1/pdf?version=1&download=1",
);
const res = await GET(req, { params: Promise.resolve({ id: "po-1" }) });
expect(res.status).toBe(200);
const [url] = fetchMock.mock.calls[0];
expect(String(url)).toContain("/api/v1/files/purchase_orders/po-1/slug-v1.pdf");
expect(res.headers.get("Content-Disposition")).toMatch(/^attachment/);
});
});
});