test: añade tests para Migrator y initializeDatabase
Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>pull/1/head
parent
b686d20caa
commit
70688df948
@ -0,0 +1,108 @@
|
||||
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 debe existir y contener eventos conocidos
|
||||
const logPath = 'data/migrations.log';
|
||||
expect(existsSync(logPath)).toBe(true);
|
||||
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);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue