/** * Tests for MailGun webhook router */ import request from 'supertest'; import app from '../../src/app'; import { createMockDeliveredEvent, createMockFailedEvent, createMockOpenedEvent, createMockClickedEvent, createMockBouncedEvent, createMockComplainedEvent, createMockUnsubscribedEvent, createInvalidEvent } from '../helpers/mockWebhookEvent'; describe('MailGun Webhook Router', () => { // Mock console.log to avoid cluttering test output let consoleLogSpy: jest.SpyInstance; beforeEach(() => { consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(); }); afterEach(() => { consoleLogSpy.mockRestore(); }); describe('POST /webhook', () => { it('should handle delivered event successfully', async () => { const event = createMockDeliveredEvent(); const response = await request(app) .post('/webhook') .send(event) .expect(200); expect(response.body).toEqual({ status: 'received', message: 'Webhook event logged successfully' }); // Verify console.log was called with event data expect(consoleLogSpy).toHaveBeenCalled(); const logOutput = consoleLogSpy.mock.calls.join('\n'); expect(logOutput).toContain('MailGun Webhook Event Received'); expect(logOutput).toContain('Event Type: delivered'); expect(logOutput).toContain('Recipient: user@example.com'); }); it('should handle failed event successfully', async () => { const event = createMockFailedEvent(); const response = await request(app) .post('/webhook') .send(event) .expect(200); expect(response.body).toEqual({ status: 'received', message: 'Webhook event logged successfully' }); const logOutput = consoleLogSpy.mock.calls.join('\n'); expect(logOutput).toContain('Event Type: failed'); expect(logOutput).toContain('Severity: permanent'); expect(logOutput).toContain('Reason: bounce'); }); it('should handle opened event successfully', async () => { const event = createMockOpenedEvent(); const response = await request(app) .post('/webhook') .send(event) .expect(200); expect(response.body.status).toBe('received'); const logOutput = consoleLogSpy.mock.calls.join('\n'); expect(logOutput).toContain('Event Type: opened'); expect(logOutput).toContain('City: San Francisco'); expect(logOutput).toContain('Device Type: desktop'); }); it('should handle clicked event successfully', async () => { const event = createMockClickedEvent(); const response = await request(app) .post('/webhook') .send(event) .expect(200); expect(response.body.status).toBe('received'); const logOutput = consoleLogSpy.mock.calls.join('\n'); expect(logOutput).toContain('Event Type: clicked'); expect(logOutput).toContain('URL Clicked: https://example.com/link'); }); it('should handle bounced event successfully', async () => { const event = createMockBouncedEvent(); const response = await request(app) .post('/webhook') .send(event) .expect(200); expect(response.body.status).toBe('received'); const logOutput = consoleLogSpy.mock.calls.join('\n'); expect(logOutput).toContain('Event Type: bounced'); expect(logOutput).toContain('SMTP Code: 550'); }); it('should handle complained event successfully', async () => { const event = createMockComplainedEvent(); const response = await request(app) .post('/webhook') .send(event) .expect(200); expect(response.body.status).toBe('received'); const logOutput = consoleLogSpy.mock.calls.join('\n'); expect(logOutput).toContain('Event Type: complained'); }); it('should handle unsubscribed event successfully', async () => { const event = createMockUnsubscribedEvent(); const response = await request(app) .post('/webhook') .send(event) .expect(200); expect(response.body.status).toBe('received'); const logOutput = consoleLogSpy.mock.calls.join('\n'); expect(logOutput).toContain('Event Type: unsubscribed'); }); it('should return 400 for invalid event missing required fields', async () => { const event = createInvalidEvent(); const response = await request(app) .post('/webhook') .send(event) .expect(400); // Error router returns plain text, not JSON expect(response.text).toBe('bad request'); }); it('should handle URL-encoded form data (MailGun default format)', async () => { const event = createMockDeliveredEvent(); const response = await request(app) .post('/webhook') .type('form') .send(event) .expect(200); expect(response.body.status).toBe('received'); }); it('should log timestamp in human-readable format', async () => { const event = createMockDeliveredEvent(); await request(app) .post('/webhook') .send(event) .expect(200); const logOutput = consoleLogSpy.mock.calls.join('\n'); expect(logOutput).toContain('Timestamp: 1234567890'); // Check that ISO format date is logged expect(logOutput).toMatch(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/); }); it('should log full event data as JSON', async () => { const event = createMockDeliveredEvent(); await request(app) .post('/webhook') .send(event) .expect(200); const logOutput = consoleLogSpy.mock.calls.join('\n'); expect(logOutput).toContain('Full Event Data (JSON)'); expect(logOutput).toContain('"event": "delivered"'); }); }); });