/** * Core HTTP/webhook validation and message‑type handling. * * These tests verify that WebhookServer correctly validates incoming * requests before they reach command processing. */ 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(); // ── Tests ────────────────────────────────────────────────────────────── describe('WebhookServer — Basic validation', () => { test('should reject non-POST requests', async () => { const request = new Request('http://localhost:3007', { method: 'GET' }); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(405); }); test('should require JSON content type', async () => { const request = new Request('http://localhost:3007', { method: 'POST', headers: { 'Content-Type': 'text/plain' }, }); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(400); }); test('should validate payload structure', async () => { const invalidPayloads = [ {}, { event: null }, { event: 'messages.upsert', instance: null }, ]; for (const invalidPayload of invalidPayloads) { const request = createTestRequest(invalidPayload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(400); } }); test('should verify instance name', async () => { process.env.TEST_VERIFY_INSTANCE = 'true'; const payload = { event: 'messages.upsert', instance: 'wrong-instance', data: {}, }; const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(403); delete process.env.TEST_VERIFY_INSTANCE; }); test('should handle valid messages.upsert', async () => { const payload = { event: 'messages.upsert', instance: 'test-instance', data: { key: { remoteJid: 'group-id@g.us', participant: 'sender-id@s.whatsapp.net', }, message: { conversation: 'tarea nueva Test' }, }, }; const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should ignore empty message content', async () => { const payload = { event: 'messages.upsert', instance: 'test-instance', data: { key: { remoteJid: 'group-id@g.us', participant: 'sender-id@s.whatsapp.net', }, message: { conversation: '' }, }, }; const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); expect(SimulatedResponseQueue.get().length).toBe(0); }); test('should handle very long messages', async () => { const longMessage = 'tarea nueva ' + 'A'.repeat(5000); const payload = { event: 'messages.upsert', instance: 'test-instance', data: { key: { remoteJid: 'group-id@g.us', participant: 'sender-id@s.whatsapp.net', }, message: { conversation: longMessage }, }, }; const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should handle messages with special characters and emojis', async () => { const specialMessage = 'tarea nueva Test 😊 你好 @#$%^&*()'; const payload = { event: 'messages.upsert', instance: 'test-instance', data: { key: { remoteJid: 'group-id@g.us', participant: 'sender-id@s.whatsapp.net', }, message: { conversation: specialMessage }, }, }; const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should ignore non-tarea commands', async () => { const payload = { event: 'messages.upsert', instance: 'test-instance', data: { key: { remoteJid: 'group-id@g.us', participant: 'sender-id@s.whatsapp.net', }, message: { conversation: '/othercommand test' }, }, }; const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); expect(SimulatedResponseQueue.get().length).toBe(0); }); test('should process command with uppercase T prefix (T area)', async () => { const payload = { event: 'messages.upsert', instance: 'test-instance', data: { key: { remoteJid: 'group-id@g.us', participant: 'sender-id@s.whatsapp.net', }, message: { conversation: 'T n Test uppercase T' }, }, }; const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should process command with uppercase Tarea prefix', async () => { const payload = { event: 'messages.upsert', instance: 'test-instance', data: { key: { remoteJid: 'group-id@g.us', participant: 'sender-id@s.whatsapp.net', }, message: { conversation: 'Tarea nueva Test uppercase Tarea' }, }, }; const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should ignore message with mentions but no command', async () => { const payload = { event: 'messages.upsert', instance: 'test-instance', data: { key: { remoteJid: 'group-id@g.us', participant: 'sender-id@s.whatsapp.net', }, message: { conversation: 'Hello everyone!', contextInfo: { mentionedJid: ['1234567890@s.whatsapp.net'], }, }, }, }; const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); expect(SimulatedResponseQueue.get().length).toBe(0); }); test('should ignore media attachment messages', async () => { const payload = { event: 'messages.upsert', instance: 'test-instance', data: { key: { remoteJid: 'group-id@g.us', participant: 'sender-id@s.whatsapp.net', }, message: { imageMessage: { caption: 'This is an image' }, }, }, }; const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); expect(SimulatedResponseQueue.get().length).toBe(0); }); test('should process command from extendedTextMessage', async () => { const payload = { event: 'messages.upsert', instance: 'test-instance', data: { key: { remoteJid: 'group-id@g.us', participant: 'sender-id@s.whatsapp.net', }, message: { extendedTextMessage: { text: 't n Test ext' }, }, }, }; const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should process command from image caption when caption starts with a command', async () => { const payload = { event: 'messages.upsert', instance: 'test-instance', data: { key: { remoteJid: 'group-id@g.us', participant: 'sender-id@s.whatsapp.net', }, message: { imageMessage: { caption: 't n From caption' }, }, }, }; const request = createTestRequest(payload); const response = await WebhookServer.handleRequest(request); expect(response.status).toBe(200); expect(SimulatedResponseQueue.get().length).toBeGreaterThan(0); }); test('should handle requests on configured port', async () => { const originalPort = process.env.PORT; process.env.PORT = '3007'; const prevEnv = { EVOLUTION_API_URL: process.env.EVOLUTION_API_URL, EVOLUTION_API_KEY: process.env.EVOLUTION_API_KEY, EVOLUTION_API_INSTANCE: process.env.EVOLUTION_API_INSTANCE, CHATBOT_PHONE_NUMBER: process.env.CHATBOT_PHONE_NUMBER, WEBHOOK_URL: process.env.WEBHOOK_URL, }; process.env.EVOLUTION_API_URL = 'http://localhost:3000'; process.env.EVOLUTION_API_KEY = 'test-key'; process.env.EVOLUTION_API_INSTANCE = 'test-instance'; process.env.CHATBOT_PHONE_NUMBER = '9999999999'; process.env.WEBHOOK_URL = 'http://localhost:3007'; try { const server = await WebhookServer.start(); const response = await fetch('http://localhost:3007/health'); expect(response.status).toBe(200); await server.stop(); } finally { process.env.PORT = originalPort; process.env.EVOLUTION_API_URL = prevEnv.EVOLUTION_API_URL; process.env.EVOLUTION_API_KEY = prevEnv.EVOLUTION_API_KEY; process.env.EVOLUTION_API_INSTANCE = prevEnv.EVOLUTION_API_INSTANCE; process.env.CHATBOT_PHONE_NUMBER = prevEnv.CHATBOT_PHONE_NUMBER; process.env.WEBHOOK_URL = prevEnv.WEBHOOK_URL; } }); });