Files
ruvnet--ruflo/tests/rvf-backend.test.ts
wehub-resource-sync 23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:02:19 +08:00

549 lines
19 KiB
TypeScript

import { describe, it, before, after, beforeEach, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync, existsSync, readFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { RvfBackend } from '../v3/@claude-flow/memory/src/rvf-backend.js';
import type {
MemoryEntry,
MemoryQuery,
SearchOptions,
IMemoryBackend,
} from '../v3/@claude-flow/memory/src/types.js';
// -- Helpers --
function makeTmpDir(): string {
return mkdtempSync(join(tmpdir(), 'rvf-test-'));
}
function makeEntry(overrides: Partial<MemoryEntry> = {}): MemoryEntry {
const now = Date.now();
return {
id: `e-${Math.random().toString(36).slice(2, 10)}`,
key: 'test-key',
content: 'test content',
type: 'semantic',
namespace: 'default',
tags: [],
metadata: {},
accessLevel: 'private',
createdAt: now,
updatedAt: now,
version: 1,
references: [],
accessCount: 0,
lastAccessedAt: now,
...overrides,
};
}
function randomVec(dim = 4): Float32Array {
const v = new Float32Array(dim);
for (let i = 0; i < dim; i++) v[i] = Math.random() - 0.5;
// normalise so cosine is meaningful
const norm = Math.sqrt(v.reduce((s, x) => s + x * x, 0));
if (norm > 0) for (let i = 0; i < dim; i++) v[i] /= norm;
return v;
}
// -- Tests --
describe('RvfBackend', () => {
let tmpDir: string;
let backend: RvfBackend;
beforeEach(async () => {
tmpDir = makeTmpDir();
backend = new RvfBackend({
databasePath: join(tmpDir, 'test.rvf'),
dimensions: 4,
autoPersistInterval: 0, // disable timer in tests
});
await backend.initialize();
});
afterEach(async () => {
try { await backend.shutdown(); } catch {}
try { rmSync(tmpDir, { recursive: true, force: true }); } catch {}
});
// ---- 1. Constructor & Config ----
describe('constructor & config', () => {
it('should apply default config values', async () => {
const b = new RvfBackend({ databasePath: ':memory:' });
await b.initialize();
const stats = await b.getStats();
assert.equal(stats.totalEntries, 0);
await b.shutdown();
});
it('should accept custom dimensions and metric', async () => {
const b = new RvfBackend({
databasePath: ':memory:',
dimensions: 8,
metric: 'dot',
});
await b.initialize();
const health = await b.healthCheck();
assert.equal(health.status, 'healthy');
await b.shutdown();
});
});
// ---- 2. Initialize / Shutdown ----
describe('initialize & shutdown', () => {
it('should be idempotent on double init', async () => {
await backend.initialize(); // second call
const stats = await backend.getStats();
assert.equal(stats.totalEntries, 0);
});
it('should persist file on shutdown when dirty', async () => {
await backend.store(makeEntry());
await backend.shutdown();
assert.ok(existsSync(join(tmpDir, 'test.rvf')));
});
it('should reload entries after shutdown + reinit', async () => {
const entry = makeEntry({ id: 'persist-1', key: 'k1' });
await backend.store(entry);
await backend.shutdown();
const b2 = new RvfBackend({
databasePath: join(tmpDir, 'test.rvf'),
dimensions: 4,
autoPersistInterval: 0,
});
await b2.initialize();
const got = await b2.get('persist-1');
assert.ok(got);
assert.equal(got.key, 'k1');
assert.equal(got.content, entry.content);
await b2.shutdown();
});
it('should be a no-op when shutdown is called before init', async () => {
const b = new RvfBackend({ databasePath: ':memory:' });
await b.shutdown(); // should not throw
});
});
// ---- 3. CRUD ----
describe('CRUD operations', () => {
it('store and get by id', async () => {
const entry = makeEntry({ id: 'crud-1' });
await backend.store(entry);
const got = await backend.get('crud-1');
assert.ok(got);
assert.equal(got.id, 'crud-1');
});
it('get returns null for missing id', async () => {
const got = await backend.get('nonexistent');
assert.equal(got, null);
});
it('get increments accessCount', async () => {
await backend.store(makeEntry({ id: 'ac-1', accessCount: 0 }));
await backend.get('ac-1');
const got = await backend.get('ac-1');
assert.ok(got);
assert.equal(got.accessCount, 2);
});
it('getByKey retrieves by namespace + key', async () => {
await backend.store(makeEntry({ id: 'bk-1', namespace: 'ns1', key: 'mykey' }));
const got = await backend.getByKey('ns1', 'mykey');
assert.ok(got);
assert.equal(got.id, 'bk-1');
});
it('getByKey returns null for wrong namespace', async () => {
await backend.store(makeEntry({ namespace: 'ns1', key: 'mykey' }));
assert.equal(await backend.getByKey('ns2', 'mykey'), null);
});
it('update modifies entry and bumps version', async () => {
await backend.store(makeEntry({ id: 'up-1', content: 'old', version: 1 }));
const updated = await backend.update('up-1', { content: 'new' });
assert.ok(updated);
assert.equal(updated.content, 'new');
assert.equal(updated.version, 2);
});
it('update returns null for missing id', async () => {
assert.equal(await backend.update('missing', { content: 'x' }), null);
});
it('delete removes entry and returns true', async () => {
await backend.store(makeEntry({ id: 'del-1' }));
assert.equal(await backend.delete('del-1'), true);
assert.equal(await backend.get('del-1'), null);
});
it('delete returns false for missing id', async () => {
assert.equal(await backend.delete('missing'), false);
});
});
// ---- 4. Query ----
describe('query', () => {
beforeEach(async () => {
await backend.bulkInsert([
makeEntry({ id: 'q1', namespace: 'alpha', key: 'a1', type: 'episodic', tags: ['urgent'], createdAt: 1000, updatedAt: 1000 }),
makeEntry({ id: 'q2', namespace: 'alpha', key: 'a2', type: 'semantic', tags: ['urgent', 'bug'], createdAt: 2000, updatedAt: 2000 }),
makeEntry({ id: 'q3', namespace: 'beta', key: 'b1', type: 'semantic', tags: [], createdAt: 3000, updatedAt: 3000 }),
makeEntry({ id: 'q4', namespace: 'beta', key: 'prefix-abc', type: 'procedural', tags: ['bug'], createdAt: 4000, updatedAt: 4000 }),
]);
});
it('filters by namespace', async () => {
const r = await backend.query({ type: 'exact', namespace: 'alpha', limit: 10 });
assert.equal(r.length, 2);
assert.ok(r.every(e => e.namespace === 'alpha'));
});
it('filters by tags (AND logic)', async () => {
const r = await backend.query({ type: 'tag', tags: ['urgent', 'bug'], limit: 10 });
assert.equal(r.length, 1);
assert.equal(r[0].id, 'q2');
});
it('filters by memoryType', async () => {
const r = await backend.query({ type: 'exact', memoryType: 'procedural', limit: 10 });
assert.equal(r.length, 1);
assert.equal(r[0].id, 'q4');
});
it('filters by keyPrefix', async () => {
const r = await backend.query({ type: 'prefix', keyPrefix: 'prefix-', limit: 10 });
assert.equal(r.length, 1);
assert.equal(r[0].id, 'q4');
});
it('filters by createdAfter / createdBefore', async () => {
const r = await backend.query({ type: 'exact', createdAfter: 1500, createdBefore: 3500, limit: 10 });
assert.equal(r.length, 2); // q2 (2000) and q3 (3000)
});
it('respects limit', async () => {
const r = await backend.query({ type: 'exact', limit: 2 });
assert.equal(r.length, 2);
});
it('respects offset', async () => {
const all = await backend.query({ type: 'exact', limit: 100 });
const page = await backend.query({ type: 'exact', limit: 100, offset: 2 });
assert.equal(page.length, all.length - 2);
});
it('excludes expired entries by default', async () => {
await backend.store(makeEntry({ id: 'exp-1', expiresAt: 1 })); // already expired
const r = await backend.query({ type: 'exact', limit: 100 });
assert.ok(!r.some(e => e.id === 'exp-1'));
});
it('includes expired entries when includeExpired is true', async () => {
await backend.store(makeEntry({ id: 'exp-2', expiresAt: 1 }));
const r = await backend.query({ type: 'exact', limit: 100, includeExpired: true });
assert.ok(r.some(e => e.id === 'exp-2'));
});
});
// ---- 5. Search ----
describe('search', () => {
it('finds similar vectors via cosine similarity', async () => {
const target = new Float32Array([1, 0, 0, 0]);
const similar = new Float32Array([0.9, 0.1, 0, 0]);
const dissimilar = new Float32Array([0, 0, 0, 1]);
await backend.store(makeEntry({ id: 's1', embedding: similar }));
await backend.store(makeEntry({ id: 's2', embedding: dissimilar }));
const results = await backend.search(target, { k: 2 });
assert.ok(results.length >= 1);
assert.equal(results[0].entry.id, 's1');
assert.ok(results[0].score > results[results.length - 1].score);
});
it('applies threshold filtering', async () => {
await backend.store(makeEntry({ id: 'th1', embedding: new Float32Array([1, 0, 0, 0]) }));
await backend.store(makeEntry({ id: 'th2', embedding: new Float32Array([0, 0, 0, 1]) }));
const results = await backend.search(new Float32Array([1, 0, 0, 0]), {
k: 10,
threshold: 0.9,
});
assert.equal(results.length, 1);
assert.equal(results[0].entry.id, 'th1');
});
it('filters by namespace in search', async () => {
const vec = new Float32Array([1, 0, 0, 0]);
await backend.store(makeEntry({ id: 'ns1', namespace: 'keep', embedding: vec }));
await backend.store(makeEntry({ id: 'ns2', namespace: 'skip', embedding: vec }));
const results = await backend.search(vec, {
k: 10,
filters: { type: 'exact', limit: 10, namespace: 'keep' },
});
assert.ok(results.every(r => r.entry.namespace === 'keep'));
});
it('limits results to k', async () => {
for (let i = 0; i < 5; i++) {
await backend.store(makeEntry({ id: `k${i}`, embedding: randomVec() }));
}
const results = await backend.search(randomVec(), { k: 2 });
assert.ok(results.length <= 2);
});
it('returns empty for empty database', async () => {
const results = await backend.search(randomVec(), { k: 5 });
assert.equal(results.length, 0);
});
});
// ---- 6. Bulk Operations ----
describe('bulk operations', () => {
it('bulkInsert stores multiple entries', async () => {
const entries = Array.from({ length: 5 }, (_, i) =>
makeEntry({ id: `bulk-${i}`, key: `k${i}` })
);
await backend.bulkInsert(entries);
assert.equal(await backend.count(), 5);
});
it('bulkDelete removes specified entries', async () => {
await backend.bulkInsert([
makeEntry({ id: 'bd-1' }),
makeEntry({ id: 'bd-2' }),
makeEntry({ id: 'bd-3' }),
]);
const deleted = await backend.bulkDelete(['bd-1', 'bd-3', 'nonexistent']);
assert.equal(deleted, 2);
assert.equal(await backend.count(), 1);
assert.ok(await backend.get('bd-2'));
});
});
// ---- 7. Namespace Operations ----
describe('namespace operations', () => {
beforeEach(async () => {
await backend.bulkInsert([
makeEntry({ id: 'n1', namespace: 'a', key: 'k1' }),
makeEntry({ id: 'n2', namespace: 'a', key: 'k2' }),
makeEntry({ id: 'n3', namespace: 'b', key: 'k3' }),
]);
});
it('count without namespace returns total', async () => {
assert.equal(await backend.count(), 3);
});
it('count with namespace returns filtered count', async () => {
assert.equal(await backend.count('a'), 2);
assert.equal(await backend.count('b'), 1);
assert.equal(await backend.count('empty'), 0);
});
it('listNamespaces returns all unique namespaces', async () => {
const ns = await backend.listNamespaces();
assert.deepEqual(ns.sort(), ['a', 'b']);
});
it('clearNamespace removes only that namespace', async () => {
const cleared = await backend.clearNamespace('a');
assert.equal(cleared, 2);
assert.equal(await backend.count(), 1);
assert.ok(await backend.get('n3'));
});
});
// ---- 8. Persistence & Binary Format ----
describe('persistence', () => {
it('persisted file starts with RVF magic bytes', async () => {
await backend.store(makeEntry({ id: 'magic-1' }));
await backend.shutdown();
const buf = readFileSync(join(tmpDir, 'test.rvf'));
assert.equal(buf[0], 0x52); // R
assert.equal(buf[1], 0x56); // V
assert.equal(buf[2], 0x46); // F
assert.equal(buf[3], 0x00); // \0
});
it('embeddings survive round-trip', async () => {
const vec = new Float32Array([0.1, 0.2, 0.3, 0.4]);
await backend.store(makeEntry({ id: 'emb-1', embedding: vec }));
await backend.shutdown();
const b2 = new RvfBackend({
databasePath: join(tmpDir, 'test.rvf'),
dimensions: 4,
autoPersistInterval: 0,
});
await b2.initialize();
const got = await b2.get('emb-1');
assert.ok(got?.embedding);
for (let i = 0; i < 4; i++) {
assert.ok(Math.abs(got.embedding[i] - vec[i]) < 1e-6);
}
await b2.shutdown();
});
it(':memory: path does not create a file', async () => {
const b = new RvfBackend({ databasePath: ':memory:', dimensions: 4 });
await b.initialize();
await b.store(makeEntry());
await b.shutdown();
// no file should be created anywhere for :memory:
});
});
// ---- 9. HNSW Index ----
describe('HNSW index', () => {
it('search reflects added and removed vectors', async () => {
const vec = new Float32Array([1, 0, 0, 0]);
await backend.store(makeEntry({ id: 'h1', embedding: vec }));
let results = await backend.search(vec, { k: 5 });
assert.ok(results.some(r => r.entry.id === 'h1'));
await backend.delete('h1');
results = await backend.search(vec, { k: 5 });
assert.ok(!results.some(r => r.entry.id === 'h1'));
});
it('indexes vectors from bulkInsert', async () => {
const entries = Array.from({ length: 3 }, (_, i) =>
makeEntry({ id: `bi-${i}`, embedding: randomVec() })
);
await backend.bulkInsert(entries);
const results = await backend.search(entries[0].embedding!, { k: 10 });
assert.ok(results.length > 0);
});
});
// ---- 10. Stats & Health ----
describe('stats & health', () => {
it('getStats returns correct counts', async () => {
await backend.bulkInsert([
makeEntry({ id: 'st1', namespace: 'x', type: 'episodic', content: 'abc' }),
makeEntry({ id: 'st2', namespace: 'x', type: 'semantic', content: 'def' }),
makeEntry({ id: 'st3', namespace: 'y', type: 'episodic', content: 'ghi' }),
]);
const stats = await backend.getStats();
assert.equal(stats.totalEntries, 3);
assert.equal(stats.entriesByNamespace['x'], 2);
assert.equal(stats.entriesByNamespace['y'], 1);
assert.equal(stats.entriesByType['episodic'], 2);
assert.equal(stats.entriesByType['semantic'], 1);
assert.ok(stats.memoryUsage > 0);
});
it('getStats includes HNSW stats when vectors present', async () => {
await backend.store(makeEntry({ embedding: randomVec() }));
const stats = await backend.getStats();
assert.ok(stats.hnswStats);
assert.equal(stats.hnswStats.vectorCount, 1);
});
it('healthCheck returns healthy after init', async () => {
const health = await backend.healthCheck();
assert.equal(health.status, 'healthy');
assert.ok(health.timestamp > 0);
assert.equal(health.issues.length, 0);
});
it('healthCheck returns unhealthy before init', async () => {
const b = new RvfBackend({ databasePath: ':memory:', dimensions: 4 });
const health = await b.healthCheck();
assert.equal(health.status, 'unhealthy');
assert.ok(health.issues.length > 0);
});
});
// ---- 11. Edge Cases ----
describe('edge cases', () => {
it('handles empty database gracefully', async () => {
assert.equal(await backend.count(), 0);
assert.deepEqual(await backend.listNamespaces(), []);
assert.deepEqual(await backend.query({ type: 'exact', limit: 10 }), []);
const stats = await backend.getStats();
assert.equal(stats.totalEntries, 0);
});
it('init with missing file dir creates it on persist', async () => {
const deepPath = join(tmpDir, 'sub', 'dir', 'deep.rvf');
const b = new RvfBackend({
databasePath: deepPath,
dimensions: 4,
autoPersistInterval: 0,
});
await b.initialize();
await b.store(makeEntry());
await b.shutdown();
assert.ok(existsSync(deepPath));
});
it('handles corrupt file without crashing', async () => {
const path = join(tmpDir, 'corrupt.rvf');
const { writeFileSync } = await import('node:fs');
writeFileSync(path, Buffer.from('RVF\0garbage data here'));
const b = new RvfBackend({
databasePath: path,
dimensions: 4,
autoPersistInterval: 0,
});
// should not throw - gracefully handles corrupt data
await b.initialize();
assert.equal(await b.count(), 0);
await b.shutdown();
});
it('store overwrites entry with same id', async () => {
await backend.store(makeEntry({ id: 'ow-1', content: 'first' }));
await backend.store(makeEntry({ id: 'ow-1', content: 'second' }));
const got = await backend.get('ow-1');
assert.equal(got?.content, 'second');
});
});
// ---- 12. IMemoryBackend Interface Conformance ----
describe('IMemoryBackend conformance', () => {
it('implements all required interface methods', () => {
const requiredMethods: (keyof IMemoryBackend)[] = [
'initialize', 'shutdown', 'store', 'get', 'getByKey',
'update', 'delete', 'query', 'search', 'bulkInsert',
'bulkDelete', 'count', 'listNamespaces', 'clearNamespace',
'getStats', 'healthCheck',
];
for (const method of requiredMethods) {
assert.equal(typeof (backend as any)[method], 'function', `missing method: ${method}`);
}
});
it('all methods return promises', async () => {
const entry = makeEntry({ id: 'iface-1' });
assert.ok(backend.store(entry) instanceof Promise);
assert.ok(backend.get('x') instanceof Promise);
assert.ok(backend.getByKey('ns', 'k') instanceof Promise);
assert.ok(backend.update('x', {}) instanceof Promise);
assert.ok(backend.delete('x') instanceof Promise);
assert.ok(backend.query({ type: 'exact', limit: 1 }) instanceof Promise);
assert.ok(backend.search(randomVec(), { k: 1 }) instanceof Promise);
assert.ok(backend.bulkInsert([]) instanceof Promise);
assert.ok(backend.bulkDelete([]) instanceof Promise);
assert.ok(backend.count() instanceof Promise);
assert.ok(backend.listNamespaces() instanceof Promise);
assert.ok(backend.clearNamespace('x') instanceof Promise);
assert.ok(backend.getStats() instanceof Promise);
assert.ok(backend.healthCheck() instanceof Promise);
});
});
});