f5c8d8661b
Adds persistence/database.ts with openDatabase, getDatabase, closeDatabase, getSchemaVersion, and CURRENT_SCHEMA_VERSION. Creates 10 tables (metadata, tiles, entities, components, relationships, bonds, narration_events, memory_events, stockpile_log, inventions) with WAL journal mode. Includes 8 tests covering table creation, schema versioning, connection reuse, and data persistence across reopen. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
118 lines
3.8 KiB
TypeScript
118 lines
3.8 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(1);
|
|
});
|
|
|
|
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 1', () => {
|
|
expect(CURRENT_SCHEMA_VERSION).toBe(1);
|
|
});
|
|
|
|
it('closeDatabase allows reopening', () => {
|
|
const dbPath = createTempDb();
|
|
openDatabase(dbPath);
|
|
closeDatabase();
|
|
// Should not throw
|
|
openDatabase(dbPath);
|
|
expect(getDatabase()).toBeDefined();
|
|
});
|
|
});
|