Files
0pt/tests/api/orders-export.test.ts
admin bc76ebc5e5
CI (SIAX Cloud) / integration (push) Skipped
CI (SIAX Cloud) / sonar (push) Skipped
masterplan-lock / check (push) Successful in 14s
CI (SIAX Cloud) / security (push) Failing after 16s
CI (SIAX Cloud) / quality (push) Successful in 52s
Security Scan (SIAX Cloud) / security (push) Successful in 58s
CI (SIAX Cloud) / sonar (pull_request) Skipped
masterplan-lock / check (pull_request) Successful in 5s
CI (SIAX Cloud) / security (pull_request) Successful in 15s
CI (SIAX Cloud) / quality (pull_request) Successful in 37s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 45s
CI (SIAX Cloud) / integration (pull_request) Successful in 1m35s
CI (SIAX Cloud) / security (push) Trivy vulndb 404 (mirror.gcr.io) - infra-flake; re-run pa identisk sha gron
feat(orders): CSV-export-endpoint — export-kernelns första adoption (våg 3.4)
2026-09-29 17:01:13 +02:00

72 lines
3.1 KiB
TypeScript

/**
* 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<typeof vi.fn>;
mocked.mockResolvedValueOnce({
response: new Response(JSON.stringify({ error: { code: "unauthorized" } }), { status: 401 }),
});
const r = await exportGET(getReq());
expect(r.status).toBe(401);
});
});