Fork Horse repository, listed by its owner. Publisher snapshot.

packages/forkhorse-dashboard/test/fixture-api.ts

Snapshot at 34e3175

Containing directory

20,373 bytes · typescriptView raw
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