cb74bc9307
- inbox-api.test.ts: pin ?download=true POST contract; blob + filename + X-Cyclone-Serialize-Errors parsing; non-2xx error surfacing. - Inbox.test.tsx: end-to-end multi-select → Resubmit + Download path verifies api call args and downloadBlob wiring. - inbox-api.ts: drop redundant isConfigured short-circuit in download variant (backend has its own auth gate) and switch error reading to res.text() to match Blob response shape.
127 lines
4.3 KiB
TypeScript
127 lines
4.3 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
fetchInboxLanes,
|
|
matchCandidate,
|
|
dismissCandidates,
|
|
resubmitRejected,
|
|
resubmitRejectedWithDownload,
|
|
exportInboxCsvUrl,
|
|
} from "./inbox-api";
|
|
|
|
const BASE = "http://test.local";
|
|
|
|
describe("inbox-api", () => {
|
|
it("fetchInboxLanes hits /api/inbox/lanes", async () => {
|
|
const fetchSpy = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ rejected: [], candidates: [], unmatched: [], done_today: [] }),
|
|
});
|
|
vi.stubGlobal("fetch", fetchSpy);
|
|
const lanes = await fetchInboxLanes();
|
|
expect(fetchSpy).toHaveBeenCalledWith(`${BASE}/api/inbox/lanes`);
|
|
expect(lanes.rejected).toEqual([]);
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it("matchCandidate posts to the candidates/{remit_id}/match endpoint", async () => {
|
|
const fetchSpy = vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) });
|
|
vi.stubGlobal("fetch", fetchSpy);
|
|
await matchCandidate("R-1", "C-1");
|
|
expect(fetchSpy).toHaveBeenCalledWith(
|
|
`${BASE}/api/inbox/candidates/R-1/match`,
|
|
expect.objectContaining({
|
|
method: "POST",
|
|
body: JSON.stringify({ claim_id: "C-1" }),
|
|
}),
|
|
);
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it("dismissCandidates posts pairs", async () => {
|
|
const fetchSpy = vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) });
|
|
vi.stubGlobal("fetch", fetchSpy);
|
|
await dismissCandidates([{ claim_id: "C-1", remit_id: "R-1" }]);
|
|
expect(fetchSpy).toHaveBeenCalledWith(
|
|
`${BASE}/api/inbox/candidates/dismiss`,
|
|
expect.objectContaining({
|
|
method: "POST",
|
|
body: JSON.stringify({ pairs: [{ claim_id: "C-1", remit_id: "R-1" }] }),
|
|
}),
|
|
);
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it("resubmitRejected posts claim_ids", async () => {
|
|
const fetchSpy = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({ ok: true, resubmitted: ["C-1"], conflicts: [] }),
|
|
});
|
|
vi.stubGlobal("fetch", fetchSpy);
|
|
const r = await resubmitRejected(["C-1"]);
|
|
expect(fetchSpy).toHaveBeenCalledWith(
|
|
`${BASE}/api/inbox/rejected/resubmit`,
|
|
expect.objectContaining({ method: "POST" }),
|
|
);
|
|
expect(r.resubmitted).toEqual(["C-1"]);
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it("exportInboxCsvUrl returns the export endpoint with the lane", () => {
|
|
expect(exportInboxCsvUrl("rejected")).toBe(`${BASE}/api/inbox/export.csv?lane=rejected`);
|
|
});
|
|
|
|
it("resubmitRejectedWithDownload posts to ?download=true and returns blob + filename", async () => {
|
|
// The download path returns a Blob (zip) plus the suggested filename
|
|
// from Content-Disposition, and surfaces per-claim serialize errors
|
|
// via X-Cyclone-Serialize-Errors (JSON-encoded array).
|
|
const zipBytes = new Uint8Array([0x50, 0x4b, 0x03, 0x04]); // PK header
|
|
const fetchSpy = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
blob: async () => new Blob([zipBytes], { type: "application/zip" }),
|
|
headers: {
|
|
get: (name: string) => {
|
|
const lower = name.toLowerCase();
|
|
if (lower === "content-disposition") {
|
|
return 'attachment; filename="resubmit-3-claims.zip"';
|
|
}
|
|
if (lower === "x-cyclone-serialize-errors") {
|
|
return JSON.stringify([{ claim_id: "C2", reason: "no raw_json" }]);
|
|
}
|
|
return null;
|
|
},
|
|
},
|
|
});
|
|
vi.stubGlobal("fetch", fetchSpy);
|
|
|
|
const out = await resubmitRejectedWithDownload(["C1", "C2", "C3"]);
|
|
expect(fetchSpy).toHaveBeenCalledWith(
|
|
`${BASE}/api/inbox/rejected/resubmit?download=true`,
|
|
expect.objectContaining({
|
|
method: "POST",
|
|
body: JSON.stringify({ claim_ids: ["C1", "C2", "C3"] }),
|
|
}),
|
|
);
|
|
expect(out.filename).toBe("resubmit-3-claims.zip");
|
|
expect(out.blob).toBeInstanceOf(Blob);
|
|
expect(out.serializeErrors).toEqual([
|
|
{ claim_id: "C2", reason: "no raw_json" },
|
|
]);
|
|
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it("resubmitRejectedWithDownload surfaces non-2xx as an Error", async () => {
|
|
const fetchSpy = vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 400,
|
|
statusText: "Bad Request",
|
|
text: async () => "claim_ids required",
|
|
headers: { get: () => null },
|
|
});
|
|
vi.stubGlobal("fetch", fetchSpy);
|
|
|
|
await expect(resubmitRejectedWithDownload([])).rejects.toThrow(/400/);
|
|
vi.unstubAllGlobals();
|
|
});
|
|
});
|