/** * 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 'none'")); // 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'); }); 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