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