feat: aislar DB por contexto en tests con AsyncLocalStorage

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
main
brobert 4 months ago
parent a153163b5e
commit b429053ee2

@ -1,4 +1,5 @@
import type { Database } from 'bun:sqlite'; import type { Database } from 'bun:sqlite';
import { AsyncLocalStorage } from 'node:async_hooks';
/** /**
* Error específico cuando se intenta acceder a la DB sin haberla configurado. * Error específico cuando se intenta acceder a la DB sin haberla configurado.
@ -12,11 +13,18 @@ export class DbNotConfiguredError extends Error {
let currentDb: Database | null = null; let currentDb: Database | null = null;
// AsyncLocalStorage para aislar DB por contexto en tests (paralelismo seguro)
let testScope: AsyncLocalStorage<Database> | null = null;
/** /**
* Establece la instancia global de DB. * Establece la instancia global de DB.
* Se permite sobrescribir (útil en tests). * Se permite sobrescribir (útil en tests).
*/ */
export function setDb(db: Database): void { export function setDb(db: Database): void {
if (process.env.NODE_ENV === 'test') {
if (!testScope) testScope = new AsyncLocalStorage<Database>();
try { testScope.enterWith(db); } catch {}
}
currentDb = db; currentDb = db;
} }
@ -24,6 +32,11 @@ export function setDb(db: Database): void {
* Obtiene la instancia global de DB o lanza si no está configurada. * Obtiene la instancia global de DB o lanza si no está configurada.
*/ */
export function getDb(): Database { export function getDb(): Database {
if (process.env.NODE_ENV === 'test') {
if (!testScope) testScope = new AsyncLocalStorage<Database>();
const scoped = testScope.getStore();
if (scoped) return scoped;
}
if (currentDb) return currentDb; if (currentDb) return currentDb;
throw new DbNotConfiguredError('Database has not been configured. Call setDb(db) before using getDb().'); throw new DbNotConfiguredError('Database has not been configured. Call setDb(db) before using getDb().');
} }
@ -33,6 +46,10 @@ export function getDb(): Database {
*/ */
export function resetDb(): void { export function resetDb(): void {
currentDb = null; currentDb = null;
if (process.env.NODE_ENV === 'test') {
// Re-inicializar el almacenamiento para limpiar el store del contexto actual
testScope = new AsyncLocalStorage<Database>();
}
} }
/** /**
@ -40,6 +57,10 @@ export function resetDb(): void {
*/ */
export function clearDb(): void { export function clearDb(): void {
currentDb = null; currentDb = null;
if (process.env.NODE_ENV === 'test') {
// Re-inicializar el almacenamiento para limpiar el store del contexto actual
testScope = new AsyncLocalStorage<Database>();
}
} }
/** /**

@ -6,15 +6,18 @@ 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;
let memdb: any;
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(); memdb = makeMemDb();
setDb(memdb); setDb(memdb);
try { AllowedGroups.resetForTests(); } catch {}
}); });
afterEach(() => { afterEach(() => {
process.env = envBackup; process.env = envBackup;
try { resetDb(); memdb.close(); } catch {}
}); });
it('bloquea comandos en grupo no permitido (desconocido)', async () => { it('bloquea comandos en grupo no permitido (desconocido)', async () => {

@ -14,6 +14,7 @@ describe('GroupSyncService - fetchGroupMembersFromAPI (parsing y fallbacks)', ()
EVOLUTION_API_INSTANCE: 'instance-1', EVOLUTION_API_INSTANCE: 'instance-1',
EVOLUTION_API_KEY: 'apikey' EVOLUTION_API_KEY: 'apikey'
}; };
try { GroupSyncService.activeGroupsCache.clear(); } catch {}
}); });
afterEach(() => { afterEach(() => {

@ -20,6 +20,8 @@ describe('GroupSyncService - reconcileGroupMembers', () => {
}); });
beforeEach(() => { beforeEach(() => {
setDb(memdb);
try { GroupSyncService.activeGroupsCache.clear(); } catch {}
// Limpiar tablas relevantes entre tests // Limpiar tablas relevantes entre tests
memdb.exec('DELETE FROM group_members'); memdb.exec('DELETE FROM group_members');
memdb.exec('DELETE FROM users'); memdb.exec('DELETE FROM users');

@ -17,6 +17,7 @@ describe('GroupSyncService - scheduler de miembros', () => {
beforeEach(() => { beforeEach(() => {
originalSyncMembers = GroupSyncService.syncMembersForActiveGroups; originalSyncMembers = GroupSyncService.syncMembersForActiveGroups;
originalSyncGroups = GroupSyncService.syncGroups; originalSyncGroups = GroupSyncService.syncGroups;
try { GroupSyncService.activeGroupsCache.clear(); } catch {}
}); });
afterEach(() => { afterEach(() => {

@ -23,6 +23,8 @@ describe('GroupSyncService - syncMembersForActiveGroups (agregado por grupos)',
beforeEach(() => { beforeEach(() => {
process.env = { ...envBackup, NODE_ENV: 'development' }; // evitar early return process.env = { ...envBackup, NODE_ENV: 'development' }; // evitar early return
setDb(memdb as any);
try { GroupSyncService.activeGroupsCache.clear(); } catch {}
// Reset tablas // Reset tablas
memdb.exec('DELETE FROM group_members'); memdb.exec('DELETE FROM group_members');
memdb.exec('DELETE FROM users'); memdb.exec('DELETE FROM users');

Loading…
Cancel
Save