Files
Momento/memento-note/tests/unit/chunk-indexing.test.ts
Antigravity a623454347
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 1m32s
CI / Deploy production (on server) (push) Has been skipped
perf: memo GridCard, fuse save fns, fix slash tab active color
2026-06-14 14:06:05 +00:00

179 lines
6.1 KiB
TypeScript

/**
* 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<void>) {
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: '<p>Test</p>',
},
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)