106 lines
5.1 KiB
TypeScript
106 lines
5.1 KiB
TypeScript
/**
|
||
* DES1GN ADR D3 (§31): ImportDispatchPolicy — ren policy-funktion, testad per
|
||
* kombination (format × bytes × rows, gränsvärden inkluderade), plus route-
|
||
* kopplingen: rows > policy.maxRows -> 413 n0d_dispatch_required med hint
|
||
* (descriptor-gate stängd), inline-taket oförändrat.
|
||
*/
|
||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||
import { NextRequest } from "next/server";
|
||
import { IMPORT_DISPATCH_POLICY, evaluateImportDispatch } from "@/lib/import-dispatch-policy";
|
||
|
||
describe("evaluateImportDispatch — verdict per kombination", () => {
|
||
it("gränsvärde maxInlineRows: exakt 500 rader -> inline", () => {
|
||
expect(evaluateImportDispatch({ format: "csv", bytes: 100, rows: IMPORT_DISPATCH_POLICY.maxInlineRows })).toEqual({ verdict: "inline" });
|
||
});
|
||
|
||
it("maxInlineRows < rows <= maxRows -> row_cap_exceeded (inline-taket, oförändrat)", () => {
|
||
expect(evaluateImportDispatch({ format: "csv", bytes: 100, rows: IMPORT_DISPATCH_POLICY.maxInlineRows + 1 })).toEqual({
|
||
verdict: "row_cap_exceeded",
|
||
rows: 501,
|
||
maxInlineRows: 500,
|
||
});
|
||
expect(evaluateImportDispatch({ format: "csv", bytes: 100, rows: IMPORT_DISPATCH_POLICY.maxRows }).verdict).toBe("row_cap_exceeded");
|
||
});
|
||
|
||
it("rows > maxRows -> dispatch_n0d (D3-signalen)", () => {
|
||
expect(evaluateImportDispatch({ format: "csv", bytes: 100, rows: IMPORT_DISPATCH_POLICY.maxRows + 1 })).toEqual({
|
||
verdict: "dispatch_n0d",
|
||
rows: 5001,
|
||
maxRows: 5000,
|
||
});
|
||
});
|
||
|
||
it("bytes > maxBytes -> byte_cap_exceeded, oavsett rows", () => {
|
||
expect(evaluateImportDispatch({ format: "csv", bytes: IMPORT_DISPATCH_POLICY.maxBytes + 1, rows: 1 }).verdict).toBe("byte_cap_exceeded");
|
||
expect(evaluateImportDispatch({ format: "csv", bytes: IMPORT_DISPATCH_POLICY.maxBytes, rows: 1 }).verdict).toBe("inline");
|
||
});
|
||
|
||
it("format utanför allowlist -> unsupported_format", () => {
|
||
expect(evaluateImportDispatch({ format: "xlsx", bytes: 10, rows: 1 })).toEqual({ verdict: "unsupported_format", format: "xlsx" });
|
||
});
|
||
|
||
it("precedens: format slår bytes, bytes slår rows", () => {
|
||
expect(evaluateImportDispatch({ format: "json", bytes: 9e9, rows: 9e9 }).verdict).toBe("unsupported_format");
|
||
expect(evaluateImportDispatch({ format: "csv", bytes: 9e9, rows: 9e9 }).verdict).toBe("byte_cap_exceeded");
|
||
});
|
||
});
|
||
|
||
describe("route-koppling: POST /api/orders/import respekterar policyn", () => {
|
||
const { requireOrderOwnerMock, createOrderMock, ingestEventMock, mockOwner } = vi.hoisted(() => ({
|
||
requireOrderOwnerMock: vi.fn(),
|
||
createOrderMock: vi.fn(),
|
||
ingestEventMock: vi.fn().mockResolvedValue({}),
|
||
mockOwner: { owner: { tenantId: "tenant-test", subject: "buyer-test" } },
|
||
}));
|
||
const _unused = mockOwner;
|
||
vi.mock("@/src/auth/require-order-owner", () => ({
|
||
requireOrderOwner: (...args: unknown[]) => requireOrderOwnerMock(...args),
|
||
}));
|
||
vi.mock("@/lib/orders-store", () => ({ createOrder: (...args: unknown[]) => createOrderMock(...args) }));
|
||
vi.mock("@/src/clients/aud0-client", () => ({ createAud0Client: () => ({ ingestEvent: ingestEventMock }) }));
|
||
|
||
let POST: typeof import("@/app/api/orders/import/route").POST;
|
||
let MAX_IMPORT_ROWS: number;
|
||
|
||
beforeEach(async () => {
|
||
vi.clearAllMocks();
|
||
requireOrderOwnerMock.mockResolvedValue(mockOwner);
|
||
({ POST, MAX_IMPORT_ROWS } = await import("@/app/api/orders/import/route"));
|
||
});
|
||
|
||
function postReq(csv: string): NextRequest {
|
||
return new NextRequest(new URL("/api/orders/import", "http://localhost"), {
|
||
method: "POST",
|
||
body: JSON.stringify({ mode: "validate", csv }),
|
||
headers: { "content-type": "application/json" },
|
||
});
|
||
}
|
||
|
||
const line = "custom,icp,Bolag,10,Engångs,SE";
|
||
|
||
it("exakt maxInlineRows rader -> 200 (inline-beteende oförändrat)", async () => {
|
||
const csv = ["flow,service_type,type,volume,frequency,markets", ...Array.from({ length: MAX_IMPORT_ROWS }, () => line)].join("\n");
|
||
const res = await POST(postReq(csv));
|
||
expect(res.status).toBe(200);
|
||
});
|
||
|
||
it("maxRows < rows -> 413 n0d_dispatch_required med UNVERIFIED-hint, ingen skrivning", async () => {
|
||
const csv = ["flow,service_type,type,volume,frequency,markets", ...Array.from({ length: IMPORT_DISPATCH_POLICY.maxRows + 1 }, () => line)].join("\n");
|
||
const res = await POST(postReq(csv));
|
||
expect(res.status).toBe(413);
|
||
const body = (await res.json()) as { error: { code: string; hint: { capability_id: string; job_type: string; status: string } } };
|
||
expect(body.error.code).toBe("n0d_dispatch_required");
|
||
expect(body.error.hint).toEqual({ capability_id: "0pt.import_execute", job_type: "0pt.import_execute_job", status: "UNVERIFIED" });
|
||
expect(createOrderMock).not.toHaveBeenCalled();
|
||
expect(ingestEventMock).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it("CSV > maxBytes -> 413 csv_too_large", async () => {
|
||
const csv = "flow,service_type,type,volume,frequency,markets\n" + "x".repeat(IMPORT_DISPATCH_POLICY.maxBytes);
|
||
const res = await POST(postReq(csv));
|
||
expect(res.status).toBe(413);
|
||
const body = (await res.json()) as { error: { code: string } };
|
||
expect(body.error.code).toBe("csv_too_large");
|
||
});
|
||
});
|