Files
elizaos--eliza/scripts/check-pr-evidence.test.mjs
wehub-resource-sync 426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

585 lines
20 KiB
JavaScript

/**
* Tests for the pull request evidence checker. The fixtures model the PR body
* instead of shelling out so the gate's parsing rules stay deterministic and
* cheap to run in PR workflows.
*/
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { describe, it } from "node:test";
import { fileURLToPath } from "node:url";
import {
boundRowBlock,
evaluatePrEvidence,
extractEvidenceRows,
findRetiredRepoEvidenceFiles,
hasArtifactReference,
hasNaWithReason,
hasOcrEvidenceReference,
hasVisualArtifactReference,
isChecked,
isRowSatisfied,
isRowSatisfiedForContext,
parseLabels,
REQUIRED_EVIDENCE_ROWS,
requiresSurfaceArtifacts,
requiresSurfaceArtifactsFromFiles,
SURFACE_ARTIFACT_ROW_IDS,
} from "./check-pr-evidence.mjs";
const HERE = dirname(fileURLToPath(import.meta.url));
const TEMPLATE_PATH = join(HERE, "..", ".github", "pull_request_template.md");
const RETIRED_REPO_EVIDENCE_PATH = [
".github",
["issue", "evidence"].join("-"),
].join("/");
function buildBody(overrides = {}) {
const defaults = {
"before-screenshots":
"- [ ] Before screenshots `N/A - backend-only change, no UI surface`.",
"after-screenshots":
"- [ ] After screenshots `N/A - backend-only change, no UI surface`.",
"walkthrough-video":
"- [x] A video walkthrough: https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000000",
"backend-logs":
"- [ ] Backend logs: [backend.txt](https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000001)",
"frontend-logs": "- [ ] Frontend logs `N/A - no frontend change`.",
"llm-trajectory":
"- [ ] Real-LLM trajectory: [report](https://example.com/report.json)",
"domain-artifacts":
"- [ ] Domain artifacts: OCR report https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000007",
};
const merged = { ...defaults, ...overrides };
return REQUIRED_EVIDENCE_ROWS.map(
({ id }) => `<!-- evidence-row:${id} -->\n${merged[id] ?? ""}`,
).join("\n\n");
}
describe("check-pr-evidence parser", () => {
it("passes when every evidence row has an artifact or N/A reason", () => {
const { ok, findings } = evaluatePrEvidence(buildBody());
assert.equal(ok, true);
assert.ok(findings.every((finding) => finding.status === "ok"));
});
it("fails on a single blank row", () => {
const { ok, findings } = evaluatePrEvidence(
buildBody({
"backend-logs":
"- [ ] Backend logs show the real code path firing end to end, or are marked `N/A - <reason>`.",
}),
);
assert.equal(ok, false);
assert.equal(
findings.find((finding) => finding.id === "backend-logs").status,
"blank",
);
assert.equal(
findings.filter((finding) => finding.status === "ok").length,
REQUIRED_EVIDENCE_ROWS.length - 1,
);
});
it("fails when a row is checked without an artifact or N/A reason", () => {
const { ok, findings } = evaluatePrEvidence(
buildBody({
"backend-logs": "- [x] Backend logs attached.",
}),
);
assert.equal(ok, false);
assert.equal(
findings.find((finding) => finding.id === "backend-logs").status,
"blank",
);
});
it("fails when every required row is checked without artifacts or N/A reasons", () => {
const checkedRows = Object.fromEntries(
REQUIRED_EVIDENCE_ROWS.map(({ id, label }) => [
id,
`- [x] ${label} attached.`,
]),
);
const { ok, findings } = evaluatePrEvidence(buildBody(checkedRows));
assert.equal(ok, false);
assert.ok(findings.every((finding) => finding.status === "blank"));
});
it("passes when every row is marked `N/A - <reason>`", () => {
const body = REQUIRED_EVIDENCE_ROWS.map(
({ id }) =>
`<!-- evidence-row:${id} -->\n- [ ] row \`N/A - not applicable to this change\`.`,
).join("\n\n");
assert.equal(evaluatePrEvidence(body).ok, true);
});
it("fails UI-labeled PRs when screenshot/video rows are only N/A", () => {
const body = REQUIRED_EVIDENCE_ROWS.map(
({ id }) =>
`<!-- evidence-row:${id} -->\n- [ ] row \`N/A - not applicable to this change\`.`,
).join("\n\n");
const { ok, findings } = evaluatePrEvidence(body, REQUIRED_EVIDENCE_ROWS, {
labels: "ui",
});
assert.equal(ok, false);
for (const id of SURFACE_ARTIFACT_ROW_IDS) {
assert.equal(
findings.find((finding) => finding.id === id).status,
"artifact-required",
);
}
assert.equal(
findings.find((finding) => finding.id === "ocr-review").status,
"ocr-required",
);
});
it("passes UI-labeled PRs when screenshot/video rows and OCR proof have concrete artifacts", () => {
const { ok, findings } = evaluatePrEvidence(
buildBody({
"before-screenshots":
"- [ ] Before screenshots: https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000002",
"after-screenshots":
"- [ ] After screenshots: https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000003",
"walkthrough-video":
"- [ ] Walkthrough video: https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000004",
"domain-artifacts":
"- [ ] OCR text readout: https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000008",
}),
REQUIRED_EVIDENCE_ROWS,
{ labels: "frontend" },
);
assert.equal(ok, true);
assert.ok(findings.every((finding) => finding.status === "ok"));
});
it("fails UI-labeled PRs when screenshot/video artifacts omit OCR proof", () => {
const { ok, findings } = evaluatePrEvidence(
buildBody({
"before-screenshots":
"- [ ] Before screenshots: https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000002",
"after-screenshots":
"- [ ] After screenshots: https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000003",
"walkthrough-video":
"- [ ] Walkthrough video: https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000004",
"domain-artifacts":
"- [ ] Domain artifacts `N/A - no domain artifacts produced`.",
}),
REQUIRED_EVIDENCE_ROWS,
{ labels: "ui" },
);
assert.equal(ok, false);
assert.equal(
findings.find((finding) => finding.id === "ocr-review").status,
"ocr-required",
);
});
it("fails on a bare `N/A` with no reason", () => {
const { ok, findings } = evaluatePrEvidence(
buildBody({ "backend-logs": "- [ ] Backend logs N/A" }),
);
assert.equal(ok, false);
assert.equal(
findings.find((finding) => finding.id === "backend-logs").status,
"blank",
);
});
it("reports a required row as missing when its marker is absent", () => {
const body = REQUIRED_EVIDENCE_ROWS.slice(1)
.map(
({ id }) =>
`<!-- evidence-row:${id} -->\n- [ ] N/A - covered elsewhere`,
)
.join("\n\n");
const { ok, findings } = evaluatePrEvidence(body);
assert.equal(ok, false);
assert.equal(
findings.find((finding) => finding.id === "before-screenshots").status,
"missing",
);
});
it("treats an empty body as all-missing", () => {
const { ok, findings } = evaluatePrEvidence("");
assert.equal(ok, false);
assert.ok(findings.every((finding) => finding.status === "missing"));
});
});
describe("check-pr-evidence row primitives", () => {
it("accepts N/A separators with a real reason", () => {
assert.equal(hasNaWithReason("N/A - backend-only, no UI"), true);
assert.equal(hasNaWithReason("N/A: nothing to show here"), true);
assert.equal(hasNaWithReason("N/A \u2014 no domain artifacts"), true);
assert.equal(hasNaWithReason("NA - agent change only"), true);
});
it("rejects bare or placeholder N/A reasons", () => {
assert.equal(hasNaWithReason("N/A"), false);
assert.equal(hasNaWithReason("N/A -"), false);
assert.equal(hasNaWithReason("N/A - "), false);
assert.equal(hasNaWithReason("N/A - <reason>."), false);
});
it("detects links and URLs", () => {
assert.equal(hasArtifactReference("[report](https://x/y.json)"), true);
assert.equal(
hasArtifactReference("see https://github.com/o/r/assets/1"),
true,
);
assert.equal(
hasArtifactReference(
"see https://user-images.githubusercontent.com/1/a.jpg",
),
true,
);
assert.equal(hasArtifactReference("just words, no artifact"), false);
});
it("detects linked OCR evidence without accepting keyword-only prose", () => {
const rows = new Map([
[
"domain-artifacts",
"- [ ] OCR report: https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000009",
],
]);
assert.equal(hasOcrEvidenceReference(rows), true);
assert.equal(
hasOcrEvidenceReference(
new Map([["domain-artifacts", "- [ ] OCR report was reviewed"]]),
),
false,
);
});
it("rejects retired repo-local evidence paths", () => {
assert.equal(
hasArtifactReference(
`committed under ${RETIRED_REPO_EVIDENCE_PATH}/13676-a.png`,
),
false,
);
assert.equal(
hasArtifactReference(
`[proof](${RETIRED_REPO_EVIDENCE_PATH}/13676-a.png)`,
),
false,
);
const { ok, findings } = evaluatePrEvidence(
buildBody({
"backend-logs": `- [ ] Backend logs: ${RETIRED_REPO_EVIDENCE_PATH}/13676-backend.txt`,
}),
);
assert.equal(ok, false);
assert.equal(
findings.find((finding) => finding.id === "backend-logs").status,
"blank",
);
});
it("rejects changed files under the retired repo-local evidence path", () => {
assert.deepEqual(
findRetiredRepoEvidenceFiles([
"packages/app/test-results/report.json",
`${RETIRED_REPO_EVIDENCE_PATH}/13676-backend.txt`,
String.raw`.github\issue-evidence\13676-windows-path.txt`,
]),
[
`${RETIRED_REPO_EVIDENCE_PATH}/13676-backend.txt`,
String.raw`.github\issue-evidence\13676-windows-path.txt`,
],
);
});
it("accepts non-repo evidence directories in the diff", () => {
assert.deepEqual(
findRetiredRepoEvidenceFiles([
"packages/evidence/src/schema.ts",
"packages/app/test-results/issue-evidence/report.json",
]),
[],
);
});
it("detects checked checkboxes without treating them as evidence", () => {
assert.equal(isChecked("- [x] done"), true);
assert.equal(isChecked("- [X] done"), true);
assert.equal(isChecked("- [ ] not done"), false);
assert.equal(isRowSatisfied("- [x] done"), false);
});
it("requires real media on visual rows — page links do not count", () => {
// The gaming vector: linking the PR itself or its /checks tab.
assert.equal(
hasVisualArtifactReference(
"- [ ] Before: https://github.com/elizaOS/eliza/pull/15178/checks",
),
false,
);
assert.equal(
hasVisualArtifactReference(
"- [ ] After: https://github.com/elizaOS/eliza/pull/15178",
),
false,
);
// Real media forms all count.
assert.equal(
hasVisualArtifactReference(
"https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000001",
),
true,
);
assert.equal(
hasVisualArtifactReference("![after](https://example.com/x/after)"),
true,
);
assert.equal(
hasVisualArtifactReference(
'<img src="https://example.com/shot" width="400">',
),
true,
);
assert.equal(
hasVisualArtifactReference("see https://example.com/walkthrough.mp4"),
true,
);
});
it("fails a UI-labeled PR whose visual rows link only to the PR/checks page", () => {
const { ok, findings } = evaluatePrEvidence(
buildBody({
"before-screenshots":
"- [ ] Before screenshots: https://github.com/elizaOS/eliza/pull/15178",
"after-screenshots":
"- [ ] After screenshots: https://github.com/elizaOS/eliza/pull/15178/checks",
"walkthrough-video":
"- [ ] Walkthrough video: https://github.com/elizaOS/eliza/pull/15178/checks",
}),
REQUIRED_EVIDENCE_ROWS,
{ labels: "ui" },
);
assert.equal(ok, false);
for (const id of SURFACE_ARTIFACT_ROW_IDS) {
assert.equal(
findings.find((finding) => finding.id === id).status,
"artifact-required",
);
}
});
it("detects rendered-UI source files in the diff", () => {
assert.equal(
requiresSurfaceArtifactsFromFiles(["packages/ui/src/components/Foo.tsx"]),
true,
);
assert.equal(
requiresSurfaceArtifactsFromFiles(["packages/app/src/views/Home.css"]),
true,
);
assert.equal(
requiresSurfaceArtifactsFromFiles([
String.raw`apps\app\src\Panel.tsx`,
]),
true,
);
// Tests, stories, and server code render nothing a user sees.
assert.equal(
requiresSurfaceArtifactsFromFiles([
"packages/ui/src/components/Foo.test.tsx",
"packages/ui/src/components/Foo.stories.tsx",
"packages/app-core/src/services/thing.ts",
"packages/app/README.md",
]),
false,
);
});
it("forces surface artifacts from a UI diff even without labels", () => {
const body = REQUIRED_EVIDENCE_ROWS.map(
({ id }) =>
`<!-- evidence-row:${id} -->\n- [ ] row \`N/A - not applicable to this change\`.`,
).join("\n\n");
const { ok, findings } = evaluatePrEvidence(body, REQUIRED_EVIDENCE_ROWS, {
changedFiles: ["packages/ui/src/components/NotificationRow.tsx"],
});
assert.equal(ok, false);
for (const id of SURFACE_ARTIFACT_ROW_IDS) {
assert.equal(
findings.find((finding) => finding.id === id).status,
"artifact-required",
);
}
assert.equal(
findings.find((finding) => finding.id === "ocr-review").status,
"ocr-required",
);
});
it("allows N/A before-screenshots when the whole UI surface is newly added", () => {
const media = (id, n) =>
`- [x] ![${id}](https://github.com/user-attachments/assets/00000000-0000-0000-0000-00000000000${n})`;
const body = buildBody({
"before-screenshots":
"- [ ] Before screenshots `N/A - the settings section is new in this PR; no prior surface existed`.",
"after-screenshots": media("after", 2),
"walkthrough-video":
"- [x] https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000003",
"domain-artifacts":
"- [ ] OCR text readout: https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000008",
});
const changedFiles = [
"packages/ui/src/components/settings/NewSection.tsx",
"packages/ui/src/components/settings/new-section.test.ts",
];
// Whole surface added → before may be N/A-with-reason.
const allNew = evaluatePrEvidence(body, REQUIRED_EVIDENCE_ROWS, {
changedFiles,
addedFiles: changedFiles,
});
assert.equal(allNew.ok, true);
// Same body but the surface file was MODIFIED → before still needs media.
const modified = evaluatePrEvidence(body, REQUIRED_EVIDENCE_ROWS, {
changedFiles,
addedFiles: [],
});
assert.equal(modified.ok, false);
assert.equal(
modified.findings.find((f) => f.id === "before-screenshots").status,
"artifact-required",
);
// A bare N/A with no reason never qualifies, even for a new surface.
const bare = evaluatePrEvidence(
buildBody({
"before-screenshots": "- [ ] Before screenshots N/A",
"after-screenshots": media("after", 2),
"walkthrough-video":
"- [x] https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000003",
"domain-artifacts":
"- [ ] OCR text readout: https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000008",
}),
REQUIRED_EVIDENCE_ROWS,
{ changedFiles, addedFiles: changedFiles },
);
assert.equal(bare.ok, false);
});
it("path detection overrides the coarse ui label when files are known", () => {
// The auto-labeler applies `ui` to any packages/ui path; a non-visual .ts
// change must not be forced to attach screenshots.
const body = REQUIRED_EVIDENCE_ROWS.map(
({ id }) =>
`<!-- evidence-row:${id} -->\n- [ ] row \`N/A - non-visual .ts module change\`.`,
).join("\n\n");
const { ok } = evaluatePrEvidence(body, REQUIRED_EVIDENCE_ROWS, {
labels: "ui",
changedFiles: ["packages/ui/src/navigation/index.ts"],
});
assert.equal(ok, true);
// But a rendered-UI file still forces artifacts regardless of labels.
const forced = evaluatePrEvidence(body, REQUIRED_EVIDENCE_ROWS, {
labels: "",
changedFiles: ["packages/ui/src/navigation/index.ts", "packages/ui/src/components/Foo.tsx"],
});
assert.equal(forced.ok, false);
});
it("normalizes labels and detects surface labels", () => {
assert.deepEqual(parseLabels("bug, UI\nNative"), ["bug", "ui", "native"]);
assert.equal(requiresSurfaceArtifacts("testing,backend"), false);
assert.equal(requiresSurfaceArtifacts(["ci", "Frontend"]), true);
});
it("requires N/A-reason or artifact to satisfy a row", () => {
assert.equal(isRowSatisfied("- [ ] `N/A - not applicable`"), true);
assert.equal(isRowSatisfied("- [ ] [proof](https://e/x.png)"), true);
assert.equal(
isRowSatisfied(
"- [ ] Before screenshots are attached, or marked `N/A - <reason>`.",
),
false,
);
});
it("requires artifacts when artifact-required mode is enabled", () => {
assert.equal(
isRowSatisfiedForContext("- [ ] `N/A - no UI`", {
artifactRequired: true,
}),
false,
);
assert.equal(
isRowSatisfiedForContext(
"- [ ] https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000005",
{
artifactRequired: true,
},
),
true,
);
});
});
describe("check-pr-evidence marker extraction", () => {
it("captures the checkbox line plus indented continuation lines", () => {
const body = [
"<!-- evidence-row:backend-logs -->",
"- [ ] Backend logs show the real code path firing end to end,",
" or are marked `N/A - no backend path in this change`.",
"",
"<!-- evidence-row:frontend-logs -->",
"- [ ] Frontend logs: https://github.com/user-attachments/assets/00000000-0000-0000-0000-000000000006",
].join("\n");
const rows = extractEvidenceRows(body);
assert.ok(rows.get("backend-logs").includes("N/A - no backend path"));
assert.ok(rows.get("frontend-logs").includes("user-attachments/assets"));
});
it("bounds the last row so trailing links do not bleed in", () => {
const body = [
"<!-- evidence-row:domain-artifacts -->",
"- [ ] Domain artifacts are attached where applicable, or marked `N/A - <reason>`.",
"",
"# Evidence Details",
"",
"See [the runner](https://example.com/report.json).",
].join("\n");
const rows = extractEvidenceRows(body);
assert.ok(!rows.get("domain-artifacts").includes("example.com"));
assert.equal(isRowSatisfied(rows.get("domain-artifacts")), false);
});
it("boundRowBlock stops at the first blank line or heading", () => {
const block = [
"- [ ] row text",
" continued indented line",
"",
"# not part of the row",
"https://example.com/should-not-be-captured",
].join("\n");
const bounded = boundRowBlock(block);
assert.ok(bounded.includes("continued indented line"));
assert.ok(!bounded.includes("example.com"));
});
});
describe("check-pr-evidence against the real PR template", () => {
it("carries a marker for every required evidence row", () => {
const template = readFileSync(TEMPLATE_PATH, "utf8");
const rows = extractEvidenceRows(template);
for (const { id } of REQUIRED_EVIDENCE_ROWS) {
assert.ok(rows.has(id), `template is missing marker evidence-row:${id}`);
}
});
it("fails the unedited template", () => {
const template = readFileSync(TEMPLATE_PATH, "utf8");
const { ok, findings } = evaluatePrEvidence(template);
assert.equal(ok, false);
assert.ok(findings.every((finding) => finding.status === "blank"));
});
});