test harness
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
// FTS keyword search — tests the REAL buildFtsQuery against a real FTS5 table.
|
||||
// Uses Node's built-in node:sqlite (no external deps) to build a throwaway index,
|
||||
// so this runs offline with nothing installed beyond Node itself.
|
||||
const { test, before } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { buildFtsQuery } = require('../packages/memory-service/src/episodic/index');
|
||||
|
||||
let DatabaseSync;
|
||||
try {
|
||||
({ DatabaseSync } = require('node:sqlite'));
|
||||
} catch {
|
||||
DatabaseSync = null;
|
||||
}
|
||||
|
||||
const ROWS = [
|
||||
'To configure the Qdrant collection you set the vector size and distance metric',
|
||||
'The embedding service wraps nomic-embed-text via Ollama',
|
||||
'Reciprocal rank fusion merges semantic and keyword results',
|
||||
'I had a great morning walk today',
|
||||
];
|
||||
|
||||
let db;
|
||||
before(() => {
|
||||
if (!DatabaseSync) return;
|
||||
db = new DatabaseSync(':memory:');
|
||||
db.exec('CREATE VIRTUAL TABLE fts USING fts5(body)');
|
||||
const ins = db.prepare('INSERT INTO fts(rowid, body) VALUES (?, ?)');
|
||||
ROWS.forEach((r, i) => ins.run(i + 1, r));
|
||||
});
|
||||
|
||||
const matchRows = (match) =>
|
||||
db.prepare('SELECT rowid FROM fts WHERE fts MATCH ? ORDER BY rank').all(match).map(r => r.rowid);
|
||||
|
||||
// --- Pure buildFtsQuery behaviour (runs without sqlite) ---
|
||||
|
||||
test('tokenizes and OR-joins significant terms, dropping stopwords', () => {
|
||||
assert.strictEqual(
|
||||
buildFtsQuery('How do I configure the Qdrant collection?'),
|
||||
'"configure" OR "qdrant" OR "collection"',
|
||||
);
|
||||
});
|
||||
|
||||
test('all-stopword / empty input returns null so caller can skip keyword search', () => {
|
||||
assert.strictEqual(buildFtsQuery('how do I do it?'), null);
|
||||
assert.strictEqual(buildFtsQuery(' '), null);
|
||||
assert.strictEqual(buildFtsQuery('!!! ??? ...'), null);
|
||||
});
|
||||
|
||||
test('FTS-significant tokens in the message are neutralized as literals', () => {
|
||||
// "OR" and the stray quote/asterisk must not become operators
|
||||
const q = buildFtsQuery('fusion OR results" hack*');
|
||||
assert.strictEqual(q, '"fusion" OR "results" OR "hack"');
|
||||
});
|
||||
|
||||
// --- Real FTS5 matching (skips cleanly if node:sqlite is unavailable) ---
|
||||
|
||||
test('a conversational query matches on shared terms (the whole point of the fix)', { skip: !DatabaseSync }, () => {
|
||||
const q = buildFtsQuery('How do I configure the Qdrant collection?');
|
||||
assert.deepStrictEqual(matchRows(q), [1], 'should hit the Qdrant-collection episode');
|
||||
});
|
||||
|
||||
test('the OLD whole-phrase approach would NOT have matched (regression guard)', { skip: !DatabaseSync }, () => {
|
||||
const oldPhrase = `"${'How do I configure the Qdrant collection?'.replace(/"/g, '""')}"`;
|
||||
assert.deepStrictEqual(matchRows(oldPhrase), [], 'verbatim phrase never appears → the bug we fixed');
|
||||
});
|
||||
|
||||
test('injection-y input runs without error and matches real terms', { skip: !DatabaseSync }, () => {
|
||||
const q = buildFtsQuery('fusion OR results" hack*');
|
||||
assert.deepStrictEqual(matchRows(q), [3]);
|
||||
});
|
||||
Reference in New Issue
Block a user