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(); }); });