feat: agrega AdminService y comandos /admin con bypass del gating
Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>pull/1/head
parent
302ba6daa8
commit
ae0a853b63
@ -0,0 +1,111 @@
|
|||||||
|
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 - /admin aprobación en 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',
|
||||||
|
ADMIN_USERS: '1234567890'
|
||||||
|
};
|
||||||
|
SimulatedResponseQueue.clear();
|
||||||
|
(ResponseQueue as any).add = SimulatedResponseQueue.add;
|
||||||
|
WebhookServer.dbInstance = testDb;
|
||||||
|
|
||||||
|
testDb.exec('DELETE FROM response_queue');
|
||||||
|
testDb.exec('DELETE FROM allowed_groups');
|
||||||
|
testDb.exec('DELETE FROM users');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env = envBackup;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('admin puede habilitar el grupo actual incluso si no está allowed', async () => {
|
||||||
|
const payload = {
|
||||||
|
event: 'messages.upsert',
|
||||||
|
instance: 'test-instance',
|
||||||
|
data: {
|
||||||
|
key: {
|
||||||
|
remoteJid: 'new-group@g.us',
|
||||||
|
participant: '1234567890@s.whatsapp.net'
|
||||||
|
},
|
||||||
|
message: { conversation: '/admin habilitar-aquí' }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await WebhookServer.handleRequest(createTestRequest(payload));
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
|
||||||
|
// Debe haber respuesta de confirmación encolada
|
||||||
|
expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// El grupo debe figurar como allowed
|
||||||
|
const row = testDb.query(`SELECT status FROM allowed_groups WHERE group_id = 'new-group@g.us'`).get() as any;
|
||||||
|
expect(row && String(row.status)).toBe('allowed');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no admin no puede usar /admin', async () => {
|
||||||
|
process.env.ADMIN_USERS = '5555555555';
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
event: 'messages.upsert',
|
||||||
|
instance: 'test-instance',
|
||||||
|
data: {
|
||||||
|
key: {
|
||||||
|
remoteJid: 'another-group@g.us',
|
||||||
|
participant: '1234567890@s.whatsapp.net'
|
||||||
|
},
|
||||||
|
message: { conversation: '/admin habilitar-aquí' }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await WebhookServer.handleRequest(createTestRequest(payload));
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
|
||||||
|
// Debe haber una respuesta indicando no autorizado
|
||||||
|
const out = SimulatedResponseQueue.get();
|
||||||
|
expect(out.length).toBe(1);
|
||||||
|
expect(String(out[0].message).toLowerCase()).toContain('no estás autorizado');
|
||||||
|
|
||||||
|
// Y el grupo no debe estar allowed
|
||||||
|
const row = testDb.query(`SELECT status FROM allowed_groups WHERE group_id = 'another-group@g.us'`).get() as any;
|
||||||
|
expect(row == null).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
import { describe, it, beforeEach, expect } from 'bun:test';
|
||||||
|
import { makeMemDb } from '../../helpers/db';
|
||||||
|
import { AdminService } from '../../../src/services/admin';
|
||||||
|
import { AllowedGroups } from '../../../src/services/allowed-groups';
|
||||||
|
|
||||||
|
describe('AdminService - comandos básicos', () => {
|
||||||
|
const envBackup = process.env;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
process.env = { ...envBackup, NODE_ENV: 'test', ADMIN_USERS: '34600123456' };
|
||||||
|
const memdb = makeMemDb();
|
||||||
|
(AdminService as any).dbInstance = memdb;
|
||||||
|
(AllowedGroups as any).dbInstance = memdb;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rechaza a usuarios no admin', async () => {
|
||||||
|
const out = await AdminService.handle({
|
||||||
|
sender: '34999888777',
|
||||||
|
groupId: 'g1@g.us',
|
||||||
|
message: '/admin pendientes'
|
||||||
|
});
|
||||||
|
expect(out.length).toBe(1);
|
||||||
|
expect(out[0].message.toLowerCase()).toContain('no estás autorizado');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lista pendientes', async () => {
|
||||||
|
AllowedGroups.upsertPending('a@g.us', 'A', 'tester');
|
||||||
|
AllowedGroups.upsertPending('b@g.us', 'B', 'tester');
|
||||||
|
|
||||||
|
const out = await AdminService.handle({
|
||||||
|
sender: '34600123456',
|
||||||
|
groupId: 'g1@g.us',
|
||||||
|
message: '/admin pendientes'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(out.length).toBe(1);
|
||||||
|
expect(out[0].message).toContain('Grupos pendientes');
|
||||||
|
expect(out[0].message).toContain('a@g.us');
|
||||||
|
expect(out[0].message).toContain('b@g.us');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('habilitar-aquí en grupo', async () => {
|
||||||
|
const out = await AdminService.handle({
|
||||||
|
sender: '34600123456',
|
||||||
|
groupId: 'g1@g.us',
|
||||||
|
message: '/admin habilitar-aquí'
|
||||||
|
});
|
||||||
|
expect(out.length).toBe(1);
|
||||||
|
expect(AllowedGroups.isAllowed('g1@g.us')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allow-group <jid> habilita explícitamente', async () => {
|
||||||
|
const out = await AdminService.handle({
|
||||||
|
sender: '34600123456',
|
||||||
|
groupId: '1234567890@s.whatsapp.net',
|
||||||
|
message: '/admin allow-group g2@g.us'
|
||||||
|
});
|
||||||
|
expect(out.length).toBe(1);
|
||||||
|
expect(AllowedGroups.isAllowed('g2@g.us')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue