Files
iios/packages/iios-service/scripts/smoke-capability.mjs
T
maaz519 f7519d36f0 feat(p9): real-egress proof (env-gated HttpProvider) + /health bindings + smoke
Task 9.5: /health now reports the egress provider backing each channel (sandbox
vs http). scripts/smoke-capability.mjs proves the slice end to end: sandbox egress
SENT through the broker; EMAIL routed to the env-gated HttpProvider and actually
delivered to a LOCAL echo server (real egress, no external network, no credentials);
idempotent replay; /health bindings. App wires CapabilityModule.

P9 slice-1 verification: 103 tests green, pnpm -r build all packages+demos,
boundary clean, capability smoke PASS, all prior smokes (realtime/inbox/support/
adapter/route/ai/calendar) still PASS through the broker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 20:38:47 +05:30

62 lines
3.2 KiB
JavaScript

// P9 slice-1 smoke: the Capability Broker as governed egress.
// (a) sandbox egress SENT via the broker (WEBHOOK, no network);
// (b) REAL egress via the env-gated HttpProvider → a LOCAL echo server (EMAIL);
// (c) idempotent replay; (d) /health shows provider bindings.
// Requires the service started with IIOS_PROVIDER_URL_EMAIL=http://127.0.0.1:4599/echo.
import 'dotenv/config';
import http from 'node:http';
import jwt from 'jsonwebtoken';
const SERVICE = process.env.SMOKE_URL ?? 'http://localhost:3200';
const APP_ID = 'portal-demo';
const APP_SECRET = JSON.parse(process.env.APP_SECRETS ?? '{"portal-demo":"dev-secret"}')[APP_ID];
const token = jwt.sign({ sub: 'egress', appId: APP_ID, orgId: `org_${APP_ID}` }, APP_SECRET, { algorithm: 'HS256', expiresIn: '1h' });
const assert = (c, m) => { if (!c) { console.error('✗', m); process.exit(1); } console.log('✓', m); };
async function send(channelType, target, payload, idempotencyKey) {
const r = await fetch(`${SERVICE}/v1/adapters/${channelType}/send`, {
method: 'POST',
headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
body: JSON.stringify({ target, payload, idempotencyKey }),
});
if (!r.ok) throw new Error(`send ${channelType} ${r.status}: ${await r.text()}`);
return r.json();
}
// Local echo server the real HttpProvider will POST to (no external network).
const received = [];
const echo = http.createServer((req, res) => {
let b = '';
req.on('data', (c) => (b += c));
req.on('end', () => { try { received.push(JSON.parse(b || '{}')); } catch { received.push({}); } res.writeHead(200, { 'content-type': 'application/json' }); res.end('{"ok":true}'); });
});
await new Promise((r) => echo.listen(4599, '127.0.0.1', r));
try {
// (a) sandbox egress through the broker
const sandboxCmd = await send('WEBHOOK', 'user@x', { text: 'hi via broker' }, `cap-sb-${Date.now()}`);
assert(sandboxCmd.status === 'SENT' && String(sandboxCmd.providerRef).startsWith('sim-'), 'sandbox egress SENT through the broker (no network)');
// (b) real egress via env-gated HttpProvider → local echo server
const before = received.length;
const httpCmd = await send('EMAIL', 'buyer@yamuna', { text: 'real egress proof' }, `cap-http-${Date.now()}`);
assert(httpCmd.status === 'SENT' && String(httpCmd.providerRef).startsWith('http-'), 'EMAIL egress routed to the real HttpProvider');
assert(received.length === before + 1, 'local echo server actually received the POST (real egress, no external network)');
assert(received[received.length - 1]?.payload?.text === 'real egress proof', 'echo received the exact payload');
// (c) idempotent replay → one command
const key = `cap-idem-${Date.now()}`;
const a = await send('EMAIL', 'buyer@yamuna', { text: 'x' }, key);
const b = await send('EMAIL', 'buyer@yamuna', { text: 'x' }, key);
assert(a.id === b.id, 'idempotent replay → one command');
// (d) /health shows provider bindings
const health = await (await fetch(`${SERVICE}/health`)).json();
assert(health.egressProviders?.EMAIL === 'http' && health.egressProviders?.WEBHOOK === 'sandbox', '/health shows EMAIL=http, WEBHOOK=sandbox bindings');
console.log('\nP9 capability smoke: PASS');
} finally {
echo.close();
}
process.exit(0);