openclaw-knowledge-engine/test/maintenance.test.ts
Claudia 8964d93c60 feat: knowledge-engine v0.1.0 — all Cerberus findings fixed
- 83/83 tests passing (was 32/45)
- New: src/http-client.ts (shared HTTP/HTTPS client, fixes C2+H1)
- Fixed: proper_noun regex exclusions (C6)
- Fixed: shutdown hooks registered in hooks.ts (C3)
- Fixed: all timers use .unref() (H6)
- Fixed: resolveConfig split into smaller functions (C4)
- Fixed: extract() split with processMatch helper (C5)
- Fixed: FactStore.addFact isLoaded guard (H3)
- Fixed: validateConfig split (H2)
- Fixed: type-safe config merge, removed as any (H4)
- Added: http-client tests, expanded coverage (H5)
- Fixed: LLM batch await (S1), fresh RegExp per call (S2)
- 1530 LOC source, 1298 LOC tests, strict TypeScript
2026-02-17 16:10:13 +01:00

117 lines
3.7 KiB
TypeScript

// test/maintenance.test.ts
import { describe, it, beforeEach, afterEach } from 'node:test';
import * as assert from 'node:assert';
import { Maintenance } from '../src/maintenance.js';
import { Embeddings } from '../src/embeddings.js';
import type { KnowledgeConfig, Logger, Fact } from '../src/types.js';
import * as timers from 'node:timers/promises';
class MockFactStore {
decayRate = 0;
decayedCount = 0;
unembeddedFacts: Fact[] = [];
markedAsEmbeddedIds: string[] = [];
decayFacts(rate: number) {
this.decayRate = rate;
this.decayedCount++;
return { decayedCount: 1 };
}
getUnembeddedFacts() { return this.unembeddedFacts; }
markFactsAsEmbedded(ids: string[]) {
this.markedAsEmbeddedIds.push(...ids);
// Clear unembedded facts after marking (mimics real behavior)
this.unembeddedFacts = this.unembeddedFacts.filter(f => !ids.includes(f.id));
}
}
class MockEmbeddings {
isEnabledState = true;
syncedFacts: Fact[] = [];
isEnabled() { return this.isEnabledState; }
sync(facts: Fact[]) {
this.syncedFacts.push(...facts);
return Promise.resolve(facts.length);
}
}
const createMockLogger = (): Logger => ({
info: () => {}, warn: () => {}, error: () => {}, debug: () => {},
});
const mockConfig: KnowledgeConfig = {
enabled: true,
workspace: '/tmp',
decay: { enabled: true, intervalHours: 0.0001, rate: 0.1 },
embeddings: {
enabled: true,
syncIntervalMinutes: 0.0001,
endpoint: 'http://test.com',
collectionName: 'test',
},
storage: { maxEntities: 100, maxFacts: 100, writeDebounceMs: 0 },
extraction: {
regex: { enabled: true },
llm: { enabled: false, model: '', endpoint: '', batchSize: 1, cooldownMs: 0 },
},
};
describe('Maintenance', () => {
let logger: Logger;
let mockFactStore: MockFactStore;
let mockEmbeddings: MockEmbeddings;
let maintenance: Maintenance;
beforeEach(() => {
logger = createMockLogger();
mockFactStore = new MockFactStore();
mockEmbeddings = new MockEmbeddings();
// @ts-ignore - Using mock class
maintenance = new Maintenance(mockConfig, logger, mockFactStore, mockEmbeddings);
});
afterEach(() => {
maintenance.stop();
});
it('should schedule and run decay task', async () => {
maintenance.start();
await timers.setTimeout(mockConfig.decay.intervalHours * 60 * 60 * 1000 + 10);
assert.strictEqual(mockFactStore.decayedCount > 0, true);
assert.strictEqual(mockFactStore.decayRate, mockConfig.decay.rate);
});
it('should schedule and run embeddings sync task', async () => {
const testFact: Fact = {
id: 'fact1', subject: 's', predicate: 'p', object: 'o',
relevance: 1, createdAt: 't', lastAccessed: 't', source: 'ingested',
};
mockFactStore.unembeddedFacts = [testFact];
maintenance.start();
await timers.setTimeout(mockConfig.embeddings.syncIntervalMinutes * 60 * 1000 + 10);
assert.strictEqual(mockEmbeddings.syncedFacts.length > 0, true);
assert.deepStrictEqual(mockEmbeddings.syncedFacts[0], testFact);
assert.deepStrictEqual(mockFactStore.markedAsEmbeddedIds, [testFact.id]);
});
it('should not schedule embeddings if disabled', async () => {
mockEmbeddings.isEnabledState = false;
maintenance.start();
await timers.setTimeout(5);
assert.strictEqual(mockEmbeddings.syncedFacts.length, 0);
});
it('should stop all timers cleanly', () => {
maintenance.start();
maintenance.stop();
// No error means timers were cleared successfully
assert.ok(true);
});
it('should run decay manually', () => {
maintenance.runDecay();
assert.strictEqual(mockFactStore.decayedCount, 1);
assert.strictEqual(mockFactStore.decayRate, mockConfig.decay.rate);
});
});