- Menu déroulant GraduationCap : Flashcards + Exercices réunis - Fix: language non défini dans toolbar (useLanguage destructuring) - Fix: équations 658071 → KaTeX dans exercices (preprocessMathInHtml partagé) - lib/text/math-preprocess.ts : utilitaire partagé wizard + exercices - Toast avec bouton 'Voir' pour rafraîchir après création exercices - emitNoteChange pour rafraîchir la liste - i18n FR/EN
133 lines
4.6 KiB
TypeScript
133 lines
4.6 KiB
TypeScript
import { test, expect, describe, beforeAll, afterAll, beforeEach } from 'vitest'
|
|
import { prisma } from '../../lib/prisma'
|
|
import { ChunkIndexingService } from '../../lib/ai/services/chunk-indexing.service'
|
|
import { embeddingService } from '../../lib/ai/services/embedding.service'
|
|
import crypto from 'crypto'
|
|
|
|
const testNoteId = 'test-chunk-000001'
|
|
|
|
describe('US-CHUNK-2 : Indexation incrémentale avec dedup', () => {
|
|
let originalEmbedText: any
|
|
|
|
beforeAll(async () => {
|
|
originalEmbedText = embeddingService.embedText
|
|
embeddingService.embedText = async (text: string) => {
|
|
const hash = crypto.createHash('md5').update(text).digest()
|
|
return Array.from({ length: 1536 }, (_, i) => hash[i % 16] / 255)
|
|
}
|
|
|
|
await prisma.note.upsert({
|
|
where: { id: testNoteId },
|
|
create: {
|
|
id: testNoteId,
|
|
title: 'Test Note for Chunk Indexing',
|
|
content: '<p>Test</p>',
|
|
},
|
|
update: {},
|
|
})
|
|
})
|
|
|
|
afterAll(async () => {
|
|
embeddingService.embedText = originalEmbedText
|
|
await prisma.noteEmbeddingChunk.deleteMany({ where: { noteId: testNoteId } })
|
|
await prisma.note.delete({ where: { id: testNoteId } }).catch(() => {})
|
|
})
|
|
|
|
beforeEach(async () => {
|
|
await prisma.noteEmbeddingChunk.deleteMany({ where: { noteId: testNoteId } })
|
|
})
|
|
|
|
const longContent = Array.from({ length: 8 }, (_, i) =>
|
|
`Section ${i} de la note de test. `.repeat(60).trim(),
|
|
).join('\n\n')
|
|
|
|
test('première indexation → tous les fragments sont nouveaux', async () => {
|
|
const service = new ChunkIndexingService()
|
|
const result = await service.indexNote(testNoteId, 'Note de test', longContent)
|
|
|
|
expect(result.newFragments).toBeGreaterThan(0)
|
|
expect(result.deleted).toBe(0)
|
|
expect(result.skipped).toBe(0)
|
|
})
|
|
|
|
test('deuxième indexation (même contenu) → tout skipped, 0 nouveau', async () => {
|
|
const service = new ChunkIndexingService()
|
|
await service.indexNote(testNoteId, 'Note de test', longContent)
|
|
const result = await service.indexNote(testNoteId, 'Note de test', longContent)
|
|
|
|
expect(result.newFragments).toBe(0)
|
|
expect(result.skipped).toBeGreaterThan(0)
|
|
expect(result.deleted).toBe(0)
|
|
})
|
|
|
|
test('modification d\'une section → 1 nouveau, reste skip', async () => {
|
|
const service = new ChunkIndexingService()
|
|
await service.indexNote(testNoteId, 'Note de test', longContent)
|
|
|
|
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)
|
|
|
|
expect(result.newFragments).toBeGreaterThanOrEqual(1)
|
|
expect(result.deleted).toBeGreaterThanOrEqual(1)
|
|
})
|
|
|
|
test('suppression d\'une section → fragments stale nettoyés', async () => {
|
|
const service = new ChunkIndexingService()
|
|
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 },
|
|
})
|
|
|
|
expect(afterCount).toBeLessThan(beforeCount)
|
|
expect(result.deleted).toBeGreaterThan(0)
|
|
})
|
|
|
|
test('note vide → tous les fragments supprimés', async () => {
|
|
const service = new ChunkIndexingService()
|
|
await service.indexNote(testNoteId, 'Note de test', longContent)
|
|
const result = await service.indexNote(testNoteId, '', '')
|
|
|
|
const count = await prisma.noteEmbeddingChunk.count({
|
|
where: { noteId: testNoteId },
|
|
})
|
|
|
|
expect(count).toBe(0)
|
|
})
|
|
|
|
test('deleteNoteChunks → supprime tout', async () => {
|
|
const service = new ChunkIndexingService()
|
|
await service.indexNote(testNoteId, 'Note de test', longContent)
|
|
await service.deleteNoteChunks(testNoteId)
|
|
|
|
const count = await prisma.noteEmbeddingChunk.count({
|
|
where: { noteId: testNoteId },
|
|
})
|
|
expect(count).toBe(0)
|
|
})
|
|
|
|
test('hasChunks → détection correcte', async () => {
|
|
const service = new ChunkIndexingService()
|
|
const before = await service.hasChunks(testNoteId)
|
|
expect(before).toBe(false)
|
|
|
|
await service.indexNote(testNoteId, 'Note de test', longContent)
|
|
const after = await service.hasChunks(testNoteId)
|
|
expect(after).toBe(true)
|
|
})
|
|
})
|