import { strict as assert } from 'node:assert'; const BASE = 'http://localhost:4003'; let passed = 0; let failed = 0; async function test(name, fn) { try { await fn(); passed++; console.log(` PASS ${name}`); } catch (err) { failed++; console.log(` FAIL ${name}: ${err.message}`); } } async function fetchJSON(path, opts) { const res = await fetch(BASE + path, opts); return { status: res.status, data: await res.json() }; } console.log('\n=== Agent Swarm Tests ===\n'); // --- Health --- await test('GET /health returns 200', async () => { const { status, data } = await fetchJSON('/health'); assert.equal(status, 200); assert.equal(data.status, 'ok'); assert.equal(data.service, 'agent-swarm'); assert.equal(data.version, '1.0.0'); assert.ok(data.workerAgents >= 3); }); // --- Agent Card --- await test('GET /.well-known/agent-card.json returns valid card', async () => { const { status, data } = await fetchJSON('/.well-known/agent-card.json'); assert.equal(status, 200); assert.equal(data.name, 'OpSpawn Agent Swarm'); assert.ok(data.skills.length >= 3); assert.ok(data.capabilities.extensions.length >= 2); assert.equal(data.provider.organization, 'OpSpawn'); }); // --- Worker Discovery --- await test('GET /agents returns coordinator + workers', async () => { const { status, data } = await fetchJSON('/agents'); assert.equal(status, 200); assert.ok(data.coordinator); assert.ok(data.workers.length >= 3); const ids = data.workers.map(w => w.id); assert.ok(ids.includes('screenshot')); assert.ok(ids.includes('pdf')); assert.ok(ids.includes('html')); }); // --- Empty swarm list --- await test('GET /swarms returns empty list initially', async () => { const { status, data } = await fetchJSON('/swarms'); assert.equal(status, 200); assert.ok(Array.isArray(data.swarms)); }); // --- Event log --- await test('GET /events returns events array', async () => { const { status, data } = await fetchJSON('/events'); assert.equal(status, 200); assert.ok(Array.isArray(data.events)); assert.ok(typeof data.total === 'number'); }); // --- A2A message/send creates swarm --- await test('POST /tasks/:id/send creates a swarm', async () => { const contextId = crypto.randomUUID(); const { status, data } = await fetchJSON(`/tasks/${contextId}/send`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: '1', method: 'message/send', params: { message: { role: 'user', parts: [{ type: 'text', text: 'Create an HTML preview of Hello World' }] } } }) }); assert.equal(status, 200); assert.equal(data.jsonrpc, '2.0'); assert.ok(data.result.id); // swarm ID assert.equal(data.result.status.state, 'working'); assert.ok(data.result.status.message.metadata['swarm.subtaskCount'] >= 1); }); // --- Invalid message rejected --- await test('POST /tasks/:id/send rejects invalid message', async () => { const { status, data } = await fetchJSON(`/tasks/${crypto.randomUUID()}/send`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: '2', method: 'message/send', params: {} }) }); assert.equal(status, 400); assert.ok(data.error); }); // --- Screenshot task planning --- await test('Screenshot instruction creates screenshot subtask', async () => { const contextId = crypto.randomUUID(); const { status, data } = await fetchJSON(`/tasks/${contextId}/send`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: '3', method: 'message/send', params: { message: { role: 'user', parts: [{ type: 'text', text: 'Take a screenshot of https://example.com' }] } } }) }); assert.equal(status, 200); const subtasks = data.result.status.message.metadata['swarm.subtasks']; assert.ok(subtasks.some(s => s.type === 'screenshot')); }); // --- Report task planning --- await test('Report instruction creates multiple subtasks', async () => { const contextId = crypto.randomUUID(); const { status, data } = await fetchJSON(`/tasks/${contextId}/send`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: '4', method: 'message/send', params: { message: { role: 'user', parts: [{ type: 'text', text: 'Create a visual report about https://opspawn.com with screenshots and a PDF' }] } } }) }); assert.equal(status, 200); const subtasks = data.result.status.message.metadata['swarm.subtasks']; assert.ok(subtasks.length >= 2, `Expected >= 2 subtasks, got ${subtasks.length}`); assert.ok(subtasks.some(s => s.type === 'screenshot')); assert.ok(subtasks.some(s => s.type === 'pdf')); }); // --- Swarm list grows --- await test('GET /swarms shows created swarms', async () => { const { status, data } = await fetchJSON('/swarms'); assert.equal(status, 200); assert.ok(data.swarms.length >= 3, `Expected >= 3 swarms, got ${data.swarms.length}`); }); // --- Events logged --- await test('GET /events shows events from swarm creation', async () => { const { status, data } = await fetchJSON('/events'); assert.equal(status, 200); assert.ok(data.events.length >= 3, `Expected >= 3 events, got ${data.events.length}`); assert.ok(data.events.some(e => e.action === 'swarm-created')); }); // --- Dashboard --- await test('GET / returns dashboard HTML', async () => { const res = await fetch(BASE + '/'); assert.equal(res.status, 200); const html = await res.text(); assert.ok(html.includes('Agent Swarm')); assert.ok(html.includes('Multi-agent')); }); // --- 404 for unknown paths --- await test('GET /unknown returns 404', async () => { const { status } = await fetchJSON('/unknown'); assert.equal(status, 404); }); // Wait for async swarm execution await new Promise(r => setTimeout(r, 2000)); // --- Check swarm execution results --- await test('Swarms execute and have results', async () => { const { data } = await fetchJSON('/swarms'); const finished = data.swarms.filter(s => s.state !== 'planned' && s.state !== 'running'); // At least some should have attempted execution assert.ok(finished.length >= 0 || data.swarms.length >= 3); }); console.log(`\n=== Results: ${passed} passed, ${failed} failed, ${passed + failed} total ===\n`); process.exit(failed > 0 ? 1 : 0);