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 { AsyncLocalStorage } from 'node:async_hooks';
/**
* 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;
// AsyncLocalStorage para aislar DB por contexto en tests (paralelismo seguro)
let testScope: AsyncLocalStorage<Database> | null = null;
/**
* Establece la instancia global de DB.
* Se permite sobrescribir (útil en tests).
*/
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;
}
@ -24,6 +32,11 @@ export function setDb(db: Database): void {
* Obtiene la instancia global de DB o lanza si no está configurada.
*/
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;
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 {
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 {
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', () => {
const envBackup = process.env;
let memdb: any;
beforeEach(() => {
process.env = { ...envBackup, NODE_ENV: 'test', GROUP_GATING_MODE: 'enforce' };
const memdb = makeMemDb();
memdb = makeMemDb();
setDb(memdb);
try { AllowedGroups.resetForTests(); } catch {}
});
afterEach(() => {
process.env = envBackup;
try { resetDb(); memdb.close(); } catch {}
});
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_KEY: 'apikey'
};
try { GroupSyncService.activeGroupsCache.clear(); } catch {}
});
afterEach(() => {

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

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

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

Loading…
Cancel
Save