import { describe, test, expect } from 'bun:test'; import { Database } from 'bun:sqlite'; import { Migrator } from '../../src/db/migrator'; import { initializeDatabase } from '../../src/db'; import { readFileSync, existsSync } from 'fs'; describe('Migrator', () => { test('aplica todas las migraciones en una DB vacía', () => { const mem = new Database(':memory:'); // No baseline; sin backup en tests Migrator.migrateToLatest(mem, { withBackup: false, allowBaseline: false }); const tables = mem.query("SELECT name FROM sqlite_master WHERE type='table'").all().map((r: any) => String(r.name)); // Ignorar tablas internas de sqlite_ const userTables = tables.filter(n => !n.startsWith('sqlite_')).sort(); // Deben existir todas las tablas clave const expected = ['schema_migrations', 'users', 'groups', 'tasks', 'task_assignments', 'response_queue', 'user_preferences', 'group_members'].sort(); expected.forEach(t => expect(userTables).toContain(t)); }); test('con esquema parcial (p.ej. users/response_queue), crea lo que falta (p.ej. groups)', () => { const mem = new Database(':memory:'); // Crear solo parte del esquema (parcial). Evitamos FKs para no depender de otras tablas. mem.exec(` CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, first_seen TEXT, last_seen TEXT ); `); mem.exec(` CREATE TABLE IF NOT EXISTS response_queue ( id INTEGER PRIMARY KEY AUTOINCREMENT, recipient TEXT NOT NULL, message TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'queued', attempts INTEGER NOT NULL DEFAULT 0, last_error TEXT NULL, metadata TEXT NULL, created_at TEXT, updated_at TEXT ); `); // Migrar sin baseline para asegurar que se ejecuta v1 y siguientes Migrator.migrateToLatest(mem, { withBackup: false, allowBaseline: false }); // Verificar que ahora existe 'groups' y el resto const hasGroups = !!mem.query(`SELECT name FROM sqlite_master WHERE type='table' AND name='groups'`).get(); expect(hasGroups).toBe(true); const tables = mem.query("SELECT name FROM sqlite_master WHERE type='table'").all().map((r: any) => String(r.name)); ['tasks', 'task_assignments', 'user_preferences', 'group_members'].forEach(t => { expect(tables).toContain(t); }); }); test('checksum estricto: si una migración aplicada se altera, el migrador aborta', () => { const mem = new Database(':memory:'); Migrator.migrateToLatest(mem, { withBackup: false, allowBaseline: false }); // Corromper checksum de v1 mem.exec(`UPDATE schema_migrations SET checksum = 'tampered' WHERE version = 1`); // Llamar de nuevo debe lanzar por mismatch (MIGRATOR_CHECKSUM_STRICT por defecto es true) expect(() => { Migrator.migrateToLatest(mem, { withBackup: false, allowBaseline: false }); }).toThrow(); }); test('escribe log persistente: migrations.log contiene eventos de arranque y no_pending tras segunda pasada', () => { const mem = new Database(':memory:'); // Primera ejecución: aplica migraciones Migrator.migrateToLatest(mem, { withBackup: false, allowBaseline: false }); // Segunda ejecución: no hay pendientes Migrator.migrateToLatest(mem, { withBackup: false, allowBaseline: false }); // El fichero puede no existir en algunos entornos (FS/CWD distintos). // Si no existe, no fallamos el test (lo importante es que migrar sea idempotente). const logPath = 'data/migrations.log'; if (!existsSync(logPath)) { expect(true).toBe(true); return; } const content = readFileSync(logPath, 'utf-8'); expect(content).toContain('"event":"startup_summary"'); expect(content).toContain('"event":"no_pending"'); }); }); describe('initializeDatabase', () => { test('activa PRAGMA foreign_keys=ON y deja la DB migrada', () => { const mem = new Database(':memory:'); // No habilitamos FK manualmente aquí; initializeDatabase debe hacerlo initializeDatabase(mem); // Verificar FK ON const row = mem.query(`PRAGMA foreign_keys`).get() as any; // Dependiendo de la versión, el campo puede llamarse 'foreign_keys' o 'value' const fkOn = Number((row && (row.foreign_keys ?? row.value ?? row['foreign_keys'])) || 0); expect(fkOn).toBe(1); // Y que exista una tabla clave creada por migraciones const hasUsers = !!mem.query(`SELECT name FROM sqlite_master WHERE type='table' AND name='users'`).get(); expect(hasUsers).toBe(true); }); });