/**
 * audit3-7 smoke — incident-timeline title/description HTML-escaped at API.
 *
 * Run: npx tsx scripts/smoke/audit3-7-timeline-xss.ts
 *
 * Why this exists (2026-05-28):
 *   /api/incidents/[id]/timeline accepts title + description from
 *   EDITORs and persists them as-is. The GET response previously
 *   returned the raw stored strings. If anywhere in the dashboard ever
 *   rendered those via dangerouslySetInnerHTML, an EDITOR could plant
 *   stored XSS for every viewer of the incident page.
 *
 *   Fix: escape `& < > " '` at the API boundary (both POST response
 *   and GET response). The DB stays raw; consumers always see safe
 *   strings.
 *
 *   This smoke reads the route file and asserts:
 *     - escapeHtml function declared
 *     - GET response maps title + description through escapeHtml
 *     - POST response maps title + description through escapeHtml
 *     - the escape covers all 5 HTML-meta characters
 */
import './_lib';
import { ok, fail } from './_lib';
import fs from 'fs';
import path from 'path';

const ROUTE = path.resolve(__dirname, '../../src/app/api/incidents/[id]/timeline/route.ts');

function main(): void {
    const src = fs.readFileSync(ROUTE, 'utf8');

    if (!/function\s+escapeHtml\s*\(/.test(src)) {
        fail('audit3-7 helper', 'escapeHtml helper not declared in timeline route');
    }
    ok('escapeHtml helper declared');

    // Every meta char must be covered.
    const expected = [
        [/replace\(\s*\/&\/g\s*,\s*['"]&amp;['"]/, '&'],
        [/replace\(\s*\/<\/g\s*,\s*['"]&lt;['"]/, '<'],
        [/replace\(\s*\/>\/g\s*,\s*['"]&gt;['"]/, '>'],
        [/replace\(\s*\/"\/g\s*,\s*['"]&quot;['"]/, '"'],
        [/replace\(\s*\/'\/g\s*,\s*['"]&#x27;['"]/, "'"],
    ] as const;
    for (const [re, char] of expected) {
        if (!re.test(src)) {
            fail('audit3-7 escape coverage', `escapeHtml does not escape ${JSON.stringify(char)}`);
        }
    }
    ok('escapeHtml covers all 5 HTML-meta characters');

    // GET handler must apply escape to title + description.
    const getBlock = src.match(/events:\s*events\.map[\s\S]+?\}\)\)/);
    if (!getBlock) {
        fail('audit3-7 GET response shape', 'expected events.map pattern in GET handler');
    } else {
        if (!/title:\s*escapeHtml\(/.test(getBlock[0])) {
            fail('audit3-7 GET title escape', 'GET response does not escape title');
        }
        if (!/description:\s*escapeHtml\(/.test(getBlock[0])) {
            fail('audit3-7 GET description escape', 'GET response does not escape description');
        }
    }
    ok('GET response escapes title + description');

    // POST handler returns the freshly-created event — also escaped.
    const postBlock = src.match(/event:\s*\{[\s\S]+?\.\.\.\s*event[\s\S]+?\}/);
    if (!postBlock) {
        fail('audit3-7 POST response shape', 'expected event:{...event} pattern in POST handler');
    } else {
        if (!/title:\s*escapeHtml\(/.test(postBlock[0])) {
            fail('audit3-7 POST title escape', 'POST response does not escape title');
        }
        if (!/description:\s*escapeHtml\(/.test(postBlock[0])) {
            fail('audit3-7 POST description escape', 'POST response does not escape description');
        }
    }
    ok('POST response escapes title + description');

    ok('audit3-7 timeline XSS', 'GET + POST both escape title + description before response');
}

main();
