import assert from 'node:assert/strict'; import test from 'node:test'; import { createMemoryV2 } from './memory-v2.mjs'; import { createPgvectorMemoryBackend, pgvectorMemoryBackendInternals, } from './memory-v2-pgvector.mjs'; test('pgvector backend is disabled by default and does not query storage', async () => { let queried = false; const backend = createPgvectorMemoryBackend({ pool: { async query() { queried = true; return { rows: [] }; }, }, }); assert.equal(backend.isAvailable(), false); assert.deepEqual(await backend.resolve({ userId: 'user-1', embedding: [0.1, 0.2], }), { memories: [], semanticMemories: [], }); assert.equal(queried, false); }); test('pgvector backend returns empty semantic result when embedding is unavailable', async () => { let queried = false; const backend = createPgvectorMemoryBackend({ enabled: true, pool: { async query() { queried = true; return { rows: [] }; }, }, }); const result = await backend.resolve({ userId: 'user-1', query: 'memory-chain', }); assert.deepEqual(result, { memories: [], semanticMemories: [], }); assert.equal(queried, false); }); test('pgvector backend performs parameterized vector lookup when explicitly enabled', async () => { const queries = []; const backend = createPgvectorMemoryBackend({ enabled: true, tableName: 'memory_embeddings', pool: { async query(sql, params) { queries.push({ sql, params }); return { rows: [ { id: 7, content: '用户关注 Memory V2 的 facade 边界', type: 'fact', score: '0.87', created_at: '2026-07-02T00:00:00.000Z', }, ], }; }, }, embedQuery: async (query) => { assert.equal(query, 'memory-chain'); return [0.25, 0.5, 0.75]; }, }); assert.equal(backend.isAvailable(), true); const result = await backend.resolve({ userId: 'user-1', query: 'memory-chain', limit: 5, }); assert.equal(queries.length, 1); assert.match(queries[0].sql, /FROM memory_embeddings/); assert.match(queries[0].sql, /WITH vector_candidates/); assert.match(queries[0].sql, /recent_candidates/); assert.deepEqual(queries[0].params, ['user-1', '[0.25,0.5,0.75]', 50]); assert.deepEqual(result.semanticMemories, ['用户关注 Memory V2 的 facade 边界']); assert.deepEqual(result.memories, [ { id: '7', label: 'fact', text: '用户关注 Memory V2 的 facade 边界', score: 0.87, createdAt: '2026-07-02T00:00:00.000Z', updatedAt: '2026-07-02T00:00:00.000Z', }, ]); }); test('pgvector hybrid ranking recovers a recent Chinese memory missed by vector top-k', async () => { const marker = 'MEM-RECALL-NEW'; const backend = createPgvectorMemoryBackend({ enabled: true, pool: { async query() { return { rows: [ { id: 1, content: '用户以前关注贵州旅游攻略', type: 'interest', score: 0.91, created_at: '2026-06-01T00:00:00.000Z', updated_at: '2026-06-01T00:00:00.000Z', }, { id: 2, content: `用户的记忆召回灰度测试代号是 ${marker}`, type: 'fact', score: -0.49, created_at: '2026-07-22T00:00:00.000Z', updated_at: '2026-07-22T00:00:00.000Z', }, { id: 3, content: '用户偏好简洁回答', type: 'preference', score: 0.3, created_at: '2026-07-21T00:00:00.000Z', updated_at: '2026-07-21T00:00:00.000Z', }, ], }; }, }, embedQuery: async () => [0.25, 0.5, 0.75], }); const result = await backend.resolve({ userId: 'user-1', query: '我之前让你记住的记忆召回灰度测试代号是什么?请只回答完整代号。', limit: 3, }); assert.match(result.memories[0].text, new RegExp(marker)); }); test('pgvector hybrid ranking keeps vector order when query has no lexical overlap', () => { const ranked = pgvectorMemoryBackendInternals.rankHybridCandidates([ { id: 1, content: 'alpha', score: 0.2 }, { id: 2, content: 'beta', score: 0.8 }, ], '完全无关的中文查询', 2); assert.equal(ranked[0].id, '2'); assert.equal(ranked[1].id, '1'); }); test('pgvector backend validates table names before building SQL', () => { assert.throws( () => createPgvectorMemoryBackend({ tableName: 'memory_embeddings;DROP TABLE users' }), /Invalid pgvector table name/, ); }); test('Memory V2 can expose pgvector as unavailable plugin without selecting it', async () => { const memory = createMemoryV2({ logger: { warn() {} }, backends: [ createPgvectorMemoryBackend({ enabled: false }), { name: 'legacy-conversation-memory', async resolve() { return { memories: [{ label: 'fact', text: 'legacy survives' }] }; }, }, ], env: { MEMORY_ENABLED: '1', MEMORY_BACKEND: 'pgvector', MEMORY_VECTOR_ENABLED: '1', }, }); const status = memory.getStatus(); const result = await memory.resolve({ userId: 'user-1', query: 'memory-chain' }); assert.equal(status.vectorEnabled, true); assert.equal(status.selectedBackend, 'legacy-conversation-memory'); assert.equal(status.backends.find((item) => item.name === 'pgvector')?.available, false); assert.deepEqual(result.memories, [{ label: 'fact', text: 'legacy survives' }]); });