From 694afb146271079ab9cf7934c6519f8ffc08ee79 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 18:53:52 +0000 Subject: [PATCH] feat: make getRelevantMemories testable and add tests - Modified `getRelevantMemories` in `src/services/memory/index.js` to accept optional `ranker` and `cache` parameters for dependency injection. - Added `test/memory-index.test.mjs` with comprehensive unit tests for `getRelevantMemories` using `node:test` mocks. - Verified that existing behavior is preserved via default parameters. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- src/services/memory/index.js | 13 +++- test/memory-index.test.mjs | 146 +++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 test/memory-index.test.mjs diff --git a/src/services/memory/index.js b/src/services/memory/index.js index 986bbbf..cf7375f 100644 --- a/src/services/memory/index.js +++ b/src/services/memory/index.js @@ -198,10 +198,15 @@ export async function ingestEvents(events, options = {}) { * @returns {Promise} Sorted list of relevant memories. */ export async function getRelevantMemories(params) { - const { repository = memoryRepository, ...queryParams } = params; + const { + repository = memoryRepository, + ranker = filterAndRankMemories, + cache: cacheProvider = cache, + ...queryParams + } = params; const telemetry = buildTelemetryEmitter(params); const cacheKey = JSON.stringify(queryParams); - const cached = cache.get(cacheKey); + const cached = cacheProvider.get(cacheKey); if (cached) { telemetry('memory:retrieved', { agent_id: queryParams.agent_id, @@ -220,9 +225,9 @@ export async function getRelevantMemories(params) { ? await repository.listMemories(queryParams) : [...memoryStore.values()]; - const ranked = await filterAndRankMemories(source, queryParams); + const ranked = await ranker(source, queryParams); await updateMemoryUsage(repository, ranked.map((memory) => memory.id)); - cache.set(cacheKey, ranked); + cacheProvider.set(cacheKey, ranked); telemetry('memory:retrieved', { agent_id: queryParams.agent_id, diff --git a/test/memory-index.test.mjs b/test/memory-index.test.mjs new file mode 100644 index 0000000..1e9709f --- /dev/null +++ b/test/memory-index.test.mjs @@ -0,0 +1,146 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { getRelevantMemories } from '../src/services/memory/index.js'; + +function createMockMemory(id) { + return { + id, + content: `content for ${id}`, + agent_id: 'agent-1', + created_at: Date.now(), + last_seen: Date.now(), + }; +} + +test('getRelevantMemories uses cached results when available', async (t) => { + const cachedMemory = createMockMemory('cached-1'); + const cacheGetMock = t.mock.fn((key) => { + if (key.includes('test-query')) return [cachedMemory]; + return null; + }); + const cacheSetMock = t.mock.fn(); + + const mockCache = { + get: cacheGetMock, + set: cacheSetMock, + }; + + const repository = { + listMemories: t.mock.fn(async () => []), + updateMemoryUsage: async () => {}, + }; + + const result = await getRelevantMemories({ + agent_id: 'agent-1', + query: 'test-query', + cache: mockCache, + repository, + }); + + assert.equal(result.length, 1); + assert.equal(result[0].id, 'cached-1'); + assert.equal(cacheGetMock.mock.callCount(), 1); + // Should NOT call listMemories on cache hit + assert.equal(repository.listMemories.mock.callCount(), 0); + // Should NOT call cache.set on cache hit + assert.equal(cacheSetMock.mock.callCount(), 0); +}); + +test('getRelevantMemories calls ranker and updates cache on miss', async (t) => { + const memories = [createMockMemory('m1'), createMockMemory('m2')]; + const repository = { + listMemories: t.mock.fn(async () => memories), + updateMemoryUsage: t.mock.fn(async () => {}), + }; + + // Mock ranker to reverse the list + const ranker = t.mock.fn(async (source) => [...source].reverse()); + + const cacheSetMock = t.mock.fn(); + const mockCache = { + get: () => null, + set: cacheSetMock, + }; + + const result = await getRelevantMemories({ + agent_id: 'agent-1', + query: 'test-query', + repository, + ranker, + cache: mockCache, + }); + + assert.equal(result.length, 2); + assert.equal(result[0].id, 'm2'); + assert.equal(result[1].id, 'm1'); + + // Verify ranker called with correct source + assert.equal(ranker.mock.callCount(), 1); + const [sourceArg] = ranker.mock.calls[0].arguments; + assert.deepEqual(sourceArg, memories); + + // Verify cache updated + assert.equal(cacheSetMock.mock.callCount(), 1); + const [keyArg, valueArg] = cacheSetMock.mock.calls[0].arguments; + assert.ok(typeof keyArg === 'string'); + assert.deepEqual(valueArg, result); // Should cache the ranked result + + // Verify memory usage updated + assert.equal(repository.updateMemoryUsage.mock.callCount(), 2); +}); + +test('getRelevantMemories passes correct parameters to ranker', async (t) => { + const repository = { + listMemories: async () => [], + updateMemoryUsage: async () => {}, + }; + const ranker = t.mock.fn(async () => []); + const mockCache = { get: () => null, set: () => {} }; + + const params = { + agent_id: 'agent-1', + query: 'my query', + tags: ['a', 'b'], + k: 5, + repository, + ranker, + cache: mockCache, + }; + + await getRelevantMemories(params); + + assert.equal(ranker.mock.callCount(), 1); + const [_, queryParams] = ranker.mock.calls[0].arguments; + + assert.equal(queryParams.agent_id, 'agent-1'); + assert.equal(queryParams.query, 'my query'); + assert.deepEqual(queryParams.tags, ['a', 'b']); + assert.equal(queryParams.k, 5); + + // Ensure injected deps are NOT in queryParams + assert.equal(queryParams.repository, undefined); + assert.equal(queryParams.ranker, undefined); + assert.equal(queryParams.cache, undefined); +}); + +test('getRelevantMemories triggers updateMemoryUsage on repository', async (t) => { + const m1 = createMockMemory('m1'); + const repository = { + listMemories: async () => [m1], + updateMemoryUsage: t.mock.fn(async () => {}), + }; + const ranker = async (source) => source; // identity ranker + const mockCache = { get: () => null, set: () => {} }; + + await getRelevantMemories({ + agent_id: 'agent-1', + repository, + ranker, + cache: mockCache, + }); + + // updateMemoryUsage helper calls repository.updateMemoryUsage for each memory + assert.equal(repository.updateMemoryUsage.mock.callCount(), 1); + const [idArg] = repository.updateMemoryUsage.mock.calls[0].arguments; + assert.equal(idArg, 'm1'); +});