|
1 | 1 | import assert from "node:assert/strict"; |
2 | 2 | import { afterEach, describe, it } from "node:test"; |
3 | | -import type { RunOptions } from "../types.js"; |
4 | | -import { makeResultFilename, preflightValidation } from "./run.js"; |
| 3 | +import type { AgentResult, EnsembleResult, RunOptions } from "../types.js"; |
| 4 | +import { |
| 5 | + findFailedAgents, |
| 6 | + makeResultFilename, |
| 7 | + mergeRetryResults, |
| 8 | + preflightValidation, |
| 9 | +} from "./run.js"; |
5 | 10 |
|
6 | 11 | function makeOpts(overrides: Partial<RunOptions> = {}): RunOptions { |
7 | 12 | return { |
@@ -117,3 +122,135 @@ describe("NO_COLOR environment variable", () => { |
117 | 122 | assert.equal(colors.bold("test"), "test"); |
118 | 123 | }); |
119 | 124 | }); |
| 125 | + |
| 126 | +function makeAgent(overrides: Partial<AgentResult> = {}): AgentResult { |
| 127 | + return { |
| 128 | + id: 1, |
| 129 | + worktree: "/tmp/thinktank-agent-1", |
| 130 | + status: "success", |
| 131 | + exitCode: 0, |
| 132 | + duration: 5000, |
| 133 | + output: "", |
| 134 | + diff: "diff --git a/file.ts b/file.ts\n+added line", |
| 135 | + filesChanged: ["file.ts"], |
| 136 | + linesAdded: 1, |
| 137 | + linesRemoved: 0, |
| 138 | + ...overrides, |
| 139 | + }; |
| 140 | +} |
| 141 | + |
| 142 | +function makeResult(overrides: Partial<EnsembleResult> = {}): EnsembleResult { |
| 143 | + return { |
| 144 | + prompt: "fix the bug", |
| 145 | + model: "sonnet", |
| 146 | + timestamp: "2026-03-28T10:00:00.000Z", |
| 147 | + scoring: "copeland", |
| 148 | + agents: [ |
| 149 | + makeAgent({ id: 1, status: "success" }), |
| 150 | + makeAgent({ id: 2, status: "error", exitCode: 1, diff: "", filesChanged: [] }), |
| 151 | + makeAgent({ id: 3, status: "timeout", exitCode: 1, diff: "", filesChanged: [] }), |
| 152 | + ], |
| 153 | + tests: [], |
| 154 | + convergence: [], |
| 155 | + recommended: 1, |
| 156 | + scores: [], |
| 157 | + ...overrides, |
| 158 | + }; |
| 159 | +} |
| 160 | + |
| 161 | +describe("findFailedAgents", () => { |
| 162 | + it("returns agents with error status", () => { |
| 163 | + const result = makeResult(); |
| 164 | + const failed = findFailedAgents(result); |
| 165 | + const ids = failed.map((a) => a.id); |
| 166 | + assert.ok(ids.includes(2)); |
| 167 | + }); |
| 168 | + |
| 169 | + it("returns agents with timeout status", () => { |
| 170 | + const result = makeResult(); |
| 171 | + const failed = findFailedAgents(result); |
| 172 | + const ids = failed.map((a) => a.id); |
| 173 | + assert.ok(ids.includes(3)); |
| 174 | + }); |
| 175 | + |
| 176 | + it("does not return successful agents", () => { |
| 177 | + const result = makeResult(); |
| 178 | + const failed = findFailedAgents(result); |
| 179 | + const ids = failed.map((a) => a.id); |
| 180 | + assert.ok(!ids.includes(1)); |
| 181 | + }); |
| 182 | + |
| 183 | + it("returns empty array when all agents succeeded", () => { |
| 184 | + const result = makeResult({ |
| 185 | + agents: [makeAgent({ id: 1, status: "success" }), makeAgent({ id: 2, status: "success" })], |
| 186 | + }); |
| 187 | + const failed = findFailedAgents(result); |
| 188 | + assert.equal(failed.length, 0); |
| 189 | + }); |
| 190 | + |
| 191 | + it("returns all agents when all failed", () => { |
| 192 | + const result = makeResult({ |
| 193 | + agents: [ |
| 194 | + makeAgent({ id: 1, status: "error" }), |
| 195 | + makeAgent({ id: 2, status: "timeout" }), |
| 196 | + makeAgent({ id: 3, status: "error" }), |
| 197 | + ], |
| 198 | + }); |
| 199 | + const failed = findFailedAgents(result); |
| 200 | + assert.equal(failed.length, 3); |
| 201 | + }); |
| 202 | +}); |
| 203 | + |
| 204 | +describe("mergeRetryResults", () => { |
| 205 | + it("replaces failed agents with retried results", () => { |
| 206 | + const original = makeResult(); |
| 207 | + const retried = [ |
| 208 | + makeAgent({ id: 2, status: "success", diff: "new diff for 2", filesChanged: ["a.ts"] }), |
| 209 | + makeAgent({ id: 3, status: "success", diff: "new diff for 3", filesChanged: ["b.ts"] }), |
| 210 | + ]; |
| 211 | + |
| 212 | + const merged = mergeRetryResults(original, retried); |
| 213 | + |
| 214 | + assert.equal(merged.length, 3); |
| 215 | + assert.equal(merged[0].id, 1); |
| 216 | + assert.equal(merged[0].status, "success"); |
| 217 | + assert.equal(merged[1].id, 2); |
| 218 | + assert.equal(merged[1].status, "success"); |
| 219 | + assert.equal(merged[1].diff, "new diff for 2"); |
| 220 | + assert.equal(merged[2].id, 3); |
| 221 | + assert.equal(merged[2].status, "success"); |
| 222 | + assert.equal(merged[2].diff, "new diff for 3"); |
| 223 | + }); |
| 224 | + |
| 225 | + it("preserves successful agents unchanged", () => { |
| 226 | + const original = makeResult(); |
| 227 | + const retried = [makeAgent({ id: 2, status: "success" })]; |
| 228 | + |
| 229 | + const merged = mergeRetryResults(original, retried); |
| 230 | + |
| 231 | + assert.equal(merged[0].id, 1); |
| 232 | + assert.equal(merged[0].status, "success"); |
| 233 | + assert.equal(merged[0].diff, original.agents[0].diff); |
| 234 | + }); |
| 235 | + |
| 236 | + it("handles retry where agent still fails", () => { |
| 237 | + const original = makeResult(); |
| 238 | + const retried = [makeAgent({ id: 2, status: "error", diff: "" })]; |
| 239 | + |
| 240 | + const merged = mergeRetryResults(original, retried); |
| 241 | + |
| 242 | + assert.equal(merged[1].id, 2); |
| 243 | + assert.equal(merged[1].status, "error"); |
| 244 | + }); |
| 245 | + |
| 246 | + it("returns same count as original agents", () => { |
| 247 | + const original = makeResult(); |
| 248 | + const retried = [ |
| 249 | + makeAgent({ id: 2, status: "success" }), |
| 250 | + makeAgent({ id: 3, status: "success" }), |
| 251 | + ]; |
| 252 | + |
| 253 | + const merged = mergeRetryResults(original, retried); |
| 254 | + assert.equal(merged.length, original.agents.length); |
| 255 | + }); |
| 256 | +}); |
0 commit comments