g1t/apps/web/app/lib/catch-up.test.ts
Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Catching up with main takes seconds when the two sides touched different files | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import type { AgentRun } from "@g1t/contracts"; | |
| 5 | ||
| 6 | import { CATCH_UP_TIMEOUT_MS, catchUpPhase, catchUpRun, catchUpTitle, catchUpWhy } from "./catch-up.ts"; | |
| 7 | ||
| 8 | const START = Date.parse("2026-10-05T12:00:00Z"); | |
| 9 | ||
| 10 | function run(kind: AgentRun["kind"], status: AgentRun["status"], createdAt: string): AgentRun { | |
| 11 | return { | |
| 12 | id: `run-${kind}-${createdAt}`, | |
| 13 | repo: { namespace: "acme", name: "app" }, | |
| 14 | number: 7, | |
| 15 | title: null, | |
| 16 | kind, | |
| 17 | agent: "g1t-agent", | |
| 18 | model: null, | |
| 19 | status, | |
| 20 | step: null, | |
| 21 | steps: [], | |
| 22 | stepCount: 0, | |
| 23 | startedBy: "octo", | |
| 24 | error: null, | |
| 25 | costUsd: null, | |
| 26 | turns: null, | |
| 27 | createdAt, | |
| 28 | startedAt: null, | |
| 29 | finishedAt: null, | |
| 30 | updatedAt: createdAt, | |
| 31 | }; | |
| 32 | } | |
| 33 | ||
| 34 | test("the update run for a request is the newest one since it was made", () => { | |
| 35 | const runs = [ | |
| 36 | run("update", "failed", "2026-10-05T11:00:00Z"), | |
| 37 | run("review", "running", "2026-10-05T12:00:05Z"), | |
| 38 | run("update", "running", "2026-10-05T12:00:03Z"), | |
| 39 | ]; | |
| 40 | assert.equal(catchUpRun(runs, START)?.createdAt, "2026-10-05T12:00:03Z"); | |
| 41 | assert.equal(catchUpRun(runs.slice(0, 2), START), null); | |
| 42 | }); | |
| 43 | ||
| 44 | test("it is done as soon as the pull request is no longer behind", () => { | |
| 45 | assert.equal(catchUpPhase({ behind: false, run: null, startedAt: START, now: START + 1000 }), "done"); | |
| 46 | const failed = run("update", "failed", "2026-10-05T12:00:03Z"); | |
| 47 | assert.equal(catchUpPhase({ behind: false, run: failed, startedAt: START, now: START }), "done"); | |
| 48 | }); | |
| 49 | ||
| 50 | test("a run that ended while still behind failed", () => { | |
| 51 | for (const status of ["failed", "stopped"] as const) { | |
| 52 | const ended = run("update", status, "2026-10-05T12:00:03Z"); | |
| 53 | assert.equal(catchUpPhase({ behind: true, run: ended, startedAt: START, now: START + 60_000 }), "failed"); | |
| 54 | } | |
| 55 | }); | |
| 56 | ||
| 57 | test("it never waits forever", () => { | |
| 58 | const going = run("update", "running", "2026-10-05T12:00:03Z"); | |
| 59 | assert.equal(catchUpPhase({ behind: true, run: going, startedAt: START, now: START + 60_000 }), "working"); | |
| 60 | assert.equal(catchUpPhase({ behind: true, run: null, startedAt: START, now: START + CATCH_UP_TIMEOUT_MS + 1 }), "timed_out"); | |
| 61 | }); | |
| 62 | ||
| 63 | test("the box says who is doing what", () => { | |
| 64 | assert.equal(catchUpTitle("conflicting", "main"), "g1t-agent is resolving conflicts with main"); | |
| 65 | assert.equal(catchUpTitle("overlap", "main"), "g1t-agent is merging main into this pull request"); | |
| 66 | assert.match(catchUpWhy({ reason: "overlap", paths: ["a.rs"] }, "main"), /both changed one file/); | |
| 67 | assert.match(catchUpWhy({ reason: "conflicting", paths: ["a.rs", "b.rs"] }, "main"), /conflicts in 2 files/); | |
| 68 | }); |