Fork Horse repository, listed by its owner. Publisher snapshot.
packages/forkhorse-dashboard/test/fixture-api.ts
Snapshot at 34e3175
1import { judgeScenarios, judgeFixtureState, judgeFixtureDetail } from "./judge-fixtures.ts";
2import { agentStatusScenarios, agentStatusFixtureState } from "./agent-status-fixtures.ts";
3import { archiveFixtureState, archiveScenarios, archiveRace } from "./archive-fixtures.ts";
4import { emptyFixtureState, emptyScenarios } from "./empty-fixtures.ts";
5
6import { usageScenarios, usageFixtureState, usageFixtureDetail } from "./usage-fixtures.ts";
7import { collisionScenarios, collisionFixtureState } from "./collision-fixtures.ts";
8import { readFileSync } from "node:fs";
9import { raceScenarios, raceFixtureState } from "./race-fixtures.ts";
10import { racesScenarios, racesFixtureState } from "./races-fixtures.ts";
11import { forkScenarios, forkFixtureState } from "./fork-graph-fixtures.ts";
12import { needsScenarios, needsFixtureState } from "./needs-you-fixtures.ts";
13import {
14 reviewScenarios,
15 reviewFixtureState,
16 reviewFixtureDetail,
17 reviewFixtureDiff,
18} from "./review-fixtures.ts";
19import type { IncomingMessage, ServerResponse } from "node:http";
20import { fixtureOwnerReceipt, fixtureReceipt, fixtureState } from "./fixtures.ts";
21
22export interface FixtureCall {
23 method: string;
24 path: string;
25}
26
27/** Loopback fixture API only; production build does not mount or bundle these routes. */
28export function fixtureMiddleware(calls: FixtureCall[] = []) {
29 const states = new Map<string, ReturnType<typeof fixtureState>>();
30 return (request: IncomingMessage, response: ServerResponse, next: () => void) => {
31 const url = new URL(request.url ?? "/", "http://fixture.idiot.tax");
32 if (url.pathname === "/__fixtures/owner-bundle" && request.method === "GET") {
33 const project = forkScenarios.includes(url.searchParams.get("scenario") ?? "")
34 ? url.searchParams.get("scenario")!
35 : "fork-hero";
36 try {
37 const html = readFileSync(new URL("../dist/index.html", import.meta.url), "utf8");
38 const config = JSON.stringify({ projects: [{ id: project, label: project }] });
39 response.setHeader("Content-Type", "text/html");
40 response.setHeader("Cache-Control", "no-store");
41 response.end(
42 html.replace(
43 "</body>",
44 `<script id="dashboard-config" type="application/json">${config}</script><script type="module" src="/test/owner-theme-fixture.ts"></script></body>`,
45 ),
46 );
47 } catch {
48 response.statusCode = 503;
49 response.end("Build the owner dashboard first.");
50 }
51 return;
52 }
53 if (/^\/app\/assets\/[A-Za-z0-9_.-]+$/.test(url.pathname) && request.method === "GET") {
54 try {
55 response.setHeader(
56 "Content-Type",
57 url.pathname.endsWith(".css") ? "text/css" : "text/javascript",
58 );
59 response.end(
60 readFileSync(
61 new URL(`../dist/assets/${url.pathname.split("/").at(-1)}`, import.meta.url),
62 ),
63 );
64 } catch {
65 response.statusCode = 404;
66 response.end("Fixture build asset missing.");
67 }
68 return;
69 }
70 if (url.pathname === "/__fixtures/gatekeeper-bundle" && request.method === "GET") {
71 try {
72 response.setHeader("Content-Type", "text/html");
73 response.setHeader("Cache-Control", "no-store");
74 response.end(
75 readFileSync(
76 new URL("../../forkhorse-gatekeeper/dist/sidebar.html", import.meta.url),
77 "utf8",
78 ),
79 );
80 } catch {
81 response.statusCode = 503;
82 response.end("Build the gatekeeper sidebar assets first.");
83 }
84 return;
85 }
86 // Test harness reset; this middleware is excluded from production builds.
87 if (url.pathname === "/__fixtures/reset" && request.method === "POST") {
88 states.clear();
89 response.statusCode = 204;
90 response.end();
91 return;
92 }
93 if (url.pathname === "/__fixtures/fork-live-push" && request.method === "POST") {
94 const state = states.get("fork-live");
95 if (state) {
96 const previous = state.activity.find((a) => a.repoName === "fixture-fork-3")!;
97 const at = new Date().toISOString();
98 state.activity.push({
99 ...previous,
100 sequence: state.activity.length + 100,
101 recordedAt: at,
102 eventTimestamp: at,
103 });
104 }
105 response.statusCode = 204;
106 response.end();
107 return;
108 }
109 if (!url.pathname.startsWith("/projects/")) {
110 next();
111 return;
112 }
113 calls.push({ method: request.method ?? "GET", path: url.pathname });
114 response.setHeader("Content-Type", "application/json");
115 response.setHeader("Cache-Control", "no-store");
116 function send(data: unknown, status = 200) {
117 response.statusCode = status;
118 response.end(JSON.stringify(data));
119 }
120 const agentStatusRoute = /^\/projects\/([^/]+)\/(state|activity)$/.exec(url.pathname);
121 if (agentStatusRoute && racesScenarios.includes(agentStatusRoute[1])) {
122 const state = racesFixtureState(agentStatusRoute[1]);
123 send(agentStatusRoute[2] === "state" ? state : { activity: [], nextAfter: 0 });
124 return;
125 }
126 if (agentStatusRoute && agentStatusScenarios.includes(agentStatusRoute[1])) {
127 if (!states.has(agentStatusRoute[1])) states.set(agentStatusRoute[1], agentStatusFixtureState(agentStatusRoute[1]));
128 const state = states.get(agentStatusRoute[1])!;
129 if (agentStatusRoute[2] === "state") send(state);
130 else send({ activity: [], nextAfter: state.activity.at(-1)?.sequence ?? 0 });
131 return;
132 }
133 const emptyRoute = /^\/projects\/([^/]+)\/(state|activity)$/.exec(url.pathname);
134 if (emptyRoute && emptyScenarios.includes(emptyRoute[1])) {
135 send(emptyRoute[2] === "state"
136 ? emptyFixtureState(emptyRoute[1])
137 : { activity: [], nextAfter: 0 });
138 return;
139 }
140 const judgeRoute = /^\/projects\/([^/]+)\/(state|activity|reviews\/([^/]+))$/.exec(url.pathname);
141 if (judgeRoute && judgeScenarios.includes(judgeRoute[1])) {
142 if (!states.has(judgeRoute[1])) states.set(judgeRoute[1], judgeFixtureState(judgeRoute[1]));
143 const state = states.get(judgeRoute[1])!;
144 if (judgeRoute[2] === "state") send(state);
145 else if (judgeRoute[2] === "activity") send({ activity: [], nextAfter: state.activity.at(-1)?.sequence ?? 0 });
146 else if (!state.reviews.some((r) => r.id === judgeRoute[3])) send({ error: "Fixture review missing" }, 404);
147 else send(judgeFixtureDetail(state, judgeRoute[3]));
148 return;
149 }
150 const archiveRoute = /^\/projects\/([^/]+)\/(state|activity|packets\/([^/]+)\/(archive|unarchive)|reviews\/([^/]+)(?:\/(receipt|diff))?)$/.exec(url.pathname);
151 if (archiveRoute && archiveScenarios.includes(archiveRoute[1])) {
152 const id = archiveRoute[1];
153 if (!states.has(id)) states.set(id, archiveFixtureState(id));
154 const state = states.get(id)!;
155 if (archiveRoute[2] === "state") send(state);
156 else if (archiveRoute[2] === "activity") send({ activity: state.activity, nextAfter: state.activity.at(-1)?.sequence ?? 0 });
157 else if (archiveRoute[5]) {
158 const review = state.reviews.find((r) => r.id === archiveRoute[5]);
159 if (!review) send({ error: "Review not found." }, 404);
160 else if (archiveRoute[6] === "receipt") send(fixtureOwnerReceipt(review));
161 else if (archiveRoute[6] === "diff") send(reviewFixtureDiff(url.searchParams.get("path") ?? ""));
162 else send(reviewFixtureDetail(state));
163 } else if (request.method !== "POST") send({ error: "Method not allowed." }, 405);
164 else {
165 const packet = state.packets.find((p) => p.id === archiveRoute[3]);
166 if (id === "archive-missing") {
167 state.packets = state.packets.filter((p) => p.id !== archiveRoute[3]);
168 send({ error: "Fixture packet missing." }, 404);
169 } else if (!packet) send({ error: "Packet not found." }, 404);
170 else if (id === "archive-conflict") {
171 state.races = [{ ...archiveRace(), packet: packet.id }];
172 send({ error: "Fixture raw conflict must not be displayed." }, 409);
173 } else if (id === "archive-error") send({ error: "Fixture raw failure must not be displayed." }, 503);
174 else {
175 packet.archived = archiveRoute[4] === "archive" ? { at: new Date().toISOString(), by: "owner" } : null;
176 send({ packet });
177 }
178 }
179 return;
180 }
181
182 const usageRoute = /^\/projects\/([^/]+)\/(state|activity|reviews\/([^/]+))$/.exec(url.pathname);
183 if (usageRoute && usageScenarios.includes(usageRoute[1])) {
184 if (!states.has(usageRoute[1])) states.set(usageRoute[1], usageFixtureState(usageRoute[1]));
185 const state = states.get(usageRoute[1])!;
186 if (usageRoute[2] === "state") send(state);
187 else if (usageRoute[2] === "activity") send({ activity: [], nextAfter: 0 });
188 else if (!state.reviews.some((r) => r.id === usageRoute[3])) send({ error: "Fixture review not found" }, 404);
189 else send(usageFixtureDetail(state, usageRoute[3]));
190 return;
191 }
192 const collisionRoute = /^\/projects\/([^/]+)\/(state|activity|reviews\/([^/]+)|blockers\/([^/]+)\/resolve)$/.exec(url.pathname);
193 if (collisionRoute && collisionScenarios.includes(collisionRoute[1])) {
194 const id = collisionRoute[1];
195 if (!states.has(id)) states.set(id, collisionFixtureState(id));
196 const state = states.get(id)!;
197 if (collisionRoute[2] === "state") send(state);
198 else if (collisionRoute[2] === "activity") send({ activity: [], nextAfter: 0 });
199 else if (collisionRoute[3]) {
200 const review = state.reviews.find((r) => r.id === collisionRoute[3]);
201 if (!review) send({ error: "Fixture review missing" }, 404);
202 else send({ ...reviewFixtureDetail({ ...state, reviews: [review] }), packet: state.packets.find((p) => p.id === review.packet)! });
203 } else if (request.method === "POST") {
204 let body = "";
205 request.on("data", (chunk) => { body += chunk; });
206 request.on("end", () => {
207 const blocker = state.blockers.find((b) => b.id === collisionRoute[4]);
208 if (id === "collision-missing" || !blocker) {
209 state.blockers = state.blockers.filter((b) => b.id !== collisionRoute[4]);
210 send({ error: "Fixture collision missing" }, 404);
211 return;
212 }
213 if (id === "collision-error") { send({ error: "Fixture raw error must stay hidden" }, 503); return; }
214 try {
215 const { reason } = JSON.parse(body);
216 if (typeof reason !== "string" || reason.trim().length < 1 || reason.length > 200) throw new Error("reason");
217 blocker.resolved ??= { at: new Date().toISOString(),
218 by: id === "collision-race-release" ? "release" : id === "collision-race-promote" ? "promote" : "owner",
219 ...(id.startsWith("collision-race") ? {} : { reason }) };
220 send({ blocker });
221 } catch { send({ error: "Invalid fixture reason" }, 400); }
222 });
223 } else send({ error: "Fixture method unavailable" }, 405);
224 return;
225 }
226 const raceRoute = /^\/projects\/([^/]+)\/(state|activity|reviews\/([^/]+))$/.exec(url.pathname);
227 if (raceRoute && raceScenarios.includes(raceRoute[1])) {
228 const id = raceRoute[1];
229 const state = raceFixtureState(id);
230 if (raceRoute[2] === "state") {
231 if (id === "race-error") send({ error: "Offline race error fixture" }, 503);
232 else if (id === "race-loading") setTimeout(() => send(state), 8000);
233 else send(state);
234 } else if (raceRoute[2] === "activity")
235 send({ activity: [], nextAfter: state.activity.at(-1)?.sequence ?? 0 });
236 else {
237 const review = state.reviews.find((candidate) => candidate.id === raceRoute[3]);
238 if (!review) send({ error: "Fixture review not found" }, 404);
239 else
240 send({
241 ...reviewFixtureDetail({ ...state, reviews: [review] }),
242 packet: state.packets[0],
243 });
244 }
245 return;
246 }
247 const forkRoute = /^\/projects\/([^/]+)\/(state|activity)$/.exec(url.pathname);
248 if (forkRoute && forkScenarios.includes(forkRoute[1])) {
249 if (!states.has(forkRoute[1])) states.set(forkRoute[1], forkFixtureState(forkRoute[1]));
250 const state = states.get(forkRoute[1])!;
251 send(forkRoute[2] === "state" ? state : { activity: [], nextAfter: 0 });
252 return;
253 }
254 const needsRoute = /^\/projects\/([^/]+)\/(state|activity|reviews\/([^/]+))$/.exec(
255 url.pathname,
256 );
257 if (needsRoute && needsScenarios.includes(needsRoute[1])) {
258 const id = needsRoute[1];
259 const state = needsFixtureState(id);
260 if (needsRoute[2] === "state") send(state);
261 else if (needsRoute[2] === "activity") send({ activity: [], nextAfter: 0 });
262 else {
263 const review = state.reviews.find((r) => r.id === needsRoute[3]);
264 if (!review) send({ error: "Fixture review not found." }, 404);
265 else
266 send({
267 ...reviewFixtureDetail({ ...state, reviews: [review] }),
268 packet: state.packets.find((p) => p.id === review.packet)!,
269 });
270 }
271 return;
272 }
273 const detailRoute =
274 /^\/projects\/([^/]+)\/(state|activity|reviews\/([^/]+)(?:\/(diff|return|approve|promote|receipt))?)$/.exec(
275 url.pathname,
276 );
277 if (detailRoute && reviewScenarios.includes(detailRoute[1])) {
278 const id = detailRoute[1];
279 if (!states.has(id)) states.set(id, reviewFixtureState(id));
280 const state = states.get(id)!;
281 const review = state.reviews[0];
282 const route = detailRoute[2];
283 const action = detailRoute[4];
284 if (route === "state") {
285 send(state);
286 return;
287 }
288 if (route === "activity") {
289 send({ activity: state.activity, nextAfter: state.activity.at(-1)?.sequence ?? 0 });
290 return;
291 }
292 if (detailRoute[3] !== review.id) {
293 send({ error: "Review not found." }, 404);
294 return;
295 }
296 if (!action) {
297 if (id === "review-error") send({ error: "Fixture detail unavailable" }, 503);
298 else if (id === "review-not-found") send({ error: "Review not found." }, 404);
299 else if (id === "review-loading") setTimeout(() => send(reviewFixtureDetail(state)), 8000);
300 else send(reviewFixtureDetail(state));
301 return;
302 }
303 if (action === "diff") {
304 const path = url.searchParams.get("path") ?? "";
305 if (
306 path === "missing.txt" ||
307 !reviewFixtureDetail(state).changes.files.some((f) => f.path === path)
308 )
309 send({ error: "Path not in changes" }, 404);
310 else send(reviewFixtureDiff(path));
311 return;
312 }
313 if (action === "receipt") {
314 send(fixtureOwnerReceipt(review));
315 return;
316 }
317 if (request.method !== "POST") {
318 send({ error: "Method not allowed" }, 405);
319 return;
320 }
321 if (action === "return") {
322 let body = "";
323 request.on("data", (chunk) => {
324 body += chunk;
325 });
326 request.on("end", () => {
327 if (id === "review-conflict" || review.promoted) {
328 // Synthetic other-tab promotion becomes authoritative before the conflict response.
329 review.promoted = true;
330 review.approval = { sha: review.sha, receiptDigest: review.receiptDigest };
331 state.ledger = reviewFixtureState("review-promoted").ledger;
332 send({ error: "Review promoted." }, 409);
333 return;
334 }
335 try {
336 const { reason } = JSON.parse(body);
337 if (typeof reason !== "string" || reason.length < 1 || reason.length > 2000)
338 throw new Error("Invalid reason");
339 review.returned = { reason, at: new Date().toISOString() };
340 send({ review });
341 } catch {
342 send({ error: "Invalid reason" }, 400);
343 }
344 });
345 return;
346 }
347 if (review.returned) {
348 send({ error: "Review was returned to the agent." }, 409);
349 return;
350 }
351 if (action === "approve") {
352 review.approval = { sha: review.sha, receiptDigest: review.receiptDigest };
353 send({ review });
354 return;
355 }
356 review.promoted = true;
357 send({ sha: review.sha });
358 return;
359 }
360 const match =
361 /^\/projects\/(demo|quiet|large|first|partial|truncated|superseded|paused|deleted|error|loading|receipts|advisory|recheck|rerun-failed|gating-recheck|history-required|history-advisory)\/(state|activity|settings|reviews\/([^/]+)\/(approve|promote|receipt))$/.exec(
362 url.pathname,
363 );
364 if (!match) {
365 send({ error: "Fixture project or route not found." }, 404);
366 return;
367 }
368 const id = match[1];
369 if (!states.has(id)) states.set(id, fixtureState(id));
370 const state = states.get(id)!;
371 if (match[2] === "settings" && request.method === "PATCH") {
372 let body = "";
373 request.on("data", (chunk) => {
374 body += chunk;
375 });
376 request.on("end", () => {
377 try {
378 const settings = JSON.parse(body);
379 if (typeof settings.requireReceipt !== "boolean") throw new Error("invalid");
380 state.project.settings = { requireReceipt: settings.requireReceipt };
381 send({ settings: state.project.settings });
382 } catch {
383 send({ error: "Invalid fixture settings." }, 400);
384 }
385 });
386 return;
387 }
388 if (match[2] === "state" && request.method === "GET") {
389 if (match[1] === "error") {
390 send({ error: "Fixture connection unavailable. Try again." }, 503);
391 return;
392 }
393 if (match[1] === "loading") {
394 setTimeout(() => send(state), 8000);
395 return;
396 }
397 send(state);
398 return;
399 }
400 if (match[2] === "activity" && request.method === "GET") {
401 const after = Number(url.searchParams.get("after") ?? 0);
402 const activity = state.activity.filter((entry) => entry.sequence > after);
403 send({ activity, nextAfter: activity.at(-1)?.sequence ?? after });
404 return;
405 }
406 const review = state.reviews.find((entry) => entry.id === match[3]);
407 if (match[4] === "receipt" && request.method === "GET") {
408 if (!review?.receipt?.digest) {
409 send({ error: "Receipt not found." }, 404);
410 return;
411 }
412 send(fixtureOwnerReceipt(review));
413 return;
414 }
415 if (request.method !== "POST") {
416 send({ error: "Fixture method not allowed." }, 405);
417 return;
418 }
419 if (!review) {
420 send({ error: "Fixture review not found." }, 404);
421 return;
422 }
423 const fork = state.forks.find((entry) => entry.repoName === review.repoName);
424 if (fork?.head !== review.sha) {
425 send({ error: "Review is stale. Request a fresh review." }, 409);
426 return;
427 }
428 if (
429 match[4] === "approve" &&
430 state.project.settings?.requireReceipt &&
431 (!review.receiptDigest || review.receipt?.status !== "passed")
432 ) {
433 send({ error: "Receipt pending." }, 409);
434 return;
435 }
436 if (match[4] === "approve") {
437 review.approval = { sha: review.sha, receiptDigest: review.receiptDigest };
438 send({ review });
439 return;
440 }
441 if (!review.approval) {
442 send({ error: "Owner approval required." }, 409);
443 return;
444 }
445 if (state.project.settings?.requireReceipt) {
446 review.receipt = fixtureReceipt("running", "before-promote");
447 review.receiptDigest = null;
448 setTimeout(
449 () => {
450 review.receipt = fixtureReceipt(
451 id === "rerun-failed" ? "failed" : "passed",
452 "before-promote",
453 );
454 review.receiptDigest = review.receipt.digest;
455 if (id === "rerun-failed") {
456 send({ error: "Checks failed before promote." }, 409);
457 return;
458 }
459 review.promoted = true;
460 send({ sha: review.sha });
461 },
462 id === "recheck" ? 8000 : 900,
463 );
464 return;
465 }
466 review.promoted = true;
467 state.ledger.push({
468 id: "merge-1",
469 review: review.id,
470 repoName: state.project.repoName,
471 ref: "refs/heads/main",
472 before: review.base,
473 after: review.sha,
474 receiptDigest: review.receiptDigest,
475 recordedAt: "2026-10-12T00:20:00.000Z",
476 });
477 send({ sha: review.sha });
478 };
479}
480