import assert from "node:assert/strict"; import test from "node:test"; import { createApp } from "../../src/app.js"; import { env } from "../../src/config/env.js"; import { OcrReadyIndexingService } from "../../src/modules/ocr/indexing.js"; import { OcrReviewService } from "../../src/modules/ocr/review.js"; import type { OcrReviewCandidate } from "../../src/modules/ocr/review.js"; const token = "admin-token"; function candidate(state: string): OcrReviewCandidate { return { versionId: "11111111-2222-4333-8444-555555555555", sourceId: "src:scan.pdf", state: state as OcrReviewCandidate["state"], baseActiveVersionId: "active-1", currentActiveVersionId: "active-1", activateRequested: true, processingFingerprint: "fp", metadataHash: "mh", candidateSha256: "candidate-hash", documents: [{ documentId: "doc:scan", pages: [{ page: 1, imageUrl: "/img", nativeText: "", ocr: { text: "CBGO4a", lines: [{ lineId: "line-1", text: "CBGO4a", confidence: 0.9, bbox: [0, 0, 1, 1], lineSha256: "hash" }] }, candidateText: "CBGO4a", differences: [], risks: [] }] }] } as OcrReviewCandidate; } test("production approval triggers durable indexing and reports ready", async (context) => { const previous = { lifecycle: env.knowledgeLifecycleEnforced, enabled: env.ocrIngestEnabled, admin: env.lifecycleAdminToken }; Object.assign(env, { knowledgeLifecycleEnforced: true, ocrIngestEnabled: true, lifecycleAdminToken: token }); context.after(() => Object.assign(env, previous)); let reviewed: string | undefined; let indexedCandidate: string | undefined; const review = new OcrReviewService({ async loadCandidate() { return candidate(reviewed ? "indexing" : "review_required"); }, async commitApproval(input) { reviewed = input.reviewedText; }, async commitRejection() { throw new Error("rejection is out of scope"); } }); const indexing = new OcrReadyIndexingService({ async findReusableVersion() { return undefined; }, async indexReviewed(value) { indexedCandidate = value.reviewedText; return 1; }, async markReady() { /* ready marker */ }, async settleReusable() { throw new Error("no reusable path"); }, async activateVersion() { throw new Error("activation is a separate unit"); } } as never); const server = createApp({ reviewService: review, indexingService: indexing, startReconciler: false }).listen(0); context.after(() => server.close()); const address = server.address(); assert.ok(address && typeof address === "object"); const base = `http://127.0.0.1:${address.port}`; const versionId = "11111111-2222-4333-8444-555555555555"; const approval = await fetch(`${base}/ingestions/${versionId}/approve`, { method: "POST", headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, body: JSON.stringify({ candidateSha256: "candidate-hash", expectedActiveVersionId: "active-1", reviewedBy: "reviewer", corrections: [] }) }); assert.equal(approval.status, 200); const body = await approval.json() as { state: string; activated: boolean }; assert.equal(body.state, "ready"); assert.equal(body.activated, false); assert.equal(reviewed, "CBGO4a"); assert.equal(indexedCandidate, "CBGO4a"); }); test("approval without a configured indexing service stays at the indexing boundary with 503", async (context) => { const previous = { lifecycle: env.knowledgeLifecycleEnforced, enabled: env.ocrIngestEnabled, admin: env.lifecycleAdminToken }; Object.assign(env, { knowledgeLifecycleEnforced: true, ocrIngestEnabled: true, lifecycleAdminToken: token }); context.after(() => Object.assign(env, previous)); const review = new OcrReviewService({ async loadCandidate() { return candidate("review_required"); }, async commitApproval() { /* recorded */ }, async commitRejection() { throw new Error("out of scope"); } }); const server = createApp({ reviewService: review, startReconciler: false }).listen(0); context.after(() => server.close()); const address = server.address(); assert.ok(address && typeof address === "object"); const base = `http://127.0.0.1:${address.port}`; const versionId = "11111111-2222-4333-8444-555555555555"; const approval = await fetch(`${base}/ingestions/${versionId}/approve`, { method: "POST", headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, body: JSON.stringify({ candidateSha256: "candidate-hash", expectedActiveVersionId: "active-1", reviewedBy: "reviewer", corrections: [] }) }); assert.equal(approval.status, 503); assert.equal(((await approval.json()) as { code?: string }).code, "OCR_INDEXING_UNAVAILABLE"); });