71 lines
4.9 KiB
TypeScript
71 lines
4.9 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" }] });
|
|
|
|
for (const [path, method] of [
|
|
["/ingestions/{versionId}", "get"],
|
|
["/ingestions/{versionId}/review", "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!;
|
|
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.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.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: ["ready", "active", "rejected"] }, activated: { type: "boolean" },
|
|
activatedVersionId: { type: "string", format: "uuid" }, errorCode: { type: "string", enum: ["DUPLICATE_REUSABLE_VERSION"] }
|
|
});
|
|
});
|
|
|
|
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, /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);
|
|
});
|