Files
AI-Rendezvous/packages/server/test/integration.test.ts
T
a.andreev fde1ae152f
deploy / deploy (push) Canceled after 0s
Canonical secrets policy for agents + best-effort redaction
- /security.md is the single canonical policy page (control plane principle,
  SECRET_TRANSFER_REQUIRED, out-of-band transfer via SSH)
- short version with link embedded in /create.md, room .md, llms.txt, landing,
  MCP create tool response; docs updated
- redactSecrets() applied on input to messages, questions, resolutions,
  contracts and room brief/goal/participant instructions (best-effort: PEM
  keys, JWTs, common token prefixes, password/token/secret assignments)
2026-09-06 21:49:15 +03:00

192 lines
9.6 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;
// observer URL: read-only view with turn indicator
assert.ok(created.observer_url, 'createRoom must return observer_url');
const obs = await fetch(created.observer_url);
assert.equal(obs.status, 200);
const obsHtml = await obs.text();
assert.match(obsHtml, /observer/i);
assert.match(obsHtml, /Whose turn/);
const obsMd = await (await fetch(`${created.observer_url}.md`)).text();
assert.match(obsMd, /Whose turn/);
// observer token must NOT work as a participant token
const obsToken = created.observer_url.split('/').pop();
const write = await fetch(`${baseUrl}/api/rooms/${created.room_id}/messages`, {
method: 'POST', headers: { authorization: `Bearer ${obsToken}`, 'content-type': 'application/json' },
body: JSON.stringify({ content: 'hi' }),
});
assert.equal(write.status, 403);
// wrong observer token -> forbidden
assert.equal((await fetch(`${baseUrl}/o/${created.room_id}/nope`)).status, 403);
// observer HTML highlights whose turn it is after a blocking question appears
const pidA = created.participants[0].id;
const tokB = created.invite_urls[1].split('/').pop();
// before join: observer turn waits on B joining
const obsHtml0 = await (await fetch(created.observer_url)).text();
assert.match(obsHtml0, /invite not confirmed/);
// B reports taking the room into work
const j = await (await fetch(`${baseUrl}/api/rooms/${created.room_id}/join`, {
method: 'POST', headers: { authorization: `Bearer ${tokB}` },
})).json();
assert.equal((j as any).joined, true);
await fetch(`${baseUrl}/api/rooms/${created.room_id}/questions`, {
method: 'POST', headers: { authorization: `Bearer ${tokB}`, 'content-type': 'application/json' },
body: JSON.stringify({ question: 'verify X on your side', blocking: true, addressed_to_participant_id: pidA }),
});
const obsHtml2 = await (await fetch(created.observer_url)).text();
assert.match(obsHtml2, /turn-role/);
assert.match(obsHtml2, /windows-1c/);
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 landing = await (await fetch(baseUrl)).text();
assert.match(landing, /Meet\. Verify\. Agree\. Disappear\./);
assert.match(landing, /How it works/);
const llms = await (await fetch(`${baseUrl}/llms.txt`)).text();
assert.match(llms, /^# http/ms);
assert.match(llms, /\/api\/rooms/);
assert.match(llms, /what_you_should_do_next/);
// canonical security policy served; short version referenced everywhere agents read
const sec = await (await fetch(`${baseUrl}/security.md`)).text();
assert.match(sec, /control plane/);
assert.match(sec, /SECRET_TRANSFER_REQUIRED/);
assert.match(llms, /security\.md/);
const createMd = await (await fetch(`${baseUrl}/create.md`)).text();
assert.match(createMd, /security\.md/);
assert.match(createMd, /SECRET_TRANSFER_REQUIRED/);
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);
// any member can destroy (checked last — it deletes the room)
const del = await fetch(`${baseUrl}/api/rooms/${created.room_id}`, {
method: 'DELETE', headers: { authorization: `Bearer ${obsToken}` },
});
assert.equal(del.status, 200);
assert.equal(
(await fetch(`${baseUrl}/api/rooms/${created.room_id}`, { headers: { authorization: `Bearer ${created.participants[0].token}` } })).status,
404,
);
// forged token cannot destroy
const c2 = (await (await fetch(`${baseUrl}/api/rooms`, {
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(ROOM_PAYLOAD),
})).json()) as any;
assert.equal(
(await fetch(`${baseUrl}/api/rooms/${c2.room_id}`, { method: 'DELETE', headers: { authorization: 'Bearer nope' } })).status,
403,
);
// participant can destroy via the UI form endpoint
const uiDel = await fetch(`${c2.invite_urls[0]}/destroy`, { method: 'POST' });
assert.equal(uiDel.status, 200);
assert.match(await uiDel.text(), /destroyed/i);
} finally {
app.close();
}
});