/** * @module tests/integration/background-dedup-lifecycle * @description The post-import background deduplication pass (a merge-DELETE * writer) obeys the same contract as the inline pass. Laws: * (1) enableDeduplication:false schedules NO background pass — the brain-owned * deduplicator is never even constructed; * (2) by default the pass IS scheduled, brain-owned, with an unref'd timer * (a pending pass never holds the process open); * (3) repeated imports debounce into ONE pending batch on ONE instance * (per-coordinator instances used to arm one timer per import); * (4) close() cancels pending work — no delete pass can fire after close. */ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { Brainy } from '../../src/brainy.js' const ROWS = [ { name: 'Alice Zephyr', role: 'engineer' }, { name: 'Bob Quill', role: 'writer' } ] // Keep imports fast and deterministic — dedup scheduling is what's under test. const FAST = { enableNeuralExtraction: false, enableRelationshipInference: false, enableConceptExtraction: false } as const // Deterministic stub embedder (hnsw-rebuild.test.ts pattern) — dedup // scheduling never inspects vector CONTENT, so skip the WASM model load. const stubEmbedding = async (text: string): Promise => { const hash = text.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) const vector = new Array(384).fill(0).map((_, i) => Math.sin(hash + i)) return vector } describe('background dedup lifecycle', () => { let brain: Brainy beforeEach(async () => { brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' as const }, embeddingFunction: stubEmbedding }) await brain.init() }) afterEach(async () => { await brain.close() }) it('enableDeduplication:false schedules no background pass at all', async () => { await brain.import(ROWS, { ...FAST, enableDeduplication: false }) expect((brain as any)._backgroundDedup).toBeUndefined() }) it('default schedules a brain-owned pass with an unref-ed timer', async () => { await brain.import(ROWS, { ...FAST }) const dedup = (brain as any)._backgroundDedup expect(dedup).toBeDefined() expect(dedup.pendingImports.size).toBe(1) const timer = dedup.debounceTimer expect(timer).toBeDefined() // Node timers expose hasRef(); an unref'd timer must not hold the process. expect(typeof timer.hasRef).toBe('function') expect(timer.hasRef()).toBe(false) }) it('imports debounce into one pending batch on one brain-owned instance', async () => { await brain.import(ROWS, { ...FAST }) const first = (brain as any)._backgroundDedup await brain.import([{ name: 'Cara Vex', role: 'analyst' }], { ...FAST }) expect((brain as any)._backgroundDedup).toBe(first) expect(first.pendingImports.size).toBe(2) }) it('close() cancels pending background dedup', async () => { await brain.import(ROWS, { ...FAST }) const dedup = (brain as any)._backgroundDedup expect(dedup.debounceTimer).toBeDefined() await brain.close() expect(dedup.debounceTimer).toBeUndefined() expect(dedup.pendingImports.size).toBe(0) }) })