refactor: usar locator para inyectar DB en tests

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
main
brobert 1 month ago
parent 6cb6c31d8d
commit ecc0cc6fd8

@ -1,5 +1,6 @@
import Database, { type Database as SqliteDatabase } from 'bun:sqlite'; import Database, { type Database as SqliteDatabase } from 'bun:sqlite';
import { initializeDatabase } from '../../src/db'; import { initializeDatabase } from '../../src/db';
import { setDb } from '../../src/db/locator';
// Servicios opcionales para inyección de DB en tests. // Servicios opcionales para inyección de DB en tests.
// Importamos con nombres existentes en la base de código para respetar convenciones. // Importamos con nombres existentes en la base de código para respetar convenciones.
@ -25,12 +26,7 @@ export function makeMemDb(): SqliteDatabase {
* Pensado para usarse en beforeAll/beforeEach de tests que usan estos servicios. * Pensado para usarse en beforeAll/beforeEach de tests que usan estos servicios.
*/ */
export function injectAllServices(db: SqliteDatabase): void { export function injectAllServices(db: SqliteDatabase): void {
try { (TaskService as any).dbInstance = db; } catch {} setDb(db);
try { (CommandService as any).dbInstance = db; } catch {}
try { (ResponseQueue as any).dbInstance = db; } catch {}
try { (IdentityService as any).dbInstance = db; } catch {}
try { (GroupSyncService as any).dbInstance = db; } catch {}
try { (RemindersService as any).dbInstance = db; } catch {}
} }
/** /**
@ -49,7 +45,6 @@ export function resetServices(): void {
* Marca como 'allowed' los groupIds indicados en la DB provista. * Marca como 'allowed' los groupIds indicados en la DB provista.
*/ */
export function seedAllowed(db: SqliteDatabase, groupIds: string[]): void { export function seedAllowed(db: SqliteDatabase, groupIds: string[]): void {
(AllowedGroups as any).dbInstance = db;
for (const gid of groupIds) { for (const gid of groupIds) {
const g = String(gid || '').trim(); const g = String(gid || '').trim();
if (!g) continue; if (!g) continue;

@ -5,6 +5,7 @@ import { ResponseQueue } from '../../src/services/response-queue';
import { GroupSyncService } from '../../src/services/group-sync'; import { GroupSyncService } from '../../src/services/group-sync';
import { initializeDatabase, ensureUserExists } from '../../src/db'; import { initializeDatabase, ensureUserExists } from '../../src/db';
import { TaskService } from '../../src/tasks/service'; import { TaskService } from '../../src/tasks/service';
import { setDb, resetDb } from '../../src/db/locator';
let originalAdd: any; let originalAdd: any;
@ -38,11 +39,8 @@ beforeEach(() => {
// Inject testDb for WebhookServer to use // Inject testDb for WebhookServer to use
WebhookServer.dbInstance = testDb; WebhookServer.dbInstance = testDb;
// Inject testDb for GroupSyncService to use // Usar el locator global para el resto de servicios
GroupSyncService.dbInstance = testDb; setDb(testDb);
// Inject testDb for TaskService to use
(TaskService as any).dbInstance = testDb;
// Ensure database is initialized (recreates tables if dropped) // Ensure database is initialized (recreates tables if dropped)
initializeDatabase(testDb); initializeDatabase(testDb);

@ -4,6 +4,7 @@ import { WebhookServer } from '../../../src/server';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db';
import { ResponseQueue } from '../../../src/services/response-queue'; import { ResponseQueue } from '../../../src/services/response-queue';
import { GroupSyncService } from '../../../src/services/group-sync'; import { GroupSyncService } from '../../../src/services/group-sync';
import { setDb, resetDb } from '../../../src/db/locator';
let testDb: Database; let testDb: Database;
let originalAdd: any; let originalAdd: any;
@ -28,6 +29,7 @@ describe('WebhookServer - discovery guarda label del grupo si está en caché',
afterAll(() => { afterAll(() => {
(ResponseQueue as any).add = originalAdd; (ResponseQueue as any).add = originalAdd;
resetDb();
testDb.close(); testDb.close();
}); });
@ -40,6 +42,7 @@ describe('WebhookServer - discovery guarda label del grupo si está en caché',
SimulatedResponseQueue.clear(); SimulatedResponseQueue.clear();
(ResponseQueue as any).add = SimulatedResponseQueue.add; (ResponseQueue as any).add = SimulatedResponseQueue.add;
WebhookServer.dbInstance = testDb; WebhookServer.dbInstance = testDb;
setDb(testDb);
// Limpiar tablas relevantes // Limpiar tablas relevantes
testDb.exec('DELETE FROM response_queue'); testDb.exec('DELETE FROM response_queue');

@ -5,6 +5,7 @@ import { initializeDatabase } from '../../../src/db';
import { ResponseQueue } from '../../../src/services/response-queue'; import { ResponseQueue } from '../../../src/services/response-queue';
import { AllowedGroups } from '../../../src/services/allowed-groups'; import { AllowedGroups } from '../../../src/services/allowed-groups';
import { GroupSyncService } from '../../../src/services/group-sync'; import { GroupSyncService } from '../../../src/services/group-sync';
import { setDb, resetDb } from '../../../src/db/locator';
let testDb: Database; let testDb: Database;
let originalAdd: any; let originalAdd: any;
@ -29,6 +30,7 @@ describe('WebhookServer - enforce gating (modo=enforce)', () => {
afterAll(() => { afterAll(() => {
(ResponseQueue as any).add = originalAdd; (ResponseQueue as any).add = originalAdd;
resetDb();
testDb.close(); testDb.close();
}); });
@ -41,7 +43,7 @@ describe('WebhookServer - enforce gating (modo=enforce)', () => {
SimulatedResponseQueue.clear(); SimulatedResponseQueue.clear();
(ResponseQueue as any).add = SimulatedResponseQueue.add; (ResponseQueue as any).add = SimulatedResponseQueue.add;
WebhookServer.dbInstance = testDb; WebhookServer.dbInstance = testDb;
(AllowedGroups as any).dbInstance = testDb; setDb(testDb);
// Limpiar tablas relevantes // Limpiar tablas relevantes
testDb.exec('DELETE FROM response_queue'); testDb.exec('DELETE FROM response_queue');

@ -3,6 +3,7 @@ import { Database } from 'bun:sqlite';
import { WebhookServer } from '../../../src/server'; import { WebhookServer } from '../../../src/server';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db';
import { ResponseQueue } from '../../../src/services/response-queue'; import { ResponseQueue } from '../../../src/services/response-queue';
import { setDb, resetDb } from '../../../src/db/locator';
let testDb: Database; let testDb: Database;
let originalAdd: any; let originalAdd: any;
@ -27,6 +28,7 @@ describe('WebhookServer - unknown group discovery (mode=discover)', () => {
afterAll(() => { afterAll(() => {
(ResponseQueue as any).add = originalAdd; (ResponseQueue as any).add = originalAdd;
resetDb();
testDb.close(); testDb.close();
}); });
@ -39,6 +41,7 @@ describe('WebhookServer - unknown group discovery (mode=discover)', () => {
SimulatedResponseQueue.clear(); SimulatedResponseQueue.clear();
(ResponseQueue as any).add = SimulatedResponseQueue.add; (ResponseQueue as any).add = SimulatedResponseQueue.add;
WebhookServer.dbInstance = testDb; WebhookServer.dbInstance = testDb;
setDb(testDb);
// Limpiar tablas relevantes // Limpiar tablas relevantes
testDb.exec('DELETE FROM response_queue'); testDb.exec('DELETE FROM response_queue');

@ -5,6 +5,7 @@ import { WebhookServer } from '../../../src/server';
import { ResponseQueue } from '../../../src/services/response-queue'; import { ResponseQueue } from '../../../src/services/response-queue';
import { AllowedGroups } from '../../../src/services/allowed-groups'; import { AllowedGroups } from '../../../src/services/allowed-groups';
import { GroupSyncService } from '../../../src/services/group-sync'; import { GroupSyncService } from '../../../src/services/group-sync';
import { setDb } from '../../../src/db/locator';
function makePayload(event: string, data: any) { function makePayload(event: string, data: any) {
return { return {
@ -31,9 +32,7 @@ describe('WebhookServer E2E - reacciones por comando', () => {
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
(WebhookServer as any).dbInstance = memdb; (WebhookServer as any).dbInstance = memdb;
(ResponseQueue as any).dbInstance = memdb; setDb(memdb);
(AllowedGroups as any).dbInstance = memdb;
(GroupSyncService as any).dbInstance = memdb;
}); });
afterAll(() => { afterAll(() => {

@ -2,6 +2,7 @@ import { describe, it, beforeEach, expect } from 'bun:test';
import { makeMemDb } from '../../helpers/db'; import { makeMemDb } from '../../helpers/db';
import { AdminService } from '../../../src/services/admin'; import { AdminService } from '../../../src/services/admin';
import { AllowedGroups } from '../../../src/services/allowed-groups'; import { AllowedGroups } from '../../../src/services/allowed-groups';
import { setDb } from '../../../src/db/locator';
describe('AdminService - comandos básicos', () => { describe('AdminService - comandos básicos', () => {
const envBackup = process.env; const envBackup = process.env;

@ -1,11 +1,12 @@
import { describe, it, beforeEach, expect } from 'bun:test'; import { describe, it, beforeEach, expect } from 'bun:test';
import { makeMemDb } from '../../helpers/db'; import { makeMemDb } from '../../helpers/db';
import { AllowedGroups } from '../../../src/services/allowed-groups'; import { AllowedGroups } from '../../../src/services/allowed-groups';
import { setDb } from '../../../src/db/locator';
describe('AllowedGroups service', () => { describe('AllowedGroups service', () => {
beforeEach(() => { beforeEach(() => {
const memdb = makeMemDb(); const memdb = makeMemDb();
(AllowedGroups as any).dbInstance = memdb; setDb(memdb);
AllowedGroups.resetForTests(); AllowedGroups.resetForTests();
}); });

@ -3,6 +3,7 @@ import { Database } from 'bun:sqlite';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db';
import { TaskService } from '../../../src/tasks/service'; import { TaskService } from '../../../src/tasks/service';
import { CommandService } from '../../../src/services/command'; import { CommandService } from '../../../src/services/command';
import { setDb } from '../../../src/db/locator';
describe('CommandService - asignación por defecto (sin dueño vs creador)', () => { describe('CommandService - asignación por defecto (sin dueño vs creador)', () => {
let memdb: Database; let memdb: Database;
@ -10,8 +11,7 @@ describe('CommandService - asignación por defecto (sin dueño vs creador)', ()
beforeAll(() => { beforeAll(() => {
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
TaskService.dbInstance = memdb; setDb(memdb);
CommandService.dbInstance = memdb;
}); });
beforeEach(() => { beforeEach(() => {

@ -3,6 +3,7 @@ import { Database } from 'bun:sqlite';
import { initializeDatabase, ensureUserExists } from '../../../src/db'; import { initializeDatabase, ensureUserExists } from '../../../src/db';
import { TaskService } from '../../../src/tasks/service'; import { TaskService } from '../../../src/tasks/service';
import { CommandService } from '../../../src/services/command'; import { CommandService } from '../../../src/services/command';
import { setDb } from '../../../src/db/locator';
describe('CommandService - /t tomar y /t soltar', () => { describe('CommandService - /t tomar y /t soltar', () => {
let memdb: Database; let memdb: Database;
@ -10,8 +11,7 @@ describe('CommandService - /t tomar y /t soltar', () => {
beforeAll(() => { beforeAll(() => {
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
TaskService.dbInstance = memdb; setDb(memdb);
CommandService.dbInstance = memdb;
}); });
beforeEach(() => { beforeEach(() => {

@ -3,6 +3,7 @@ import { Database } from 'bun:sqlite';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db';
import { TaskService } from '../../../src/tasks/service'; import { TaskService } from '../../../src/tasks/service';
import { CommandService } from '../../../src/services/command'; import { CommandService } from '../../../src/services/command';
import { setDb } from '../../../src/db/locator';
describe('CommandService - formato dd/MM en ACK de creación', () => { describe('CommandService - formato dd/MM en ACK de creación', () => {
let memdb: Database; let memdb: Database;
@ -10,8 +11,7 @@ describe('CommandService - formato dd/MM en ACK de creación', () => {
beforeAll(() => { beforeAll(() => {
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
TaskService.dbInstance = memdb; setDb(memdb);
CommandService.dbInstance = memdb;
}); });
beforeEach(() => { beforeEach(() => {

@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import { makeMemDb } from '../../helpers/db'; import { makeMemDb } from '../../helpers/db';
import { CommandService } from '../../../src/services/command'; import { CommandService } from '../../../src/services/command';
import { AllowedGroups } from '../../../src/services/allowed-groups'; import { AllowedGroups } from '../../../src/services/allowed-groups';
import { setDb, resetDb } from '../../../src/db/locator';
describe('CommandService - gating en modo enforce', () => { describe('CommandService - gating en modo enforce', () => {
const envBackup = process.env; const envBackup = process.env;
@ -9,8 +10,7 @@ describe('CommandService - gating en modo enforce', () => {
beforeEach(() => { beforeEach(() => {
process.env = { ...envBackup, NODE_ENV: 'test', GROUP_GATING_MODE: 'enforce' }; process.env = { ...envBackup, NODE_ENV: 'test', GROUP_GATING_MODE: 'enforce' };
const memdb = makeMemDb(); const memdb = makeMemDb();
(CommandService as any).dbInstance = memdb; setDb(memdb);
(AllowedGroups as any).dbInstance = memdb;
}); });
afterEach(() => { afterEach(() => {

@ -3,6 +3,7 @@ import { Database } from 'bun:sqlite';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db';
import { TaskService } from '../../../src/tasks/service'; import { TaskService } from '../../../src/tasks/service';
import { CommandService } from '../../../src/services/command'; import { CommandService } from '../../../src/services/command';
import { setDb } from '../../../src/db/locator';
describe('CommandService - formato dd/MM en listados', () => { describe('CommandService - formato dd/MM en listados', () => {
let memdb: Database; let memdb: Database;
@ -10,8 +11,7 @@ describe('CommandService - formato dd/MM en listados', () => {
beforeAll(() => { beforeAll(() => {
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
TaskService.dbInstance = memdb; setDb(memdb);
CommandService.dbInstance = memdb;
}); });
beforeEach(() => { beforeEach(() => {

@ -4,6 +4,7 @@ import { initializeDatabase } from '../../../src/db';
import { TaskService } from '../../../src/tasks/service'; import { TaskService } from '../../../src/tasks/service';
import { CommandService } from '../../../src/services/command'; import { CommandService } from '../../../src/services/command';
import { Metrics } from '../../../src/services/metrics'; import { Metrics } from '../../../src/services/metrics';
import { setDb } from '../../../src/db/locator';
describe('CommandService - /t nueva (A2: fallback menciones)', () => { describe('CommandService - /t nueva (A2: fallback menciones)', () => {
let memdb: Database; let memdb: Database;
@ -11,8 +12,7 @@ describe('CommandService - /t nueva (A2: fallback menciones)', () => {
beforeAll(() => { beforeAll(() => {
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
TaskService.dbInstance = memdb; setDb(memdb);
CommandService.dbInstance = memdb;
}); });
beforeEach(() => { beforeEach(() => {

@ -4,6 +4,7 @@ import { initializeDatabase } from '../../../src/db';
import { CommandService } from '../../../src/services/command'; import { CommandService } from '../../../src/services/command';
import { ResponseQueue } from '../../../src/services/response-queue'; import { ResponseQueue } from '../../../src/services/response-queue';
import { TaskService } from '../../../src/tasks/service'; import { TaskService } from '../../../src/tasks/service';
import { setDb } from '../../../src/db/locator';
describe('CommandService - JIT onboarding para menciones @lid y números demasiado largos', () => { describe('CommandService - JIT onboarding para menciones @lid y números demasiado largos', () => {
let memdb: Database; let memdb: Database;
@ -11,9 +12,7 @@ describe('CommandService - JIT onboarding para menciones @lid y números demasia
beforeAll(() => { beforeAll(() => {
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
(CommandService as any).dbInstance = memdb; setDb(memdb);
(TaskService as any).dbInstance = memdb;
(ResponseQueue as any).dbInstance = memdb;
}); });
beforeEach(() => { beforeEach(() => {

@ -4,6 +4,7 @@ import { initializeDatabase } from '../../../src/db';
import { TaskService } from '../../../src/tasks/service'; import { TaskService } from '../../../src/tasks/service';
import { CommandService } from '../../../src/services/command'; import { CommandService } from '../../../src/services/command';
import { Metrics } from '../../../src/services/metrics'; import { Metrics } from '../../../src/services/metrics';
import { setDb } from '../../../src/db/locator';
describe('CommandService - A4 JIT DM al asignador', () => { describe('CommandService - A4 JIT DM al asignador', () => {
let memdb: Database; let memdb: Database;
@ -11,8 +12,7 @@ describe('CommandService - A4 JIT DM al asignador', () => {
beforeAll(() => { beforeAll(() => {
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
TaskService.dbInstance = memdb as any; setDb(memdb as any);
CommandService.dbInstance = memdb as any;
}); });
beforeEach(() => { beforeEach(() => {

@ -1,7 +1,8 @@
import { describe, it, beforeEach, expect } from 'bun:test'; import { describe, it, beforeEach, expect } from 'bun:test';
import { Database } from 'bun:sqlite'; import { Database } from 'bun:sqlite';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db'];
import { CommandService } from '../../../src/services/command'; import { CommandService } from '../../../src/services/command';
import { setDb } from '../../../src/db/locator';
describe('CommandService - configurar recordatorios', () => { describe('CommandService - configurar recordatorios', () => {
let memdb: Database; let memdb: Database;
@ -15,7 +16,7 @@ describe('CommandService - configurar recordatorios', () => {
initializeDatabase(memdb); initializeDatabase(memdb);
// Inyectar DB // Inyectar DB
(CommandService as any).dbInstance = memdb; setDb(memdb);
// Limpiar tablas // Limpiar tablas
memdb.exec(`DELETE FROM response_queue;`); memdb.exec(`DELETE FROM response_queue;`);

@ -4,6 +4,7 @@ import { initializeDatabase } from '../../../src/db';
import { TaskService } from '../../../src/tasks/service'; import { TaskService } from '../../../src/tasks/service';
import { CommandService } from '../../../src/services/command'; import { CommandService } from '../../../src/services/command';
import { Metrics } from '../../../src/services/metrics'; import { Metrics } from '../../../src/services/metrics';
import { setDb } from '../../../src/db/locator';
describe('CommandService - autoasignación con "yo" / "@yo"', () => { describe('CommandService - autoasignación con "yo" / "@yo"', () => {
let memdb: Database; let memdb: Database;
@ -11,8 +12,7 @@ describe('CommandService - autoasignación con "yo" / "@yo"', () => {
beforeAll(() => { beforeAll(() => {
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
TaskService.dbInstance = memdb; setDb(memdb);
CommandService.dbInstance = memdb;
}); });
beforeEach(() => { beforeEach(() => {

@ -4,6 +4,7 @@ import { initializeDatabase } from '../../../src/db';
import { TaskService } from '../../../src/tasks/service'; import { TaskService } from '../../../src/tasks/service';
import { CommandService } from '../../../src/services/command'; import { CommandService } from '../../../src/services/command';
import { GroupSyncService } from '../../../src/services/group-sync'; import { GroupSyncService } from '../../../src/services/group-sync';
import { setDb } from '../../../src/db/locator';
describe('CommandService - inserta task_origins al crear en grupo con messageId', () => { describe('CommandService - inserta task_origins al crear en grupo con messageId', () => {
let memdb: Database; let memdb: Database;
@ -11,8 +12,7 @@ describe('CommandService - inserta task_origins al crear en grupo con messageId'
beforeAll(() => { beforeAll(() => {
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
(TaskService as any).dbInstance = memdb; setDb(memdb);
(CommandService as any).dbInstance = memdb;
// Sembrar grupo activo y cache // Sembrar grupo activo y cache
memdb.exec(` memdb.exec(`

@ -4,6 +4,7 @@ import { initializeDatabase } from '../../../src/db';
import { CommandService } from '../../../src/services/command'; import { CommandService } from '../../../src/services/command';
import { TaskService } from '../../../src/tasks/service'; import { TaskService } from '../../../src/tasks/service';
import { GroupSyncService } from '../../../src/services/group-sync'; import { GroupSyncService } from '../../../src/services/group-sync';
import { setDb, resetDb } from '../../../src/db/locator';
let memDb: Database; let memDb: Database;
const testContextBase = { const testContextBase = {
@ -15,9 +16,7 @@ const testContextBase = {
beforeEach(() => { beforeEach(() => {
memDb = new Database(':memory:'); memDb = new Database(':memory:');
initializeDatabase(memDb); initializeDatabase(memDb);
(CommandService as any).dbInstance = memDb; setDb(memDb);
(TaskService as any).dbInstance = memDb;
(GroupSyncService as any).dbInstance = memDb;
GroupSyncService.activeGroupsCache.clear(); GroupSyncService.activeGroupsCache.clear();
}); });
@ -282,7 +281,7 @@ test('ver todos por DM: “Tus tareas” + nota instructiva para ver sin dueño
}); });
afterEach(() => { afterEach(() => {
try { memDb.close(); } catch { } try { resetDb(); memDb.close(); } catch { }
}); });
describe('CommandService', () => { describe('CommandService', () => {

@ -4,6 +4,7 @@ import { initializeDatabase } from '../../../src/db';
import { CommandService } from '../../../src/services/command'; import { CommandService } from '../../../src/services/command';
import { sha256Hex } from '../../../src/utils/crypto'; import { sha256Hex } from '../../../src/utils/crypto';
import { Metrics } from '../../../src/services/metrics'; import { Metrics } from '../../../src/services/metrics';
import { setDb } from '../../../src/db/locator';
const envBackup = { ...process.env }; const envBackup = { ...process.env };
let memdb: Database; let memdb: Database;
@ -20,7 +21,7 @@ describe('CommandService - /t web (emisión de token de login)', () => {
Metrics.reset?.(); Metrics.reset?.();
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
(CommandService as any).dbInstance = memdb; setDb(memdb);
}); });
afterEach(() => { afterEach(() => {

@ -14,8 +14,6 @@ describe('GroupSyncService - alias_coverage_ratio', () => {
Metrics.reset(); Metrics.reset();
db = makeMemDb(); db = makeMemDb();
injectAllServices(db); injectAllServices(db);
(GroupSyncService as any).dbInstance = db;
(IdentityService as any).dbInstance = db;
// Crear grupo activo requerido por FK // Crear grupo activo requerido por FK
db.prepare(`INSERT INTO groups (id, community_id, name, active) VALUES (?, ?, ?, 1)`) db.prepare(`INSERT INTO groups (id, community_id, name, active) VALUES (?, ?, ?, 1)`)

@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import { makeMemDb } from '../../helpers/db'; import { makeMemDb } from '../../helpers/db';
import { GroupSyncService } from '../../../src/services/group-sync'; import { GroupSyncService } from '../../../src/services/group-sync';
import { AllowedGroups } from '../../../src/services/allowed-groups'; import { AllowedGroups } from '../../../src/services/allowed-groups';
import { setDb } from '../../../src/db/locator';
describe('GroupSyncService - gating en syncMembersForGroup (enforce)', () => { describe('GroupSyncService - gating en syncMembersForGroup (enforce)', () => {
const envBackup = process.env; const envBackup = process.env;
@ -9,8 +10,7 @@ describe('GroupSyncService - gating en syncMembersForGroup (enforce)', () => {
beforeEach(() => { beforeEach(() => {
process.env = { ...envBackup, NODE_ENV: 'test', GROUP_GATING_MODE: 'enforce' }; process.env = { ...envBackup, NODE_ENV: 'test', GROUP_GATING_MODE: 'enforce' };
const memdb = makeMemDb(); const memdb = makeMemDb();
(GroupSyncService as any).dbInstance = memdb; setDb(memdb);
(AllowedGroups as any).dbInstance = memdb;
// Stub fetchGroupMembersFromAPI para no hacer red // Stub fetchGroupMembersFromAPI para no hacer red
(GroupSyncService as any).fetchGroupMembersFromAPI = async (_groupId: string) => { (GroupSyncService as any).fetchGroupMembersFromAPI = async (_groupId: string) => {

@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import Database from 'bun:sqlite'; import Database from 'bun:sqlite';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db';
import { GroupSyncService } from '../../../src/services/group-sync'; import { GroupSyncService } from '../../../src/services/group-sync';
import { setDb, resetDb } from '../../../src/db/locator';
describe('GroupSyncService - upsertGroups actualiza label en allowed_groups', () => { describe('GroupSyncService - upsertGroups actualiza label en allowed_groups', () => {
const envBackup = process.env; const envBackup = process.env;
@ -11,7 +12,7 @@ describe('GroupSyncService - upsertGroups actualiza label en allowed_groups', ()
process.env = { ...envBackup, NODE_ENV: 'test', WHATSAPP_COMMUNITY_ID: 'comm-1' }; process.env = { ...envBackup, NODE_ENV: 'test', WHATSAPP_COMMUNITY_ID: 'comm-1' };
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
(GroupSyncService as any).dbInstance = memdb; setDb(memdb);
// Limpiar tablas // Limpiar tablas
memdb.exec('DELETE FROM allowed_groups'); memdb.exec('DELETE FROM allowed_groups');
@ -19,6 +20,7 @@ describe('GroupSyncService - upsertGroups actualiza label en allowed_groups', ()
}); });
afterEach(() => { afterEach(() => {
resetDb();
memdb.close(); memdb.close();
process.env = envBackup; process.env = envBackup;
}); });

@ -2,6 +2,7 @@ import { Database } from 'bun:sqlite';
import { beforeAll, beforeEach, afterAll, describe, expect, test } from 'bun:test'; import { beforeAll, beforeEach, afterAll, describe, expect, test } from 'bun:test';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db';
import { GroupSyncService } from '../../../src/services/group-sync'; import { GroupSyncService } from '../../../src/services/group-sync';
import { setDb, resetDb } from '../../../src/db/locator';
describe('GroupSyncService - reconcileGroupMembers', () => { describe('GroupSyncService - reconcileGroupMembers', () => {
let memdb: Database; let memdb: Database;
@ -10,11 +11,11 @@ describe('GroupSyncService - reconcileGroupMembers', () => {
memdb = new Database(':memory:'); memdb = new Database(':memory:');
memdb.exec('PRAGMA foreign_keys = ON;'); memdb.exec('PRAGMA foreign_keys = ON;');
initializeDatabase(memdb); initializeDatabase(memdb);
// Inyectar DB en el servicio setDb(memdb);
GroupSyncService.dbInstance = memdb as any;
}); });
afterAll(() => { afterAll(() => {
resetDb();
memdb.close(); memdb.close();
}); });

@ -3,6 +3,7 @@ import Database from 'bun:sqlite';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db';
import { GroupSyncService } from '../../../src/services/group-sync'; import { GroupSyncService } from '../../../src/services/group-sync';
import { AllowedGroups } from '../../../src/services/allowed-groups'; import { AllowedGroups } from '../../../src/services/allowed-groups';
import { setDb, resetDb } from '../../../src/db/locator';
const envBackup = { ...process.env } as NodeJS.ProcessEnv; const envBackup = { ...process.env } as NodeJS.ProcessEnv;
@ -22,8 +23,7 @@ describe('GroupSyncService - onboarding A3', () => {
}; };
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
(GroupSyncService as any).dbInstance = memdb; setDb(memdb);
(AllowedGroups as any).dbInstance = memdb;
// Sembrar grupo activo // Sembrar grupo activo
memdb.prepare(`INSERT INTO groups (id, community_id, name, active, last_verified) VALUES (?,?,?,?, strftime('%Y-%m-%d %H:%M:%f','now'))`) memdb.prepare(`INSERT INTO groups (id, community_id, name, active, last_verified) VALUES (?,?,?,?, strftime('%Y-%m-%d %H:%M:%f','now'))`)
@ -31,6 +31,7 @@ describe('GroupSyncService - onboarding A3', () => {
}); });
afterEach(() => { afterEach(() => {
resetDb();
memdb.close(); memdb.close();
process.env = envBackup; process.env = envBackup;
}); });

@ -3,6 +3,7 @@ import Database from 'bun:sqlite';
import { initializeDatabase, ensureUserExists } from '../../../src/db'; import { initializeDatabase, ensureUserExists } from '../../../src/db';
import { GroupSyncService } from '../../../src/services/group-sync'; import { GroupSyncService } from '../../../src/services/group-sync';
import { ResponseQueue } from '../../../src/services/response-queue'; import { ResponseQueue } from '../../../src/services/response-queue';
import { setDb, resetDb } from '../../../src/db/locator';
const envBackup = { ...process.env }; const envBackup = { ...process.env };
let originalSyncMembers: any; let originalSyncMembers: any;
@ -90,8 +91,7 @@ describe('GroupSyncService - scheduler de miembros', () => {
const memdb = new Database(':memory:'); const memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
(GroupSyncService as any).dbInstance = memdb; setDb(memdb);
(ResponseQueue as any).dbInstance = memdb;
// Sembrar grupo activo con miembro y token de calendario // Sembrar grupo activo con miembro y token de calendario
memdb.exec(`INSERT INTO groups (id, community_id, name, active, archived, last_verified) VALUES ('g1@g.us','comm-1','G1',1,0,strftime('%Y-%m-%d %H:%M:%f','now'))`); memdb.exec(`INSERT INTO groups (id, community_id, name, active, archived, last_verified) VALUES ('g1@g.us','comm-1','G1',1,0,strftime('%Y-%m-%d %H:%M:%f','now'))`);
@ -121,5 +121,9 @@ describe('GroupSyncService - scheduler de miembros', () => {
// Notificación encolada a admins // Notificación encolada a admins
const msg = memdb.query(`SELECT message FROM response_queue ORDER BY id DESC LIMIT 1`).get() as any; const msg = memdb.query(`SELECT message FROM response_queue ORDER BY id DESC LIMIT 1`).get() as any;
expect(msg && String(msg.message)).toContain('/admin archivar-grupo g1@g.us'); expect(msg && String(msg.message)).toContain('/admin archivar-grupo g1@g.us');
// Limpieza de DB/locator local a este test
resetDb();
try { memdb.close(); } catch {}
}); });
}); });

@ -2,6 +2,7 @@ import { describe, test, expect, beforeEach, afterEach, beforeAll, afterAll } fr
import { Database } from 'bun:sqlite'; import { Database } from 'bun:sqlite';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db';
import { GroupSyncService } from '../../../src/services/group-sync'; import { GroupSyncService } from '../../../src/services/group-sync';
import { setDb, resetDb } from '../../../src/db/locator';
let memdb: Database; let memdb: Database;
const envBackup = { ...process.env }; const envBackup = { ...process.env };
@ -12,10 +13,11 @@ describe('GroupSyncService - syncMembersForActiveGroups (agregado por grupos)',
memdb = new Database(':memory:'); memdb = new Database(':memory:');
memdb.exec('PRAGMA foreign_keys = ON;'); memdb.exec('PRAGMA foreign_keys = ON;');
initializeDatabase(memdb); initializeDatabase(memdb);
GroupSyncService.dbInstance = memdb as any; setDb(memdb as any);
}); });
afterAll(() => { afterAll(() => {
resetDb();
memdb.close(); memdb.close();
}); });

@ -3,6 +3,7 @@ import { GroupSyncService } from '../../../src/services/group-sync';
import { db } from '../../../src/db'; import { db } from '../../../src/db';
import { Database } from 'bun:sqlite'; import { Database } from 'bun:sqlite';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db';
import { setDb } from '../../../src/db/locator';
// Store original globals // Store original globals
const originalFetch = globalThis.fetch; const originalFetch = globalThis.fetch;
@ -17,8 +18,8 @@ describe('GroupSyncService', () => {
testDb = new Database(':memory:'); testDb = new Database(':memory:');
initializeDatabase(testDb); initializeDatabase(testDb);
// Assign the isolated DB to the service // Assign the isolated DB to the service (via locator)
GroupSyncService.dbInstance = testDb; setDb(testDb);
// Clear and reset test data // Clear and reset test data
testDb.exec('DELETE FROM groups'); testDb.exec('DELETE FROM groups');

@ -4,6 +4,7 @@ import { initializeDatabase } from '../../../src/db';
import { IdentityService } from '../../../src/services/identity'; import { IdentityService } from '../../../src/services/identity';
import { WebhookServer } from '../../../src/server'; import { WebhookServer } from '../../../src/server';
import { GroupSyncService } from '../../../src/services/group-sync'; import { GroupSyncService } from '../../../src/services/group-sync';
import { setDb, resetDb } from '../../../src/db/locator';
const ORIGINAL_FETCH = globalThis.fetch; const ORIGINAL_FETCH = globalThis.fetch;
const envBackup = { ...process.env }; const envBackup = { ...process.env };
@ -18,9 +19,8 @@ describe('Alias @lid ↔ número: aprendizaje y uso en sync de miembros', () =>
initializeDatabase(memdb); initializeDatabase(memdb);
// Inyectar DB en servicios implicados // Inyectar DB en servicios implicados
(IdentityService as any).dbInstance = memdb;
(WebhookServer as any).dbInstance = memdb; (WebhookServer as any).dbInstance = memdb;
(GroupSyncService as any).dbInstance = memdb; setDb(memdb);
// Limpiar caché de grupos para evitar interferencias // Limpiar caché de grupos para evitar interferencias
GroupSyncService.activeGroupsCache.clear(); GroupSyncService.activeGroupsCache.clear();
@ -29,6 +29,7 @@ describe('Alias @lid ↔ número: aprendizaje y uso en sync de miembros', () =>
afterEach(() => { afterEach(() => {
globalThis.fetch = ORIGINAL_FETCH; globalThis.fetch = ORIGINAL_FETCH;
process.env = envBackup; process.env = envBackup;
resetDb();
memdb.close(); memdb.close();
}); });

@ -3,6 +3,7 @@ import { WebhookServer } from '../../../src/server';
import { Metrics } from '../../../src/services/metrics'; import { Metrics } from '../../../src/services/metrics';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db';
import { Database } from 'bun:sqlite'; import { Database } from 'bun:sqlite';
import { setDb, resetDb } from '../../../src/db/locator';
import { toIsoSql as toIso } from '../../helpers/dates'; import { toIsoSql as toIso } from '../../helpers/dates';
@ -16,11 +17,12 @@ describe('/metrics y /health (detallado)', () => {
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
(WebhookServer as any).dbInstance = memdb; (WebhookServer as any).dbInstance = memdb;
setDb(memdb);
}); });
afterEach(() => { afterEach(() => {
process.env = envBackup; process.env = envBackup;
try { memdb.close(); } catch {} try { resetDb(); memdb.close(); } catch {}
}); });
test('/metrics devuelve métricas en formato Prometheus', async () => { test('/metrics devuelve métricas en formato Prometheus', async () => {

@ -6,6 +6,7 @@ import { RemindersService } from '../../../src/services/reminders';
import { AllowedGroups } from '../../../src/services/allowed-groups'; import { AllowedGroups } from '../../../src/services/allowed-groups';
import { ResponseQueue } from '../../../src/services/response-queue'; import { ResponseQueue } from '../../../src/services/response-queue';
import { toIsoSql } from '../../helpers/dates'; import { toIsoSql } from '../../helpers/dates';
import { setDb } from '../../../src/db/locator';
function seedGroup(db: Database, groupId: string) { function seedGroup(db: Database, groupId: string) {
const cols = db.query(`PRAGMA table_info(groups)`).all() as any[]; const cols = db.query(`PRAGMA table_info(groups)`).all() as any[];
@ -49,9 +50,7 @@ describe('RemindersService - gating por grupos en modo enforce', () => {
process.env = { ...envBackup, NODE_ENV: 'test', GROUP_GATING_MODE: 'enforce', TZ: 'Europe/Madrid', REMINDERS_GRACE_MINUTES: '60' }; process.env = { ...envBackup, NODE_ENV: 'test', GROUP_GATING_MODE: 'enforce', TZ: 'Europe/Madrid', REMINDERS_GRACE_MINUTES: '60' };
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
(TaskService as any).dbInstance = memdb; setDb(memdb);
(RemindersService as any).dbInstance = memdb;
(AllowedGroups as any).dbInstance = memdb;
// Stub de ResponseQueue // Stub de ResponseQueue
originalAdd = (ResponseQueue as any).add; originalAdd = (ResponseQueue as any).add;

@ -4,6 +4,7 @@ import { initializeDatabase } from '../../../src/db';
import { TaskService } from '../../../src/tasks/service'; import { TaskService } from '../../../src/tasks/service';
import { RemindersService } from '../../../src/services/reminders'; import { RemindersService } from '../../../src/services/reminders';
import { ResponseQueue } from '../../../src/services/response-queue'; import { ResponseQueue } from '../../../src/services/response-queue';
import { setDb } from '../../../src/db/locator';
import { toIsoSql as toIso } from '../../helpers/dates'; import { toIsoSql as toIso } from '../../helpers/dates';
@ -20,9 +21,7 @@ describe('RemindersService', () => {
initializeDatabase(memdb); initializeDatabase(memdb);
// Inyectar DB en servicios // Inyectar DB en servicios
(TaskService as any).dbInstance = memdb; setDb(memdb);
(RemindersService as any).dbInstance = memdb;
(ResponseQueue as any).dbInstance = memdb;
// Limpiar tablas entre tests por seguridad // Limpiar tablas entre tests por seguridad
memdb.exec(`DELETE FROM response_queue;`); memdb.exec(`DELETE FROM response_queue;`);

@ -4,20 +4,19 @@ import { initializeDatabase } from '../../../src/db';
import { ResponseQueue } from '../../../src/services/response-queue'; import { ResponseQueue } from '../../../src/services/response-queue';
let testDb: Database; let testDb: Database;
let originalDbInstance: Database;
import { toIsoSql as toIso } from '../../helpers/dates'; import { toIsoSql as toIso } from '../../helpers/dates';
import { setDb, resetDb } from '../../../src/db/locator';
describe('ResponseQueue cleanup/retention', () => { describe('ResponseQueue cleanup/retention', () => {
beforeAll(() => { beforeAll(() => {
testDb = new Database(':memory:'); testDb = new Database(':memory:');
initializeDatabase(testDb); initializeDatabase(testDb);
originalDbInstance = (ResponseQueue as any).dbInstance; setDb(testDb);
(ResponseQueue as any).dbInstance = testDb;
}); });
afterAll(() => { afterAll(() => {
(ResponseQueue as any).dbInstance = originalDbInstance; resetDb();
testDb.close(); testDb.close();
}); });

@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import { Database } from 'bun:sqlite'; import { Database } from 'bun:sqlite';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db';
import { ResponseQueue } from '../../../src/services/response-queue'; import { ResponseQueue } from '../../../src/services/response-queue';
import { setDb, resetDb } from '../../../src/db/locator';
const ORIGINAL_FETCH = globalThis.fetch; const ORIGINAL_FETCH = globalThis.fetch;
const envBackup = { ...process.env }; const envBackup = { ...process.env };
@ -24,7 +25,7 @@ describe('ResponseQueue - jobs de reacción (enqueue + sendOne)', () => {
memdb.exec('PRAGMA foreign_keys = ON;'); memdb.exec('PRAGMA foreign_keys = ON;');
initializeDatabase(memdb); initializeDatabase(memdb);
(ResponseQueue as any).dbInstance = memdb; setDb(memdb);
globalThis.fetch = async (url: RequestInfo | URL, init?: RequestInit) => { globalThis.fetch = async (url: RequestInfo | URL, init?: RequestInit) => {
captured.url = String(url); captured.url = String(url);
@ -46,7 +47,7 @@ describe('ResponseQueue - jobs de reacción (enqueue + sendOne)', () => {
afterEach(() => { afterEach(() => {
globalThis.fetch = ORIGINAL_FETCH; globalThis.fetch = ORIGINAL_FETCH;
process.env = envBackup; process.env = envBackup;
try { memdb.close(); } catch {} try { resetDb(); memdb.close(); } catch {}
}); });
it('enqueueReaction aplica idempotencia por (chatId, messageId, emoji) en ventana 24h', async () => { it('enqueueReaction aplica idempotencia por (chatId, messageId, emoji) en ventana 24h', async () => {

@ -3,19 +3,18 @@ import { Database } from 'bun:sqlite';
import { initializeDatabase } from '../../../src/db'; import { initializeDatabase } from '../../../src/db';
import { ResponseQueue } from '../../../src/services/response-queue'; import { ResponseQueue } from '../../../src/services/response-queue';
import { toIsoSql } from '../../helpers/dates'; import { toIsoSql } from '../../helpers/dates';
import { setDb, resetDb } from '../../../src/db/locator';
let testDb: Database; let testDb: Database;
let envBackup: NodeJS.ProcessEnv; let envBackup: NodeJS.ProcessEnv;
let originalDbInstance: Database;
describe('ResponseQueue (persistent add)', () => { describe('ResponseQueue (persistent add)', () => {
beforeAll(() => { beforeAll(() => {
envBackup = { ...process.env }; envBackup = { ...process.env };
testDb = new Database(':memory:'); testDb = new Database(':memory:');
initializeDatabase(testDb); initializeDatabase(testDb);
// Guardar e inyectar DB de pruebas // Inyectar DB de pruebas vía locator
originalDbInstance = (ResponseQueue as any).dbInstance; setDb(testDb);
(ResponseQueue as any).dbInstance = testDb;
}); });
afterAll(() => { afterAll(() => {
@ -111,8 +110,8 @@ describe('ResponseQueue (persistent add)', () => {
describe('ResponseQueue (retries/backoff)', () => { describe('ResponseQueue (retries/backoff)', () => {
// Reutiliza la misma DB inyectada en el bloque anterior // Reutiliza la misma DB inyectada en el bloque anterior
afterAll(() => { afterAll(() => {
// Restaurar DB original y cerrar la de prueba al finalizar todos los tests de reintentos // Restablecer locator y cerrar la DB de prueba
(ResponseQueue as any).dbInstance = originalDbInstance; resetDb();
testDb.close(); testDb.close();
}); });

@ -2,6 +2,7 @@ import { describe, it, expect, beforeAll, beforeEach } from 'bun:test';
import { Database } from 'bun:sqlite'; import { Database } from 'bun:sqlite';
import { initializeDatabase, ensureUserExists } from '../../../src/db'; import { initializeDatabase, ensureUserExists } from '../../../src/db';
import { TaskService } from '../../../src/tasks/service'; import { TaskService } from '../../../src/tasks/service';
import { setDb } from '../../../src/db/locator';
describe('TaskService - claim/unassign', () => { describe('TaskService - claim/unassign', () => {
let memdb: Database; let memdb: Database;
@ -9,7 +10,7 @@ describe('TaskService - claim/unassign', () => {
beforeAll(() => { beforeAll(() => {
memdb = new Database(':memory:'); memdb = new Database(':memory:');
initializeDatabase(memdb); initializeDatabase(memdb);
TaskService.dbInstance = memdb; setDb(memdb);
}); });
beforeEach(() => { beforeEach(() => {

@ -2,17 +2,19 @@ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import { Database } from 'bun:sqlite'; import { Database } from 'bun:sqlite';
import { initializeDatabase, ensureUserExists } from '../../../src/db'; import { initializeDatabase, ensureUserExists } from '../../../src/db';
import { TaskService } from '../../../src/tasks/service'; import { TaskService } from '../../../src/tasks/service';
import { setDb, resetDb } from '../../../src/db/locator';
let memDb: Database; let memDb: Database;
beforeEach(() => { beforeEach(() => {
memDb = new Database(':memory:'); memDb = new Database(':memory:');
initializeDatabase(memDb); initializeDatabase(memDb);
TaskService.dbInstance = memDb; setDb(memDb);
}); });
afterEach(() => { afterEach(() => {
try { try {
resetDb();
memDb.close(); memDb.close();
} catch {} } catch {}
}); });

Loading…
Cancel
Save