import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import type Database from 'better-sqlite3'; import { initDb, closeDb } from '../src/db/index'; let db: InstanceType; beforeAll(() => { db = initDb(':memory:'); }); afterAll(() => { closeDb(); }); describe('Database initialization', () => { it('should enable WAL or memory journal mode (WAL for file, memory for :memory:)', () => { const result = db.prepare('PRAGMA journal_mode').get() as { journal_mode: string }; // :memory: databases use 'memory' journal mode, file DBs use 'wal' expect(['wal', 'memory']).toContain(result.journal_mode); }); it('should enable foreign keys', () => { const result = db.prepare('PRAGMA foreign_keys').get() as { foreign_keys: number }; expect(result.foreign_keys).toBe(1); }); }); describe('Table creation', () => { const expectedTables = [ 'users', 'mqtt_brokers', 'mqtt_topics', 'pg_sources', 'pg_queries', 'datapoints', 'layouts', 'layout_elements', 'displays', ]; it('should create all 9 tables', () => { const tables = db .prepare(`SELECT name FROM sqlite_master WHERE type='table' ORDER BY name`) .all() as Array<{ name: string }>; const tableNames = tables.map((t) => t.name); for (const expected of expectedTables) { expect(tableNames).toContain(expected); } expect(tableNames.filter((n) => !n.startsWith('sqlite_')).length).toBe(9); }); it('should create users table with correct columns', () => { const cols = db.prepare('PRAGMA table_info(users)').all() as Array<{ name: string }>; const colNames = cols.map((c) => c.name); expect(colNames).toContain('id'); expect(colNames).toContain('username'); expect(colNames).toContain('password_hash'); expect(colNames).toContain('created_at'); }); it('should create mqtt_brokers table with correct columns', () => { const cols = db.prepare('PRAGMA table_info(mqtt_brokers)').all() as Array<{ name: string }>; const colNames = cols.map((c) => c.name); expect(colNames).toContain('id'); expect(colNames).toContain('name'); expect(colNames).toContain('host'); expect(colNames).toContain('port'); expect(colNames).toContain('use_tls'); expect(colNames).toContain('enabled'); expect(colNames).toContain('status'); }); it('should create datapoints table with source_type CHECK constraint columns', () => { const cols = db.prepare('PRAGMA table_info(datapoints)').all() as Array<{ name: string }>; const colNames = cols.map((c) => c.name); expect(colNames).toContain('source_type'); expect(colNames).toContain('source_id'); expect(colNames).toContain('topic_id'); expect(colNames).toContain('query_id'); expect(colNames).toContain('transform'); }); it('should create layout_elements with all required columns', () => { const cols = db.prepare('PRAGMA table_info(layout_elements)').all() as Array<{ name: string }>; const colNames = cols.map((c) => c.name); expect(colNames).toContain('layout_id'); expect(colNames).toContain('datapoint_id'); expect(colNames).toContain('type'); expect(colNames).toContain('x'); expect(colNames).toContain('y'); expect(colNames).toContain('font_size'); expect(colNames).toContain('style'); }); }); describe('Broker insert and retrieve', () => { it('should insert and retrieve a broker', () => { const result = db.prepare( `INSERT INTO mqtt_brokers (name, host, port) VALUES (?, ?, ?)` ).run('Test Broker', 'mqtt.example.com', 1883); expect(result.lastInsertRowid).toBeGreaterThan(0); const broker = db .prepare(`SELECT * FROM mqtt_brokers WHERE id = ?`) .get(result.lastInsertRowid) as { name: string; host: string; port: number; status: string; } | undefined; expect(broker).toBeDefined(); expect(broker!.name).toBe('Test Broker'); expect(broker!.host).toBe('mqtt.example.com'); expect(broker!.port).toBe(1883); expect(broker!.status).toBe('disconnected'); }); it('should cascade delete topics when broker is deleted', () => { const broker = db.prepare(`INSERT INTO mqtt_brokers (name, host) VALUES (?, ?)`).run( 'Cascade Broker', 'cascade.example.com', ); db.prepare(`INSERT INTO mqtt_topics (broker_id, topic) VALUES (?, ?)`).run( broker.lastInsertRowid, 'sensors/temp', ); const topicsBefore = db .prepare(`SELECT * FROM mqtt_topics WHERE broker_id = ?`) .all(broker.lastInsertRowid); expect(topicsBefore.length).toBe(1); db.prepare(`DELETE FROM mqtt_brokers WHERE id = ?`).run(broker.lastInsertRowid); const topicsAfter = db .prepare(`SELECT * FROM mqtt_topics WHERE broker_id = ?`) .all(broker.lastInsertRowid); expect(topicsAfter.length).toBe(0); }); it('should reject invalid source_type values', () => { expect(() => { db.prepare( `INSERT INTO datapoints (name, label, source_type, source_id) VALUES (?, ?, ?, ?)` ).run('dp1', 'Datapoint 1', 'invalid_source', 1); }).toThrow(); }); });