rag-service/tests/catalog/migration-002.test.ts

67 lines
3.1 KiB
TypeScript

import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
const migrationUrl = new URL("../../migrations/002_ocr_review.sql", import.meta.url);
async function readMigration(): Promise<string> {
return readFile(migrationUrl, "utf8");
}
test("migration 002 persists durable OCR jobs with idempotency and lease fields", async () => {
const sql = await readMigration();
assert.match(sql, /CREATE TABLE IF NOT EXISTS rag_ocr_jobs/i);
assert.match(sql, /remote_job_id text NULL/i);
assert.match(sql, /remote_idempotency_key text NOT NULL/i);
assert.match(sql, /requested_pages integer\[\] NOT NULL/i);
assert.match(sql, /state text NOT NULL CHECK \(state IN \('queued', 'running', 'succeeded', 'failed'\)\)/i);
assert.match(sql, /attempt_count integer NOT NULL DEFAULT 0/i);
assert.match(sql, /heartbeat_at timestamptz NULL/i);
assert.match(sql, /lease_expires_at timestamptz NULL/i);
assert.match(sql, /next_attempt_at timestamptz NULL/i);
assert.match(sql, /UNIQUE \(version_id, document_id\)/i);
assert.match(
sql,
/FOREIGN KEY \(version_id, document_id\)\s+REFERENCES rag_version_documents\(version_id, document_id\) ON DELETE RESTRICT/i
);
});
test("migration 002 stores auditable page extraction hashes, metrics, and risks", async () => {
const sql = await readMigration();
assert.match(sql, /CREATE TABLE IF NOT EXISTS rag_document_pages/i);
assert.match(sql, /page_number integer NOT NULL CHECK \(page_number > 0\)/i);
assert.match(sql, /extraction_method text NOT NULL CHECK \(extraction_method IN \('native', 'ocr', 'blank'\)\)/i);
assert.match(sql, /native_text_hash char\(64\) NULL/i);
assert.match(sql, /ocr_text_hash char\(64\) NULL/i);
assert.match(sql, /candidate_text_hash char\(64\) NULL/i);
assert.match(sql, /reviewed_text_hash char\(64\) NULL/i);
assert.match(sql, /metrics jsonb NOT NULL DEFAULT '\{\}'/i);
assert.match(sql, /risk_tokens jsonb NOT NULL DEFAULT '\[\]'/i);
assert.match(sql, /blocked_reason text NULL/i);
assert.match(sql, /PRIMARY KEY \(version_id, document_id, page_number\)/i);
});
test("migration 002 makes review correction targets unique and page-bound", async () => {
const sql = await readMigration();
assert.match(sql, /CREATE TABLE IF NOT EXISTS rag_review_corrections/i);
assert.match(sql, /expected_line_hash char\(64\) NOT NULL/i);
assert.match(sql, /replacement_text text NOT NULL/i);
assert.match(sql, /reviewed_by text NOT NULL/i);
assert.match(
sql,
/FOREIGN KEY \(version_id, document_id, page_number\)\s+REFERENCES rag_document_pages\(version_id, document_id, page_number\) ON DELETE RESTRICT/i
);
assert.match(sql, /UNIQUE \(version_id, document_id, page_number, line_id\)/i);
});
test("migration 002 prevents duplicate non-terminal OCR identities", async () => {
const sql = await readMigration();
assert.match(
sql,
/CREATE UNIQUE INDEX IF NOT EXISTS rag_one_pending_ocr_identity\s+ON rag_source_versions\(source_id, original_manifest_hash, processing_fingerprint, metadata_hash\)\s+WHERE source_content_hash IS NULL\s+AND state IN \('pending', 'indexing', 'review_required'\)/i
);
});