fix: prevent user DMs from being treated as groups in gating
handleGroupDiscovery and handleGroupEnforcement lacked isGroupId() guards, causing user JIDs (e.g. 1234567890@s.whatsapp.net) to be incorrectly registered as 'pending' groups in discover mode, and silently blocked in enforce mode. Added isGroupId() bail-out at the top of both functions, matching the existing pattern in ensureGroupActive(). Added 5 regression tests covering both modes for DMs and preserving correct group behavior.main
parent
b7ed1ad013
commit
5c1d2f2251
@ -0,0 +1,165 @@
|
|||||||
|
/**
|
||||||
|
* Regression tests: DM (direct message) gating.
|
||||||
|
*
|
||||||
|
* Ensures that user DMs are NEVER treated as groups for gating purposes.
|
||||||
|
* A user JID (e.g. 1234567890@s.whatsapp.net) must not trigger group
|
||||||
|
* discovery (notify admins of a "new group") or group enforcement
|
||||||
|
* (silently block the message).
|
||||||
|
*/
|
||||||
|
import { describe, test, expect } from 'bun:test';
|
||||||
|
import { WebhookServer } from '../../src/server';
|
||||||
|
import { SimulatedResponseQueue } from '../helpers/queue';
|
||||||
|
import { createTestRequest, registerServerTestLifecycle } from '../helpers/server-test-harness';
|
||||||
|
|
||||||
|
const testDb = registerServerTestLifecycle();
|
||||||
|
|
||||||
|
// ── Helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function makeDmPayload(text: string) {
|
||||||
|
return {
|
||||||
|
event: 'messages.upsert',
|
||||||
|
instance: 'test-instance',
|
||||||
|
data: {
|
||||||
|
key: {
|
||||||
|
remoteJid: '1234567890@s.whatsapp.net', // user JID, NOT a group
|
||||||
|
fromMe: false,
|
||||||
|
},
|
||||||
|
message: { conversation: text },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function countAllowedGroups(): number {
|
||||||
|
try {
|
||||||
|
const row = testDb.prepare('SELECT COUNT(*) AS c FROM allowed_groups').get() as { c: number };
|
||||||
|
return row.c;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Tests ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe('DM gating — user DMs must not be treated as groups', () => {
|
||||||
|
|
||||||
|
test('discover mode: DM does NOT register user JID in allowed_groups', async () => {
|
||||||
|
process.env.GROUP_GATING_MODE = 'discover';
|
||||||
|
|
||||||
|
const request = createTestRequest(makeDmPayload('t ver mis'));
|
||||||
|
const response = await WebhookServer.handleRequest(request);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
|
||||||
|
// No allowed_groups entry should exist for the user JID
|
||||||
|
const count = countAllowedGroups();
|
||||||
|
expect(count).toBe(0);
|
||||||
|
|
||||||
|
// Admin should NOT be notified about a "new group"
|
||||||
|
const out = SimulatedResponseQueue.get();
|
||||||
|
const adminNotices = out.filter(r => r.message && (r.message as string).includes('Nuevo grupo detectado'));
|
||||||
|
expect(adminNotices.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('enforce mode: DM is NOT blocked (user can still use commands)', async () => {
|
||||||
|
process.env.GROUP_GATING_MODE = 'enforce';
|
||||||
|
|
||||||
|
const request = createTestRequest(makeDmPayload('t ver mis'));
|
||||||
|
const response = await WebhookServer.handleRequest(request);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
|
||||||
|
// Command should have processed and produced a response
|
||||||
|
const out = SimulatedResponseQueue.get();
|
||||||
|
// "ver mis" in DM should produce at least one response
|
||||||
|
expect(out.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('discover mode: unknown GROUP still gets discovered (regression check)', async () => {
|
||||||
|
process.env.GROUP_GATING_MODE = 'discover';
|
||||||
|
process.env.NOTIFY_ADMINS_ON_DISCOVERY = 'false'; // don't spam admins in test
|
||||||
|
|
||||||
|
// Ensure the group is NOT already in allowed_groups or active groups
|
||||||
|
testDb.exec('DELETE FROM allowed_groups WHERE group_id = \'unknown-group@g.us\'');
|
||||||
|
testDb.exec('DELETE FROM groups WHERE id = \'unknown-group@g.us\'');
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
event: 'messages.upsert',
|
||||||
|
instance: 'test-instance',
|
||||||
|
data: {
|
||||||
|
key: {
|
||||||
|
remoteJid: 'unknown-group@g.us', // group JID
|
||||||
|
participant: '1234567890@s.whatsapp.net',
|
||||||
|
},
|
||||||
|
message: { conversation: 'hola' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const request = createTestRequest(payload);
|
||||||
|
await WebhookServer.handleRequest(request);
|
||||||
|
|
||||||
|
// Should have registered the group as pending
|
||||||
|
const row = testDb.prepare('SELECT * FROM allowed_groups WHERE group_id = ?').get('unknown-group@g.us') as any;
|
||||||
|
expect(row).toBeDefined();
|
||||||
|
expect(row.status).toBe('pending');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('enforce mode: non-allowed GROUP is still blocked (regression check)', async () => {
|
||||||
|
process.env.GROUP_GATING_MODE = 'enforce';
|
||||||
|
|
||||||
|
// Ensure the group is NOT allowed
|
||||||
|
testDb.exec('DELETE FROM allowed_groups WHERE group_id = \'blocked-group@g.us\'');
|
||||||
|
testDb.exec('DELETE FROM groups WHERE id = \'blocked-group@g.us\'');
|
||||||
|
// Add it as active so it passes ensureGroupActive
|
||||||
|
testDb.exec(`
|
||||||
|
INSERT OR IGNORE INTO groups (id, community_id, name, active)
|
||||||
|
VALUES ('blocked-group@g.us', 'test-community', 'Blocked Group', 1)
|
||||||
|
`);
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
event: 'messages.upsert',
|
||||||
|
instance: 'test-instance',
|
||||||
|
data: {
|
||||||
|
key: {
|
||||||
|
remoteJid: 'blocked-group@g.us',
|
||||||
|
participant: '1234567890@s.whatsapp.net',
|
||||||
|
},
|
||||||
|
message: { conversation: 't ver mis' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const request = createTestRequest(payload);
|
||||||
|
await WebhookServer.handleRequest(request);
|
||||||
|
|
||||||
|
// Message should be silently blocked (no response)
|
||||||
|
const out = SimulatedResponseQueue.get();
|
||||||
|
expect(out.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('enforce mode: allowed GROUP can still use commands (regression check)', async () => {
|
||||||
|
process.env.GROUP_GATING_MODE = 'enforce';
|
||||||
|
|
||||||
|
testDb.exec(`
|
||||||
|
INSERT OR REPLACE INTO allowed_groups (group_id, label, status)
|
||||||
|
VALUES ('group-id@g.us', 'Test Group', 'allowed')
|
||||||
|
`);
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
event: 'messages.upsert',
|
||||||
|
instance: 'test-instance',
|
||||||
|
data: {
|
||||||
|
key: {
|
||||||
|
remoteJid: 'group-id@g.us',
|
||||||
|
participant: '1234567890@s.whatsapp.net',
|
||||||
|
},
|
||||||
|
message: { conversation: 't n Test tarea mañana' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const request = createTestRequest(payload);
|
||||||
|
await WebhookServer.handleRequest(request);
|
||||||
|
|
||||||
|
const out = SimulatedResponseQueue.get();
|
||||||
|
expect(out.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue