087426e894
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>
134 lines
4.4 KiB
TypeScript
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();
|
|
});
|
|
});
|