|
|
/**
|
|
|
* 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 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;
|
|
|
}
|
|
|
});
|
|
|
});
|