Spaces:
Running
Running
File size: 8,232 Bytes
cc11e77 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | /**
* 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);
});
});
|