You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
333 lines
9.5 KiB
TypeScript
333 lines
9.5 KiB
TypeScript
import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test';
|
|
import { WebhookServer } from '../../src/server';
|
|
import { ResponseQueue } from '../../src/services/response-queue';
|
|
|
|
// Mock the ResponseQueue
|
|
let mockAdd: any;
|
|
|
|
beforeEach(() => {
|
|
mockAdd = mock(() => Promise.resolve());
|
|
ResponseQueue.add = mockAdd;
|
|
});
|
|
|
|
describe('WebhookServer', () => {
|
|
const envBackup = process.env;
|
|
|
|
beforeEach(() => {
|
|
process.env = {
|
|
...envBackup,
|
|
INSTANCE_NAME: 'test-instance',
|
|
NODE_ENV: 'test'
|
|
};
|
|
});
|
|
|
|
afterEach(() => {
|
|
process.env = envBackup;
|
|
});
|
|
|
|
const createTestRequest = (payload: any) =>
|
|
new Request('http://localhost:3007', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
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 payload of invalidPayloads) {
|
|
const request = createTestRequest(payload);
|
|
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(mockAdd).toHaveBeenCalled();
|
|
});
|
|
|
|
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(mockAdd).not.toHaveBeenCalled();
|
|
});
|
|
|
|
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(mockAdd).toHaveBeenCalled();
|
|
});
|
|
|
|
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(mockAdd).toHaveBeenCalled();
|
|
});
|
|
|
|
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(mockAdd).not.toHaveBeenCalled();
|
|
});
|
|
|
|
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(mockAdd).not.toHaveBeenCalled();
|
|
});
|
|
|
|
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(mockAdd).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('should handle requests on configured port', async () => {
|
|
const originalPort = process.env.PORT;
|
|
process.env.PORT = '3007';
|
|
|
|
try {
|
|
const server = await WebhookServer.start();
|
|
const response = await fetch('http://localhost:3007/health');
|
|
expect(response.status).toBe(200);
|
|
server.stop();
|
|
} finally {
|
|
process.env.PORT = originalPort;
|
|
}
|
|
});
|
|
|
|
describe('/tarea command logging', () => {
|
|
let consoleSpy: any;
|
|
|
|
beforeEach(() => {
|
|
consoleSpy = mock(() => {});
|
|
console.log = consoleSpy;
|
|
});
|
|
|
|
afterEach(() => {
|
|
consoleSpy.mockRestore();
|
|
});
|
|
|
|
test('should log basic /tarea command', async () => {
|
|
const payload = {
|
|
event: 'messages.upsert',
|
|
instance: 'test-instance',
|
|
data: {
|
|
key: {
|
|
remoteJid: 'group123@g.us',
|
|
participant: 'user123@s.whatsapp.net'
|
|
},
|
|
message: { conversation: '/tarea test' }
|
|
}
|
|
};
|
|
|
|
await WebhookServer.handleRequest(createTestRequest(payload));
|
|
|
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
'🔍 Detected /tarea command:',
|
|
expect.objectContaining({
|
|
rawMessage: '/tarea test'
|
|
})
|
|
);
|
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
'✅ Successfully parsed command:',
|
|
expect.objectContaining({
|
|
action: 'test',
|
|
description: '',
|
|
dueDate: 'none',
|
|
mentionCount: 0
|
|
})
|
|
);
|
|
});
|
|
|
|
test('should log command with due date', async () => {
|
|
const payload = {
|
|
event: 'messages.upsert',
|
|
instance: 'test-instance',
|
|
data: {
|
|
key: {
|
|
remoteJid: 'group123@g.us',
|
|
participant: 'user123@s.whatsapp.net'
|
|
},
|
|
message: {
|
|
conversation: '/tarea nueva Finish project @user2 2025-04-30',
|
|
contextInfo: {
|
|
mentionedJid: ['user2@s.whatsapp.net']
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
await WebhookServer.handleRequest(createTestRequest(payload));
|
|
|
|
// Verify the detection log
|
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
'🔍 Detected /tarea command:',
|
|
expect.objectContaining({
|
|
timestamp: expect.any(String),
|
|
from: 'user123@s.whatsapp.net',
|
|
group: 'group123@g.us',
|
|
rawMessage: '/tarea nueva Finish project @user2 2025-04-30'
|
|
})
|
|
);
|
|
|
|
// Verify the parsing log
|
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
'✅ Successfully parsed command:',
|
|
expect.objectContaining({
|
|
action: 'nueva',
|
|
description: 'Finish project @user2',
|
|
dueDate: '2025-04-30',
|
|
mentionCount: 1
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
test('should handle XSS/SQL injection attempts', async () => {
|
|
const maliciousMessage = `/tarea nueva <script>alert('xss')</script>'; DROP TABLE tasks; --`;
|
|
const payload = {
|
|
event: 'messages.upsert',
|
|
instance: 'test-instance',
|
|
data: {
|
|
key: {
|
|
remoteJid: 'group-id@g.us',
|
|
participant: 'sender-id@s.whatsapp.net'
|
|
},
|
|
message: { conversation: maliciousMessage }
|
|
}
|
|
};
|
|
const request = createTestRequest(payload);
|
|
const response = await WebhookServer.handleRequest(request);
|
|
expect(response.status).toBe(200);
|
|
expect(mockAdd).toHaveBeenCalled();
|
|
});
|
|
});
|