migration consolidation patch
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
// Schema completeness — execs the REAL schema.js into a throwaway SQLite DB (via
|
||||
// built-in node:sqlite) and asserts a fresh database gets every column the old
|
||||
// per-boot ALTER statements used to add. This is the guard for the migration
|
||||
// consolidation: if someone drops a column from schema.js, a fresh install would
|
||||
// silently lose it, and this test goes red.
|
||||
const { test, before } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const schema = require('../packages/memory-service/src/db/schema');
|
||||
|
||||
let DatabaseSync;
|
||||
try { ({ DatabaseSync } = require('node:sqlite')); } catch { DatabaseSync = null; }
|
||||
|
||||
// The complete intended shape = original base tables + every historical ALTER.
|
||||
const EXPECTED = {
|
||||
sessions: ['id','external_id','created_at','updated_at','metadata','name','project_id'],
|
||||
episodes: ['id','session_id','user_message','ai_response','created_at','token_count','metadata'],
|
||||
entities: ['id','name','type','notes','created_at','updated_at','metadata','mention_count','confidence','source','last_seen_at'],
|
||||
relationships: ['id','from_id','to_id','label','created_at','metadata','mention_count','notes'],
|
||||
entity_episodes: ['entity_id','episode_id'],
|
||||
projects: ['id','name','description','colour','icon','created_at','isolated','notes','system_prompt'],
|
||||
summaries: ['id','session_id','project_id','content','token_count','episode_range','created_at','metadata'],
|
||||
};
|
||||
|
||||
const EXPECTED_OBJECTS = [
|
||||
'idx_relationships_from','idx_relationships_to','idx_entity_episodes_entity','idx_entity_episodes_episode',
|
||||
'idx_episodes_session','idx_episodes_created','idx_entities_type','idx_sessions_project',
|
||||
'idx_summaries_project','idx_summaries_session',
|
||||
'episodes_fts','episodes_fts_insert','episodes_fts_delete','episodes_fts_update',
|
||||
];
|
||||
|
||||
let db;
|
||||
before(() => {
|
||||
if (!DatabaseSync) return;
|
||||
db = new DatabaseSync(':memory:');
|
||||
db.exec('PRAGMA foreign_keys = ON');
|
||||
db.exec(schema); // throws here if schema.js has a syntax/ordering error
|
||||
});
|
||||
|
||||
for (const [table, cols] of Object.entries(EXPECTED)) {
|
||||
test(`${table} has exactly its full column set`, { skip: !DatabaseSync }, () => {
|
||||
const got = db.prepare(`PRAGMA table_info(${table})`).all().map(r => r.name);
|
||||
assert.deepStrictEqual([...got].sort(), [...cols].sort(), `${table} columns differ from the intended shape`);
|
||||
});
|
||||
}
|
||||
|
||||
test('all indexes, FTS table, and triggers are present', { skip: !DatabaseSync }, () => {
|
||||
const names = db.prepare(`SELECT name FROM sqlite_master WHERE name LIKE 'idx_%' OR name LIKE 'episodes_fts%'`)
|
||||
.all().map(r => r.name);
|
||||
const missing = EXPECTED_OBJECTS.filter(o => !names.includes(o));
|
||||
assert.deepStrictEqual(missing, [], `missing schema objects: ${missing}`);
|
||||
});
|
||||
|
||||
test('FTS trigger populates the index on episode insert', { skip: !DatabaseSync }, () => {
|
||||
db.prepare(`INSERT INTO sessions(external_id) VALUES ('s-test')`).run();
|
||||
db.prepare(`INSERT INTO episodes(session_id, user_message, ai_response) VALUES (1,'find me qdrant','ok')`).run();
|
||||
const hit = db.prepare(`SELECT rowid FROM episodes_fts WHERE episodes_fts MATCH 'qdrant'`).all();
|
||||
assert.strictEqual(hit.length, 1);
|
||||
});
|
||||
Reference in New Issue
Block a user