import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'; import http from 'node:http'; import type { AddressInfo } from 'node:net'; import { createServer } from '../src/server.js'; import { store } from '../src/store/memory-store.js'; // Start a mock GoChat webhook server to capture the POSTs from FakeMessagePlatform. let mockGochatPort: number; let mockGochatServer: http.Server; let receivedByGochat: any[] = []; function startMockGochat(): Promise { return new Promise((resolve) => { const srv = http.createServer((req, res) => { let body = ''; req.on('data', (chunk) => { body += chunk; }); req.on('end', () => { if (req.method === 'POST') { try { receivedByGochat.push(JSON.parse(body)); } catch { /* ignore */ } } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'ok' })); }); }); srv.listen(0, '127.0.0.1', () => { const addr = srv.address() as AddressInfo; mockGochatPort = addr.port; resolve(srv); }); }); } let fakeApp: http.Server; let fakePort: number; function startFakeServer(webhookUrl: string): Promise { const app = createServer({ port: 0, gochatWebhookUrl: webhookUrl, gochatFakeToken: '', }); return new Promise((resolve) => { const srv = app.listen(0, '127.0.0.1', () => { const addr = srv.address() as AddressInfo; fakePort = addr.port; resolve(srv as unknown as http.Server); }); }); } async function fakeRequest(path: string, method: string = 'GET', body?: any): Promise { const res = await fetch(`http://127.0.0.1:${fakePort}${path}`, { method, headers: body ? { 'Content-Type': 'application/json' } : undefined, body: body ? JSON.stringify(body) : undefined, }); return { status: res.status, data: await res.json() }; } beforeAll(async () => { mockGochatServer = await startMockGochat(); fakeApp = await startFakeServer(`http://127.0.0.1:${mockGochatPort}/webhooks/fake/fake_test_1`); }); afterAll(async () => { await new Promise((resolve) => fakeApp.close(() => resolve())); await new Promise((resolve) => mockGochatServer.close(() => resolve())); }); beforeEach(() => { store.reset(); receivedByGochat = []; }); describe('FakeMessagePlatform', () => { describe('/health', () => { it('returns ok status', async () => { const res = await fakeRequest('/health'); expect(res.status).toBe(200); expect(res.data.status).toBe('ok'); expect(res.data.service).toBe('fake-message-platform'); }); }); describe('/api/send', () => { it('sends a message to GoChat webhook', async () => { const res = await fakeRequest('/api/send', 'POST', { inbox_identifier: 'fake_test_1', sender_id: 'customer_001', sender_name: 'Test Customer', content: 'Hello, I need help', }); expect(res.status).toBe(200); expect(res.data.status).toBe('sent'); expect(res.data.message_id).toBeDefined(); expect(res.data.gochat_status).toBe(200); // Verify GoChat received the webhook expect(receivedByGochat).toHaveLength(1); expect(receivedByGochat[0].event).toBe('message.incoming'); expect(receivedByGochat[0].content).toBe('Hello, I need help'); expect(receivedByGochat[0].sender_id).toBe('customer_001'); }); it('returns 400 for missing required fields', async () => { const res = await fakeRequest('/api/send', 'POST', { inbox_identifier: 'fake_test_1', }); expect(res.status).toBe(400); expect(res.data.error).toContain('required'); }); }); describe('/api/agent/online + /api/status', () => { it('marks an agent as online and shows in status', async () => { const res = await fakeRequest('/api/agent/online', 'POST', { agent_id: '1', agent_name: 'Admin', }); expect(res.status).toBe(200); expect(res.data.online).toBe(true); const statusRes = await fakeRequest('/api/status'); expect(statusRes.data.online_agents).toHaveLength(1); expect(statusRes.data.online_agents[0].agent_id).toBe('1'); expect(statusRes.data.online_agents[0].agent_name).toBe('Admin'); }); it('marks an agent as offline', async () => { await fakeRequest('/api/agent/online', 'POST', { agent_id: '2', agent_name: 'Agent2', }); const offlineRes = await fakeRequest('/api/agent/offline', 'POST', { agent_id: '2', }); expect(offlineRes.status).toBe(200); expect(offlineRes.data.online).toBe(false); const statusRes = await fakeRequest('/api/status'); expect(statusRes.data.online_agents).toHaveLength(0); }); }); describe('/api/reset', () => { it('clears all state', async () => { await fakeRequest('/api/agent/online', 'POST', { agent_id: '1', agent_name: 'Admin', }); await fakeRequest('/api/send', 'POST', { inbox_identifier: 'fake_test_1', sender_id: 'cust_1', sender_name: 'C', content: 'hi', }); const res = await fakeRequest('/api/reset', 'POST'); expect(res.status).toBe(200); const statusRes = await fakeRequest('/api/status'); expect(statusRes.data.online_agents).toHaveLength(0); expect(statusRes.data.total_sent).toBe(0); }); }); describe('/receive', () => { it('stores GoChat outbound messages', async () => { const res = await fakeRequest('/receive', 'POST', { message_id: 123, conversation_id: 456, content: 'Hello from agent', content_type: 'text', sender: { id: 1, name: 'Agent Wang', type: 'agent' }, }); expect(res.status).toBe(200); expect(res.data.message_id).toBe(123); const msgRes = await fakeRequest('/api/messages'); expect(msgRes.data.received).toHaveLength(1); expect(msgRes.data.received[0].content).toBe('Hello from agent'); expect(msgRes.data.received[0].sender.type).toBe('agent'); }); }); describe('/api/typing', () => { it('sends typing event to GoChat', async () => { const res = await fakeRequest('/api/typing', 'POST', { inbox_identifier: 'fake_test_1', sender_id: 'customer_001', typing: true, }); expect(res.status).toBe(200); expect(res.data.typing).toBe(true); expect(receivedByGochat[0].event).toBe('typing.start'); }); it('sends typing stop event', async () => { const res = await fakeRequest('/api/typing', 'POST', { inbox_identifier: 'fake_test_1', sender_id: 'customer_001', typing: false, }); expect(res.status).toBe(200); expect(res.data.typing).toBe(false); expect(receivedByGochat[0].event).toBe('typing.stop'); }); }); describe('/api/close', () => { it('sends session.end event', async () => { const res = await fakeRequest('/api/close', 'POST', { inbox_identifier: 'fake_test_1', conversation_id: 'ext_conv_1', sender_id: 'customer_001', }); expect(res.status).toBe(200); expect(receivedByGochat[0].event).toBe('session.end'); expect(receivedByGochat[0].content).toBe('[session ended]'); }); }); });