diff --git a/src/app/api/dn-batches/[id]/notes/[noteId]/resolve-item/route.test.ts b/src/app/api/dn-batches/[id]/notes/[noteId]/resolve-item/route.test.ts index fc7ed85..e0b9467 100644 --- a/src/app/api/dn-batches/[id]/notes/[noteId]/resolve-item/route.test.ts +++ b/src/app/api/dn-batches/[id]/notes/[noteId]/resolve-item/route.test.ts @@ -82,4 +82,19 @@ describe("resolve-item route", () => { const { POST } = await import("./route"); await expect(POST(request({ lineIndex: 0, purchaseOrderItemId: ITEM, reason: "verified", rememberAlias: false }), { params: Promise.resolve({ id: BATCH, noteId: NOTE }) })).rejects.toThrow("batch transaction failed"); }); + + it("submits note, audit, and alias mutations as one failing transaction with no committed rows", async () => { + const queued: Array<{ collection: string; method: string }> = []; + lease.batch.mockImplementationOnce(async (mutations: Array<{ collection: string; method: string }>) => { + queued.push(...mutations); + // Simulate PocketBase's all-or-nothing batch response: the server + // rejects after validating the queued mutations, so none commit. + throw new Error("batch transaction failed after validation"); + }); + const { POST } = await import("./route"); + await expect(POST(request({ lineIndex: 0, purchaseOrderItemId: ITEM, reason: "verified", rememberAlias: true }), { params: Promise.resolve({ id: BATCH, noteId: NOTE }) })).rejects.toThrow("batch transaction failed after validation"); + expect(queued.map((mutation) => mutation.collection)).toEqual(["dn_batch_notes", "dn_review_events", "dn_item_aliases"]); + expect(rows.dn_review_events).toHaveLength(0); + expect(rows.dn_item_aliases).toHaveLength(0); + }); }); diff --git a/src/lib/dn-pipeline/readiness.test.ts b/src/lib/dn-pipeline/readiness.test.ts index 57c66fc..93b6065 100644 --- a/src/lib/dn-pipeline/readiness.test.ts +++ b/src/lib/dn-pipeline/readiness.test.ts @@ -59,4 +59,10 @@ describe("evaluateCandidateReadiness", () => { expect(result.ready).toBe(false); expect(result.reasons).toContain("assembly_revision_missing"); }); + + it("fails closed on an unknown persisted blocking check", () => { + const result = evaluateCandidateReadiness({ ...base, checks: [...base.checks, { id: "future_financial_gate", passed: false }] }); + expect(result.ready).toBe(false); + expect(result.reasons).toContain("unknown_check_not_passed:future_financial_gate"); + }); });