Spaces:
Sleeping
Sleeping
File size: 8,407 Bytes
cc11e77 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | /**
* debugLoop.test.ts — Unit test S26
*
* Testa il ciclo di esecuzione + auto-fix:
* - run: successo al primo tentativo
* - run: fallisce → llm_fix → riprova → successo
* - run: esaurisce i retry → fallisce
* - run: fix uguale al codice originale → break anticipato
* - Emissioni eventi: agent_step_start, agent_step_complete, agent_step_error
* - Callbacks: onFix, onSuccess
*
* Mock: fetch (globale), @/core/events, @/config/env
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
vi.mock("@/core/events", () => ({
eventRuntime: { emit: vi.fn() },
}));
vi.mock("@/config/env", () => ({
ENV: { BACKEND_URL: "http://localhost:8000" },
}));
import { debugLoop } from "@/lib/debugLoop";
import { eventRuntime } from "@/core/events";
const mockEmit = vi.mocked(eventRuntime.emit);
// ─── Helpers fetch ────────────────────────────────────────────────────────────
function execOk(stdout = "Hello"): Response {
// S770-Fix2: backend sends exit_code (snake_case), not exitCode
return { ok: true, json: async () => ({ stdout, stderr: "", exit_code: 0, durationMs: 5 }) } as unknown as Response;
}
function execFail(stderr = "ReferenceError: x is not defined"): Response {
// S770-Fix2: backend sends exit_code (snake_case)
return { ok: true, json: async () => ({ stdout: "", stderr, exit_code: 1, durationMs: 3 }) } as unknown as Response;
}
function llmFix(code: string): Response {
return { ok: true, json: async () => ({ fixed_code: code }) } as unknown as Response;
}
beforeEach(() => vi.clearAllMocks());
afterEach(() => vi.unstubAllGlobals());
// ─── successo immediato ───────────────────────────────────────────────────────
describe("debugLoop.run — successo immediato", () => {
it("ritorna success=true, attempts=1 se exec ha exitCode=0", async () => {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(execOk("output line 1")));
const result = await debugLoop.run("console.log('hello')", {
taskId: "t-001",
lang: "javascript",
});
expect(result.success).toBe(true);
expect(result.attempts).toBe(1);
expect(result.lastResult?.exitCode).toBe(0);
expect(result.lastResult?.stdout).toBe("output line 1");
expect(result.finalCode).toBe("console.log('hello')");
});
it("emette agent_step_start + agent_step_complete al successo", async () => {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(execOk()));
await debugLoop.run("print(1)", { taskId: "t-002", lang: "python" });
expect(mockEmit).toHaveBeenCalledWith(
expect.objectContaining({ kind: "agent_step_start", taskId: "t-002", step: 1 }),
);
expect(mockEmit).toHaveBeenCalledWith(
expect.objectContaining({ kind: "agent_step_complete", success: true }),
);
});
it("chiama onSuccess con il risultato dell'exec", async () => {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(execOk("42")));
const onSuccess = vi.fn();
await debugLoop.run("print(42)", { taskId: "t-003", lang: "python", onSuccess });
expect(onSuccess).toHaveBeenCalledOnce();
expect(onSuccess).toHaveBeenCalledWith(
expect.objectContaining({ stdout: "42", exitCode: 0 }),
);
});
});
// ─── fallisce → llm fix → successo ───────────────────────────────────────────
describe("debugLoop.run — fail → fix → successo", () => {
it("riprova con il codice fixato e ritorna success=true, attempts=2", async () => {
const fixedCode = "const x = 1; console.log(x)";
vi.stubGlobal("fetch", vi.fn()
.mockResolvedValueOnce(execFail("ReferenceError: x is not defined")) // exec 1 → fail
.mockResolvedValueOnce(llmFix(fixedCode)) // llm_fix
.mockResolvedValueOnce(execOk("1"))); // exec 2 → ok
const result = await debugLoop.run("console.log(x)", {
taskId: "t-010",
lang: "javascript",
});
expect(result.success).toBe(true);
expect(result.attempts).toBe(2);
expect(result.finalCode).toBe(fixedCode);
});
it("chiama onFix con attempt e codice fixato", async () => {
const fixedCode = "print('fixed')";
const onFix = vi.fn();
vi.stubGlobal("fetch", vi.fn()
.mockResolvedValueOnce(execFail("NameError: name 'oops' is not defined"))
.mockResolvedValueOnce(llmFix(fixedCode))
.mockResolvedValueOnce(execOk("fixed")));
await debugLoop.run("print(oops)", { taskId: "t-011", lang: "python", onFix });
expect(onFix).toHaveBeenCalledWith(1, fixedCode);
});
it("emette agent_step_error al fallimento prima del fix", async () => {
const fixedCode = "const x = 1;";
vi.stubGlobal("fetch", vi.fn()
.mockResolvedValueOnce(execFail("SyntaxError"))
.mockResolvedValueOnce(llmFix(fixedCode))
.mockResolvedValueOnce(execOk()));
await debugLoop.run("const = 1;", { taskId: "t-012", lang: "typescript" });
expect(mockEmit).toHaveBeenCalledWith(
expect.objectContaining({ kind: "agent_step_error", taskId: "t-012" }),
);
});
});
// ─── esaurisce i retry ────────────────────────────────────────────────────────
describe("debugLoop.run — esaurisce retry", () => {
it("ritorna success=false dopo maxRetries tentativi falliti", async () => {
// exec sempre fallisce, llm_fix ritorna codice diverso ogni volta
vi.stubGlobal("fetch", vi.fn()
.mockResolvedValueOnce(execFail("err1"))
.mockResolvedValueOnce(llmFix("code_v2"))
.mockResolvedValueOnce(execFail("err2"))
.mockResolvedValueOnce(llmFix("code_v3"))
.mockResolvedValueOnce(execFail("err3"))
.mockResolvedValueOnce(llmFix("code_v4"))
.mockResolvedValueOnce(execFail("err4"))); // attempt oltre maxRetries
const result = await debugLoop.run("broken code", {
taskId: "t-020",
lang: "javascript",
maxRetries: 3,
});
expect(result.success).toBe(false);
expect(result.lastResult?.exitCode).toBe(1);
});
it("maxRetries=0 → un solo tentativo, nessun fix", async () => {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(execFail("error")));
const onFix = vi.fn();
const result = await debugLoop.run("bad code", {
taskId: "t-021", lang: "python", maxRetries: 0, onFix,
});
expect(result.success).toBe(false);
expect(result.attempts).toBe(1);
expect(onFix).not.toHaveBeenCalled();
});
});
// ─── break anticipato (fix invariato) ────────────────────────────────────────
describe("debugLoop.run — break se fix = codice originale", () => {
it("esce dal loop se llm_fix ritorna lo stesso codice (nessun progresso)", async () => {
const code = "console.log(undefined_var)";
vi.stubGlobal("fetch", vi.fn()
.mockResolvedValueOnce(execFail("ReferenceError"))
.mockResolvedValueOnce(llmFix(code))); // stessa stringa → break
const result = await debugLoop.run(code, {
taskId: "t-030", lang: "javascript",
});
expect(result.success).toBe(false);
// S697: semantic repair (3° chiamata): exec → llm_fix → semantic llm_fix
// (il semantic repair è best-effort anche quando fix === codice originale)
expect(vi.mocked(fetch)).toHaveBeenCalledTimes(3);
});
});
// ─── resilienza fetch ─────────────────────────────────────────────────────────
describe("debugLoop.run — resilienza", () => {
it("gestisce fetch che lancia eccezione (network error)", async () => {
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("Network failure")));
const result = await debugLoop.run("code", {
taskId: "t-040", lang: "javascript", maxRetries: 0,
});
// Deve ritornare senza crash, con exitCode non 0
expect(result.success).toBe(false);
});
});
|