Files
dflike/server/src/persistence/__tests__/database.test.ts
T
root 087426e894 feat: add llm_debug_log table for persistent LLM failure diagnostics
Captures full prompt content, response text, HTTP status, and finish
reason on every LLM failure path. Replaces silent null returns in
openRouterClient with structured CompletionFailure objects so error
details propagate up through the queue and service layers.

Also removes recipeList from backstoryAndDesires prompt (not useful
for backstory generation) and adds schema migration v3.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 18:35:44 +00:00

134 lines
4.4 KiB
TypeScript

import { describe, it, expect, afterEach } from 'vitest';
import { openDatabase, getDatabase, closeDatabase, getSchemaVersion, CURRENT_SCHEMA_VERSION } from '../database.js';
import fs from 'fs';
import path from 'path';
import os from 'os';
function tempDbPath(): string {
return path.join(os.tmpdir(), `dflike-test-${Date.now()}-${Math.random().toString(36).slice(2)}.db`);
}
describe('database', () => {
const dbPaths: string[] = [];
function createTempDb(): string {
const p = tempDbPath();
dbPaths.push(p);
return p;
}
afterEach(() => {
try { closeDatabase(); } catch { /* ignore if not open */ }
for (const p of dbPaths) {
try { fs.unlinkSync(p); } catch { /* ignore */ }
try { fs.unlinkSync(p + '-wal'); } catch { /* ignore */ }
try { fs.unlinkSync(p + '-shm'); } catch { /* ignore */ }
}
dbPaths.length = 0;
});
it('creates a new database with all tables', () => {
const dbPath = createTempDb();
openDatabase(dbPath);
const db = getDatabase();
// Query sqlite_master for all tables
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name").all() as { name: string }[];
const tableNames = tables.map(t => t.name);
expect(tableNames).toContain('metadata');
expect(tableNames).toContain('tiles');
expect(tableNames).toContain('entities');
expect(tableNames).toContain('components');
expect(tableNames).toContain('relationships');
expect(tableNames).toContain('bonds');
expect(tableNames).toContain('narration_events');
expect(tableNames).toContain('memory_events');
expect(tableNames).toContain('stockpile_log');
expect(tableNames).toContain('inventions');
});
it('sets schema_version in metadata', () => {
const dbPath = createTempDb();
openDatabase(dbPath);
const version = getSchemaVersion();
expect(version).toBe(CURRENT_SCHEMA_VERSION);
expect(version).toBe(3);
});
it('returns same db on multiple getDatabase calls', () => {
const dbPath = createTempDb();
openDatabase(dbPath);
const db1 = getDatabase();
const db2 = getDatabase();
expect(db1).toBe(db2);
});
it('throws if getDatabase called before openDatabase', () => {
expect(() => getDatabase()).toThrow();
});
it('can reopen an existing database and read previously written data', () => {
const dbPath = createTempDb();
// First open: write some data
openDatabase(dbPath);
const db = getDatabase();
db.prepare("INSERT INTO entities (id, type, name, x, y) VALUES (?, ?, ?, ?, ?)").run(1, 'npc', 'TestNPC', 10.5, 20.5);
db.prepare("INSERT INTO tiles (x, y, terrain) VALUES (?, ?, ?)").run(0, 0, 3);
closeDatabase();
// Second open: read the data back
openDatabase(dbPath);
const db2 = getDatabase();
const entity = db2.prepare("SELECT * FROM entities WHERE id = ?").get(1) as any;
expect(entity.type).toBe('npc');
expect(entity.name).toBe('TestNPC');
expect(entity.x).toBe(10.5);
expect(entity.y).toBe(20.5);
const tile = db2.prepare("SELECT * FROM tiles WHERE x = 0 AND y = 0").get() as any;
expect(tile.terrain).toBe(3);
// Schema version should still be set
expect(getSchemaVersion()).toBe(CURRENT_SCHEMA_VERSION);
});
it('uses WAL journal mode', () => {
const dbPath = createTempDb();
openDatabase(dbPath);
const db = getDatabase();
const result = db.prepare("PRAGMA journal_mode").get() as any;
expect(result.journal_mode).toBe('wal');
});
it('CURRENT_SCHEMA_VERSION is 3', () => {
expect(CURRENT_SCHEMA_VERSION).toBe(3);
});
it('creates llm_call_log table in new databases', () => {
const dbPath = createTempDb();
openDatabase(dbPath);
const db = getDatabase();
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all() as { name: string }[];
expect(tables.map(t => t.name)).toContain('llm_call_log');
});
it('creates llm_tracking_enabled metadata in new databases', () => {
const dbPath = createTempDb();
openDatabase(dbPath);
const db = getDatabase();
const row = db.prepare("SELECT value FROM metadata WHERE key = 'llm_tracking_enabled'").get() as { value: string };
expect(row.value).toBe('1');
});
it('closeDatabase allows reopening', () => {
const dbPath = createTempDb();
openDatabase(dbPath);
closeDatabase();
// Should not throw
openDatabase(dbPath);
expect(getDatabase()).toBeDefined();
});
});