mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-29 17:01:55 +00:00
- Added `test/memory-run-prune-cycle.test.mjs` to test `runPruneCycle` logic. - Covered active pruning, dry-run, off mode, telemetry/metrics, and scheduling. - Utilized environment isolation (temp dir) to prevent side effects on `.scheduler-memory/memory-store.json`. - Addressed testing gap for `runPruneCycle` in `src/services/memory/index.js`. Co-authored-by: PR0M3TH3AN <[email protected]>
157 lines
4.6 KiB
JavaScript
157 lines
4.6 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import os from 'node:os';
|
|
import crypto from 'node:crypto';
|
|
|
|
const originalCwd = process.cwd();
|
|
let tmpDir;
|
|
|
|
function createMemory(overrides = {}) {
|
|
return {
|
|
schema_version: 1,
|
|
id: overrides.id ?? crypto.randomUUID(),
|
|
agent_id: overrides.agent_id ?? 'agent-1',
|
|
session_id: overrides.session_id ?? 's1',
|
|
type: overrides.type ?? 'event',
|
|
content: overrides.content ?? 'test content',
|
|
summary: overrides.summary ?? 'test summary',
|
|
tags: overrides.tags ?? [],
|
|
importance: overrides.importance ?? 0.5,
|
|
embedding_id: overrides.embedding_id ?? null,
|
|
created_at: overrides.created_at ?? Date.now(),
|
|
last_seen: overrides.last_seen ?? Date.now(),
|
|
source: overrides.source ?? 'ingest',
|
|
ttl_days: overrides.ttl_days ?? null,
|
|
merged_into: overrides.merged_into ?? null,
|
|
pinned: overrides.pinned ?? false,
|
|
};
|
|
}
|
|
|
|
// Global state variables for the module under test
|
|
let runPruneCycle;
|
|
let listMemories;
|
|
|
|
// Setup temp environment and load module
|
|
test.before(async () => {
|
|
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'memory-prune-test-'));
|
|
|
|
// Create .scheduler-memory directory
|
|
const memDir = path.join(tmpDir, '.scheduler-memory');
|
|
await fs.mkdir(memDir, { recursive: true });
|
|
|
|
// Create initial state
|
|
const now = 200_000;
|
|
// retentionMs will be 100,000
|
|
// cutoff = 100,000
|
|
|
|
const memories = [
|
|
createMemory({ id: 'recent', last_seen: 150_000 }), // > 100,000 -> Keep
|
|
createMemory({ id: 'pinned', last_seen: 50_000, pinned: true }), // < 100,000 but pinned -> Keep
|
|
createMemory({ id: 'stale', last_seen: 50_000 }), // < 100,000 -> Delete
|
|
createMemory({ id: 'stale-dry', last_seen: 50_000 }), // < 100,000 -> Delete candidate
|
|
createMemory({ id: 'stale-off', last_seen: 50_000 }), // < 100,000 -> Delete candidate
|
|
];
|
|
|
|
// memory-store.json expects an array of entries [[id, memory], ...]
|
|
const entries = memories.map(m => [m.id, m]);
|
|
await fs.writeFile(path.join(memDir, 'memory-store.json'), JSON.stringify(entries), 'utf8');
|
|
|
|
// Change CWD
|
|
process.chdir(tmpDir);
|
|
|
|
// Import module (it will load the file we just wrote)
|
|
const mod = await import('../src/services/memory/index.js');
|
|
runPruneCycle = mod.runPruneCycle;
|
|
listMemories = mod.listMemories;
|
|
});
|
|
|
|
test.after(async () => {
|
|
if (originalCwd) process.chdir(originalCwd);
|
|
if (tmpDir) await fs.rm(tmpDir, { recursive: true, force: true });
|
|
});
|
|
|
|
test('runPruneCycle respects off mode', async () => {
|
|
const now = 200_000;
|
|
const retentionMs = 100_000;
|
|
|
|
const result = await runPruneCycle({
|
|
now,
|
|
retentionMs,
|
|
pruneMode: 'off',
|
|
});
|
|
|
|
assert.equal(result.mode, 'off');
|
|
|
|
// Verify nothing deleted
|
|
const memories = await listMemories();
|
|
assert.equal(memories.length, 5);
|
|
});
|
|
|
|
test('runPruneCycle respects dry-run mode', async () => {
|
|
const now = 200_000;
|
|
const retentionMs = 100_000;
|
|
|
|
const result = await runPruneCycle({
|
|
now,
|
|
retentionMs,
|
|
pruneMode: 'dry-run',
|
|
});
|
|
|
|
assert.equal(result.mode, 'dry-run');
|
|
// Expect 3 candidates: stale, stale-dry, stale-off
|
|
assert.equal(result.candidates.length, 3);
|
|
|
|
// Verify nothing deleted
|
|
const memories = await listMemories();
|
|
assert.equal(memories.length, 5);
|
|
});
|
|
|
|
test('runPruneCycle removes stale unpinned memories in active mode', async () => {
|
|
const now = 200_000;
|
|
const retentionMs = 100_000;
|
|
|
|
const telemetry = [];
|
|
const metrics = [];
|
|
|
|
const result = await runPruneCycle({
|
|
now,
|
|
retentionMs,
|
|
pruneMode: 'active',
|
|
emitTelemetry: (event, payload) => telemetry.push({ event, payload }),
|
|
emitMetric: (metric, payload) => metrics.push({ metric, payload }),
|
|
});
|
|
|
|
assert.equal(result.pruned.length, 3); // stale, stale-dry, stale-off
|
|
|
|
// Verify deletion
|
|
const memories = await listMemories();
|
|
assert.equal(memories.length, 2);
|
|
const ids = memories.map(m => m.id);
|
|
assert.ok(ids.includes('recent'));
|
|
assert.ok(ids.includes('pinned'));
|
|
assert.ok(!ids.includes('stale'));
|
|
|
|
// Verify telemetry
|
|
const prunedEvent = telemetry.find(t => t.event === 'memory:pruned');
|
|
assert.ok(prunedEvent);
|
|
assert.equal(prunedEvent.payload.pruned_count, 3);
|
|
|
|
// Verify metrics
|
|
const prunedMetric = metrics.find(m => m.metric === 'memory_pruned_total');
|
|
assert.ok(prunedMetric);
|
|
assert.equal(prunedMetric.payload.count, 3);
|
|
});
|
|
|
|
test('runPruneCycle starts scheduler if requested', async () => {
|
|
const result = await runPruneCycle({
|
|
scheduleEveryMs: 1000,
|
|
pruneMode: 'active',
|
|
});
|
|
|
|
assert.ok(result.scheduler);
|
|
assert.equal(typeof result.scheduler.stop, 'function');
|
|
result.scheduler.stop();
|
|
});
|