Files
SnapOtter/tests/unit/helpers/generated-case-accounting.test.ts
SnapOtterandGitHub 1b41da7615 ci(nightly): repair five job classes broken by the #649 QA hardening (#695)
Nightly has been red since 07-28; per-PR CI and main are green. Two investigations traced all five failing classes to #649: extended-matrix required AI bundles it never installs (removed), nightly SYSTEM_DEPS drifted from ci.yml (added libreoffice + a doc-binaries composite for pandoc/pdfcpu), the generated-case classifier only skipped ffmpeg (widened to pdfcpu/soffice/pandoc + excluded repo-audit specs from the lean docker image), a settings spec capped loginAttemptLimit and 429-cascaded the serial bucket (restore via API), and type-to-search refused keystrokes under a route announcer's programmatic focus (guard added). A nightly dispatch on the branch confirmed all five classes green.
2026-07-31 02:09:39 +08:00

160 lines
5.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
featureUnavailableDisposition,
GeneratedCaseAccounting,
isEngineUnavailableFailure,
} from "../../helpers/generated-case-accounting.js";
describe("isEngineUnavailableFailure", () => {
// The lean docker test image skips binary-gated tools by design, so every
// spawn engine gates the same way ffmpeg does: a missing binary is a skip,
// not a product failure. A real crash (exit code + stderr) still fails.
it.each([
"ffmpeg binary not found (set FFMPEG_PATH or install ffmpeg)",
"pdfcpu binary not found (set PDFCPU_PATH or install pdfcpu)",
"soffice binary not found (set SOFFICE_PATH or install LibreOffice)",
])("treats a missing-binary message as engine-unavailable: %s", (message) => {
expect(isEngineUnavailableFailure(new Error(message))).toBe(true);
});
it.each(["spawn pandoc ENOENT", "spawn /usr/local/bin/pdfcpu ENOENT", "spawn soffice ENOENT"])(
"treats a spawn ENOENT for a known engine as engine-unavailable: %s",
(message) => {
expect(isEngineUnavailableFailure(new Error(message))).toBe(true);
},
);
it("keeps a real processing crash a failure", () => {
expect(
isEngineUnavailableFailure(new Error("pdfcpu exited with code 1: invalid page range")),
).toBe(false);
});
});
describe("GeneratedCaseAccounting", () => {
it("fails a tool that executes no generated cases", () => {
const accounting = new GeneratedCaseAccounting("resize");
expect(() => accounting.assertCovered()).toThrow(
"resize: generated coverage incomplete (attempted=0, accepted=0, rejected=0, skipped=0)",
);
});
it("fails a tool whose generated cases are all rejected", () => {
const accounting = new GeneratedCaseAccounting("resize");
accounting.attempt();
accounting.reject();
expect(() => accounting.assertCovered()).toThrow(
"resize: generated coverage incomplete (attempted=1, accepted=0, rejected=1, skipped=0)",
);
});
it("reports accepted and rejected cases after proving coverage", () => {
const accounting = new GeneratedCaseAccounting("resize");
accounting.attempt();
accounting.accept();
accounting.attempt();
accounting.reject();
expect(accounting.assertCovered()).toEqual({
attempted: 2,
accepted: 1,
rejected: 1,
skipped: 0,
skips: [],
});
});
it("records bounded machine-readable skip categories and reasons", () => {
const accounting = new GeneratedCaseAccounting("remove-background");
accounting.attempt();
accounting.skip("optional-feature", "background-removal bundle is not installed");
accounting.attempt();
accounting.accept();
expect(accounting.assertCovered()).toEqual({
attempted: 2,
accepted: 1,
rejected: 0,
skipped: 1,
skips: [
{
category: "optional-feature",
reason: "background-removal bundle is not installed",
count: 1,
},
],
});
});
it("rejects impossible accounting where accepted exceeds attempted", () => {
const accounting = new GeneratedCaseAccounting("resize");
accounting.attempt();
accounting.accept();
accounting.accept();
expect(() => accounting.assertCovered()).toThrow(
"resize: generated accounting is not conserved (attempted=1, accepted=2, rejected=0, skipped=0)",
);
});
it("rejects campaigns with no accepted case even when all attempts have outcomes", () => {
const accounting = new GeneratedCaseAccounting("resize");
for (let index = 0; index < 99; index += 1) {
accounting.attempt();
accounting.reject();
}
accounting.attempt();
accounting.skip("missing-fixture", "one optional fixture was unavailable");
expect(() => accounting.assertCovered()).toThrow(
"resize: generated coverage incomplete (attempted=100, accepted=0, rejected=99, skipped=1)",
);
});
it("rejects an unbounded or unknown skip reason", () => {
const accounting = new GeneratedCaseAccounting("resize");
accounting.attempt();
expect(() =>
accounting.skip("missing-fixture", `fixture unavailable: ${"x".repeat(300)}`),
).toThrow("resize: generated skip reason must be 1-240 characters");
});
});
describe("featureUnavailableDisposition", () => {
it("turns an absent optional AI prerequisite into an explicit skip", () => {
expect(
featureUnavailableDisposition({
toolId: "remove-background",
statusCode: 501,
code: "FEATURE_NOT_INSTALLED",
requireAiFeatures: false,
}),
).toBe("skip");
});
it("fails a missing AI feature in an installed-feature campaign", () => {
expect(() =>
featureUnavailableDisposition({
toolId: "remove-background",
statusCode: 501,
code: "FEATURE_NOT_INSTALLED",
requireAiFeatures: true,
}),
).toThrow("remove-background: required AI feature returned 501 FEATURE_NOT_INSTALLED");
});
it("does not classify unrelated responses as prerequisite skips", () => {
expect(
featureUnavailableDisposition({
toolId: "resize",
statusCode: 422,
code: "PROCESSING_FAILED",
requireAiFeatures: false,
}),
).toBe("continue");
});
});