confluence_web/frontend/tests/contract.test.js
Artur Mukhamadiev a9908a533f frontend: web UI track handoff (contract revision 1)
Static frontend with vendored marked/DOMPurify, bounded Markdown
pipeline, same-origin mock server with scenario selection, unit,
contract and CDP end-to-end tests under frontend/**.
2026-09-14 21:57:54 +03:00

304 lines
10 KiB
JavaScript

/**
* 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(!/<script(?![^>]*src=)[^>]*>/i.test(html), 'Root HTML in dev mode must not contain inline <script>');
assert.ok(!/style\s*=\s*["'][^"']*["']/i.test(html), 'Root HTML in dev mode must not contain inline style attributes');
});
});