rag-service/tests/ocr/contracts-deploy.test.ts

83 lines
5.7 KiB
TypeScript

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<string, Record<string, Record<string, unknown>>>;
components: { schemas: Record<string, Record<string, unknown>> };
};
test("OpenAPI describes authenticated OCR ingestion, status, review, approval, and rejection", () => {
const ingestResponses = api.paths["/ingest"]!.post!.responses as Record<string, { content?: Record<string, { schema?: unknown }> }>;
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<string, unknown>).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<string, unknown>;
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<string, unknown>));
assert.ok("400" in (reject.responses as Record<string, unknown>));
assert.ok("409" in (reject.responses as Record<string, unknown>));
assert.ok("422" in (recover.responses as Record<string, unknown>));
assert.deepEqual((approve.requestBody as { content: Record<string, { schema: unknown }> }).content["application/json"]!.schema,
{ $ref: "#/components/schemas/OcrApprovalRequest" });
assert.deepEqual((reject.requestBody as { content: Record<string, { schema: unknown }> }).content["application/json"]!.schema,
{ $ref: "#/components/schemas/OcrRejectionRequest" });
assert.deepEqual((recover.requestBody as { content: Record<string, { schema: unknown }> }).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<string, { items?: unknown }>;
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.deepEqual({
root: env.ocrArtifactRoot,
uploadBytes: (env as unknown as Record<string, unknown>).ocrMaxUploadBytes,
pages: (env as unknown as Record<string, unknown>).ocrMaxPages,
pageTimeoutMs: (env as unknown as Record<string, unknown>).ocrPageTimeoutMs,
totalTimeoutMs: (env as unknown as Record<string, unknown>).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, /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);
});