diff --git a/src/server.ts b/src/server.ts index 5fe1b3a..d0061aa 100644 --- a/src/server.ts +++ b/src/server.ts @@ -14,6 +14,7 @@ import { RemindersService } from './services/reminders'; import { Metrics } from './services/metrics'; import { MaintenanceService } from './services/maintenance'; import { IdentityService } from './services/identity'; +import { AllowedGroups } from './services/allowed-groups'; // Bun is available globally when running under Bun runtime declare global { @@ -298,6 +299,29 @@ export class WebhookServer { return; } + // Etapa 2: Descubrimiento seguro de grupos (modo 'discover') + if (isGroupId(remoteJid)) { + try { (AllowedGroups as any).dbInstance = WebhookServer.dbInstance; } catch {} + const gatingMode = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase(); + if (gatingMode === 'discover') { + try { + const exists = WebhookServer.dbInstance + .prepare(`SELECT 1 FROM allowed_groups WHERE group_id = ? LIMIT 1`) + .get(remoteJid) as any; + if (!exists) { + try { AllowedGroups.upsertPending(remoteJid, null, normalizedSenderId); } catch {} + try { Metrics.inc('unknown_groups_discovered_total'); } catch {} + return; + } + } catch { + // Si la tabla no existe por alguna razón, intentar upsert y retornar igualmente + try { AllowedGroups.upsertPending(remoteJid, null, normalizedSenderId); } catch {} + try { Metrics.inc('unknown_groups_discovered_total'); } catch {} + return; + } + } + } + // Check/ensure group exists (allow DMs always) if (isGroupId(data.key.remoteJid) && !GroupSyncService.isGroupActive(data.key.remoteJid)) { // En tests, mantener comportamiento anterior: ignorar mensajes de grupos inactivos diff --git a/tests/unit/server/unknown-group-discovery.test.ts b/tests/unit/server/unknown-group-discovery.test.ts new file mode 100644 index 0000000..6763b46 --- /dev/null +++ b/tests/unit/server/unknown-group-discovery.test.ts @@ -0,0 +1,86 @@ +import { describe, test, expect, beforeAll, afterAll, beforeEach, afterEach } from 'bun:test'; +import { Database } from 'bun:sqlite'; +import { WebhookServer } from '../../../src/server'; +import { initializeDatabase } from '../../../src/db'; +import { ResponseQueue } from '../../../src/services/response-queue'; + +let testDb: Database; +let originalAdd: any; + +let simulatedQueue: any[] = []; +const SimulatedResponseQueue = { + async add(responses: any[]) { + simulatedQueue.push(...responses); + }, + clear() { simulatedQueue = []; }, + get() { return simulatedQueue; } +}; + +const createTestRequest = (payload: any) => + new Request('http://localhost:3007', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + +describe('WebhookServer - unknown group discovery (mode=discover)', () => { + const envBackup = process.env; + + beforeAll(() => { + testDb = new Database(':memory:'); + initializeDatabase(testDb); + originalAdd = (ResponseQueue as any).add; + }); + + afterAll(() => { + (ResponseQueue as any).add = originalAdd; + testDb.close(); + }); + + beforeEach(() => { + process.env = { + ...envBackup, + NODE_ENV: 'test', + GROUP_GATING_MODE: 'discover' + }; + SimulatedResponseQueue.clear(); + (ResponseQueue as any).add = SimulatedResponseQueue.add; + WebhookServer.dbInstance = testDb; + + // Limpiar tablas relevantes + testDb.exec('DELETE FROM response_queue'); + testDb.exec('DELETE FROM allowed_groups'); + testDb.exec('DELETE FROM users'); + }); + + afterEach(() => { + process.env = envBackup; + }); + + test('registra grupo desconocido como pending y no procesa comandos', async () => { + const payload = { + event: 'messages.upsert', + instance: 'test-instance', + data: { + key: { + remoteJid: 'new-group@g.us', + participant: '1234567890@s.whatsapp.net' + }, + message: { conversation: '/t n hola' } + } + }; + + const res = await WebhookServer.handleRequest(createTestRequest(payload)); + expect(res.status).toBe(200); + + // No debe haber respuestas encoladas (retorno temprano) + expect(SimulatedResponseQueue.get().length).toBe(0); + + // Debe existir registro pending en allowed_groups + const row = testDb + .query(`SELECT status FROM allowed_groups WHERE group_id = 'new-group@g.us'`) + .get() as any; + expect(row).toBeDefined(); + expect(String(row.status)).toBe('pending'); + }); +});