// P9 slice-5 DLQ smoke: a poison chaos event is dead-lettered (not lost), shows up // tenant-scoped in /v1/dlq, another tenant can neither see nor replay it, and the // owner's replay resolves it (KG-13 / KG-06). Requires the service running with // IIOS_DEV_TOKENS=1 (the relay ticks by default, publishing the poison to the consumer). import 'dotenv/config'; const SERVICE = process.env.SMOKE_URL ?? 'http://localhost:3200'; const APP_ID = 'portal-demo'; const assert = (c, m) => { if (!c) { console.error('✗', m); process.exit(1); } console.log('✓', m); }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function devToken(userId, orgId) { const r = await fetch(`${SERVICE}/v1/dev/token`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ appId: APP_ID, userId, name: userId, orgId }), }); if (!r.ok) throw new Error(`devToken ${r.status} (service with IIOS_DEV_TOKENS=1?)`); return (await r.json()).token; } async function call(token, path, method = 'GET', body) { return fetch(`${SERVICE}${path}`, { method, headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` }, body: body ? JSON.stringify(body) : undefined, }); } async function json(token, path, method = 'GET', body) { const r = await call(token, path, method, body); if (!r.ok) throw new Error(`${method} ${path} ${r.status}: ${await r.text()}`); return r.json(); } const A = await devToken('a1', 'org_A'); const B = await devToken('b1', 'org_B'); // A injects a poison chaos event. const { eventId } = await json(A, '/v1/dev/chaos/poison', 'POST'); assert(eventId.startsWith('evt_chaos_'), `A injected a poison chaos event (${eventId})`); // Poll until the relay publishes it and the chaos consumer dead-letters it. let item; for (let i = 0; i < 40; i++) { const list = await json(A, '/v1/dlq'); item = list.find((d) => d.sourceId === eventId); if (item) break; await sleep(250); } assert(item, 'poison surfaced in A\'s DLQ (relay published → consumer failed → dead-lettered)'); assert(item.status === 'QUARANTINED', `DLQ item is QUARANTINED (got ${item.status})`); assert(item.consumerName === 'chaos-consumer', `dead-lettered by the chaos-consumer lane (got ${item.consumerName})`); // Tenant B cannot see A's DLQ item. const bList = await json(B, '/v1/dlq'); assert(!bList.some((d) => d.sourceId === eventId), "tenant B's DLQ excludes A's item (isolation)"); // Tenant B cannot replay A's DLQ item. const bReplay = await call(B, `/v1/dlq/${item.id}/replay`, 'POST'); assert(bReplay.status === 403, `tenant B replaying A's item → 403 (got ${bReplay.status}) — KG-02`); // A replays → resolves. const replayed = await json(A, `/v1/dlq/${item.id}/replay`, 'POST'); assert(replayed.status === 'RESOLVED', `A's replay resolves the poison (got ${replayed.status})`); // It now shows RESOLVED in A's DLQ. const afterList = await json(A, '/v1/dlq'); const after = afterList.find((d) => d.sourceId === eventId); assert(after && after.status === 'RESOLVED', 'DLQ item is RESOLVED after replay'); console.log('\nP9 DLQ smoke: PASS'); process.exit(0);