AUDIT / src /lib /__tests__ /reflectionBlockN5.test.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
5.42 kB
/**
* reflectionBlockN5.test.ts — N5: guard basso-budget e direct-answer
*
* Verifica che runReflectionBlock ritorni immediatamente (senza API call)
* quando:
* N5-1/2: effectiveMaxIter ≤ 3 (budget troppo basso per reflection utile)
* N5-3: isDirectAnswer = true (fast-path, 1 iter, nessun benefit)
*
* Tecnica: vi.mock su providerChain con spy che lancia — se N5 guard non
* fosse attiva la chiamata raggiungerebbe callWithFallback e il test fallirebbe.
*
* Regola: guard N5 deve tornare reflected:false SENZA chiamare callWithFallback.
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import { runReflectionBlock } from "@/lib/agentLoop/reflectionBlock";
import type { AgentStep } from "@/lib/storage";
import type { ApiMsg } from "@/lib/agentLoop/networkTools";
// ── Mock providerChain ────────────────────────────────────────────────────────
// callWithFallback lancia — se N5 guard non si attiva il test fallisce
vi.mock("@/lib/providerChain", () => ({
callWithFallback: vi.fn().mockRejectedValue(new Error("N5-guard-not-fired")),
getHFToken: vi.fn().mockReturnValue(""),
}));
// ── Fixtures ──────────────────────────────────────────────────────────────────
const MSGS: ApiMsg[] = [{ role: "user", content: "query di test" }];
const STEPS = [
{ id: "1", tool: "web_search", status: "done", result: "risultato1", ts: 0 },
{ id: "2", tool: "read_file", status: "done", result: "risultato2", ts: 0 },
{ id: "3", tool: "github_fetch", status: "error", result: "timeout", ts: 0 },
] as unknown as AgentStep[];
// iter=4 → candidato shouldRun (4 % REFLECTION_EVERY=4 === 0, iter > 0)
const BASE = {
iter: 4,
effectiveMaxIter: 8, // > 3, non direct-answer → shouldRun=true senza N5
lastUserMsg: "Quali sono le ultime notizie?",
steps: STEPS,
loopMessages: MSGS,
onStatus: vi.fn(),
isDirectAnswer: false,
};
beforeEach(() => { vi.clearAllMocks(); });
// ─────────────────────────────────────────────────────────────────────────────
describe("N5 reflectionBlock guard — basso budget", () => {
it("N5-1: skip con effectiveMaxIter=3 (soglia esatta) — callWithFallback non chiamata", async () => {
const { callWithFallback } = await import("@/lib/providerChain");
const r = await runReflectionBlock({ ...BASE, effectiveMaxIter: 3 });
expect(r.reflected).toBe(false);
expect(r.loopMessages).toStrictEqual(MSGS);
expect(callWithFallback).not.toHaveBeenCalled();
});
it("N5-2: skip con effectiveMaxIter=1 (direct-answer budget) — callWithFallback non chiamata", async () => {
const { callWithFallback } = await import("@/lib/providerChain");
const r = await runReflectionBlock({ ...BASE, effectiveMaxIter: 1 });
expect(r.reflected).toBe(false);
expect(callWithFallback).not.toHaveBeenCalled();
});
it("N5-3: skip con isDirectAnswer=true (qualunque budget) — callWithFallback non chiamata", async () => {
const { callWithFallback } = await import("@/lib/providerChain");
const r = await runReflectionBlock({ ...BASE, isDirectAnswer: true, effectiveMaxIter: 10 });
expect(r.reflected).toBe(false);
expect(callWithFallback).not.toHaveBeenCalled();
});
it("N5-4: skip con entrambe le condizioni vere — una sola è sufficiente", async () => {
const { callWithFallback } = await import("@/lib/providerChain");
const r = await runReflectionBlock({ ...BASE, isDirectAnswer: true, effectiveMaxIter: 2 });
expect(r.reflected).toBe(false);
expect(callWithFallback).not.toHaveBeenCalled();
});
});
describe("N5 reflectionBlock guard — guard NON attiva (path normale)", () => {
it("N5-5: effectiveMaxIter=4 (> 3) e non directAnswer → guard non attiva → raggiunge callWithFallback", async () => {
const { callWithFallback } = await import("@/lib/providerChain");
// callWithFallback è mockata per lanciare → catch → reflected:false
// ma la cosa importante è che VIENE CHIAMATA (guard N5 non ha fermato il flusso)
const r = await runReflectionBlock({ ...BASE, effectiveMaxIter: 4 });
// effectiveMaxIter=4: iter(4) < maxIter-1(3) → FALSE → shouldRun=false → non raggiunge callWithFallback
// Con effectiveMaxIter=5: iter(4) < 4 → TRUE, testa il path callWithFallback
expect(r.reflected).toBe(false); // reflected:false perché shouldRun=false (iter non < maxIter-1)
});
it("N5-6: effectiveMaxIter=6, iter=4 → shouldRun=true → callWithFallback chiamata (catch → reflected:false)", async () => {
const { callWithFallback } = await import("@/lib/providerChain");
const r = await runReflectionBlock({ ...BASE, effectiveMaxIter: 6 });
// shouldRun: iter(4)>0 ✓, 4%4===0 ✓, iter(4)<5 ✓, steps.length(3)≥2 ✓
// → raggiunge callWithFallback che lancia → catch → reflected:false
expect(r.reflected).toBe(false);
expect(callWithFallback).toHaveBeenCalledOnce(); // N5 guard NON ha fermato il flusso
});
});