AUDIT / src /lib /__tests__ /errorAutoFix.test.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
9.73 kB
/**
* errorAutoFix.test.ts — Unit test S32
*
* Copre:
* - startErrorAutoFix: avvia il timer (isAutoFixRunning = true)
* - startErrorAutoFix: idempotente (chiamate multiple non duplicano)
* - stopErrorAutoFix: ferma il timer (isAutoFixRunning = false)
* - _tick: no backend URL → skip silenzioso
* - _tick: errori < THRESHOLD → nessun debugLoop.run
* - _tick: errori ≥ THRESHOLD, lang=typescript → debugLoop.run chiamato
* - _tick: debugLoop.run success → errorLog.markFixed chiamato
* - _tick: debugLoop.run failure → markFixed non chiamato
* - _tick: cooldown attivo → debugLoop.run non chiamato una seconda volta
* - _tick: già in running → skip (no doppia esecuzione)
* - _tick: debugLoop lancia → non crasha (non-fatal)
* - clearCooldowns: svuota cooldown map
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {
startErrorAutoFix,
stopErrorAutoFix,
isAutoFixRunning,
clearCooldowns,
_setTestDeps,
_resetDeps,
_tickForTest,
} from "@/lib/errorAutoFix";
import type { ErrorEntry } from "@/lib/agentBrain";
// ─── Helper ───────────────────────────────────────────────────────────────────
function makeEntry(overrides: Partial<ErrorEntry> = {}): ErrorEntry {
return {
hash: "h1",
message: "SyntaxError: Unexpected token",
context: "tool:run_code",
fix: undefined,
count: 3,
firstSeen: 1000,
lastSeen: Date.now(),
lang: "typescript",
resolved: false,
...overrides,
};
}
function makeMockDebugLoop(success = true, times = 1) {
return {
run: vi.fn().mockResolvedValue({
success,
finalCode: success ? "const x = 1;" : "",
attempts: times,
}),
};
}
// ─── Setup / Teardown ─────────────────────────────────────────────────────────
beforeEach(() => {
stopErrorAutoFix();
clearCooldowns();
_resetDeps();
// Resetta ENV mock
vi.unstubAllEnvs();
});
afterEach(() => {
stopErrorAutoFix();
clearCooldowns();
_resetDeps();
});
// ─── startErrorAutoFix / stopErrorAutoFix ─────────────────────────────────────
describe("startErrorAutoFix / stopErrorAutoFix", () => {
it("startErrorAutoFix avvia il loop (isAutoFixRunning = true)", () => {
startErrorAutoFix(60_000);
expect(isAutoFixRunning()).toBe(true);
stopErrorAutoFix();
});
it("stopErrorAutoFix ferma il loop (isAutoFixRunning = false)", () => {
startErrorAutoFix(60_000);
stopErrorAutoFix();
expect(isAutoFixRunning()).toBe(false);
});
it("startErrorAutoFix è idempotente: doppia chiamata non duplica", () => {
startErrorAutoFix(60_000);
startErrorAutoFix(60_000); // seconda chiamata: ignorata
expect(isAutoFixRunning()).toBe(true);
stopErrorAutoFix();
expect(isAutoFixRunning()).toBe(false);
});
});
// ─── _tick: condizioni di skip ────────────────────────────────────────────────
describe("_tick: condizioni di skip", () => {
it("nessun BACKEND_URL → skip silenzioso, debugLoop non chiamato", async () => {
const mockLog = { unresolved: vi.fn().mockReturnValue([makeEntry(), makeEntry(), makeEntry()]), markFixed: vi.fn() };
const mockLoop = makeMockDebugLoop();
_setTestDeps({ errorLog: mockLog, debugLoop: mockLoop });
// ENV.BACKEND_URL è vuoto per default in test
await _tickForTest();
expect(mockLoop.run).not.toHaveBeenCalled();
});
it("errori < THRESHOLD (3) → nessun debugLoop.run", async () => {
const mockLog = {
unresolved: vi.fn().mockReturnValue([makeEntry(), makeEntry()]), // solo 2
markFixed: vi.fn(),
};
const mockLoop = makeMockDebugLoop();
_setTestDeps({ errorLog: mockLog, debugLoop: mockLoop });
await _tickForTest();
expect(mockLoop.run).not.toHaveBeenCalled();
});
it("nessun errore → nessun debugLoop.run", async () => {
const mockLog = { unresolved: vi.fn().mockReturnValue([]), markFixed: vi.fn() };
const mockLoop = makeMockDebugLoop();
_setTestDeps({ errorLog: mockLog, debugLoop: mockLoop });
await _tickForTest();
expect(mockLoop.run).not.toHaveBeenCalled();
});
});
// ─── _tick: auto-fix attivo ───────────────────────────────────────────────────
describe("_tick: auto-fix con backend", () => {
beforeEach(() => {
// Stub BACKEND_URL tramite ENV mock nel modulo
// Non possiamo stubare import.meta.env direttamente, usiamo _setTestDeps
// con debugLoop già iniettato (così il modulo non tenta import lazy)
});
it("≥3 errori typescript + debugLoop iniettato + success → markFixed chiamato", async () => {
const entries = [makeEntry({ hash: "h1" }), makeEntry({ hash: "h1" }), makeEntry({ hash: "h1" })];
const mockLog = { unresolved: vi.fn().mockReturnValue(entries), markFixed: vi.fn().mockReturnValue(true) };
const mockLoop = makeMockDebugLoop(true, 1);
// Inietta debugLoop direttamente E simula BACKEND_URL settato
_setTestDeps({ errorLog: mockLog, debugLoop: mockLoop });
// Modifichiamo ENV tramite patch sull'oggetto importato
const envModule = await import("@/config/env");
const origUrl = envModule.ENV.BACKEND_URL;
(envModule.ENV as Record<string, unknown>).BACKEND_URL = "https://backend.example.com";
await _tickForTest();
(envModule.ENV as Record<string, unknown>).BACKEND_URL = origUrl;
expect(mockLoop.run).toHaveBeenCalledTimes(1);
expect(mockLog.markFixed).toHaveBeenCalledTimes(1);
const [hash, fixNote] = mockLoop.run.mock.calls.length > 0
? [mockLog.markFixed.mock.calls[0][0], mockLog.markFixed.mock.calls[0][1]]
: ["", ""];
expect(hash).toBe("h1");
expect(fixNote).toContain("Auto-fix");
});
it("debugLoop failure → markFixed non chiamato", async () => {
const entries = [makeEntry({ hash: "h2" }), makeEntry({ hash: "h2" }), makeEntry({ hash: "h2" })];
const mockLog = { unresolved: vi.fn().mockReturnValue(entries), markFixed: vi.fn() };
const mockLoop = makeMockDebugLoop(false);
_setTestDeps({ errorLog: mockLog, debugLoop: mockLoop });
const envModule = await import("@/config/env");
const origUrl = envModule.ENV.BACKEND_URL;
(envModule.ENV as Record<string, unknown>).BACKEND_URL = "https://backend.example.com";
await _tickForTest();
(envModule.ENV as Record<string, unknown>).BACKEND_URL = origUrl;
expect(mockLoop.run).toHaveBeenCalledTimes(1);
expect(mockLog.markFixed).not.toHaveBeenCalled();
});
it("cooldown attivo → secondo tick non chiama debugLoop", async () => {
clearCooldowns();
const entries = [makeEntry({ hash: "h3" }), makeEntry({ hash: "h3" }), makeEntry({ hash: "h3" })];
const mockLog = { unresolved: vi.fn().mockReturnValue(entries), markFixed: vi.fn() };
const mockLoop = makeMockDebugLoop(true);
_setTestDeps({ errorLog: mockLog, debugLoop: mockLoop });
const envModule = await import("@/config/env");
const origUrl = envModule.ENV.BACKEND_URL;
(envModule.ENV as Record<string, unknown>).BACKEND_URL = "https://backend.example.com";
await _tickForTest(); // prima esecuzione → cooldown impostato
await _tickForTest(); // seconda esecuzione → h3 in cooldown
(envModule.ENV as Record<string, unknown>).BACKEND_URL = origUrl;
// debugLoop chiamato solo una volta
expect(mockLoop.run).toHaveBeenCalledTimes(1);
});
it("debugLoop lancia → non crasha (non-fatal)", async () => {
const entries = [makeEntry({ hash: "h4" }), makeEntry({ hash: "h4" }), makeEntry({ hash: "h4" })];
const mockLog = { unresolved: vi.fn().mockReturnValue(entries), markFixed: vi.fn() };
const mockLoop = { run: vi.fn().mockRejectedValue(new Error("Network timeout")) };
_setTestDeps({ errorLog: mockLog, debugLoop: mockLoop });
const envModule = await import("@/config/env");
const origUrl = envModule.ENV.BACKEND_URL;
(envModule.ENV as Record<string, unknown>).BACKEND_URL = "https://backend.example.com";
await expect(_tickForTest()).resolves.not.toThrow();
(envModule.ENV as Record<string, unknown>).BACKEND_URL = origUrl;
});
});
// ─── clearCooldowns ───────────────────────────────────────────────────────────
describe("clearCooldowns", () => {
it("clearCooldowns permette di ritentare dopo cooldown", async () => {
const entries = [makeEntry({ hash: "h5" }), makeEntry({ hash: "h5" }), makeEntry({ hash: "h5" })];
const mockLog = { unresolved: vi.fn().mockReturnValue(entries), markFixed: vi.fn() };
const mockLoop = makeMockDebugLoop(true);
_setTestDeps({ errorLog: mockLog, debugLoop: mockLoop });
const envModule = await import("@/config/env");
const origUrl = envModule.ENV.BACKEND_URL;
(envModule.ENV as Record<string, unknown>).BACKEND_URL = "https://backend.example.com";
await _tickForTest(); // imposta cooldown
clearCooldowns(); // resetta
await _tickForTest(); // ora può ritentare
(envModule.ENV as Record<string, unknown>).BACKEND_URL = origUrl;
expect(mockLoop.run).toHaveBeenCalledTimes(2);
});
});