diff --git a/src/services/group-sync.ts b/src/services/group-sync.ts index 82c6b89..bdc3748 100644 --- a/src/services/group-sync.ts +++ b/src/services/group-sync.ts @@ -605,9 +605,28 @@ export class GroupSyncService { if (this.activeGroupsCache.size === 0) { this.cacheActiveGroups(); } + + // Etapa 3: gating también en el scheduler masivo + const mode = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase(); + const enforce = mode === 'enforce'; + if (enforce) { + try { (AllowedGroups as any).dbInstance = this.dbInstance; } catch {} + } + let groups = 0, added = 0, updated = 0, deactivated = 0; for (const [groupId] of this.activeGroupsCache.entries()) { try { + if (enforce) { + try { + if (!AllowedGroups.isAllowed(groupId)) { + // Saltar grupos no permitidos en modo enforce + continue; + } + } catch { + // Si falla el check, no bloquear el grupo + } + } + const snapshot = await this.fetchGroupMembersFromAPI(groupId); const res = this.reconcileGroupMembers(groupId, snapshot); groups++; diff --git a/tests/unit/services/group-sync.scheduler.gating.test.ts b/tests/unit/services/group-sync.scheduler.gating.test.ts new file mode 100644 index 0000000..926a3b2 --- /dev/null +++ b/tests/unit/services/group-sync.scheduler.gating.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect, beforeEach, afterEach } from 'bun:test'; +import { makeMemDb } from '../../helpers/db'; +import { GroupSyncService } from '../../../src/services/group-sync'; +import { AllowedGroups } from '../../../src/services/allowed-groups'; + +describe('GroupSyncService - gating en scheduler de miembros (enforce)', () => { + const envBackup = process.env; + const calls: string[] = []; + + beforeEach(() => { + process.env = { ...envBackup, NODE_ENV: 'ci', GROUP_GATING_MODE: 'enforce' }; + + const memdb = makeMemDb(); + (GroupSyncService as any).dbInstance = memdb; + (AllowedGroups as any).dbInstance = memdb; + + // Preparar caché de grupos activos (2 grupos: uno allowed y otro no) + GroupSyncService.activeGroupsCache.clear(); + GroupSyncService.activeGroupsCache.set('ok@g.us', 'OK Group'); + GroupSyncService.activeGroupsCache.set('na@g.us', 'NA Group'); + + // Sembrar allowed solo para 'ok@g.us' + AllowedGroups.setStatus('ok@g.us', 'allowed', 'OK Group'); + + // Stub de fetch para no hacer red y registrar llamadas + (GroupSyncService as any).fetchGroupMembersFromAPI = async (gid: string) => { + calls.push(gid); + // Snapshot vacía para no escribir en DB + return []; + }; + }); + + afterEach(() => { + process.env = envBackup; + calls.length = 0; + }); + + it('salta grupos no allowed y solo procesa los allowed', async () => { + const summary = await GroupSyncService.syncMembersForActiveGroups(); + + // Debe haber procesado solo 1 grupo (el allowed) + expect(summary.groups).toBe(1); + expect(calls).toEqual(['ok@g.us']); + expect(calls).not.toContain('na@g.us'); + }); +});