This commit is contained in:
101
benchmarks/harness/tests/substrate.test.ts
Normal file
101
benchmarks/harness/tests/substrate.test.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Task 2.5 Stage 1 — substrate factory tests.
|
||||
*
|
||||
* Covers the lifecycle contract: construct with `:memory:` + an injected
|
||||
* fake embedder, verify the FTS5 + vec0 tables exist, ingest a handful of
|
||||
* frames, round-trip-search, tear down.
|
||||
*
|
||||
* No Ollama / network dependency — tests inject a deterministic fake embedder.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { Embedder } from '@waggle/core';
|
||||
import { createSubstrate } from '../src/substrate.js';
|
||||
|
||||
const VEC_DIMS = 1024;
|
||||
|
||||
function createFakeEmbedder(dims: number = VEC_DIMS): Embedder {
|
||||
const fnv1a = (s: string): number => {
|
||||
let h = 2166136261 >>> 0;
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
h ^= s.charCodeAt(i);
|
||||
h = Math.imul(h, 16777619) >>> 0;
|
||||
}
|
||||
return h || 1;
|
||||
};
|
||||
const embedOne = (text: string): Float32Array => {
|
||||
let state = fnv1a(text);
|
||||
const v = new Float32Array(dims);
|
||||
for (let i = 0; i < dims; i++) {
|
||||
state ^= state << 13; state >>>= 0;
|
||||
state ^= state >>> 17;
|
||||
state ^= state << 5; state >>>= 0;
|
||||
v[i] = ((state >>> 0) / 0x100000000) * 2 - 1;
|
||||
}
|
||||
let mag = 0;
|
||||
for (let i = 0; i < dims; i++) mag += v[i] * v[i];
|
||||
mag = Math.sqrt(mag);
|
||||
if (mag > 0) for (let i = 0; i < dims; i++) v[i] /= mag;
|
||||
return v;
|
||||
};
|
||||
return {
|
||||
dimensions: dims,
|
||||
async embed(text) { return embedOne(text); },
|
||||
async embedBatch(texts) { return texts.map(embedOne); },
|
||||
};
|
||||
}
|
||||
|
||||
describe('createSubstrate', () => {
|
||||
it('constructs an ephemeral :memory: substrate with injected embedder', () => {
|
||||
const sub = createSubstrate({ embedder: createFakeEmbedder() });
|
||||
try {
|
||||
expect(sub.db).toBeDefined();
|
||||
expect(sub.frames).toBeDefined();
|
||||
expect(sub.search).toBeDefined();
|
||||
expect(sub.embedder.dimensions).toBe(VEC_DIMS);
|
||||
// Verify schema bootstrap: memory_frames + vec table exist.
|
||||
const raw = sub.db.getDatabase();
|
||||
const row = raw.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='memory_frames'",
|
||||
).get();
|
||||
expect(row).toBeDefined();
|
||||
} finally {
|
||||
sub.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('createIFrame + indexFramesBatch round-trip via the substrate', async () => {
|
||||
const sub = createSubstrate({ embedder: createFakeEmbedder() });
|
||||
try {
|
||||
// memory_frames.gop_id FKs to sessions.gop_id — ensure rows first.
|
||||
sub.sessions.ensure('gop-a');
|
||||
sub.sessions.ensure('gop-b');
|
||||
const f1 = sub.frames.createIFrame('gop-a', 'Alice: The sunrise painting', 'normal', 'import');
|
||||
const f2 = sub.frames.createIFrame('gop-a', 'Bob: Nice painting Alice', 'normal', 'import');
|
||||
const f3 = sub.frames.createIFrame('gop-b', 'Carol: Morning Dan', 'normal', 'import');
|
||||
await sub.search.indexFramesBatch([
|
||||
{ id: f1.id, content: f1.content },
|
||||
{ id: f2.id, content: f2.content },
|
||||
{ id: f3.id, content: f3.content },
|
||||
]);
|
||||
|
||||
const results = await sub.search.search('sunrise painting', { limit: 3 });
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].frame.content).toContain('sunrise');
|
||||
} finally {
|
||||
sub.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('close() is idempotent', () => {
|
||||
const sub = createSubstrate({ embedder: createFakeEmbedder() });
|
||||
sub.close();
|
||||
expect(() => sub.close()).not.toThrow();
|
||||
});
|
||||
|
||||
it('after close(), DB access throws', () => {
|
||||
const sub = createSubstrate({ embedder: createFakeEmbedder() });
|
||||
sub.close();
|
||||
expect(() => sub.db.getDatabase().prepare('SELECT 1').get()).toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user