Files
Claude-Code-Workflow/ccw/tests/core-memory-store.test.ts
catlog22 267426e332 test(core-memory-store): add unit tests for memory store operations
Solution-ID: SOL-1735386000004

Issue-ID: ISS-1766921318981-18

Task-ID: T1
2025-12-29 09:42:01 +08:00

185 lines
5.9 KiB
TypeScript

/**
* Unit tests for CoreMemoryStore (core memory persistence + clustering helpers).
*
* Notes:
* - Targets the runtime implementation shipped in `ccw/dist`.
* - Uses a real temporary CCW data directory to avoid touching user state.
* - Exercises SQLite-backed CRUD behavior (better-sqlite3) in an isolated temp dir.
*/
import { after, afterEach, before, beforeEach, describe, it, mock } from 'node:test';
import assert from 'node:assert/strict';
import { existsSync, mkdtempSync, mkdirSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const TEST_CCW_HOME = mkdtempSync(join(tmpdir(), 'ccw-core-memory-store-home-'));
const TEST_PROJECT_ROOT = mkdtempSync(join(tmpdir(), 'ccw-core-memory-store-project-'));
const coreMemoryStoreUrl = new URL('../dist/core/core-memory-store.js', import.meta.url);
coreMemoryStoreUrl.searchParams.set('t', String(Date.now()));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let mod: any;
const originalEnv = { CCW_DATA_DIR: process.env.CCW_DATA_DIR };
function resetDir(dirPath: string): void {
if (existsSync(dirPath)) {
rmSync(dirPath, { recursive: true, force: true });
}
mkdirSync(dirPath, { recursive: true });
}
describe('CoreMemoryStore', async () => {
before(async () => {
process.env.CCW_DATA_DIR = TEST_CCW_HOME;
mod = await import(coreMemoryStoreUrl.href);
});
beforeEach(() => {
process.env.CCW_DATA_DIR = TEST_CCW_HOME;
mock.method(console, 'warn', () => {});
try {
mod?.closeAllStores?.();
} catch {
// ignore
}
resetDir(TEST_CCW_HOME);
});
afterEach(() => {
mock.restoreAll();
});
after(() => {
try {
mod?.closeAllStores?.();
} catch {
// ignore
}
process.env.CCW_DATA_DIR = originalEnv.CCW_DATA_DIR;
rmSync(TEST_CCW_HOME, { recursive: true, force: true });
rmSync(TEST_PROJECT_ROOT, { recursive: true, force: true });
});
it('upserts, lists, archives, and deletes memories', () => {
const store = mod.getCoreMemoryStore(TEST_PROJECT_ROOT);
const created = store.upsertMemory({
id: 'CMEM-TEST-1',
content: 'Hello world',
summary: 'Greeting',
});
assert.equal(created.id, 'CMEM-TEST-1');
assert.equal(created.content, 'Hello world');
assert.equal(created.archived, false);
assert.equal(store.getMemory('CMEM-MISSING'), null);
const fetched = store.getMemory('CMEM-TEST-1');
assert.ok(fetched);
assert.equal(fetched?.summary, 'Greeting');
// Default listing excludes archived
const active = store.getMemories({ limit: 10 });
assert.equal(active.length, 1);
assert.equal(active[0].id, 'CMEM-TEST-1');
// Update existing record (including archived flag)
const updated = store.upsertMemory({
id: 'CMEM-TEST-1',
content: 'Hello updated',
archived: true,
metadata: JSON.stringify({ source: 'test' }),
});
assert.equal(updated.content, 'Hello updated');
assert.equal(updated.archived, true);
assert.equal(updated.metadata, JSON.stringify({ source: 'test' }));
assert.equal(store.getMemories({ limit: 10 }).length, 0);
assert.equal(store.getMemories({ archived: true, limit: 10 }).length, 1);
// Delete should remove the record (no throw for missing ids)
store.deleteMemory('CMEM-TEST-1');
assert.equal(store.getMemory('CMEM-TEST-1'), null);
store.deleteMemory('CMEM-TEST-1');
});
it('lists projects with memory/cluster counts', () => {
const store = mod.getCoreMemoryStore(TEST_PROJECT_ROOT);
store.upsertMemory({ id: 'CMEM-TEST-2', content: 'Project memory' });
const cluster = store.createCluster({ name: 'Cluster A' });
assert.ok(cluster);
const projects = mod.listAllProjects();
assert.equal(projects.length, 1);
assert.equal(projects[0].memoriesCount, 1);
assert.equal(projects[0].clustersCount, 1);
assert.ok(typeof projects[0].id === 'string' && projects[0].id.length > 0);
});
it('manages cluster membership and session metadata cache', () => {
const store = mod.getCoreMemoryStore(TEST_PROJECT_ROOT);
const meta1 = store.upsertSessionMetadata({
session_id: 'WFS-TEST-1',
session_type: 'workflow',
title: 'Auth flow',
summary: 'JWT + refresh',
keywords: ['auth', 'jwt'],
token_estimate: 42,
file_patterns: ['src/auth/**'],
});
assert.ok(meta1);
assert.equal(meta1?.access_count, 1);
const meta2 = store.upsertSessionMetadata({
session_id: 'WFS-TEST-1',
session_type: 'workflow',
title: 'Auth flow (updated)',
keywords: ['auth', 'jwt', 'refresh'],
});
assert.ok(meta2);
assert.equal(meta2?.access_count, 2);
const matches = store.searchSessionsByKeyword('auth');
assert.equal(matches.length, 1);
assert.equal(matches[0].session_id, 'WFS-TEST-1');
const cluster = store.createCluster({ name: 'Cluster B', description: 'Testing clusters' });
assert.ok(cluster);
const activeClusters = store.listClusters('active');
assert.ok(activeClusters.some((c: any) => c.id === cluster.id));
assert.equal(store.listClusters('archived').length, 0);
store.addClusterMember({
cluster_id: cluster.id,
session_id: 'WFS-TEST-1',
session_type: 'workflow',
sequence_order: 1,
relevance_score: 1.0,
});
const sessionClusters = store.getSessionClusters('WFS-TEST-1');
assert.equal(sessionClusters.length, 1);
assert.equal(sessionClusters[0].id, cluster.id);
assert.equal(store.removeClusterMember(cluster.id, 'WFS-TEST-1'), true);
assert.equal(store.getSessionClusters('WFS-TEST-1').length, 0);
});
it('throws clear errors for invalid cross-project access', () => {
assert.throws(
() => mod.getMemoriesFromProject('missing-project-id'),
(err: any) => err instanceof Error && err.message.includes('Project not found'),
);
assert.equal(mod.findMemoryAcrossProjects('CMEM-NOT-THERE'), null);
});
});