test: Add comprehensive tests for memoryStats function

Adds `test/memory-stats.test.mjs` to validats `memoryStats` behavior including:
- Empty and populated repository states
- Ingest throughput calculation with mocked time
- Deleted rate metrics
- Window parameter handling

Co-authored-by: PR0M3TH3AN <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-17 18:53:07 +00:00
1 parent b3f4a5c9cd
commit fde5dbc67d
1 file changed
+138
+138
View File
@@ -0,0 +1,138 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
memoryStats,
ingestEvents,
runPruneCycle,
} from '../src/services/memory/index.js';
test('memoryStats with empty repository returns zero counts and rates', async () => {
const repository = {
async listMemories() {
return [];
},
};
const stats = await memoryStats({ repository });
assert.deepEqual(stats.countsByType, {});
assert.equal(stats.totals.total, 0);
assert.equal(stats.totals.pinned, 0);
assert.equal(stats.totals.archived, 0);
assert.equal(stats.rates.archivedRate, 0);
assert.ok(Number.isFinite(stats.rates.deletedRate));
assert.equal(stats.indexSizeEstimateBytes, 0);
assert.ok(Number.isFinite(stats.ingestThroughputPerMinute));
});
test('memoryStats with populated repository returns correct counts and totals', async () => {
const memories = [
{ type: 'event', pinned: false, merged_into: null },
{ type: 'event', pinned: true, merged_into: null },
{ type: 'summary', pinned: false, merged_into: 'some-id' },
];
const repository = {
async listMemories() {
return memories;
},
};
const stats = await memoryStats({ repository });
assert.equal(stats.countsByType.event, 2);
assert.equal(stats.countsByType.summary, 1);
assert.equal(stats.totals.total, 3);
assert.equal(stats.totals.pinned, 1);
assert.equal(stats.totals.archived, 1);
// archivedRate = archived / max(1, total) = 1 / 3
assert.equal(stats.rates.archivedRate, 1 / 3);
// indexSizeEstimateBytes = 3 * 512 = 1536
assert.equal(stats.indexSizeEstimateBytes, 1536);
});
test('memoryStats calculates ingest throughput based on window', async (t) => {
t.mock.timers.enable({ apis: ['Date'] });
const baseTime = 1000000;
t.mock.timers.setTime(baseTime);
const repository = {
async insertMemory() { return {}; },
async hasIngestionFingerprint() { return false; },
async storeIngestionFingerprint() { },
async linkEmbedding() { },
async listMemories() { return []; },
};
// Create 3 distinct records (different agents to avoid batching into one record)
await ingestEvents([
{ content: 'e1', agent_id: 'agent-1', timestamp: baseTime },
{ content: 'e2', agent_id: 'agent-2', timestamp: baseTime },
{ content: 'e3', agent_id: 'agent-3', timestamp: baseTime },
], { repository });
// Move time forward 30 mins
const checkTime = baseTime + 30 * 60 * 1000;
t.mock.timers.setTime(checkTime);
// Check stats with 60 min window (should include baseTime)
// Window: [checkTime - 60min, checkTime] = [baseTime - 30min, baseTime + 30min]
const stats1 = await memoryStats({
repository,
now: checkTime,
windowMs: 60 * 60 * 1000
});
// We expect 3 ingested items in this window.
// throughput = 3 / (60 mins) = 0.05
assert.equal(stats1.ingestThroughputPerMinute, 3 / 60);
// Check stats with window that excludes the events (checkTime + 90 mins)
const farTime = baseTime + 90 * 60 * 1000;
// Window: [farTime - 60min, farTime] = [baseTime + 30min, baseTime + 90min]
// Events at baseTime are < baseTime + 30min, so excluded.
const stats2 = await memoryStats({
repository,
now: farTime,
windowMs: 60 * 60 * 1000
});
assert.equal(stats2.ingestThroughputPerMinute, 0);
});
test('memoryStats reflects deleted count from prune cycle', async () => {
const repository = {
async listPruneCandidates() {
// Return 2 candidates to be pruned
return [
{ id: 'p1', agent_id: 'a', type: 'event', last_seen: 0, pinned: false },
{ id: 'p2', agent_id: 'a', type: 'event', last_seen: 0, pinned: false },
];
},
async listMemories() { return []; }
};
// Run prune cycle
await runPruneCycle({ repository, retentionMs: 100, now: 1000 });
const stats = await memoryStats({ repository });
// deletedObserved should be increased by 2.
assert.ok(stats.totals.deletedObserved >= 2);
// deletedRate should be > 0.
assert.ok(stats.rates.deletedRate > 0);
});
test('memoryStats handles windowMs parameter correctly', async () => {
const repository = { async listMemories() { return []; } };
// Test with invalid windowMs, should default to 1 hour (60 mins)
const statsDefault = await memoryStats({ repository, windowMs: 'invalid' });
assert.ok(Number.isFinite(statsDefault.ingestThroughputPerMinute));
// Test with very small window
const statsSmall = await memoryStats({ repository, windowMs: 1 });
assert.ok(Number.isFinite(statsSmall.ingestThroughputPerMinute));
});