import { PgvectorRecallTracker, NullRecallTracker, RECALL_TABLE, } from '../recallTracking'; import type { Pool } from 'pg'; interface FakeQuery { text: string; values?: unknown[]; } function makeFakePool(): Pool & { calls: FakeQuery[] } { const calls: FakeQuery[] = []; const p = { calls, async query(text: string, values?: unknown[]) { calls.push({ text, values }); return { rows: [] }; }, }; return p as unknown as Pool & { calls: FakeQuery[] }; } describe('PgvectorRecallTracker.migrate', () => { it('creates table + indexes idempotently', async () => { const pool = makeFakePool(); const t = new PgvectorRecallTracker(pool); await t.migrate(); const joined = pool.calls.map((c) => c.text).join(' '); expect(joined).toContain(`CREATE TABLE IF NOT EXISTS ${RECALL_TABLE}`); expect(joined).toContain(`${RECALL_TABLE}_agent_day_idx`); expect(joined).toContain(`${RECALL_TABLE}_memory_idx`); expect(joined).toContain(`${RECALL_TABLE}_dedupe_idx`); }); }); describe('PgvectorRecallTracker.record', () => { it('no-ops on empty hits / missing agent / empty query', async () => { const pool = makeFakePool(); const t = new PgvectorRecallTracker(pool); await t.record({ agentId: 'a', query: 'q', hits: [] }); await t.record({ agentId: '', query: 'q', hits: [{ id: '1', path: 'p', score: 0.5 }], }); await t.record({ agentId: 'a', query: ' ', hits: [{ id: '1', path: 'p', score: 0.5 }], }); expect(pool.calls).toHaveLength(0); }); it('emits a single upsert per record call with one row per hit', async () => { const pool = makeFakePool(); const t = new PgvectorRecallTracker(pool); await t.record({ agentId: 'poc-sales', query: 'pricing tiers', hits: [ { id: '1', path: 'memory/2026-04-13.md', score: 0.9 }, { id: '2', path: 'memory/2026-04-12.md', score: 0.8 }, ], }); expect(pool.calls).toHaveLength(1); const sql = pool.calls[0].text; expect(sql).toContain('INSERT INTO'); expect(sql).toContain('ON CONFLICT'); // Two value groups → 16 bound parameters (7 per hit + clause). expect(pool.calls[0].values).toHaveLength(14); }); it('dedupe per (agent, memory, query_hash, day_bucket) via ON CONFLICT DO UPDATE', async () => { const pool = makeFakePool(); const t = new PgvectorRecallTracker(pool); await t.record({ agentId: 'a', query: 'q', hits: [{ id: '1', path: 'p', score: 0.5 }], }); expect(pool.calls[0].text).toMatch( /ON CONFLICT \(agent_id, memory_id, query_hash, day_bucket\)/ ); expect(pool.calls[0].text).toMatch(/GREATEST/); }); }); describe('NullRecallTracker', () => { it('no-ops without errors', async () => { const t = new NullRecallTracker(); await expect(t.migrate()).resolves.toBeUndefined(); await expect(t.record()).resolves.toBeUndefined(); }); });