import { installDom } from "./dom.ts";
import { createRequire } from "node:module";
import assert from "node:assert/strict";
import { test } from "node:test";
import { mountDashboard } from "../src/dashboard.ts";
import { createHttpTransport } from "../src/transport.ts";
import {
promotionReceipt,
recentReceipts,
receiptGateCaption,
reviewGate,
stateView,
} from "../src/model.ts";
import { relativeTime } from "../src/time.ts";
import { duration, ReceiptDetails } from "../src/receipts.tsx";
import { fixtureOwnerReceipt, fixtureReceipt, fixtureState } from "./fixtures.ts";
import { createElement } from "react";
import { createRoot } from "react-dom/client";
import { flushSync } from "react-dom";
const { JSDOM } = createRequire(import.meta.url)("jsdom");
const origin = "https://fixture.idiot.tax";
async function until(check: () => boolean) {
for (let i = 0; i < 200; i++) {
if (check()) return;
await new Promise((resolve) => setTimeout(resolve, 5));
}
assert.fail("Receipt DOM did not settle");
}
function setup(id = "receipts", owner = true, fetcher?: typeof fetch) {
const dom = new JSDOM('
', { url: `${origin}/#review` });
installDom(dom.window);
const root = dom.window.document.getElementById("dashboard")! as HTMLElement;
const wire = fixtureState(id);
const calls: { path: string; method: string; body?: BodyInit | null }[] = [];
const transport = createHttpTransport({
origin,
fetch:
fetcher ??
(async (input, init) => {
const path = new URL(String(input)).pathname;
calls.push({ path, method: init?.method ?? "GET", body: init?.body });
if (path.endsWith("/state")) return Response.json(wire);
if (path.endsWith("/activity")) return Response.json({ activity: [], nextAfter: 0 });
if (path.endsWith("/settings")) {
wire.project.settings = JSON.parse(String(init!.body));
return Response.json({ settings: wire.project.settings });
}
const review = wire.reviews.find((r) => path.includes(`/reviews/${r.id}/`))!;
if (path.endsWith("/receipt")) {
return review?.receipt?.digest
? Response.json(fixtureOwnerReceipt(review))
: Response.json({ error: "Receipt not found." }, { status: 404 });
}
if (path.endsWith("/approve")) {
review.approval = { sha: review.sha, receiptDigest: review.receiptDigest };
return Response.json({ review });
}
if (path.endsWith("/promote")) {
if (wire.project.settings!.requireReceipt) {
review.receipt = fixtureReceipt("failed", "before-promote");
review.receiptDigest = review.receipt.digest;
return Response.json({ error: "Checks failed before promote." }, { status: 409 });
}
review.promoted = true;
return Response.json({ sha: review.sha });
}
return Response.json({ error: "Fixture missing route" }, { status: 404 });
}),
});
const app = mountDashboard(root, {
brand: { name: "Fixture product", tagline: "Fixture" },
fixture: true,
owner,
transport,
initialProject: id,
});
return {
dom,
root,
wire,
calls,
app,
close() {
app.dispose();
dom.window.close();
},
};
}
test("receipt wire maps every status without logs; durations and timeout use supplied check time", async () => {
for (const [ms, text] of [[0, "0 ms"], [1, "1 ms"], [227, "227 ms"], [999, "999 ms"], [1000, "1 s"], [1499, "1 s"], [1500, "2 s"]] as const)
assert.equal(duration(ms), text);
assert.equal(duration(-1), "");
assert.equal(duration(Number.NaN), "");
assert.equal(duration(41000), "41 s");
assert.equal(duration(110000), "1 min 50 s");
assert.equal(duration(60000), "1 min");
assert.equal(duration(59999), "1 min");
assert.equal(duration(null), "");
const c = setup();
try {
await until(() => !!c.root.querySelector("#reviews"));
const rows = [...c.root.querySelectorAll(".review-card")];
assert.equal(rows.length, 7);
for (const word of [
"Checks running",
"Re-checking",
"Passed",
"Failed",
"Timed out",
"Couldn't run",
"Not started",
])
assert.ok(
rows.some((row) => row.textContent!.includes(word)),
word,
);
assert.match(rows[4].textContent!, /Stopped after 1 min 50 s · brand/);
assert.match(rows[5].textContent!, /The checks didn't start/);
assert.equal(
c.root.querySelectorAll('[data-action="approve"]').length,
0,
"only passing row is already approved",
);
assert.equal(JSON.stringify(c.wire).includes("stdout"), false);
assert.deepEqual(stateView(c.wire).reviews[4].receipt, c.wire.reviews[4].receipt);
} finally {
c.close();
}
});
test("queue and recent receipts use singular and plural check counts, including failed totals", async () => {
for (const count of [1, 2]) {
const c = setup("history-required");
try {
await until(() => !!c.root.querySelector(".receipt-history-row"));
for (const review of c.wire.reviews) {
if (review.receipt) {
review.receipt.checks = review.receipt.checks.slice(0, count);
review.receipt.durationMs = 4000;
}
}
await c.app.refresh();
const expected = `${count} ${count === 1 ? "check" : "checks"} · 4 s`;
await until(() =>
c.root.querySelector(".receipt-history-row")!.textContent!.includes(expected),
);
const passedReview = c.wire.reviews.find((review) => review.id === "receipt-3")!;
const passed = [...c.root.querySelectorAll(".review-card")].find((row) => row.textContent!.includes(passedReview.repoName))!;
assert.ok(passed.querySelector(".receipt-cell")!.textContent!.includes(expected));
const history = [...c.root.querySelectorAll(".receipt-history-row")];
assert.equal(history.length, 5);
assert.ok(history.every((row) => row.textContent!.includes(expected)));
const failed = c.root.querySelectorAll(".review-card")[3];
assert.ok(failed.textContent!.includes(`1 of ${count} failed`));
assert.doesNotMatch(c.root.textContent!, /1 checks/);
} finally {
c.close();
}
}
});
test("Promote stays hidden during an in-flight re-check and returns after an interrupted run", async () => {
const wire = fixtureState("receipts");
const review = wire.reviews[2];
let finish: (() => void) | undefined;
let promoteCalls = 0;
const c = setup("receipts", true, async (input) => {
const path = new URL(String(input)).pathname;
if (path.endsWith("/state")) return Response.json(wire);
if (path.endsWith("/activity")) return Response.json({ activity: [], nextAfter: 0 });
if (path.endsWith("/promote")) {
promoteCalls++;
await new Promise((resolve) => {
finish = resolve;
});
review.receipt = fixtureReceipt("timed-out", "before-promote");
return Response.json({ error: "Checks failed before promote." }, { status: 409 });
}
return Response.json({ error: "Fixture missing route" }, { status: 404 });
});
const selector = '[data-action="promote"][data-review="receipt-3"]';
try {
await until(() => !!c.root.querySelector(selector));
c.root.querySelector(selector)!.click();
await until(() => !!c.dom.window.document.querySelector('[data-action="confirm-promote"]'));
c.dom.window.document.querySelector('[data-action="confirm-promote"]')!.click();
await until(() => !!finish);
assert.equal(c.root.querySelector(selector), null);
const confirm = c.dom.window.document.querySelector(
'[data-action="confirm-promote"]',
) as HTMLButtonElement;
assert.equal(confirm.textContent, "Re-checking…");
assert.equal(confirm.disabled, true);
assert.equal(promoteCalls, 1);
finish!();
await until(() => !!c.root.querySelector(selector));
assert.match(
c.root.querySelector(selector)!.closest("tr")!.textContent!,
/Promote runs the checks again/,
);
assert.equal(promoteCalls, 1, "interrupted runs are never automatically retried");
} finally {
finish?.();
c.close();
}
});
test("advisory receipt outcomes never gate; approved reviews re-satisfy the gate after setting flip", () => {
const state = stateView(fixtureState("advisory"));
for (const review of state.reviews) {
review.status = "pending";
assert.equal(reviewGate(review, state).approve, true);
review.status = "approved";
assert.equal(reviewGate(review, state).promote, true);
}
state.project.settings!.requireReceipt = true;
const failed = state.reviews[3];
failed.status = "pending";
assert.equal(reviewGate(failed, state).approve, false);
failed.status = "approved";
assert.equal(reviewGate(failed, state).promote, true, "promote enters core's fresh re-run");
failed.status = "stale";
assert.equal(reviewGate(failed, state).promote, false);
assert.equal(
receiptGateCaption(state.reviews[4], true),
"Checks timed out. A new push runs them again.",
);
const advisory = fixtureState("advisory");
advisory.reviews[3].approval = { sha: advisory.reviews[3].sha, receiptDigest: null };
assert.equal(
stateView(advisory).reviews[3].status,
"approved",
"advisory digest movement is not a moved fork",
);
});
test("owner logs use encoded owner route, last 200 lines, Show all; agent has no logs or switch", async () => {
const c = setup();
try {
await until(() => !!c.root.querySelector("#reviews"));
c.root.querySelectorAll(".column-receipts .receipt-cell")[2].click();
await until(() => !!c.root.querySelector(".receipt-details"));
const logs = [...c.root.querySelectorAll("button")].find(
(b) => b.textContent === "View logs",
)!;
logs.click();
await until(() => !!c.root.querySelector(".receipt-logs"));
assert.equal(c.root.querySelector(".receipt-logs")!.textContent!.split("\n").length, 200);
[...c.root.querySelectorAll("button")]
.find((b) => b.textContent === "Show all")!
.click();
await until(() => c.root.querySelector(".receipt-logs")!.textContent!.split("\n").length > 200);
const hide = [...c.root.querySelectorAll("button")].find(
(b) => b.textContent === "Hide logs",
)!;
hide.click();
await until(() => !c.root.querySelector(".receipt-logs"));
assert.ok(
[...c.root.querySelectorAll("button")].some(
(b) => b.textContent === "View logs",
),
);
assert.equal(c.calls.filter((call) => call.path.endsWith("/receipt")).length, 1);
assert.match(c.calls.at(-1)!.path, /\/projects\/receipts\/reviews\/receipt-3\/receipt$/);
} finally {
c.close();
}
const agent = setup("advisory", false);
try {
await until(() => !!agent.root.querySelector("#reviews"));
agent.root.querySelectorAll(".column-receipts .receipt-cell")[2].click();
await until(() => !!agent.root.querySelector(".receipt-details"));
assert.doesNotMatch(agent.root.textContent!, /View logs/);
assert.equal(agent.root.querySelector('[role="switch"]'), null);
assert.match(agent.root.textContent!, /Require passing receipts: Off/);
assert.equal(
agent.calls.some((call) => call.path.endsWith("/receipt")),
false,
);
} finally {
agent.close();
}
});
test("required promote 409 keeps dialog with failure and opens new receipt; advisory has no re-check copy", async () => {
const c = setup();
try {
await until(() => !!c.root.querySelector('[data-action="promote"]'));
c.root
.querySelector('[data-action="promote"][data-review="receipt-3"]')!
.click();
await until(() => !!c.dom.window.document.querySelector('[data-action="confirm-promote"]'));
assert.match(c.dom.window.document.body.textContent!, /Checks run again/);
c.dom.window.document.querySelector('[data-action="confirm-promote"]').click();
await until(() => c.dom.window.document.body.textContent!.includes("Nothing was promoted."));
assert.ok(c.dom.window.document.querySelector(".promotion-dialog"));
assert.equal(
c.root.querySelector('[role="alert"]'),
null,
"handled conflict has no page error banner",
);
assert.equal(c.dom.window.document.querySelectorAll('[role="alert"]').length, 1);
assert.doesNotMatch(c.dom.window.document.body.textContent!, /Checks failed before promote\./);
assert.ok(
c.dom.window.document.querySelector(".receipt-error-line svg"),
"Warning icon accompanies error copy",
);
assert.equal(c.root.querySelector('[data-action="promote"][data-review="receipt-3"]'), null);
assert.equal(c.calls.filter((call) => call.path.endsWith("/promote")).length, 1);
[...c.dom.window.document.querySelectorAll(".promotion-dialog button")]
.find((b: HTMLButtonElement) => b.textContent === "View receipt")!
.click();
await until(
() => c.root.querySelector(".receipt-details")?.textContent?.includes("Failed") === true,
);
assert.equal(
c.root.textContent!.includes("Fork moved"),
false,
"fresh re-run digest doesn't imply fork movement",
);
} finally {
c.close();
}
const a = setup("advisory");
try {
await until(() => !!a.root.querySelector('[data-action="approve"][data-review="receipt-4"]'));
a.root
.querySelector('[data-action="approve"][data-review="receipt-4"]')!
.click();
await until(() => !!a.root.querySelector('[data-action="promote"][data-review="receipt-4"]'));
a.root
.querySelector('[data-action="promote"][data-review="receipt-4"]')!
.click();
await until(() => !!a.dom.window.document.querySelector(".promotion-dialog"));
assert.match(
a.dom.window.document.body.textContent!,
/latest checks failed; receipts are advisory/,
);
assert.doesNotMatch(a.dom.window.document.body.textContent!, /Checks run again|Re-checking/);
} finally {
a.close();
}
});
test("settings PATCH sends only requireReceipt and reloads authoritative state", async () => {
const c = setup("advisory");
try {
await until(() => !!c.root.querySelector('[role="switch"]'));
c.root.querySelector('[role="switch"]')!.click();
await until(
() =>
c.wire.project.settings!.requireReceipt &&
c.root.querySelector('[role="switch"]')!.getAttribute("aria-checked") === "true",
);
const patches = c.calls.filter((call) => call.method === "PATCH");
assert.equal(patches.length, 1);
assert.equal(patches[0].body, '{"requireReceipt":true}');
assert.ok(c.calls.at(-1)!.path.endsWith("/activity"));
assert.equal(c.root.querySelector('[data-action="approve"][data-review="receipt-5"]'), null);
} finally {
c.close();
}
});
test("late owner log response is discarded when receipt changes", async () => {
const dom = new JSDOM('', { url: origin });
installDom(dom.window);
const root = dom.window.document.getElementById("dashboard");
const react = createRoot(root);
const review = stateView(fixtureState("receipts")).reviews[2];
let finish!: (value: ReturnType) => void;
const load = () =>
new Promise>((resolve) => {
finish = resolve;
});
const draw = () =>
flushSync(() =>
react.render(
createElement(ReceiptDetails, { review, project: "receipts", owner: true, load }),
),
);
try {
draw();
root.querySelector('button[aria-label="Copy to clipboard"]')?.blur();
[...root.querySelectorAll("button")]
.find((b: HTMLButtonElement) => b.textContent === "View logs")!
.click();
await until(() => !!finish);
const stored = fixtureOwnerReceipt(fixtureState("receipts").reviews[2]);
review.receipt = fixtureReceipt("running");
draw();
finish(stored);
await new Promise((resolve) => setTimeout(resolve, 20));
assert.equal(root.querySelector(".receipt-logs"), null);
} finally {
flushSync(() => react.unmount());
dom.window.close();
}
});
test("owner receipt and settings calls encode IDs, preserve session safeguards, and never retry", async () => {
const requests: { url: string; init?: RequestInit }[] = [];
const transport = createHttpTransport({
origin,
fetch: async (input, init) => {
requests.push({ url: String(input), init });
return Response.json({ error: "Receipt not found." }, { status: 404 });
},
});
await assert.rejects(transport.getReceipt!("a/b", "r/id"), /Receipt not found/);
await assert.rejects(
transport.setSettings!("a/b", { requireReceipt: false }),
/Receipt not found/,
);
assert.equal(requests.length, 2);
assert.equal(requests[0].url, `${origin}/projects/a%2Fb/reviews/r%2Fid/receipt`);
for (const request of requests) {
assert.equal(request.init!.credentials, "same-origin");
assert.equal(request.init!.cache, "no-store");
assert.equal(request.init!.redirect, "error");
}
assert.equal(requests[1].init!.method, "PATCH");
assert.equal(requests[1].init!.body, '{"requireReceipt":false}');
assert.deepEqual(requests[1].init!.headers, { "Content-Type": "application/json" });
});
test("receipt completion announces once after an observed running state", async () => {
const c = setup();
try {
await until(() => !!c.root.querySelector("#reviews"));
const live = c.root.querySelector('.sr-only[aria-live="polite"]')!;
assert.equal(live.textContent, "");
c.wire.reviews[0].receipt = fixtureReceipt("passed");
c.wire.reviews[0].receiptDigest = c.wire.reviews[0].receipt.digest;
await c.app.refresh();
await until(() => live.textContent!.includes("Checks passed for ui-01"));
let mutations = 0;
const observer = new c.dom.window.MutationObserver(() => mutations++);
observer.observe(live, { childList: true, subtree: true, characterData: true });
await c.app.refresh();
await new Promise((resolve) => setTimeout(resolve, 20));
assert.equal(mutations, 0, "identical snapshots don't repeat the announcement");
observer.disconnect();
} finally {
c.close();
}
});
test("approved before-promote outcomes distinguish failed code from an interrupted run", async () => {
const wire = fixtureState("gating-recheck");
const state = stateView(wire);
const running = state.reviews[1];
assert.equal(running.status, "approved");
assert.equal(receiptGateCaption(running, true), "");
const failed = state.reviews[2];
assert.equal(reviewGate(failed, state).promote, false);
assert.equal(receiptGateCaption(failed, true), "Checks failed. The agent must push a fix.");
for (const index of [4, 5]) {
assert.equal(reviewGate(state.reviews[index], state).promote, true);
assert.equal(receiptGateCaption(state.reviews[index], true), "Promote runs the checks again.");
}
state.project.settings!.requireReceipt = false;
assert.equal(
reviewGate(failed, state).promote,
true,
"advisory mode never gates on a failed run",
);
const c = setup("gating-recheck");
try {
await until(() => !!c.root.querySelector("#reviews"));
const rows = [...c.root.querySelectorAll(".review-card")];
assert.match(rows[1].textContent!, /Approved/);
assert.doesNotMatch(rows[1].textContent!, /Waiting for checks/);
assert.match(rows[1].textContent!, /Re-checking/);
assert.equal(rows[1].querySelector('[data-action="promote"]'), null);
assert.equal(rows[2].querySelector('[data-action="promote"]'), null);
assert.ok(rows[4].querySelector('[data-action="promote"]'));
assert.match(rows[4].textContent!, /Promote runs the checks again/);
assert.equal(rows[6].querySelectorAll(".state-reason").length, 1);
assert.equal((rows[6].textContent!.match(/Waiting for checks\./g) ?? []).length, 1);
assert.match(rows[6].querySelector(".column-receipts")!.textContent!, /Not started/);
} finally {
c.close();
}
});
test("recent receipts retain promoted summaries, sort by receipt time and cap at five without changing the queue", () => {
for (const id of ["history-required", "history-advisory"]) {
const state = stateView(fixtureState(id));
const order = state.reviews.map((r) => r.id);
const history = recentReceipts(state);
assert.deepEqual(
history.map((r) => r.id),
["promoted-6", "promoted-5", "promoted-4", "promoted-3", "promoted-2"],
);
assert.ok(history.every((r) => r.status === "promoted"));
assert.deepEqual(
state.reviews.map((r) => r.id),
order,
"source order stays intact",
);
assert.equal(history[0].receipt.status, id === "history-required" ? "passed" : "failed");
const running = state.reviews[0];
running.receipt!.startedAt = "2026-10-12T02:00:00Z";
assert.equal(
recentReceipts(state)[0].id,
running.id,
"unfinished receipts order by their actual start",
);
for (const review of state.reviews) review.receipt = null;
assert.deepEqual(recentReceipts(state), []);
}
});
test("last promotion binds exact ledger review, SHA and digest, including advisory failures", () => {
const state = stateView(fixtureState("history-advisory"));
const entry = state.ledger.at(-1)!;
assert.equal(entry.receiptDigest, fixtureState("history-advisory").ledger.at(-1)!.receiptDigest);
const matching = promotionReceipt(state, entry)!;
assert.equal(matching.id, "promoted-6");
assert.equal(matching.receipt!.status, "failed");
for (const change of [
{ receiptDigest: null },
{ receiptDigest: undefined },
{ receiptDigest: "9".repeat(64) },
{ review: "missing" },
{ sha: "9".repeat(40) },
]) {
assert.equal(promotionReceipt(state, { ...entry, ...change }), undefined);
}
matching.receipt = { ...matching.receipt!, status: "running" };
assert.equal(promotionReceipt(state, entry), undefined);
matching.receipt = null;
assert.equal(promotionReceipt(state, entry), undefined);
});
test("promoted history and Last promotion open the same receipt with owner-only escaped logs", async () => {
for (const id of ["history-required", "history-advisory"]) {
const c = setup(id);
try {
await until(() => c.root.querySelectorAll(".receipt-history-row").length === 5);
assert.equal(c.root.querySelectorAll(".review-card").length, 7);
const rows = [...c.root.querySelectorAll(".receipt-history-link")];
assert.match(rows[0].closest("tr")!.textContent!, /history-6.*7777777/);
assert.match(rows[0].closest("tr")!.textContent!, /3 checks · 41 s/);
assert.match(
rows[0].closest("tr")!.textContent!,
id === "history-required" ? /Before promote/ : /On review/,
);
assert.ok(rows[0].closest("tr")!.querySelector("time"));
rows[0].focus();
rows[0].click();
await until(
() => c.root.querySelector(".receipt-details")?.textContent?.includes("history-6") === true,
);
[...c.root.querySelectorAll("button")]
.find((b) => b.textContent === "View logs")!
.click();
await until(() => !!c.root.querySelector(".receipt-logs"));
assert.equal(c.calls.filter((call) => call.path.endsWith("/receipt")).length, 1);
assert.match(c.calls.at(-1)!.path, /reviews\/promoted-6\/receipt$/);
assert.equal(c.root.querySelector(".receipt-logs script"), null);
assert.match(
c.root.querySelector(".receipt-logs")!.textContent!,
/