import { describe, expect, it } from "vitest"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { invokeReferentialIa, testReferentialIaConnection, type ReferentielIaConnection, } from "./referentialIa"; import { readSecretFile, resolveServerSecret } from "./_core/env"; const connection: ReferentielIaConnection = { baseUrl: "https://referentiel-ia-santinova.recette.santinova-soft.org", token: "test-token", }; function jsonResponse(body: unknown, status = 200) { return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" }, }); } describe("Référentiel IA", () => { it("lit un jeton serveur depuis un fichier monté sans le journaliser", () => { const directory = mkdtempSync(join(tmpdir(), "referentiel-ia-")); const secretFile = join(directory, "token"); try { writeFileSync(secretFile, " server-only-token\n", { mode: 0o600 }); expect(readSecretFile(secretFile)).toBe("server-only-token"); expect(readSecretFile(join(directory, "absent"))).toBe(""); } finally { rmSync(directory, { recursive: true, force: true }); } }); it("utilise le secret monté lorsque la variable d'environnement est vide", () => { const directory = mkdtempSync(join(tmpdir(), "referentiel-ia-")); const secretFile = join(directory, "token"); try { writeFileSync(secretFile, " mounted-secret\n", { mode: 0o600 }); expect(resolveServerSecret("", secretFile)).toBe("mounted-secret"); expect(resolveServerSecret(" ", secretFile)).toBe("mounted-secret"); expect(resolveServerSecret("environment-secret", secretFile)).toBe("environment-secret"); } finally { rmSync(directory, { recursive: true, force: true }); } }); it("transmet le slug du moteur décidé avant la requête OpenAI-compatible", async () => { let receivedUrl = ""; let receivedInit: RequestInit | undefined; let callCount = 0; const result = await invokeReferentialIa( { messages: [{ role: "user", content: "Extrais la facture" }], max_tokens: 1200, }, connection, async (url, init) => { callCount += 1; receivedUrl = String(url); receivedInit = init; if (callCount === 1) { expect(receivedUrl).toBe("https://referentiel-ia-santinova.recette.santinova-soft.org/api/integration/v1/recommendations"); expect(JSON.parse(String(receivedInit?.body))).toEqual({ useCase: "texte", optimizeCost: true, needsFreeTier: false, includeConnection: false, }); return jsonResponse({ application: { name: "Démat. Facturation DSI" }, decision: { selected: { engine: { name: "SANTINO Expert", slug: "santino-expert" } } }, }); } return jsonResponse({ id: "ref-1", created: 1, model: "selected-by-reference", choices: [{ index: 0, message: { role: "assistant", content: "{}" }, finish_reason: "stop" }], }); }, ); expect(receivedUrl).toBe("https://referentiel-ia-santinova.recette.santinova-soft.org/api/integration/v1/invoke"); expect(receivedInit?.headers).toMatchObject({ authorization: "Bearer test-token" }); expect(JSON.parse(String(receivedInit?.body))).toMatchObject({ engineSlug: "santino-expert", useCase: "texte", payload: { messages: [{ role: "user", content: "Extrais la facture" }], max_tokens: 1200, stream: false, }, }); expect(callCount).toBe(2); expect(result.choices[0]?.message.content).toBe("{}"); }); it("retourne un diagnostic non sensible lorsqu'aucun jeton n'est configuré", async () => { await expect( invokeReferentialIa({ messages: [{ role: "user", content: "test" }] }, { baseUrl: "", token: "" }), ).rejects.toThrow("Référentiel IA"); }); it("confirme le moteur choisi sans déclencher d'analyse de facture", async () => { const result = await testReferentialIaConnection( connection, async (_url, init) => { expect(JSON.parse(String(init?.body))).toEqual({ useCase: "texte", optimizeCost: true, needsFreeTier: false, includeConnection: false, }); return jsonResponse({ application: { name: "Démat. Facturation DSI" }, decision: { selected: { engine: { name: "Manus Forge", slug: "manus-forge" }, connection: undefined } }, }); }, ); expect(result).toEqual({ applicationName: "Démat. Facturation DSI", engineName: "Manus Forge", model: null }); }); });