feat: activar gating de grupos en CommandService y GroupSyncService
Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>pull/1/head
parent
a03604d293
commit
d747e7aa4b
@ -0,0 +1,115 @@
|
||||
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';
|
||||
import { AllowedGroups } from '../../../src/services/allowed-groups';
|
||||
import { GroupSyncService } from '../../../src/services/group-sync';
|
||||
|
||||
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 - enforce gating (modo=enforce)', () => {
|
||||
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: 'enforce'
|
||||
};
|
||||
SimulatedResponseQueue.clear();
|
||||
(ResponseQueue as any).add = SimulatedResponseQueue.add;
|
||||
WebhookServer.dbInstance = testDb;
|
||||
(AllowedGroups as any).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('bloquea mensaje de grupo no permitido (no se encolan respuestas)', async () => {
|
||||
const payload = {
|
||||
event: 'messages.upsert',
|
||||
instance: 'test-instance',
|
||||
data: {
|
||||
key: {
|
||||
remoteJid: 'blocked-group@g.us',
|
||||
participant: '1234567890@s.whatsapp.net'
|
||||
},
|
||||
message: { conversation: '/t ayuda' }
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
// allowed_groups no contiene allowed para ese grupo
|
||||
const row = testDb.query(`SELECT status FROM allowed_groups WHERE group_id = 'blocked-group@g.us'`).get() as any;
|
||||
expect(row).toBeUndefined();
|
||||
});
|
||||
|
||||
test('permite mensaje en grupo allowed y procesa comando', async () => {
|
||||
// Sembrar grupo como allowed
|
||||
testDb.exec(`
|
||||
INSERT INTO allowed_groups (group_id, status, discovered_at, updated_at)
|
||||
VALUES ('allowed-group@g.us', 'allowed', strftime('%Y-%m-%d %H:%M:%f','now'), strftime('%Y-%m-%d %H:%M:%f','now'))
|
||||
`);
|
||||
|
||||
// Marcar el grupo como activo en la caché para evitar retorno temprano por "grupo inactivo" en tests
|
||||
GroupSyncService.activeGroupsCache.set('allowed-group@g.us', 'Allowed Group');
|
||||
|
||||
const payload = {
|
||||
event: 'messages.upsert',
|
||||
instance: 'test-instance',
|
||||
data: {
|
||||
key: {
|
||||
remoteJid: 'allowed-group@g.us',
|
||||
participant: '1234567890@s.whatsapp.net'
|
||||
},
|
||||
message: { conversation: '/t ayuda' }
|
||||
}
|
||||
};
|
||||
|
||||
const res = await WebhookServer.handleRequest(createTestRequest(payload));
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
// Debe haberse encolado al menos una respuesta (ayuda)
|
||||
expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,44 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
||||
import { makeMemDb } from '../../helpers/db';
|
||||
import { CommandService } from '../../../src/services/command';
|
||||
import { AllowedGroups } from '../../../src/services/allowed-groups';
|
||||
|
||||
describe('CommandService - gating en modo enforce', () => {
|
||||
const envBackup = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...envBackup, NODE_ENV: 'test', GROUP_GATING_MODE: 'enforce' };
|
||||
const memdb = makeMemDb();
|
||||
(CommandService as any).dbInstance = memdb;
|
||||
(AllowedGroups as any).dbInstance = memdb;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = envBackup;
|
||||
});
|
||||
|
||||
it('bloquea comandos en grupo no permitido (desconocido)', async () => {
|
||||
const res = await CommandService.handle({
|
||||
sender: '34600123456',
|
||||
groupId: 'g1@g.us',
|
||||
message: '/t ayuda',
|
||||
mentions: []
|
||||
});
|
||||
expect(Array.isArray(res)).toBe(true);
|
||||
expect(res.length).toBe(0);
|
||||
});
|
||||
|
||||
it('permite comandos en grupo permitido', async () => {
|
||||
AllowedGroups.setStatus('g2@g.us', 'allowed', 'G2');
|
||||
|
||||
const res = await CommandService.handle({
|
||||
sender: '34600123456',
|
||||
groupId: 'g2@g.us',
|
||||
message: '/t ayuda',
|
||||
mentions: []
|
||||
});
|
||||
|
||||
expect(res.length).toBeGreaterThan(0);
|
||||
expect(res[0].recipient).toBe('34600123456');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,44 @@
|
||||
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 syncMembersForGroup (enforce)', () => {
|
||||
const envBackup = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...envBackup, NODE_ENV: 'test', GROUP_GATING_MODE: 'enforce' };
|
||||
const memdb = makeMemDb();
|
||||
(GroupSyncService as any).dbInstance = memdb;
|
||||
(AllowedGroups as any).dbInstance = memdb;
|
||||
|
||||
// Stub fetchGroupMembersFromAPI para no hacer red
|
||||
(GroupSyncService as any).fetchGroupMembersFromAPI = async (_groupId: string) => {
|
||||
return [{ userId: '34600111111', isAdmin: false }];
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = envBackup;
|
||||
});
|
||||
|
||||
it('no sincroniza miembros para grupo no allowed', async () => {
|
||||
const res = await GroupSyncService.syncMembersForGroup('na@g.us');
|
||||
expect(res).toEqual({ added: 0, updated: 0, deactivated: 0 });
|
||||
|
||||
const db = (GroupSyncService as any).dbInstance;
|
||||
const row = db.query(`SELECT COUNT(*) AS c FROM group_members WHERE group_id = 'na@g.us'`).get() as any;
|
||||
expect(Number(row?.c || 0)).toBe(0);
|
||||
});
|
||||
|
||||
it('sincroniza miembros para grupo allowed', async () => {
|
||||
AllowedGroups.setStatus('ok@g.us', 'allowed', 'OK');
|
||||
|
||||
const res = await GroupSyncService.syncMembersForGroup('ok@g.us');
|
||||
expect(res.added + res.updated + res.deactivated).toBeGreaterThan(0);
|
||||
|
||||
const db = (GroupSyncService as any).dbInstance;
|
||||
const row = db.query(`SELECT COUNT(*) AS c FROM group_members WHERE group_id = 'ok@g.us'`).get() as any;
|
||||
expect(Number(row?.c || 0)).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue