/** * Test du ChunkIndexingService — dedup, stale deletion, upsert. * Mocke l'embedding (pas d'appel API), utilise la vraie DB. */ import { prisma } from '../../lib/prisma' import { ChunkIndexingService } from '../../lib/ai/services/chunk-indexing.service' const testNoteId = 'test-chunk-000001' function test(name: string, fn: () => Promise) { return fn() .then(() => console.log(` ✓ ${name}`)) .catch((err: any) => { console.error(` ✗ ${name}: ${err.message}`) process.exitCode = 1 }) } // Mock l'embedding service : vecteur déterministe basé sur le hash du contenu const originalEmbedText = require('../../lib/ai/services/embedding.service').embeddingService.embedText function mockEmbedding() { const svc = require('../../lib/ai/services/embedding.service').embeddingService svc.embedText = async (text: string) => { const crypto = require('crypto') const hash = crypto.createHash('md5').update(text).digest() return Array.from({ length: 1536 }, (_, i) => hash[i % 16] / 255) } } function restoreEmbedding() { const svc = require('../../lib/ai/services/embedding.service').embeddingService svc.embedText = originalEmbedText } async function ensureTestNote() { await prisma.note.upsert({ where: { id: testNoteId }, create: { id: testNoteId, title: 'Test Note for Chunk Indexing', content: '

Test

', }, update: {}, }) } async function cleanup() { await prisma.noteEmbeddingChunk.deleteMany({ where: { noteId: testNoteId } }) } async function removeTestNote() { await prisma.note.delete({ where: { id: testNoteId } }).catch(() => {}) } async function main() { mockEmbedding() await ensureTestNote() await cleanup() console.log('\n=== US-CHUNK-2 : Indexation incrémentale avec dedup ===\n') const service = new ChunkIndexingService() const longContent = Array.from({ length: 8 }, (_, i) => `Section ${i} de la note de test. `.repeat(60).trim(), ).join('\n\n') await test('première indexation → tous les fragments sont nouveaux', async () => { const result = await service.indexNote(testNoteId, 'Note de test', longContent) if (result.newFragments === 0) throw new Error(`attendu newFragments > 0, reçu ${result.newFragments}`) if (result.deleted !== 0) throw new Error(`attendu deleted=0, reçu ${result.deleted}`) if (result.skipped !== 0) throw new Error(`attendu skipped=0, reçu ${result.skipped}`) console.log(` → ${result.newFragments} nouveaux, ${result.totalFragments} total`) }) await test('deuxième indexation (même contenu) → tout skipped, 0 nouveau', async () => { const result = await service.indexNote(testNoteId, 'Note de test', longContent) if (result.newFragments !== 0) throw new Error(`attendu 0 nouveau, reçu ${result.newFragments}`) if (result.skipped === 0) throw new Error(`attendu skipped > 0, reçu ${result.skipped}`) if (result.deleted !== 0) throw new Error(`attendu deleted=0, reçu ${result.deleted}`) console.log(` → ${result.skipped} skip, 0 nouveau ✓`) }) await test('modification d\'une section → 1 nouveau, reste skip', async () => { const sections = Array.from({ length: 8 }, (_, i) => `Section ${i === 3 ? 'MODIFIÉE' : i} de la note de test. `.repeat(60).trim(), ) const modified = sections.join('\n\n') const result = await service.indexNote(testNoteId, 'Note de test', modified) if (result.newFragments === 0) throw new Error(`attendu au moins 1 nouveau fragment, reçu ${result.newFragments}`) if (result.deleted === 0) throw new Error(`attendu au moins 1 stale supprimé, reçu ${result.deleted}`) console.log(` → ${result.newFragments} nouveau(x), ${result.skipped} skip, ${result.deleted} supprimé(s)`) }) await test('suppression d\'une section → fragments stale nettoyés', async () => { const sections = Array.from({ length: 8 }, (_, i) => `Section ${i} de la note de test. `.repeat(60).trim(), ) await service.indexNote(testNoteId, 'Note de test', sections.join('\n\n')) const beforeCount = await prisma.noteEmbeddingChunk.count({ where: { noteId: testNoteId }, }) const shorter = sections.slice(0, 4).join('\n\n') const result = await service.indexNote(testNoteId, 'Note de test', shorter) const afterCount = await prisma.noteEmbeddingChunk.count({ where: { noteId: testNoteId }, }) if (afterCount >= beforeCount) throw new Error(`attendu count < ${beforeCount}, reçu ${afterCount}`) if (result.deleted === 0) throw new Error(`attendu deleted > 0, reçu ${result.deleted}`) console.log(` → ${beforeCount} → ${afterCount} fragments (${result.deleted} supprimés)`) }) await test('note vide → tous les fragments supprimés', async () => { await service.indexNote(testNoteId, 'Note de test', longContent) const result = await service.indexNote(testNoteId, '', '') const count = await prisma.noteEmbeddingChunk.count({ where: { noteId: testNoteId }, }) if (count !== 0) throw new Error(`attendu 0 fragments, reçu ${count}`) console.log(` → tous les fragments supprimés ✓`) }) await test('deleteNoteChunks → supprime tout', async () => { await service.indexNote(testNoteId, 'Note de test', longContent) await service.deleteNoteChunks(testNoteId) const count = await prisma.noteEmbeddingChunk.count({ where: { noteId: testNoteId }, }) if (count !== 0) throw new Error(`attendu 0, reçu ${count}`) console.log(` → 0 fragment restant ✓`) }) await test('hasChunks → détection correcte', async () => { const before = await service.hasChunks(testNoteId) if (before) throw new Error('attendu false avant indexation') await service.indexNote(testNoteId, 'Note de test', longContent) const after = await service.hasChunks(testNoteId) if (!after) throw new Error('attendu true après indexation') console.log(` → false avant, true après ✓`) }) await cleanup() restoreEmbedding() await prisma.$disconnect() console.log('\n=== Tests terminés ===') } main().catch(console.error)