Files
AI-Rendezvous/packages/server/test/integration.test.ts
T

117 lines
5.8 KiB
TypeScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createApp } from '../src/index.js';
async function startServer() {
const port = 30000 + Math.floor(Math.random() * 10000);
const app = createApp({ port, baseUrl: `http://127.0.0.1:${port}`, dbPath: ':memory:', cleanupIntervalMs: 3_600_000 });
await new Promise<void>((r) => app.server.listen(port, r));
return { app, baseUrl: `http://127.0.0.1:${port}` };
}
const ROOM_PAYLOAD = {
title: '1C <-> app integration contract',
goal: 'Agree endpoints, schedule, auth',
participants: [
{ role: 'windows-1c', knows: ['IIS', '1C', 'Windows logs'], needs_to_determine: ['frequency'] },
{ role: 'application', knows: ['app code'], needs_to_determine: ['contract'] },
],
};
test('HTTP integration: create -> markdown invite -> negotiate -> finalize -> final.md', async () => {
const { app, baseUrl } = await startServer();
try {
// 1. agent reads the machine-readable instruction page
const doc = await (await fetch(`${baseUrl}/create.md`)).text();
assert.match(doc, /POST .*\/api\/rooms/);
// 2. creates the room
const created = (await (await fetch(`${baseUrl}/api/rooms`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(ROOM_PAYLOAD),
})).json()) as any;
assert.equal(created.invite_urls.length, 2);
const [aUrl, bUrl] = created.invite_urls;
const tokenA = aUrl.split('/').pop();
const tokenB = bUrl.split('/').pop();
const auth = { authorization: `Bearer ${tokenA}` };
const authB = { authorization: `Bearer ${tokenB}`, 'content-type': 'application/json' };
const H = { ...auth, 'content-type': 'application/json' };
// 3. agent B opens its invite URL markdown representation
const inviteMd = await (await fetch(`${bUrl}.md`)).text();
assert.match(inviteMd, /AI Rendezvous/);
assert.match(inviteMd, /application/);
// 4. A states facts
await fetch(`${baseUrl}/api/rooms/${created.room_id}/messages`, { method: 'POST', headers: H, body: JSON.stringify({ content: 'IIS exposes /exchange/hs with basic auth.' }) });
// 5. B asks a blocking question
const q = (await (await fetch(`${baseUrl}/api/rooms/${created.room_id}/questions`, {
method: 'POST', headers: authB,
body: JSON.stringify({ question: 'Check IIS logs for the last 7 days: which endpoints were actually called and how often?', blocking: true, addressed_to_participant_id: created.participants[0].id }),
})).json()) as any;
assert.equal(q.status, 'open');
// 6. room view tells A what to do next
const viewA = (await (await fetch(`${baseUrl}/api/rooms/${created.room_id}`, { headers: auth })).json()) as any;
assert.equal(viewA.your_role, 'windows-1c');
assert.match(viewA.what_you_should_do_next, /blocking question/i);
assert.ok(Array.isArray(viewA.available_actions) && viewA.available_actions.length > 0);
// 7. A verifies and resolves
await fetch(`${baseUrl}/api/rooms/${created.room_id}/messages`, { method: 'POST', headers: H, body: JSON.stringify({ content: 'IIS logs: /exchange/hs called every 15 min by svc_exchange.' }) });
await fetch(`${baseUrl}/api/rooms/${created.room_id}/questions/${q.id}/resolve`, { method: 'POST', headers: H, body: JSON.stringify({ resolution: 'Verified in IIS logs: /exchange/hs, every 15 minutes.' }) });
// 8. B proposes contract, both agree
await fetch(`${baseUrl}/api/rooms/${created.room_id}/contract`, {
method: 'PUT', headers: authB,
body: JSON.stringify({ markdown: '## Facts\n- /exchange/hs every 15 min\n## Interface\nPOST /exchange/hs\n## Authentication\nBasic auth\n## Error handling\nHTTP 5xx -> retry 3x\n## Unresolved\nnone' }),
});
const agree1 = (await (await fetch(`${baseUrl}/api/rooms/${created.room_id}/agree`, { method: 'POST', headers: authB, body: '{}' })).json()) as any;
assert.equal(agree1.room_status, 'open');
const agree2 = (await (await fetch(`${baseUrl}/api/rooms/${created.room_id}/agree`, { method: 'POST', headers: H, body: '{}' })).json()) as any;
assert.equal(agree2.room_status, 'agreed');
// 9. final markdown artifact
const final = await (await fetch(`${baseUrl}/api/rooms/${created.room_id}/final.md`, { headers: auth })).text();
assert.match(final, /Agreed Contract/);
assert.match(final, /exchange\/hs/);
// 10. token isolation: tokenB cannot use room of another room; forged token rejected
const forged = await fetch(`${baseUrl}/api/rooms/${created.room_id}`, { headers: { authorization: 'Bearer nope' } });
assert.equal(forged.status, 403);
// room id without token -> forbidden
const noToken = await fetch(`${baseUrl}/api/rooms/${created.room_id}`);
assert.equal(noToken.status, 403);
} finally {
app.close();
}
});
test('HTTP: cannot read room by id without token; unknown routes 404; human pages render', async () => {
const { app, baseUrl } = await startServer();
try {
const created = (await (await fetch(`${baseUrl}/api/rooms`, {
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(ROOM_PAYLOAD),
})).json()) as any;
const r = await fetch(`${baseUrl}/api/rooms/${created.room_id}`);
assert.equal(r.status, 403);
const r2 = await fetch(`${baseUrl}/api/rooms/${created.room_id}?token=${created.participants[0].token}`);
assert.equal(r2.status, 200);
const r3 = await fetch(`${baseUrl}/api/nosuch`);
assert.equal(r3.status, 404);
const html = await (await fetch(`${baseUrl}/create`)).text();
assert.match(html, /AI Rendezvous/);
const roomHtml = await (await fetch(created.invite_urls[0])).text();
assert.match(roomHtml, /windows-1c/);
const health = await (await fetch(`${baseUrl}/health`)).json();
assert.ok(health.ok);
} finally {
app.close();
}
});