Assessor_2.0/tests/reporting/security-view-report.test.ts

66 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { readFileSync, writeFileSync } from 'node:fs';
import { JSDOM } from 'jsdom';
import { describe, expect, it, vi } from 'vitest';
import { buildSecurityViewReportHtml, type SecurityViewReadPort } from '../../src/reporting/security-view-report';
vi.mock('../../src/styles/ui-tokens.css?raw', async () => ({ default: (await import('node:fs')).readFileSync('src/styles/ui-tokens.css', 'utf8') }));
vi.mock('../../src/styles/tokens.css?raw', async () => ({ default: (await import('node:fs')).readFileSync('src/styles/tokens.css', 'utf8') }));
vi.mock('../../src/styles/base.css?raw', async () => ({ default: (await import('node:fs')).readFileSync('src/styles/base.css', 'utf8') }));
vi.mock('../../src/styles/components.css?raw', async () => ({ default: (await import('node:fs')).readFileSync('src/styles/components.css', 'utf8') }));
vi.mock('../../src/styles/datahub.css?raw', async () => ({ default: (await import('node:fs')).readFileSync('src/styles/datahub.css', 'utf8') }));
vi.mock('../../src/styles/client-security.css?raw', async () => ({ default: (await import('node:fs')).readFileSync('src/styles/client-security.css', 'utf8') }));
vi.mock('../../src/styles/policy-analysis.css?raw', async () => ({ default: (await import('node:fs')).readFileSync('src/styles/policy-analysis.css', 'utf8') }));
vi.mock('../../src/styles/security-scoring.css?raw', async () => ({ default: (await import('node:fs')).readFileSync('src/styles/security-scoring.css', 'utf8') }));
vi.mock('../../src/styles/functional-icons.css?raw', async () => ({ default: (await import('node:fs')).readFileSync('src/styles/functional-icons.css', 'utf8') }));
const snapshot = Object.freeze({ markup: '<div class="client-security-panel"><details><summary>Native finding</summary>Original source text</details></div>',
width: 1910, theme: 'light' as const, client: 'Synthetic <client>', assessments: 'Synthetic assessment', generatedAt: '12.10.2026',
versions: [['Datenstand', 'Revision 1']] as const });
const port: SecurityViewReadPort = { read: () => snapshot };
describe('Standalone Security view renderer', () => {
it.each(['light', 'dark'] as const)('retains native script-free category navigation and independent maximizable popups (%s)', theme => {
const markup = '<a href="#security-category-synthetic">Show synthetic category</a><div style="height:1400px">Synthetic overview</div><details class="client-security-group" id="security-category-synthetic"><summary>Synthetic category</summary><div class="client-security-group-content">Original synthetic finding</div></details><button popovertarget="synthetic-popup">Results</button><div style="height:1400px">Synthetic footer</div>';
const popups = '<section popover="auto" id="synthetic-popup" class="security-export-popup" role="dialog" aria-label="Synthetic results"><header><h2>Synthetic results</h2><button class="btn" popovertarget="synthetic-popup" popovertargetaction="hide" aria-label="Close synthetic results">×</button></header><div class="security-export-popup-body"><dl>' + Array.from({ length: 9 }, (_, i) => `<div><dt>Fact ${i}</dt><dd>Original ${i}</dd></div>`).join('') + '</dl><div style="height:1600px">Synthetic long evidence</div><p>Evidence end</p></div></section>';
const html = buildSecurityViewReportHtml({ read: () => ({ ...snapshot, theme, markup, popups }) });
expect(html).not.toMatch(/<script|\son[a-z]+=/i);
expect(html).toContain('href="#security-category-content-synthetic"');
expect(html).toContain('<div class="client-security-group-content" id="security-category-content-synthetic">');
expect(html).toContain('overscroll-behavior:contain');
expect(html).toContain('100vw * 2 / 3');
expect(html).toContain('html,body{background:var(--surface-panel)}');
expect(html).toContain('grid-template-columns:repeat(5,minmax(0,1fr))');
expect(html).toContain('aria-label="Maximize popup"');
expect(html.indexOf('aria-label="Maximize popup"')).toBeLessThan(html.indexOf('aria-label="Close synthetic results"'));
expect(html).toContain('Original synthetic finding');
const dom = new JSDOM(html.replace(/<style>[\s\S]*?<\/style>/g, ''));
const doc = dom.window.document;
const input = doc.querySelector<HTMLInputElement>('.security-export-maximize input')!;
input.click(); expect(input.checked).toBe(true);
input.click(); expect(input.checked).toBe(false);
const link = doc.querySelector<HTMLAnchorElement>('a')!;
const target = doc.getElementById(link.hash.slice(1))!;
expect(target.closest('details')?.id).toBe('security-category-synthetic');
expect(target.textContent).toBe('Original synthetic finding');
expect(doc.querySelector('.security-export-popup>header')?.lastElementChild?.textContent).toBe('×');
dom.window.close();
if (process.env.ASSESSOR_REPORT_PREVIEW_DIR) writeFileSync(`${process.env.ASSESSOR_REPORT_PREVIEW_DIR}/security-${theme}.html`, html);
});
it('embeds the unchanged application styles, local assets and frozen authorized snapshot with no script or external resource', () => {
const before = JSON.stringify(snapshot); const html = buildSecurityViewReportHtml(port);
for (const file of ['ui-tokens', 'tokens', 'base', 'components', 'datahub', 'client-security', 'policy-analysis', 'security-scoring', 'functional-icons']) {
expect(html.includes(readFileSync(`src/styles/${file}.css`, 'utf8'))).toBe(true);
}
expect(html).toContain(snapshot.markup); expect(html).toContain('Synthetic &lt;client&gt;');
expect(html).toContain('width:1910px'); expect(html).toContain('VERTRAULICH');
expect(html).toContain('data:font/woff2;base64,'); expect(html).toContain('data:image/png;base64,');
expect(html).not.toMatch(/<script|<(?:img|link)\b[^>]*(?:src|href)=["']https?:/i);
expect(JSON.stringify(snapshot)).toBe(before);
});
it('rejects an unavailable width and active source markup instead of creating a partial or executable export', () => {
expect(() => buildSecurityViewReportHtml({ read: () => ({ ...snapshot, width: 0 }) })).toThrow('measured width');
for (const markup of ['<script>malicious()</script>', '<img onerror="malicious()">', '<a href="javascript:malicious()">bad</a>']) {
expect(() => buildSecurityViewReportHtml({ read: () => ({ ...snapshot, markup }) })).toThrow('active content');
}
});
});