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
72 lines
3.1 KiB
TypeScript
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);
|
|
});
|
|
});
|