/** * Contract and Mock Server tests. * Validates wire formats, security headers, session cookies, and scenarios against CONTRACTS.md. */ import { test, describe, before, after } from 'node:test'; import assert from 'node:assert/strict'; import http from 'node:http'; import { createMockServer, SCENARIOS } from '../dev/mock-server.js'; const TEST_PORT = 5199; const BASE_URL = `http://127.0.0.1:${TEST_PORT}`; describe('Mock Server and Wire Contract Tests', () => { let server; before(async () => { server = createMockServer(); await new Promise((resolve) => { server.listen(TEST_PORT, '127.0.0.1', resolve); }); }); after(async () => { await new Promise((resolve) => { server.close(resolve); }); }); test('GET / sets cw_session cookie and serves security headers', async () => { const res = await fetch(`${BASE_URL}/`); assert.equal(res.status, 200); // Security headers assert.equal(res.headers.get('x-content-type-options'), 'nosniff'); assert.equal(res.headers.get('referrer-policy'), 'no-referrer'); const csp = res.headers.get('content-security-policy'); assert.ok(csp.includes("default-src 'none'")); assert.ok(csp.includes("script-src 'self'")); assert.ok(csp.includes("style-src 'self'")); assert.ok(csp.includes("connect-src 'self'")); assert.ok(csp.includes("img-src 'self'")); // Session cookie const cookie = res.headers.get('set-cookie'); assert.ok(cookie, 'Set-Cookie header must be present'); assert.ok(cookie.includes('cw_session='), 'Cookie name must be cw_session'); assert.ok(cookie.includes('HttpOnly'), 'Cookie must be HttpOnly'); assert.ok(cookie.includes('SameSite=Strict'), 'Cookie must be SameSite=Strict'); }); test('POST /api/v1/auth/verify succeeds with dummy credentials', async () => { const res = await fetch(`${BASE_URL}/api/v1/auth/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: JSON.stringify({ url: 'https://confluence.example.com', pat: 'dummy-pat-token-123' }) }); assert.equal(res.status, 200); assert.equal(res.headers.get('cache-control'), 'no-store'); const body = await res.json(); assert.deepEqual(body, { valid: true }); }); test('POST /api/v1/auth/verify fails with 403 in 403_verify scenario', async () => { const res = await fetch(`${BASE_URL}/api/v1/auth/verify?scenario=403_verify`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: JSON.stringify({ url: 'https://confluence.example.com', pat: 'dummy-pat-token-123' }) }); assert.equal(res.status, 403); const body = await res.json(); assert.ok(body.error); assert.equal(body.error.code, 'confluence_auth_failed'); }); test('POST /api/v1/query handles normal scenario matching section 7 shared contract', async () => { const res = await fetch(`${BASE_URL}/api/v1/query?scenario=normal`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: JSON.stringify({ prompt: 'deploy service X', credentials: { url: 'https://approved.example.com', pat: 'dummy-pat-123' } }) }); assert.equal(res.status, 200); assert.equal(res.headers.get('cache-control'), 'no-store'); const data = await res.json(); assert.ok(data.session_id); assert.ok(data.markdown.includes('Deployment Guide')); assert.equal(data.pages_accessed.length, 1); assert.equal(data.pages_accessed[0].page_id, '847291'); assert.equal(data.pages_accessed[0].space, 'OPS'); assert.ok(data.pages_accessed[0].accessed_at); assert.equal(data.tool_history.length, 2); assert.equal(data.tool_history[0].tool, 'confluence_search'); assert.equal(data.tool_history[1].tool, 'confluence_view'); assert.equal(data.tool_history[1].cache_hit, false); assert.equal(data.artifacts.length, 1); assert.equal(data.artifacts[0].name, 'checklist.md'); assert.equal(data.artifacts[0].size_bytes, 32); assert.ok(data.artifacts[0].expires_at); }); test('GET /api/v1/artifacts/:id downloads exact 32 bytes for checklist.md', async () => { const res = await fetch(`${BASE_URL}/api/v1/artifacts/art-checklist-01`); assert.equal(res.status, 200); assert.equal(res.headers.get('content-type'), 'application/octet-stream'); assert.equal(res.headers.get('x-content-type-options'), 'nosniff'); assert.ok(res.headers.get('content-disposition').includes('attachment; filename="checklist.md"')); const text = await res.text(); assert.equal(text, '# Checklist\n\n- Deploy service X\n'); assert.equal(Buffer.byteLength(text, 'utf-8'), 32); }); test('GET /api/v1/artifacts/:id returns 404 in unknown_expired_download scenario', async () => { const res = await fetch(`${BASE_URL}/api/v1/artifacts/art-checklist-01?scenario=unknown_expired_download`); assert.equal(res.status, 404); const body = await res.json(); assert.equal(body.error.code, 'artifact_not_found'); }); test('POST /api/v1/query handles 409 busy scenario', async () => { const res = await fetch(`${BASE_URL}/api/v1/query?scenario=409_busy`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: JSON.stringify({ prompt: 'test prompt', credentials: { url: 'https://example.com', pat: 'dummy' } }) }); assert.equal(res.status, 409); const body = await res.json(); assert.equal(body.error.code, 'busy'); }); test('POST /api/v1/query handles 504 timeout scenario', async () => { const res = await fetch(`${BASE_URL}/api/v1/query?scenario=504_timeout`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: JSON.stringify({ prompt: 'test prompt', credentials: { url: 'https://example.com', pat: 'dummy' } }) }); assert.equal(res.status, 504); const body = await res.json(); assert.equal(body.error.code, 'query_timeout'); }); test('POST /api/v1/query handles empty_search scenario', async () => { const res = await fetch(`${BASE_URL}/api/v1/query?scenario=empty_search`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: JSON.stringify({ prompt: 'non-existent query', credentials: { url: 'https://example.com', pat: 'dummy' } }) }); assert.equal(res.status, 200); const body = await res.json(); assert.equal(body.pages_accessed.length, 0); assert.equal(body.artifacts.length, 0); assert.equal(body.tool_history.length, 1); assert.equal(body.tool_history[0].result.pages.length, 0); }); test('POST /api/v1/query handles repeated_cached_view scenario', async () => { const res = await fetch(`${BASE_URL}/api/v1/query?scenario=repeated_cached_view`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: JSON.stringify({ prompt: 'cache test', credentials: { url: 'https://example.com', pat: 'dummy' } }) }); assert.equal(res.status, 200); const body = await res.json(); assert.equal(body.pages_accessed.length, 1); assert.equal(body.tool_history.length, 2); assert.equal(body.tool_history[0].cache_hit, false); assert.equal(body.tool_history[1].cache_hit, true); }); test('POST /api/v1/query handles failed_tool scenario with status="error"', async () => { const res = await fetch(`${BASE_URL}/api/v1/query?scenario=failed_tool`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: JSON.stringify({ prompt: 'failed tool test', credentials: { url: 'https://example.com', pat: 'dummy' } }) }); assert.equal(res.status, 200); const body = await res.json(); const errorTool = body.tool_history.find((t) => t.status === 'error'); assert.ok(errorTool); assert.equal(errorTool.result, null); assert.equal(errorTool.error.code, 'page_not_found'); }); test('POST /api/v1/query handles warning_truncated_history scenario', async () => { const res = await fetch(`${BASE_URL}/api/v1/query?scenario=warning_truncated_history`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: JSON.stringify({ prompt: 'truncation test', credentials: { url: 'https://example.com', pat: 'dummy' } }) }); assert.equal(res.status, 200); const body = await res.json(); assert.ok(body.warnings.length > 0); assert.equal(body.tool_history[0].parameters_truncated, true); assert.equal(body.tool_history[0].result_truncated, true); }); test('Origin check rejects untrusted external origins', async () => { const res = await fetch(`${BASE_URL}/api/v1/query`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': 'https://evil-attacker.example.com' }, body: JSON.stringify({ prompt: 'attack', credentials: { url: 'https://example.com', pat: 'dummy' } }) }); assert.equal(res.status, 403); const body = await res.json(); assert.equal(body.error.code, 'origin_denied'); }); /** * Extracts `cw_session=...` from a Set-Cookie header so subsequent requests in the same * test can carry the session forward (raw fetch has no cookie jar). */ function extractSessionCookie(res, previous) { const setCookie = res.headers.get('set-cookie'); if (!setCookie) return previous; const match = setCookie.match(/cw_session=[^;]+/); return match ? match[0] : previous; } describe('Admission queue scenarios (docs/QUEUE_SPECIFICATION.md ยง10)', () => { test('queue_full: join answers 503 queue_full', async () => { const res = await fetch(`${BASE_URL}/api/v1/queue/join?scenario=queue_full`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: '{}' }); assert.equal(res.status, 503); assert.equal(res.headers.get('cache-control'), 'no-store'); const body = await res.json(); assert.equal(body.error.code, 'queue_full'); }); test('queued: position 3 with eta, decrements on each poll, ready on the third, query succeeds', async () => { let cookie; const joinRes = await fetch(`${BASE_URL}/api/v1/queue/join?scenario=queued`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: '{}' }); cookie = extractSessionCookie(joinRes, cookie); assert.equal(joinRes.status, 200); assert.equal(joinRes.headers.get('cache-control'), 'no-store'); const joinBody = await joinRes.json(); assert.equal(joinBody.status, 'queued'); assert.equal(joinBody.position, 3); assert.equal(joinBody.ahead, 2); assert.equal(joinBody.eta_seconds, 150); assert.ok(joinBody.ticket_id); const poll1 = await fetch(`${BASE_URL}/api/v1/queue/status?scenario=queued`, { headers: { Cookie: cookie } }); const poll1Body = await poll1.json(); assert.equal(poll1Body.status, 'queued'); assert.equal(poll1Body.position, 2); assert.equal(poll1Body.ticket_id, joinBody.ticket_id); const poll2 = await fetch(`${BASE_URL}/api/v1/queue/status?scenario=queued`, { headers: { Cookie: cookie } }); const poll2Body = await poll2.json(); assert.equal(poll2Body.status, 'queued'); assert.equal(poll2Body.position, 1); const poll3 = await fetch(`${BASE_URL}/api/v1/queue/status?scenario=queued`, { headers: { Cookie: cookie } }); const poll3Body = await poll3.json(); assert.equal(poll3Body.status, 'ready'); assert.ok(poll3Body.reservation_expires_in_seconds > 0); const queryRes = await fetch(`${BASE_URL}/api/v1/query?scenario=queued`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL, Cookie: cookie }, body: JSON.stringify({ prompt: 'deploy service X', credentials: { url: 'https://example.com', pat: 'dummy' } }) }); assert.equal(queryRes.status, 200); const queryBody = await queryRes.json(); assert.ok(queryBody.markdown.includes('Deployment Guide')); }); test('queued_no_estimate: eta_seconds is null at every step', async () => { let cookie; const joinRes = await fetch(`${BASE_URL}/api/v1/queue/join?scenario=queued_no_estimate`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: '{}' }); cookie = extractSessionCookie(joinRes, cookie); const joinBody = await joinRes.json(); assert.equal(joinBody.status, 'queued'); assert.equal(joinBody.eta_seconds, null); const poll1Body = await (await fetch(`${BASE_URL}/api/v1/queue/status?scenario=queued_no_estimate`, { headers: { Cookie: cookie } })).json(); assert.equal(poll1Body.eta_seconds, null); const poll2Body = await (await fetch(`${BASE_URL}/api/v1/queue/status?scenario=queued_no_estimate`, { headers: { Cookie: cookie } })).json(); assert.equal(poll2Body.eta_seconds, null); const poll3Body = await (await fetch(`${BASE_URL}/api/v1/queue/status?scenario=queued_no_estimate`, { headers: { Cookie: cookie } })).json(); assert.equal(poll3Body.status, 'ready'); }); test('reservation_lost: query 409s once after ready, rejoin succeeds, second query succeeds', async () => { let cookie; const joinRes = await fetch(`${BASE_URL}/api/v1/queue/join?scenario=reservation_lost`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: '{}' }); cookie = extractSessionCookie(joinRes, cookie); const joinBody = await joinRes.json(); assert.equal(joinBody.status, 'ready'); const firstQuery = await fetch(`${BASE_URL}/api/v1/query?scenario=reservation_lost`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL, Cookie: cookie }, body: JSON.stringify({ prompt: 'p', credentials: { url: 'https://example.com', pat: 'dummy' } }) }); assert.equal(firstQuery.status, 409); const firstQueryBody = await firstQuery.json(); assert.equal(firstQueryBody.error.code, 'busy'); const rejoinRes = await fetch(`${BASE_URL}/api/v1/queue/join?scenario=reservation_lost`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL, Cookie: cookie }, body: '{}' }); const rejoinBody = await rejoinRes.json(); assert.equal(rejoinBody.status, 'ready'); const secondQuery = await fetch(`${BASE_URL}/api/v1/query?scenario=reservation_lost`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL, Cookie: cookie }, body: JSON.stringify({ prompt: 'p', credentials: { url: 'https://example.com', pat: 'dummy' } }) }); assert.equal(secondQuery.status, 200); }); test('ticket_lost: second status poll answers 404, rejoin answers ready', async () => { let cookie; const joinRes = await fetch(`${BASE_URL}/api/v1/queue/join?scenario=ticket_lost`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL }, body: '{}' }); cookie = extractSessionCookie(joinRes, cookie); const joinBody = await joinRes.json(); assert.equal(joinBody.status, 'queued'); const poll1Body = await (await fetch(`${BASE_URL}/api/v1/queue/status?scenario=ticket_lost`, { headers: { Cookie: cookie } })).json(); assert.equal(poll1Body.status, 'queued'); const poll2 = await fetch(`${BASE_URL}/api/v1/queue/status?scenario=ticket_lost`, { headers: { Cookie: cookie } }); assert.equal(poll2.status, 404); const poll2Body = await poll2.json(); assert.equal(poll2Body.error.code, 'ticket_not_found'); const rejoinRes = await fetch(`${BASE_URL}/api/v1/queue/join?scenario=ticket_lost`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Origin': BASE_URL, Cookie: cookie }, body: '{}' }); const rejoinBody = await rejoinRes.json(); assert.equal(rejoinBody.status, 'ready'); }); test('GET /api/v1/queue/status with no ticket answers 404 ticket_not_found', async () => { const res = await fetch(`${BASE_URL}/api/v1/queue/status?scenario=normal`); assert.equal(res.status, 404); const body = await res.json(); assert.equal(body.error.code, 'ticket_not_found'); }); test('DELETE /api/v1/queue/ticket always answers 204', async () => { const res = await fetch(`${BASE_URL}/api/v1/queue/ticket`, { method: 'DELETE', headers: { Origin: BASE_URL } }); assert.equal(res.status, 204); assert.equal(res.headers.get('cache-control'), 'no-store'); }); test('POST /api/v1/queue/join rejects a body containing credentials or pat', async () => { const withCredentials = await fetch(`${BASE_URL}/api/v1/queue/join`, { method: 'POST', headers: { 'Content-Type': 'application/json', Origin: BASE_URL }, body: JSON.stringify({ credentials: { url: 'https://x', pat: 'y' } }) }); assert.equal(withCredentials.status, 400); assert.equal((await withCredentials.json()).error.code, 'invalid_input'); const withPat = await fetch(`${BASE_URL}/api/v1/queue/join`, { method: 'POST', headers: { 'Content-Type': 'application/json', Origin: BASE_URL }, body: JSON.stringify({ pat: 'y' }) }); assert.equal(withPat.status, 400); assert.equal((await withPat.json()).error.code, 'invalid_input'); }); test('non-queue scenarios answer join with ready immediately (unchanged flows)', async () => { const res = await fetch(`${BASE_URL}/api/v1/queue/join?scenario=normal`, { method: 'POST', headers: { 'Content-Type': 'application/json', Origin: BASE_URL }, body: '{}' }); assert.equal(res.status, 200); const body = await res.json(); assert.equal(body.status, 'ready'); }); }); test('Mock dev toolbar external assets served with correct headers and zero inline script/style', async () => { // CSS asset const cssRes = await fetch(`${BASE_URL}/dev/scenario-toolbar.css`); assert.equal(cssRes.status, 200); assert.ok(cssRes.headers.get('content-type').includes('text/css')); assert.equal(cssRes.headers.get('x-content-type-options'), 'nosniff'); // JS asset const jsRes = await fetch(`${BASE_URL}/dev/scenario-toolbar.js`); assert.equal(jsRes.status, 200); assert.ok(jsRes.headers.get('content-type').includes('javascript')); assert.equal(jsRes.headers.get('x-content-type-options'), 'nosniff'); // Root HTML page must not contain inline scripts or inline style attributes const htmlRes = await fetch(`${BASE_URL}/`); const html = await htmlRes.text(); assert.ok(!/]*src=)[^>]*>/i.test(html), 'Root HTML in dev mode must not contain inline