mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
test(qa): align AI oracles with fixtures and honor QA_OUT_DIR (#686)
The ocr oracle demanded six pangram words when ocr-clean.png prints five; the ocr-pdf oracle expected a searchable PDF when the tool's contract is text extraction; media-30s.mp4 was eight seconds long. Fix all three, regenerate tool-contract.json from current schemas, record the new fixture in the manifest, and add a QA_OUT_DIR override so parallel machines stop clobbering each other's lane output. Fixes #677.
This commit is contained in:
Vendored
+3
-3
@@ -402,11 +402,11 @@
|
||||
},
|
||||
{
|
||||
"path": "video/valid/media-30s.mp4",
|
||||
"sourceUrl": "Big Buck Bunny (Blender Foundation, www.bigbuckbunny.org) via archive.org, 8s segment at 30s, 480x270",
|
||||
"sourceUrl": "Big Buck Bunny (Blender Foundation, www.bigbuckbunny.org) via archive.org, 8s segment at 30s, 480x270, stream-looped to a true 30.1s (#677)",
|
||||
"license": "CC-BY",
|
||||
"toolsServed": [],
|
||||
"sha256": "6ad37435eeb01cc694b918f8588255fead60eb01f2a06ca0dc904070064e9136",
|
||||
"bytes": 247284
|
||||
"sha256": "19c3256788660ee49766497dee364c38bf508050f505f949b8ad41714d7db4de",
|
||||
"bytes": 941146
|
||||
},
|
||||
{
|
||||
"path": "video/valid/speech-10s.mp4",
|
||||
|
||||
BIN
Binary file not shown.
@@ -570,17 +570,6 @@ export function expectJapaneseScript(text: string, minimumChars = 4): void {
|
||||
);
|
||||
}
|
||||
|
||||
/** PDF OCR: a real PDF that now carries a selectable text layer. */
|
||||
export function expectSearchablePdf(output: Buffer): void {
|
||||
assertOracle(output.subarray(0, 5).toString("latin1") === "%PDF-", "artifact is not a PDF");
|
||||
assertOracle(output.length > 1000, "PDF artifact is implausibly small");
|
||||
const body = output.toString("latin1");
|
||||
assertOracle(
|
||||
body.includes("/Font") || body.includes("BT\n") || body.includes("Tj"),
|
||||
"PDF carries no text-drawing operators, so no OCR layer was added",
|
||||
);
|
||||
}
|
||||
|
||||
/** Animated cut-outs must stay animated: a single-frame result is a regression. */
|
||||
export async function expectAnimatedFrames(output: Buffer, minimumFrames = 2): Promise<void> {
|
||||
const meta = await sharp(output, { pages: -1 }).metadata();
|
||||
|
||||
@@ -44,10 +44,15 @@ const PASSWORD = process.env.QA_PASSWORD ?? "";
|
||||
const CONCURRENCY = Number(process.env.QA_CONCURRENCY ?? 4);
|
||||
const PAIRWISE_CAP = Number(process.env.QA_PAIRWISE_CAP ?? 6);
|
||||
const FORMAT_WITNESSES = Number(process.env.QA_FORMAT_WITNESSES ?? 3);
|
||||
// QA_OUT_DIR lets several machines sweep in parallel without clobbering each
|
||||
// other's lane-<mode>.jsonl (#677).
|
||||
const OUT_DIR_OVERRIDE = process.env.QA_OUT_DIR;
|
||||
/* biome-ignore-end lint/suspicious/noUndeclaredEnvVars: QA harness runs outside Turbo. */
|
||||
|
||||
const REPO = join(import.meta.dirname, "..", "..");
|
||||
const OUT_DIR = join(REPO, "docs", "qa", "master-20260724", "evidence", "processing-ai", "final");
|
||||
const OUT_DIR =
|
||||
OUT_DIR_OVERRIDE ??
|
||||
join(REPO, "docs", "qa", "master-20260724", "evidence", "processing-ai", "final");
|
||||
|
||||
/** Class-aware hard timeouts. Exceeding one is a finding, never a silent skip. */
|
||||
const TIMEOUT_MS: Record<string, number> = { fast: 240_000, long: 480_000, ai: 900_000 };
|
||||
|
||||
@@ -31,12 +31,15 @@ const BASE = process.env.QA_BASE_URL ?? "http://localhost:13492";
|
||||
const USERNAME = process.env.QA_USERNAME ?? "admin";
|
||||
const PASSWORD = process.env.QA_PASSWORD ?? "";
|
||||
const PROJECT = process.env.QA_COMPOSE_PROJECT ?? "snapotter-qa-f4c2bde9";
|
||||
const OUT_DIR_OVERRIDE = process.env.QA_OUT_DIR;
|
||||
/* biome-ignore-end lint/suspicious/noUndeclaredEnvVars: QA harness runs outside Turbo. */
|
||||
|
||||
const APP = `${PROJECT}-app`;
|
||||
const PG = `${PROJECT}-postgres`;
|
||||
const REPO = join(import.meta.dirname, "..", "..");
|
||||
const OUT_DIR = join(REPO, "docs", "qa", "master-20260724", "evidence", "processing-ai", "final");
|
||||
const OUT_DIR =
|
||||
OUT_DIR_OVERRIDE ??
|
||||
join(REPO, "docs", "qa", "master-20260724", "evidence", "processing-ai", "final");
|
||||
|
||||
const PNG = resolveFixture(".png", "image") as string;
|
||||
const JPG = resolveFixture(".jpg", "image") as string;
|
||||
|
||||
@@ -3374,7 +3374,7 @@
|
||||
},
|
||||
{
|
||||
"key": "sobelThreshold",
|
||||
"values": [1, 10.5, 20, null]
|
||||
"values": [1, 11, 20, null]
|
||||
},
|
||||
{
|
||||
"key": "square",
|
||||
|
||||
@@ -37,7 +37,6 @@ import {
|
||||
expectRedPixelsReduced,
|
||||
expectRegionRewritten,
|
||||
expectSameSizeButChanged,
|
||||
expectSearchablePdf,
|
||||
expectSrtArtifact,
|
||||
expectUpscaled,
|
||||
} from "../helpers/installed-ai-output-oracles.js";
|
||||
@@ -102,7 +101,11 @@ const SPEECH_WAV = `${FIXTURES}/audio/valid/speech-10s.wav`;
|
||||
const SPEECH_MP4 = `${FIXTURES}/video/valid/speech-10s.mp4`;
|
||||
|
||||
/** The English OCR fixture's known answer, checked word by word. */
|
||||
const OCR_ENGLISH_TERMS = ["the", "quick", "brown", "fox", "jumps", "over", "lazy", "dog"] as const;
|
||||
// ocr-clean.png literally reads "The quick brown fox 12345"; the oracle must
|
||||
// demand the fixture's words, not the full pangram (#677).
|
||||
const OCR_ENGLISH_TERMS = ["the", "quick", "brown", "fox", "12345"] as const;
|
||||
// ocr-scanned.pdf is a chat screenshot about Redis scaling.
|
||||
const OCR_PDF_TERMS = ["redis", "replicas", "cache", "bottleneck"] as const;
|
||||
|
||||
const CASES: ToolCase[] = [
|
||||
{
|
||||
@@ -398,10 +401,10 @@ const CASES: ToolCase[] = [
|
||||
fixture: OCR_CLEAN,
|
||||
contentType: "image/png",
|
||||
settings: { quality: "fast", language: "en", enhance: false },
|
||||
oracle: "recognizes >=6 of the 8 known English fixture words",
|
||||
oracle: "recognizes >=4 of the 5 words printed in the fixture",
|
||||
verify: (out) => {
|
||||
const text = out.toString("utf8");
|
||||
expectRecognizedTerms(text, OCR_ENGLISH_TERMS, 6);
|
||||
expectRecognizedTerms(text, OCR_ENGLISH_TERMS, 4);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -409,8 +412,13 @@ const CASES: ToolCase[] = [
|
||||
fixture: `${FIXTURES}/document/valid/ocr-scanned.pdf`,
|
||||
contentType: "application/pdf",
|
||||
settings: { quality: "fast", language: "en", enhance: false },
|
||||
oracle: "returns a PDF carrying text-drawing operators",
|
||||
verify: (out) => expectSearchablePdf(out),
|
||||
// The tool's contract is text extraction (it writes <name>_ocr.txt); a
|
||||
// searchable-PDF output would be a feature, not this oracle (#677).
|
||||
oracle: "extracts the known scanned text as plain text",
|
||||
verify: (out) => {
|
||||
const text = out.toString("utf8");
|
||||
expectRecognizedTerms(text, OCR_PDF_TERMS, 3);
|
||||
},
|
||||
},
|
||||
{
|
||||
toolId: "transcribe-audio",
|
||||
|
||||
Reference in New Issue
Block a user