Public: anyone can read this; the crew can change it.
A test pins cutting a wave: an earlier wave that does not parse stops the cut, named, and nothing is written #6837
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/6827-a-test-pins-cutting-a-wave-an-earlier-wa → main 2 commits fork 2 threads · 2 unresolved
1 file
changed +75
−1
test/cut-wave.test.ts +75 −1
| @@ -3,7 +3,9 @@ import { mkdtemp, rm } from "node:fs/promises"; | ||
| 3 | 3 | import { tmpdir } from "node:os"; |
| 4 | 4 | import { join } from "node:path"; |
| 5 | 5 | import { mergePull, openPull } from "../src/actions/pulls.ts"; |
| 6 | import { commitToBranch } from "../src/actions/repos.ts"; | |
| 6 | import { cutWaveFor } from "../src/actions/cut_wave.ts"; | |
| 7 | import { createAgent } from "../src/actions/crew.ts"; | |
| 8 | import { commitToBranch, createRepo } from "../src/actions/repos.ts"; | |
| 7 | 9 | import { createApp, type App } from "../src/app.ts"; |
| 8 | 10 | import { getPull } from "../src/core/pulls.ts"; |
| 9 | 11 | import { getRepo, type Repo } from "../src/core/repos.ts"; |
| @@ -141,6 +143,8 @@ describe("Cut a wave in Settings", () => { | ||
| 141 | 143 | redirect: "manual", |
| 142 | 144 | }); |
| 143 | 145 | repo = getRepo(app.db, "olive", "proj")!; |
| 146 | const bot = await createAgent(app, getUserByName(app.db, "olive")!, { username: "bot" }); | |
| 147 | if (!bot.ok) throw new Error(String(bot.error)); | |
| 144 | 148 | }); |
| 145 | 149 | afterAll(async () => { |
| 146 | 150 | servers.stop(); |
| @@ -184,4 +188,74 @@ describe("Cut a wave in Settings", () => { | ||
| 184 | 188 | if (parsed.ok) |
| 185 | 189 | expect(parsed.value.pulls.map((p) => p.title)).toEqual(["#1 Feature one", "#2 Feature two"]); |
| 186 | 190 | }); |
| 191 | ||
| 192 | const branches = async () => { | |
| 193 | const p = Bun.spawn(["git", "branch", "--format=%(refname:short)"], { | |
| 194 | cwd: join(dataDir, "repos", "olive", "proj.git"), | |
| 195 | stdout: "pipe", | |
| 196 | }); | |
| 197 | return (await new Response(p.stdout).text()).split("\n").filter(Boolean).sort(); | |
| 198 | }; | |
| 199 | const pullCount = () => | |
| 200 | (app.db.query("SELECT COUNT(*) AS n FROM issue WHERE kind = 'pull'").get() as { n: number }).n; | |
| 201 | ||
| 202 | test("no model is called: a successful cut makes no network request", async () => { | |
| 203 | const olive = getUserByName(app.db, "olive")!; | |
| 204 | const made = await createRepo(app, olive, { name: "proj2", visibility: "public" }); | |
| 205 | if (!made.ok) throw new Error(String(made.error)); | |
| 206 | const repo2 = made.value; | |
| 207 | await commitToBranch(app, olive, repo2, { message: "seed", files: [{ path: "a.txt", content: "a\n" }] }); | |
| 208 | await commitToBranch(app, olive, repo2, { branch: "x", message: "x", files: [{ path: "x.txt", content: "x" }] }); | |
| 209 | const p = await openPull(app, olive, repo2, { title: "Feature x", baseBranch: "main", headBranch: "x" }); | |
| 210 | if (!p.ok) throw new Error(p.error); | |
| 211 | expect((await mergePull(app, olive, repo2, p.value, "merge")).ok).toBe(true); | |
| 212 | const real = globalThis.fetch; | |
| 213 | const seen: string[] = []; | |
| 214 | globalThis.fetch = ((input: string | URL | Request) => { | |
| 215 | seen.push(String(input)); | |
| 216 | throw new Error("network is off in this test"); | |
| 217 | }) as unknown as typeof fetch; | |
| 218 | let cutDone; | |
| 219 | try { | |
| 220 | cutDone = await cutWaveFor(app, olive, repo2); | |
| 221 | } finally { | |
| 222 | globalThis.fetch = real; | |
| 223 | } | |
| 224 | expect(cutDone?.ok).toBe(true); | |
| 225 | expect(seen).toEqual([]); | |
| 226 | }); | |
| 227 | ||
| 228 | // This test commits a junk waves/0.0.9.md to main of olive/proj; later tests on that repo inherit it, so new tests that need a clean wave directory go above it or use their own repo. | |
outdated
The junk wave from the first test is still on main here, so cutWaveFor stops at the parse and never reaches any code that could fetch. This passes whether or not a model is called. Run it on a repo where the cut succeeds, assert ok, and keep the fetch stub; otherwise delete it. |
||
| 229 | test("an earlier wave that does not parse stops the cut, named, and nothing is written", async () => { | |
| 230 | const olive = getUserByName(app.db, "olive")!; | |
| 231 | await commitToBranch(app, olive, repo, { | |
| 232 | message: "junk wave", | |
| 233 | files: [{ path: "waves/0.0.9.md", content: "not a wave" }], | |
| 234 | }); | |
| 235 | const beforeBranches = await branches(); | |
| 236 | const beforePulls = pullCount(); | |
| 237 | const direct = await cutWaveFor(app, olive, repo); | |
| 238 | expect(direct.ok).toBe(false); | |
| 239 | if (!direct.ok) expect(direct.error).toContain("waves/0.0.9.md"); | |
| 240 | const res = await cut("olive"); | |
| 241 | expect(res.status).toBe(303); | |
| 242 | expect(res.headers.get("location")).toBe("/olive/proj/settings"); | |
| 243 | expect(decodeURIComponent(res.headers.get("set-cookie") ?? "")).toContain("waves/0.0.9.md"); | |
| 244 | expect(await branches()).toEqual(beforeBranches); | |
| 245 | expect(pullCount()).toBe(beforePulls); | |
| 246 | }); | |
| 247 | ||
| 248 | test("cutWaveFor refuses a non-admin, an agent and an anonymous caller, writing nothing", async () => { | |
| 249 | const beforeBranches = await branches(); | |
| 250 | const beforePulls = pullCount(); | |
| 251 | const stranger = getUserByName(app.db, "stranger")!; | |
| 252 | const bot = getUserByName(app.db, "bot")!; | |
| 253 | expect(bot.kind).toBe("agent"); | |
| 254 | for (const who of [stranger, bot, undefined]) { | |
| 255 | const r = await cutWaveFor(app, who, repo); | |
| 256 | expect(r).toEqual({ ok: false, error: "Only a repository admin can cut a wave." }); | |
| 257 | } | |
| 258 | expect(await branches()).toEqual(beforeBranches); | |
| 259 | expect(pullCount()).toBe(beforePulls); | |
| 260 | }); | |
| 187 | 261 | }); |
new above
offline
Add an agent actor to this loop;
actor.kind === "agent"is a refusal branch with no test.