import test from "node:test"; import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { buildApiHelp, openApiDocument } from "../../src/api/openapi.js"; import { env } from "../../src/config/env.js"; const api = openApiDocument as unknown as { paths: Record>>; components: { schemas: Record> }; }; test("OpenAPI describes authenticated OCR ingestion, status, review, approval, and rejection", () => { const ingestResponses = api.paths["/ingest"]!.post!.responses as Record }>; const uploadResponses = api.paths["/ingest/upload"]!.post!.responses as typeof ingestResponses; assert.deepEqual(ingestResponses["202"]!.content!["application/json"]!.schema, { oneOf: [{ $ref: "#/components/schemas/OcrAccepted" }, { $ref: "#/components/schemas/IngestResponse" }] }); assert.deepEqual(uploadResponses["202"]!.content!["application/json"]!.schema, { oneOf: [{ $ref: "#/components/schemas/OcrUploadAccepted" }, { $ref: "#/components/schemas/UploadIngestResponse" }] }); const uploadRequest = api.components.schemas.UploadIngestRequest!; assert.deepEqual(uploadRequest.required, ["file"]); assert.deepEqual((uploadRequest.properties as Record).sourceRef, { type: "string", minLength: 1, description: "Optional logical source reference; trimmed and defaults to the uploaded filename." }); for (const [path, method] of [ ["/ingestions/{versionId}", "get"], ["/ingestions/{versionId}/review", "get"], ["/ingestions/{versionId}/recover", "post"], ["/ingestions/{versionId}/documents/{documentId}/pages/{page}/image", "get"], ["/ingestions/{versionId}/approve", "post"], ["/ingestions/{versionId}/reject", "post"] ]) { const operation = api.paths[path]![method]!; assert.deepEqual(operation.security, [{ bearerAuth: [] }], `${method.toUpperCase()} ${path} must require bearer auth`); const responses = operation.responses as Record; assert.ok("200" in responses && "401" in responses && "404" in responses && "503" in responses); } const approve = api.paths["/ingestions/{versionId}/approve"]!.post!; const reject = api.paths["/ingestions/{versionId}/reject"]!.post!; const recover = api.paths["/ingestions/{versionId}/recover"]!.post!; assert.ok("409" in (approve.responses as Record)); assert.ok("400" in (reject.responses as Record)); assert.ok("409" in (reject.responses as Record)); assert.ok("422" in (recover.responses as Record)); assert.deepEqual((approve.requestBody as { content: Record }).content["application/json"]!.schema, { $ref: "#/components/schemas/OcrApprovalRequest" }); assert.deepEqual((reject.requestBody as { content: Record }).content["application/json"]!.schema, { $ref: "#/components/schemas/OcrRejectionRequest" }); assert.deepEqual((recover.requestBody as { content: Record }).content["application/json"]!.schema, { $ref: "#/components/schemas/OcrRecoveryRequest" }); assert.match(buildApiHelp().authentication, /Bearer.*OCR/u); }); test("OpenAPI OCR schemas preserve progress, review evidence, corrections, and decision states", () => { const schemas = api.components.schemas; assert.deepEqual(schemas.OcrPhase!.enum, ["native_extracting", "ocr_queued", "ocr_running", "review_required", "indexing", "ready", "active", "failed", "rejected"]); assert.deepEqual(schemas.OcrAccepted!.required, ["accepted", "sourceId", "versionId", "versionNumber", "state", "phase", "statusUrl", "reviewUrl", "activated"]); assert.deepEqual(schemas.IngestionStatus!.required, ["sourceId", "versionId", "state", "phase", "activated", "documents", "error", "statusUrl", "reviewUrl"]); assert.deepEqual(schemas.OcrReview!.required, ["versionId", "sourceId", "state", "candidateSha256", "baseActiveVersionId", "currentActiveVersionId", "activateRequested", "processingFingerprint", "metadataHash", "documents"]); const approvalProperties = schemas.OcrApprovalRequest!.properties as Record; assert.deepEqual(approvalProperties.corrections!.items, { $ref: "#/components/schemas/OcrCorrection" }); assert.deepEqual(schemas.OcrDecisionResponse!.properties, { versionId: { type: "string", format: "uuid" }, state: { type: "string", enum: ["indexing", "rejected"] }, activated: { type: "boolean" } }); }); test("OCR deployment defaults and container wiring match the private durable contract", async () => { assert.equal(env.ragVersion, "0.2.0"); assert.equal(env.buildRevision, "unknown"); assert.deepEqual({ root: env.ocrArtifactRoot, uploadBytes: (env as unknown as Record).ocrMaxUploadBytes, pages: (env as unknown as Record).ocrMaxPages, pageTimeoutMs: (env as unknown as Record).ocrPageTimeoutMs, totalTimeoutMs: (env as unknown as Record).ocrTotalTimeoutMs }, { root: "/data/ingestions", uploadBytes: 50 * 1024 * 1024, pages: 100, pageTimeoutMs: 60_000, totalTimeoutMs: 15 * 60_000 }); const dockerfile = await readFile(new URL("../../Dockerfile", import.meta.url), "utf8"); assert.match(dockerfile, /VOLUME \["\/data\/ingestions"\]/u); assert.match(dockerfile, /ENV NODE_ENV=production OCR_ARTIFACT_ROOT=\/data\/ingestions/u); assert.match(dockerfile, /RAG_VERSION=\$\{RAG_VERSION\}/u); assert.match(dockerfile, /BUILD_REVISION=\$\{BUILD_REVISION\}/u); assert.match(dockerfile, /org\.opencontainers\.image\.version/u); assert.match(dockerfile, /org\.opencontainers\.image\.revision/u); assert.match(dockerfile, /USER node/u); assert.match(dockerfile, /dist\/modules\/catalog\/migrations\.js.*dist\/server\.js/u); assert.doesNotMatch(dockerfile, /(?:COPY|ADD)\s+\.env|ENV\s+(?:OCR_INTERNAL_TOKEN|LIFECYCLE_ADMIN_TOKEN)=/u); });