Files
dflike/server/src/persistence/__tests__/database.test.ts
T
root f5c8d8661b feat(server): add SQLite database module with schema and connection management
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>
2026-03-09 14:08:49 +00:00

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