AUDIT / src /lib /__tests__ /debugLoop.test.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
8.41 kB
/**
* 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);
});
});