MEGAINFRA-WEBAPP/src/lib/document-status.test.ts
DEls 8654c65e6f feat(ingest): add honest document status resolution
The three synchronous importers (timesheets, fuel, plant hours) used to
write status: "processed" on their audit row before a single insert ran,
and never updated it. Production had 21 such phantom-processed batches
across payroll and plant hours. resolveDocumentOutcome() computes the
real status from actual insert counts, so no route can claim success it
didn't earn.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 13:43:15 +02:00

80 lines
2.6 KiB
TypeScript

// src/lib/document-status.test.ts
import { describe, it, expect } from "vitest";
import { resolveDocumentOutcome } from "./document-status";
const NOW = new Date("2026-09-11T10:00:00.000Z");
describe("resolveDocumentOutcome", () => {
it("marks a clean import processed with the real row count", () => {
const r = resolveDocumentOutcome(
{ rowsTotal: 40, rowsCreated: 40, rowErrors: [] },
NOW,
);
expect(r.processing_status).toBe("processed");
expect(r.rows_created).toBe(40);
expect(r.rows_failed).toBe(0);
expect(r.last_error).toBe("");
expect(r.processed_at).toBe(NOW.toISOString());
});
it("marks a partial import processed but records the failures", () => {
const r = resolveDocumentOutcome(
{
rowsTotal: 10,
rowsCreated: 7,
rowErrors: [
{ row: 3, reason: "unknown vehicle qr=1234" },
{ row: 8, reason: "invalid date" },
{ row: 9, reason: "invalid litres" },
],
},
NOW,
);
expect(r.processing_status).toBe("processed");
expect(r.rows_created).toBe(7);
expect(r.rows_failed).toBe(3);
expect(r.last_error).toBe("3 of 10 rows failed");
expect(r.row_errors).toHaveLength(3);
});
// THE REGRESSION GUARD.
// Production had 19 plant_hours batches marked `processed` against a
// single plant_hours_log row, because status was hardcoded at create
// time before any insert ran. A document that created nothing is not
// processed, no matter what the caller thinks.
it("never reports processed when nothing was created", () => {
const r = resolveDocumentOutcome(
{
rowsTotal: 12,
rowsCreated: 0,
rowErrors: [{ row: 1, reason: "unknown vehicle qr=9999" }],
},
NOW,
);
expect(r.processing_status).toBe("failed");
expect(r.rows_created).toBe(0);
expect(r.last_error).toBe("unknown vehicle qr=9999");
});
it("fails a file that parsed to no rows at all", () => {
const r = resolveDocumentOutcome(
{ rowsTotal: 0, rowsCreated: 0, rowErrors: [] },
NOW,
);
expect(r.processing_status).toBe("failed");
expect(r.last_error).toBe("no data rows found in file");
});
it("caps row_errors so a pathological sheet cannot blow the json field", () => {
const many = Array.from({ length: 500 }, (_, i) => ({
row: i,
reason: "bad",
}));
const r = resolveDocumentOutcome(
{ rowsTotal: 500, rowsCreated: 0, rowErrors: many },
NOW,
);
expect(r.row_errors).toHaveLength(200);
expect(r.rows_failed).toBe(500);
});
});