Spaces:
Sleeping
Sleeping
| /** | |
| * capabilityRouter.test.ts — Unit test S38 Capability Router | |
| * | |
| * Copertura (12 test): | |
| * 1. route: restituisce gemini per reasoning_depth="high" (score massimo) | |
| * 2. route: restituisce groq per speed=true (velocità 10/10) | |
| * 3. route: provider con vision=false escluso (score basso) quando req.vision=true | |
| * 4. route: provider senza tool calling penalizzato quando requires_tools=true | |
| * 5. route: prefer_free=true favorisce provider gratuiti | |
| * 6. route: long_context favorisce provider con max_context>=100k | |
| * 7. route: ritorna null se nessun provider disponibile | |
| * 8. route: ignora provider non disponibili (DI isAvailable=false) | |
| * 9. rankAll: ordina tutti i candidati per score decrescente | |
| * 10. scoreProfile: score > 0 per profilo compatibile con request | |
| * 11. scoreProfile: score molto negativo per vision req su provider senza vision | |
| * 12. getProfile / getAllProfiles: dati statici corretti | |
| * | |
| * DI pattern: _setTestDeps / _resetDeps — zero vi.mock su moduli ESM | |
| * Modulo puro: nessun side-effect, nessuna dipendenza da browser API | |
| * | |
| * SESSIONE: S38 — Capability Router | |
| * S342: aggiornato 12b per includere cerebras (8° provider, aggiunto in S325) | |
| */ | |
| import { describe, it, expect, beforeEach, afterEach } from "vitest"; | |
| import { | |
| route, | |
| rankAll, | |
| scoreProfile, | |
| getProfile, | |
| getAllProfiles, | |
| buildReason, | |
| CAPABILITY_PROFILES, | |
| _setTestDeps, | |
| _resetDeps, | |
| } from "@/lib/capabilityRouter"; | |
| // ─── Setup / Teardown ───────────────────────────────────────────────────────── | |
| beforeEach(() => { | |
| // Per default tutti i provider disponibili | |
| _setTestDeps({ isAvailable: () => true }); | |
| }); | |
| afterEach(() => { | |
| _resetDeps(); | |
| }); | |
| // ─── route ──────────────────────────────────────────────────────────────────── | |
| describe("route", () => { | |
| it("1. reasoning_depth=high → gemini o openai (reasoning_score >= 9)", () => { | |
| const result = route({ reasoning_depth: "high" }); | |
| expect(result).not.toBeNull(); | |
| expect(result!.profile.reasoning_score).toBeGreaterThanOrEqual(9); | |
| expect(["gemini", "openai"]).toContain(result!.provider); | |
| }); | |
| it("2. speed=true → groq (speed_score 10/10)", () => { | |
| const result = route({ speed: true }); | |
| expect(result).not.toBeNull(); | |
| expect(result!.provider).toBe("groq"); | |
| expect(result!.profile.speed_score).toBe(10); | |
| }); | |
| it("3. vision=true → provider con vision:true (gemini, openai, openrouter)", () => { | |
| const result = route({ vision: true }); | |
| expect(result).not.toBeNull(); | |
| expect(result!.profile.vision).toBe(true); | |
| expect(["gemini", "openai", "openrouter"]).toContain(result!.provider); | |
| }); | |
| it("4. requires_tools=true → provider con tools:true", () => { | |
| const result = route({ requires_tools: true }); | |
| expect(result).not.toBeNull(); | |
| expect(result!.profile.tools).toBe(true); | |
| }); | |
| it("5. prefer_free=true → provider con free:true", () => { | |
| const result = route({ prefer_free: true }); | |
| expect(result).not.toBeNull(); | |
| expect(result!.profile.free).toBe(true); | |
| }); | |
| it("6. long_context=true → provider con max_context >= 100k", () => { | |
| const result = route({ long_context: true }); | |
| expect(result).not.toBeNull(); | |
| expect(result!.profile.max_context).toBeGreaterThanOrEqual(100_000); | |
| }); | |
| it("7. nessun provider disponibile → null", () => { | |
| _setTestDeps({ isAvailable: () => false }); | |
| const result = route({ speed: true }); | |
| expect(result).toBeNull(); | |
| }); | |
| it("8. DI isAvailable filtra i provider — solo groq disponibile → route = groq", () => { | |
| _setTestDeps({ isAvailable: (id) => id === "groq" }); | |
| const result = route({ reasoning_depth: "high" }); | |
| expect(result).not.toBeNull(); | |
| expect(result!.provider).toBe("groq"); | |
| }); | |
| }); | |
| // ─── rankAll ───────────────────────────────────────────────────────────────── | |
| describe("rankAll", () => { | |
| it("9. ordina tutti i candidati per score decrescente", () => { | |
| const ranked = rankAll({ speed: true }); | |
| expect(ranked.length).toBeGreaterThan(1); | |
| for (let i = 0; i < ranked.length - 1; i++) { | |
| expect(ranked[i].score).toBeGreaterThanOrEqual(ranked[i + 1].score); | |
| } | |
| }); | |
| }); | |
| // ─── scoreProfile ───────────────────────────────────────────────────────────── | |
| describe("scoreProfile", () => { | |
| it("10. score > 0 per profilo compatibile con request", () => { | |
| const gemini = CAPABILITY_PROFILES["gemini"]; | |
| const score = scoreProfile(gemini, { reasoning_depth: "high", code: true }); | |
| expect(score).toBeGreaterThan(0); | |
| }); | |
| it("11. score molto negativo per vision req su provider senza vision (groq)", () => { | |
| const groq = CAPABILITY_PROFILES["groq"]; | |
| const score = scoreProfile(groq, { vision: true }); | |
| expect(score).toBeLessThan(-50); | |
| }); | |
| it("11b. score molto negativo per requires_tools su provider senza tools (browser-llm)", () => { | |
| const browserLlm = CAPABILITY_PROFILES["browser-llm"]; | |
| const score = scoreProfile(browserLlm, { requires_tools: true }); | |
| expect(score).toBeLessThan(-50); | |
| }); | |
| }); | |
| // ─── getProfile / getAllProfiles ─────────────────────────────────────────────── | |
| describe("getProfile / getAllProfiles", () => { | |
| it("12. getProfile ritorna profilo valido per ogni ProviderId", () => { | |
| // S342: incluso cerebras (aggiunto in S325 — 2000+ tok/s, fast-chat senza tools) | |
| const ids = ["gemini", "openai", "groq", "openrouter", "huggingface", "together", "browser-llm", "cerebras", "sambanova"] as const; | |
| for (const id of ids) { | |
| const p = getProfile(id); | |
| expect(p.id).toBe(id); | |
| expect(p.reasoning_score).toBeGreaterThanOrEqual(0); | |
| expect(p.reasoning_score).toBeLessThanOrEqual(10); | |
| expect(p.speed_score).toBeGreaterThanOrEqual(0); | |
| expect(p.speed_score).toBeLessThanOrEqual(10); | |
| expect(p.code_score).toBeGreaterThanOrEqual(0); | |
| expect(p.code_score).toBeLessThanOrEqual(10); | |
| expect(p.max_context).toBeGreaterThan(0); | |
| expect(typeof p.vision).toBe("boolean"); | |
| expect(typeof p.free).toBe("boolean"); | |
| expect(typeof p.tools).toBe("boolean"); | |
| } | |
| }); | |
| it("12b. getAllProfiles ritorna 9 profili (uno per provider, incluso cerebras S325 + sambanova S422)", () => { | |
| const profiles = getAllProfiles(); | |
| // S342: 9 provider = gemini, openai, groq, openrouter, huggingface, together, browser-llm, cerebras, sambanova (S422/S502-FIX) | |
| expect(profiles).toHaveLength(9); | |
| const ids = profiles.map(p => p.id); | |
| const unique = new Set(ids); | |
| expect(unique.size).toBe(8); | |
| // Invariante: cerebras deve essere presente (aggiunto S325) | |
| expect(ids).toContain("cerebras"); | |
| // Invariante: sambanova deve essere presente (aggiunto S422, inserito in ALL_PROVIDERS in S502-FIX) | |
| expect(ids).toContain("sambanova"); | |
| }); | |
| }); | |
| // ─── buildReason ────────────────────────────────────────────────────────────── | |
| describe("buildReason", () => { | |
| it("buildReason include dettagli rilevanti per il request", () => { | |
| const gemini = CAPABILITY_PROFILES["gemini"]; | |
| const reason = buildReason(gemini, { reasoning_depth: "high", vision: true }); | |
| expect(reason).toContain("gemini"); | |
| expect(reason).toContain("reasoning"); | |
| expect(reason).toContain("vision"); | |
| }); | |
| it("buildReason funziona anche con request vuoto", () => { | |
| const groq = CAPABILITY_PROFILES["groq"]; | |
| const reason = buildReason(groq, {}); | |
| expect(typeof reason).toBe("string"); | |
| expect(reason.length).toBeGreaterThan(0); | |
| }); | |
| }); | |