import { CompositeMemoryBackend } from '../compositeBackend'; import { MockMemoryBackend } from './mockBackend'; import type { MemoryBackend, MemoryEntry } from '../types'; class FakeGraphBackend implements MemoryBackend { readonly kind = 'graph' as const; private data: MemoryEntry[] = []; constructor(seed: Array<{ content: string; score: number }>) { this.data = seed.map((s, i) => ({ id: `g${i}`, path: 'graph/node.md', content: s.content, score: s.score, createdAt: new Date(), source: 'graph', })); } async search() { return this.data; } async get() { return null; } async append() {} async health() { return { ok: true, backend: 'graph' as const }; } } describe('CompositeMemoryBackend', () => { const scope = { agentId: 'A', userId: '1' }; it('merges and ranks results from every backend', async () => { const vector = new MockMemoryBackend(); await vector.append(scope, { path: 'memory/agent/playbook.md', content: 'decision about X', }); const graph = new FakeGraphBackend([ { content: 'graph node about X', score: 0.95 }, ]); const composite = new CompositeMemoryBackend([vector, graph]); const results = await composite.search(scope, 'X', { maxResults: 5 }); expect(results.length).toBeGreaterThanOrEqual(2); expect(results[0].score).toBeGreaterThanOrEqual( results[results.length - 1].score ); expect(results.map((r) => r.source)).toEqual( expect.arrayContaining(['vector', 'graph']) ); }); it('fan-out append stops on first failure', async () => { const ok = new MockMemoryBackend(); const broken: MemoryBackend = { kind: 'graph', async search() { return []; }, async get() { return null; }, async append() { throw new Error('graph down'); }, async health() { return { ok: false, backend: 'graph', error: 'down' }; }, }; const composite = new CompositeMemoryBackend([ok, broken]); await expect( composite.append(scope, { path: 'memory/agent/pitfalls.md', content: 'test', }) ).rejects.toThrow('graph down'); }); });