import { describe, expect, it } from "vitest"; import { resolveCitations } from "../lib/rag/citations.ts"; import type { ContextBlock, RetrievedChunk } from "../lib/rag/search.ts"; function chunk(id: number): RetrievedChunk { return { id, document_id: 1, course_code: "IMM1003", space: "official-imm1003", ref_type: "slide", ref_number: 18, ref_label: "Séance 4 — Diapositive 18", section_title: "", title: "Titre", content: "Contenu de la diapositive.", box_types: "", week: 4, doc_title: "Séance 4", doc_path: "x.tex", filename: "seance04.tex", score: 1, }; } const context: ContextBlock = { text: "", sources: [ { tag: "S1", chunk: chunk(11) }, { tag: "S2", chunk: chunk(22) }, ], }; describe("Validation des citations", () => { it("résout les balises valides en citations cliquables", () => { const { cleaned, citations, invalidCount } = resolveCitations("La valeur [S1] et le coût [S2].", context); expect(invalidCount).toBe(0); expect(citations).toHaveLength(2); expect(citations[0].chunkId).toBe(11); expect(cleaned).toContain("[S1]"); }); it("NEUTRALISE toute balise inventée par le modèle", () => { const { cleaned, citations, invalidCount } = resolveCitations("Faux [S9] mais vrai [S1].", context); expect(invalidCount).toBe(1); expect(cleaned).not.toContain("[S9]"); expect(cleaned).toContain("[S1]"); expect(citations).toHaveLength(1); }); it("normalise les groupes [S1, S2]", () => { const { citations, cleaned } = resolveCitations("Ensemble [S1, S2].", context); expect(citations).toHaveLength(2); expect(cleaned).toContain("[S1][S2]"); }); it("ne cite chaque source qu'une fois dans la liste résolue", () => { const { citations } = resolveCitations("[S1] puis encore [S1] et [S1].", context); expect(citations).toHaveLength(1); }); });