test(agent-manager): cover diff cancellation races

This commit is contained in:
marius-kilocode
2026-08-25 13:35:04 +02:00
parent 44b438363a
commit befab09aa4
4 changed files with 55 additions and 1 deletions
@@ -667,6 +667,20 @@ describe("GitOps", () => {
})
})
it("kills an in-flight exec when its request signal aborts", async () => {
await withRepo(async (cwd) => {
const git = new GitOps({ log: () => undefined, binary: async () => process.execPath })
const ctl = new AbortController()
const pending = git.execGit(["-e", "setTimeout(() => {}, 5000)"], cwd, { signal: ctl.signal })
await sleep(25)
ctl.abort()
const result = await pending
expect(result.code).not.toBe(0)
git.dispose()
})
})
it("is safe to call multiple times", () => {
const git = ops(async () => "ok")
git.dispose()
@@ -365,6 +365,22 @@ describe("diffFile", () => {
})
})
it("does not cache detail that is aborted before Git completes", async () => {
await withRepo(async (dir, base) => {
await fs.writeFile(path.join(dir, "seed.txt"), "seed\ncached\n")
const local = createLocalDiff(git())
await local.summary(dir, base)
const ctl = new AbortController()
const pending = local.file(dir, base, "seed.txt", ctl.signal)
ctl.abort()
await expect(pending).rejects.toThrow()
const result = await local.file(dir, base, "seed.txt")
expect(result?.after).toBe("seed\ncached\n")
})
})
it("invalidates cached detail after the summary stamp changes", async () => {
await withRepo(async (dir, base) => {
await fs.writeFile(path.join(dir, "seed.txt"), "seed\nfirst\n")
@@ -489,4 +489,29 @@ describe("SourceController.refresh", () => {
controller.stop()
})
it("shares an in-flight fetch between refresh and polling callers", async () => {
let release!: () => void
const gate = new Promise<void>((resolve) => {
release = resolve
})
let fetches = 0
const source: DiffSource = {
descriptor: SESSION_DESC,
async fetch() {
fetches++
await gate
return { diffs: [] }
},
}
const { controller } = make({ "session:s1": source })
controller.setContext({ workspaceRoot: "/repo", sessionId: "s1" })
const activation = controller.activate("session:s1", { poll: false })
const refresh = controller.refresh()
release()
await Promise.all([activation, refresh])
expect(fetches).toBe(1)
controller.stop()
})
})
@@ -711,7 +711,6 @@ const AgentManagerContent: Component = () => {
createEffect(() => {
const ids = new Set(worktrees().map((wt) => wt.id))
composers.prune(ids)
composers.prune(ids)
setReviewOpenByContext((prev) => {
const next = pruneReviewState(prev, currentProjectId() ?? "single", ids)
if (Object.keys(next).length === Object.keys(prev).length) return prev