You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
4.5 KiB
TypeScript
114 lines
4.5 KiB
TypeScript
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);
|
|
});
|
|
});
|