/** * Våg 3.4 (DoD §56): unit tests for GET /api/orders/export — the first * adoption of the export kernel. Follows the suite pattern of calling the * route handler directly with a hand-built NextRequest and mocked auth/store. */ import { describe, it, expect, vi } from "vitest"; import { NextRequest } from "next/server"; vi.mock("@/src/auth/require-order-owner", () => ({ requireOrderOwner: vi.fn().mockResolvedValue({ owner: { tenantId: "tenant-test", subject: "buyer-test" } }), })); vi.mock("@/lib/orders-store", () => ({ listOwnedOrders: vi.fn().mockResolvedValue([ { order_id: "ord_1", flow: "custom", status: "draft", deposit_ore: 5000, total_ore: 10000 }, { order_id: 'ord,"2"', flow: "platform", status: "delivered", deposit_ore: 0, total_ore: 12345 }, ]), })); import { GET as exportGET } from "@/app/api/orders/export/route"; function getReq(): NextRequest { return new NextRequest(new URL("/api/orders/export", "http://localhost"), { method: "GET" }); } describe("GET /api/orders/export — CSV (våg 3.4, DoD §56)", () => { it("emits the documented header row and CRLF line endings", async () => { const r = await exportGET(getReq()); expect(r.status).toBe(200); expect(r.headers.get("content-type")).toContain("text/csv"); expect(r.headers.get("content-disposition")).toMatch(/^attachment; filename="orders-\d{4}-\d{2}-\d{2}\.csv"$/); const body = await r.text(); const lines = body.split("\r\n"); expect(lines[0]).toBe("order_id,flow,status,deposit_ore,total_ore"); expect(lines[1]).toBe("ord_1,custom,draft,5000,10000"); }); it("RFC4180-quotes fields containing comma/quote (doubled quotes)", async () => { const r = await exportGET(getReq()); const body = await r.text(); expect(body).toContain('"ord,""2"""'); }); it("exports RAW öre — no locale formatting in the export (öre is canonical)", async () => { const r = await exportGET(getReq()); const body = await r.text(); expect(body).toContain("ord_1,custom,draft,5000,10000"); expect(body).not.toContain("100,00"); expect(body).not.toMatch(/\u00A0| kr/); }); it("no row truncation: export is complete per DoD §56 (cap documented)", async () => { const r = await exportGET(getReq()); const body = await r.text(); const rows = body.trimEnd().split("\r\n").length - 1; expect(rows).toBe(2); }); it("auth is fail-closed: the route delegates to requireOrderOwner (401 contract)", async () => { // The mocked requireOrderOwner resolves with an owner; the 401 path is the // auth.response passthrough asserted in tests/api/anonymous-routes-auth. // This test pins the wiring: the handler must return auth.response // verbatim when present, before any store access. const { requireOrderOwner } = await import("@/src/auth/require-order-owner"); const mocked = requireOrderOwner as ReturnType; mocked.mockResolvedValueOnce({ response: new Response(JSON.stringify({ error: { code: "unauthorized" } }), { status: 401 }), }); const r = await exportGET(getReq()); expect(r.status).toBe(401); }); });