225 lines
7.2 KiB
TypeScript
225 lines
7.2 KiB
TypeScript
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<http.Server> {
|
|
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<http.Server> {
|
|
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<any> {
|
|
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<void>((resolve) => fakeApp.close(() => resolve()));
|
|
await new Promise<void>((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]');
|
|
});
|
|
});
|
|
});
|