AUDIT / src /lib /__tests__ /skillPersistence.test.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
9.39 kB
/**
* skillPersistence.test.ts — Unit test S32
*
* Copre:
* - recordPattern: nuovo pattern → {successCount:1, totalCount:1, confidence:1}
* - recordPattern: pattern esistente → aggiorna successCount/totalCount/confidence
* - recordPattern: success=false → confidence < 1
* - recordPattern: toolSequence troncata a MAX_SEQUENCE_LEN (8)
* - recordPattern: taskSignature normalizzata (lowercase, punteggiatura rimossa)
* - suggestPattern: nessun pattern → null
* - suggestPattern: pattern sotto soglia successCount → null
* - suggestPattern: pattern sotto soglia confidence → null
* - suggestPattern: task dissimile (Jaccard < 0.25) → null
* - suggestPattern: task simile + soglie ok → stringa hint
* - suggestPattern: hint contiene toolSequence e %
* - suggestPattern: hint ≤ 300 chars
* - getPatterns: ritorna tutti i pattern in memoria
* - clearPatterns: svuota la memoria
* - initSkillPersistence: carica da DB mock
* - _setTestDb: sostituisce Dexie reale con mock
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {
recordPattern,
suggestPattern,
getPatterns,
clearPatterns,
initSkillPersistence,
_setTestDb,
_resetDb,
type SkillPattern,
} from "@/lib/skillPersistence";
// ─── Mock DB factory ──────────────────────────────────────────────────────────
function makeMockDb(initialRows: SkillPattern[] = []) {
const rows: SkillPattern[] = [...initialRows];
return {
patterns: {
put: vi.fn().mockResolvedValue(undefined),
clear: vi.fn().mockResolvedValue(undefined),
toArray: vi.fn().mockResolvedValue(rows),
},
};
}
// ─── Setup / Teardown ─────────────────────────────────────────────────────────
let mockDb: ReturnType<typeof makeMockDb>;
beforeEach(() => {
mockDb = makeMockDb();
_setTestDb(mockDb);
clearPatterns();
});
afterEach(() => {
_resetDb();
clearPatterns();
});
// ─── recordPattern ────────────────────────────────────────────────────────────
describe("recordPattern", () => {
it("nuovo pattern success → successCount=1, totalCount=1, confidence=1", () => {
const p = recordPattern("cerca meteo Roma", ["web_search", "read_page"], true);
expect(p.successCount).toBe(1);
expect(p.totalCount).toBe(1);
expect(p.confidence).toBe(1);
expect(p.toolSequence).toEqual(["web_search", "read_page"]);
});
it("nuovo pattern failure → successCount=0, confidence=0", () => {
const p = recordPattern("cerca meteo Roma", ["web_search"], false);
expect(p.successCount).toBe(0);
expect(p.confidence).toBe(0);
});
it("pattern esistente aggiornato: 2 success → confidence=1", () => {
recordPattern("scrivi file TypeScript", ["write_file"], true);
const p = recordPattern("scrivi file TypeScript", ["write_file"], true);
expect(p.successCount).toBe(2);
expect(p.totalCount).toBe(2);
expect(p.confidence).toBe(1);
});
it("1 success + 1 failure → confidence=0.5", () => {
recordPattern("debug codice", ["run_code", "read_file"], true);
const p = recordPattern("debug codice", ["run_code", "read_file"], false);
expect(p.totalCount).toBe(2);
expect(p.confidence).toBe(0.5);
});
it("toolSequence troncata a 8 elementi", () => {
const tools = ["a","b","c","d","e","f","g","h","i","j"];
const p = recordPattern("task lungo", tools, true);
expect(p.toolSequence.length).toBe(8);
expect(p.toolSequence).toEqual(["a","b","c","d","e","f","g","h"]);
});
it("taskSignature normalizzata: lowercase e punteggiatura rimossa", () => {
const p = recordPattern("CERCA! meteo a Roma???", ["web_search"], true);
expect(p.taskSignature).not.toContain("!");
expect(p.taskSignature).not.toContain("?");
expect(p.taskSignature).toBe(p.taskSignature.toLowerCase());
});
it("stesso task (normalizzato) → stesso id", () => {
const p1 = recordPattern("cerca meteo Roma", ["web_search"], true);
const p2 = recordPattern("CERCA METEO ROMA", ["web_search"], false);
expect(p1.id).toBe(p2.id);
});
it("salva su DB mock (write-behind)", () => {
recordPattern("test task", ["web_search"], true);
expect(mockDb.patterns.put).toHaveBeenCalledTimes(1);
});
it("aggiorna lastUsed al timestamp corrente", () => {
const before = Date.now();
const p = recordPattern("task test", ["web_search"], true);
expect(p.lastUsed).toBeGreaterThanOrEqual(before);
});
});
// ─── suggestPattern ───────────────────────────────────────────────────────────
describe("suggestPattern", () => {
it("nessun pattern → null", () => {
expect(suggestPattern("cerca meteo Roma")).toBeNull();
});
it("pattern con successCount < 2 → null", () => {
recordPattern("cerca meteo Roma", ["web_search"], true); // successCount=1
expect(suggestPattern("cerca meteo Roma")).toBeNull();
});
it("pattern con confidence < 0.6 → null", () => {
recordPattern("cerca meteo Roma", ["web_search"], true); // 1/2
recordPattern("cerca meteo Roma", ["web_search"], false); // confidence=0.5
expect(suggestPattern("cerca meteo Roma")).toBeNull();
});
it("task completamente diverso (Jaccard bassa) → null", () => {
// Registro pattern per "meteo Roma"
recordPattern("meteo Roma oggi", ["web_search"], true);
recordPattern("meteo Roma oggi", ["web_search"], true);
// Cerco qualcosa di completamente diverso
expect(suggestPattern("scrivi codice Python per ordinare lista")).toBeNull();
});
it("task simile + soglie ok → stringa hint non null", () => {
recordPattern("cerca meteo Roma", ["web_search", "read_page"], true);
recordPattern("cerca meteo Roma", ["web_search", "read_page"], true);
const hint = suggestPattern("meteo Roma previsioni");
expect(hint).not.toBeNull();
expect(typeof hint).toBe("string");
});
it("hint contiene la sequenza tool", () => {
recordPattern("scrivi file ts", ["write_file", "run_code"], true);
recordPattern("scrivi file ts", ["write_file", "run_code"], true);
const hint = suggestPattern("scrivi file typescript");
expect(hint).toContain("write_file");
expect(hint).toContain("run_code");
});
it("hint contiene la percentuale di successo", () => {
recordPattern("cerca notizie", ["web_search"], true);
recordPattern("cerca notizie", ["web_search"], true);
const hint = suggestPattern("notizie recenti");
expect(hint).toContain("100%");
});
it("hint ≤ 300 chars", () => {
const longTools = Array(8).fill("web_search_tool_very_long_name");
recordPattern("cerca meteo Roma", longTools, true);
recordPattern("cerca meteo Roma", longTools, true);
const hint = suggestPattern("meteo Roma oggi");
if (hint) expect(hint.length).toBeLessThanOrEqual(300);
});
it("hint contiene SKILL NOTO", () => {
recordPattern("scrivi codice", ["run_code"], true);
recordPattern("scrivi codice", ["run_code"], true);
const hint = suggestPattern("scrivi codice typescript");
if (hint) expect(hint).toContain("SKILL NOTO");
});
});
// ─── getPatterns / clearPatterns ──────────────────────────────────────────────
describe("getPatterns / clearPatterns", () => {
it("getPatterns restituisce array vuoto inizialmente", () => {
expect(getPatterns()).toEqual([]);
});
it("getPatterns ritorna i pattern registrati", () => {
recordPattern("task A", ["web_search"], true);
recordPattern("task B", ["run_code"], false);
expect(getPatterns().length).toBe(2);
});
it("clearPatterns svuota la memoria", () => {
recordPattern("task A", ["web_search"], true);
clearPatterns();
expect(getPatterns()).toEqual([]);
});
});
// ─── initSkillPersistence ─────────────────────────────────────────────────────
describe("initSkillPersistence", () => {
it("carica pattern dal DB mock in memoria", async () => {
const stored: SkillPattern = {
id: "abc",
taskSignature: "test task",
toolSequence: ["web_search"],
successCount: 3,
totalCount: 3,
lastUsed: Date.now(),
confidence: 1,
};
const db = makeMockDb([stored]);
_setTestDb(db);
clearPatterns();
await initSkillPersistence();
const patterns = getPatterns();
expect(patterns.length).toBe(1);
expect(patterns[0].id).toBe("abc");
});
it("non crasha se DB lancia", async () => {
const db = makeMockDb();
db.patterns.toArray = vi.fn().mockRejectedValue(new Error("DB error"));
_setTestDb(db);
clearPatterns();
await expect(initSkillPersistence()).resolves.not.toThrow();
expect(getPatterns()).toEqual([]);
});
});