mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-29 17:01:55 +00:00
Merge pull request #250 from PR0M3TH3AN/test-improvement-get-relevant-memories-15158310533650216935
Test Improvement: Make getRelevantMemories testable and add unit tests
This commit is contained in:
2 files changed
+136
-65
No files matched your search
@@ -198,10 +198,15 @@ export async function ingestEvents(events, options = {}) {
|
||||
* @returns {Promise<import('./schema.js').MemoryRecord[]>} 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,
|
||||
|
||||
+127
-61
@@ -1,80 +1,146 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import crypto from 'node:crypto';
|
||||
import { triggerPruneDryRun } from '../src/services/memory/index.js';
|
||||
import { getRelevantMemories } from '../src/services/memory/index.js';
|
||||
|
||||
function createMemory(overrides = {}) {
|
||||
function createMockMemory(id) {
|
||||
return {
|
||||
id: overrides.id ?? crypto.randomUUID(),
|
||||
agent_id: overrides.agent_id ?? 'agent-1',
|
||||
type: overrides.type ?? 'event',
|
||||
pinned: overrides.pinned ?? false,
|
||||
last_seen: overrides.last_seen ?? Date.now(),
|
||||
merged_into: overrides.merged_into ?? null,
|
||||
// Add other fields to match schema if necessary, though selectPrunableMemories mainly checks last_seen and pinned
|
||||
id,
|
||||
content: `content for ${id}`,
|
||||
agent_id: 'agent-1',
|
||||
created_at: Date.now(),
|
||||
last_seen: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
test('triggerPruneDryRun uses repository listPruneCandidates if available', async () => {
|
||||
const candidates = [createMemory({ id: 'c1' }), createMemory({ id: 'c2' })];
|
||||
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 = {
|
||||
listPruneCandidates: async () => candidates,
|
||||
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,
|
||||
};
|
||||
|
||||
const result = await triggerPruneDryRun({ repository });
|
||||
await getRelevantMemories(params);
|
||||
|
||||
assert.equal(result.dryRun, true);
|
||||
assert.equal(result.candidateCount, 2);
|
||||
assert.deepEqual(result.candidates.map(c => c.id), ['c1', 'c2']);
|
||||
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('triggerPruneDryRun falls back to in-memory filtering if listPruneCandidates returns empty', async () => {
|
||||
const now = Date.now();
|
||||
const retentionMs = 1000;
|
||||
const oldMemory = createMemory({ id: 'old', last_seen: now - 2000 });
|
||||
const newMemory = createMemory({ id: 'new', last_seen: now - 500 });
|
||||
|
||||
test('getRelevantMemories triggers updateMemoryUsage on repository', async (t) => {
|
||||
const m1 = createMockMemory('m1');
|
||||
const repository = {
|
||||
listPruneCandidates: async () => [],
|
||||
listMemories: async () => [oldMemory, newMemory],
|
||||
listMemories: async () => [m1],
|
||||
updateMemoryUsage: t.mock.fn(async () => {}),
|
||||
};
|
||||
const ranker = async (source) => source; // identity ranker
|
||||
const mockCache = { get: () => null, set: () => {} };
|
||||
|
||||
const result = await triggerPruneDryRun({ repository, retentionMs, now });
|
||||
await getRelevantMemories({
|
||||
agent_id: 'agent-1',
|
||||
repository,
|
||||
ranker,
|
||||
cache: mockCache,
|
||||
});
|
||||
|
||||
assert.equal(result.candidateCount, 1);
|
||||
assert.equal(result.candidates[0].id, 'old');
|
||||
});
|
||||
|
||||
test('triggerPruneDryRun handles empty candidates correctly', async () => {
|
||||
const repository = {
|
||||
listPruneCandidates: async () => [],
|
||||
listMemories: async () => [],
|
||||
};
|
||||
|
||||
const result = await triggerPruneDryRun({ repository });
|
||||
|
||||
assert.equal(result.candidateCount, 0);
|
||||
assert.deepEqual(result.candidates, []);
|
||||
});
|
||||
|
||||
test('triggerPruneDryRun output structure validation', async () => {
|
||||
const memory = createMemory({ id: 'test-mem', agent_id: 'agent-x', type: 'thought', pinned: false, last_seen: 100, merged_into: 'parent-mem' });
|
||||
const repository = {
|
||||
listPruneCandidates: async () => [memory],
|
||||
};
|
||||
|
||||
const result = await triggerPruneDryRun({ repository });
|
||||
|
||||
assert.equal(result.dryRun, true);
|
||||
assert.ok(result.retentionMs > 0);
|
||||
assert.equal(result.candidateCount, 1);
|
||||
|
||||
const candidate = result.candidates[0];
|
||||
assert.equal(candidate.id, 'test-mem');
|
||||
assert.equal(candidate.agent_id, 'agent-x');
|
||||
assert.equal(candidate.type, 'thought');
|
||||
assert.equal(candidate.pinned, false);
|
||||
assert.equal(candidate.last_seen, 100);
|
||||
assert.equal(candidate.merged_into, 'parent-mem');
|
||||
// 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');
|
||||
});
|
||||
Reference in new issue
Block a user