import { describe, expect, it } from 'vitest'; import { buildManagementOverviewHtml } from '../../src/exports/management-overview-report'; import type { ReportProjection } from '../../src/domain/report-projection'; function projection(overrides: Partial = {}): ReportProjection { return { version: '1.1.0', generatedAt: '2026-09-18T10:00:00.000Z', project: { id: 'synthetic-project', name: 'Synthetic GmbH' }, assessmentId: 'synthetic-assessment', environment: [{ key: 'Domain', value: 'synthetic.example' }, { key: 'Domain Controller', value: undefined }], overallScore: 62, scoreSources: [], domains: [], priorities: [], coverage: { total: 0, assessed: 0, deviations: 0, passed: 0, notRun: 0, notApplicable: 0, executionErrors: 0, unknown: 0, perSource: [] }, inventory: { bySeverity: {} as never, byOutcome: {} as never, byDomain: {}, bySource: {} }, findings: [], policyEntries: [], workItems: [], sourcePriorities: [], convergence: [], bundles: [], singleSourcePolicyActions: [], diagnostics: [], provenance: [], ...overrides } as ReportProjection; } const finding = (overrides = {}) => ({ resultProjectionId: 'synthetic-1', domainId: 'identity', toolId: 'purple-knight', title: 'Synthetic indicator', severity: 'critical' as const, outcome: 'fail' as const, rankBasis: { kind: 'severity' as const }, ranked: true, ...overrides }); describe('management overview renderer', () => { it('renders a self-contained document with only inline script and no remote resources', () => { const html = buildManagementOverviewHtml(projection()); expect(html.startsWith('')).toBe(true); expect(html).not.toMatch(/]*\bsrc\s*=/i); expect(html).not.toMatch(/\son[a-z]+\s*=/i); expect(html).not.toMatch(/https?:\/\//i); expect(html).not.toMatch(/ { const html = buildManagementOverviewHtml(projection()); expect(html).toContain('color-scheme:light'); expect(html).not.toMatch(/prefers-color-scheme\s*:\s*dark/i); }); it('declares German as the document language', () => { expect(buildManagementOverviewHtml(projection())).toContain(''); }); it('keeps the binding score wording and never claims baseline compliance', () => { const html = buildManagementOverviewHtml(projection()); expect(html).toContain('Konformität der konfigurierten, vergleichbaren'); expect(html).not.toMatch(/Baseline-?Compliance/i); expect(html).not.toMatch(/Baseline-?Abdeckung/i); }); it('keeps established English technical terms untranslated', () => { const html = buildManagementOverviewHtml(projection({ domains: [{ domainId: 'gpo', domainLabel: 'GPO & Baseline', passed: 1, deviations: 1, assessed: 2, passRate: 0.5 }], priorities: [{ domainId: 'identity', domainLabel: 'Identity & Account Hygiene', deviations: 1, sources: [{ toolId: 'purple-knight', basis: { kind: 'severity' }, ranked: true, shown: [finding()], total: 1 }] }] })); expect(html).toContain('Priority Findings'); expect(html).toContain('Overall Score'); expect(html).toContain('Security Domains'); expect(html).toContain('Deviations'); expect(html).toContain('>Critical<'); expect(html).toContain('GPO & Baseline'); // German carries the prose around the English terms. expect(html).toContain('Gruppiert nach Security Domain'); }); it('renders an unknown value as an em dash and never as a zero', () => { const html = buildManagementOverviewHtml(projection({ overallScore: undefined })); expect(html).toContain('—'); // No zero-length arc is drawn, and the readout shows no fabricated value. expect(html).not.toContain(''); expect(html).toContain('nicht verfügbar'); expect(html).toContain('—'); }); it('draws the donut as a full circle starting at twelve o\'clock with flat caps', () => { const html = buildManagementOverviewHtml(projection({ overallScore: 50 })); expect(html).toContain('rotate(-90 64 64)'); expect(html).toContain('stroke-linecap:butt'); expect(html).toContain('donut__track'); }); it('names the ordering basis of every source and never merges them', () => { const html = buildManagementOverviewHtml(projection({ priorities: [{ domainId: 'hardening', domainLabel: 'System Hardening', deviations: 2, sources: [ { toolId: 'cis-cat', basis: { kind: 'equal-weight', reason: 'CIS benchmarks state no criticality.' }, ranked: false, shown: [finding({ severity: 'unknown', toolId: 'cis-cat' })], total: 1 }, { toolId: 'pingcastle', basis: { kind: 'numeric', property: 'rule.points', scale: '0-50+', direction: 'higher-is-worse' }, ranked: true, shown: [finding({ toolId: 'pingcastle', severity: 'medium', rankValue: 35, rankBasis: { kind: 'numeric', property: 'rule.points', scale: '0-50+', direction: 'higher-is-worse' } })], total: 1 } ] }] })); expect(html).toContain('Ohne Rangfolge: CIS benchmarks state no criticality.'); expect(html).toContain('Sortiert nach rule.points (0-50+, höher ist kritischer)'); expect(html).toContain('quellenübergreifende Rangfolge'); }); it('points at the technical report instead of silently truncating a domain', () => { const html = buildManagementOverviewHtml(projection({ priorities: [{ domainId: 'identity', domainLabel: 'Identity & Account Hygiene', deviations: 9, sources: [{ toolId: 'purple-knight', basis: { kind: 'severity' }, ranked: true, shown: [finding()], total: 9 }] }] })); expect(html).toContain('8 weitere Deviations dieser Quelle stehen im Technical Report.'); }); it('keeps GPO equal-weight and marks the score-neutral verdicts as such', () => { const html = buildManagementOverviewHtml(projection({ gpo: { basis: { kind: 'equal-weight', reason: 'A baseline deviation carries no native criticality.' }, counts: { Fail: 3, Pass: 10, 'Not configured': 40, 'No baseline rule': 5, 'Not comparable': 2, 'Review required': 1 }, conflicts: 1, duplicates: 2, affectedSettings: 3, affectedBackups: 2, scopeLimited: true } })); expect(html).toContain('A baseline deviation carries no native criticality.'); expect(html).toContain('gleichgewichtig'); expect(html).toContain('sind score-neutral und begründen keine Maßnahme'); expect(html).toContain('trifft keine Aussage über die gesamte Domäne'); }); it('shows GPO deviations once in the GPO block instead of per comparison in Priority Findings', () => { const html = buildManagementOverviewHtml(projection({ priorities: [{ domainId: 'gpo', domainLabel: 'GPO & Baseline', deviations: 4, sources: [ { toolId: 'pingcastle', basis: { kind: 'severity' }, ranked: true, shown: [finding({ toolId: 'pingcastle', title: 'Synthetic scanner item' })], total: 1 }, { toolId: 'GPOs', basis: { kind: 'equal-weight', reason: 'A baseline deviation carries no native criticality.' }, ranked: false, shown: [1, 2, 3].map(index => finding({ toolId: 'GPOs', severity: 'unknown', title: `Synthetic comparison ${index}` })), total: 3 } ] }], findings: [finding({ toolId: 'pingcastle' }), ...[1, 2, 3].map(() => finding({ toolId: 'GPOs', severity: 'unknown' }))], gpo: { basis: { kind: 'equal-weight', reason: 'A baseline deviation carries no native criticality.' }, counts: { Fail: 1, Pass: 0, 'Not configured': 0, 'No baseline rule': 0, 'Not comparable': 0, 'Review required': 0 }, conflicts: 0, duplicates: 0, affectedSettings: 1, affectedBackups: 1, scopeLimited: false } })); expect(html).toContain('Synthetic scanner item'); expect(html).not.toContain('Synthetic comparison'); expect(html).toContain('1 Deviations'); // One scanner finding plus one GPO policy setting, not three comparisons. expect(html).toContain('1 aus Scans · 1 GPO Policy Settings'); }); it('omits the remediation section entirely when no reviews exist', () => { expect(buildManagementOverviewHtml(projection())).not.toContain('Remediation-Status'); }); it('marks remediation as self reported and not as technical proof', () => { const html = buildManagementOverviewHtml(projection({ remediation: { selfReported: true, items: 4, byStatus: { Actionable: 4 }, withoutOwner: 1, withoutDue: 2, riskAccepted: 0 } })); expect(html).toContain('Remediation-Status'); expect(html).toContain('Selbstauskunft'); expect(html).toContain('kein technischer Nachweis'); expect(html).toContain('Rescan'); }); it('names the score method per source and explains an inverted native risk', () => { const html = buildManagementOverviewHtml(projection({ scoreSources: [{ toolId: 'pingcastle', score: 55, method: 'inverted-native', weight: 1, passed: 0, deviations: 20, assessed: 20, observedAt: '2026-07-22T00:00:00.000Z', issues: [] }] })); expect(html).toContain('Nativer Risk, invertiert als 100 − Risk'); expect(html).toContain('100 − Risk in den Index ein'); expect(html).toContain('22.07.2026'); }); it('escapes hostile content from a finding title', () => { const html = buildManagementOverviewHtml(projection({ priorities: [{ domainId: 'identity', domainLabel: 'Identity & Account Hygiene', deviations: 1, sources: [{ toolId: 'purple-knight', basis: { kind: 'severity' }, ranked: true, total: 1, shown: [finding({ title: '' })] }] }] })); expect(html).toContain('<img src=x onerror=alert(1)>'); expect(html).not.toMatch(/ { it('keeps the agreed section order and heading levels', async () => { const { JSDOM } = await import('jsdom'); const html = buildManagementOverviewHtml(projection({ remediation: { selfReported: true, items: 1, byStatus: {}, withoutOwner: 0, withoutDue: 0, riskAccepted: 0 } })); const { document } = new JSDOM(html).window; expect([...document.querySelectorAll('h1')].map(node => node.textContent)) .toEqual(['Synthetic GmbH']); expect([...document.querySelectorAll('h2')].map(node => node.textContent)).toEqual([ 'Overall Posture', 'Priority Findings', 'Remediation-Status', 'Abdeckung und Lücken', 'Konvergenz der Quellen', 'Maßnahmenbündel', 'Source Overview' ]); // Every section is labelled for assistive technology. for (const section of document.querySelectorAll('section.segment')) { expect(section.getAttribute('aria-labelledby')).toBeTruthy(); } }); it('never nests tables', async () => { const { JSDOM } = await import('jsdom'); const { document } = new JSDOM(buildManagementOverviewHtml(projection({ coverage: { total: 1, assessed: 1, deviations: 1, passed: 0, notRun: 0, notApplicable: 0, executionErrors: 0, unknown: 0, perSource: [{ toolId: 'alpha', total: 1, assessed: 1, notRun: 0 }] } }))).window; expect(document.querySelectorAll('table table')).toHaveLength(0); }); it('draws the value arc proportional to the score on a full circle', async () => { const { JSDOM } = await import('jsdom'); const { document } = new JSDOM(buildManagementOverviewHtml(projection({ overallScore: 25 }))).window; const arc = document.querySelector('circle.donut__value')!; const [drawn, gap] = arc.getAttribute('stroke-dasharray')!.split(' ').map(Number); const circumference = 2 * Math.PI * 54; expect(drawn + gap).toBeCloseTo(circumference, 2); expect(drawn / circumference).toBeCloseTo(0.25, 4); }); it('filters Priority Findings by severity chip and search through the inline script', async () => { const { JSDOM } = await import('jsdom'); const html = buildManagementOverviewHtml(projection({ priorities: [{ domainId: 'identity', domainLabel: 'Identity & Account Hygiene', deviations: 2, sources: [{ toolId: 'purple-knight', basis: { kind: 'severity' }, ranked: true, total: 2, shown: [finding({ title: 'Synthetic critical' }), finding({ resultProjectionId: 'synthetic-2', title: 'Synthetic high', severity: 'high' })] }] }], findings: [finding(), finding({ resultProjectionId: 'synthetic-2', severity: 'high' })] })); const { document } = new JSDOM(html, { runScripts: 'dangerously' }).window; const rows = [...document.querySelectorAll('[data-filter-item]')]; const visible = () => rows.filter(row => !row.hidden).map(row => row.querySelector('.finding__title')!.textContent); expect(document.querySelector('[data-filter-toolbar]')!.hidden).toBe(false); expect(visible()).toEqual(['Synthetic critical', 'Synthetic high']); document.querySelector('[data-filter-value="critical"]')!.click(); expect(visible()).toEqual(['Synthetic critical']); expect(document.querySelector('[data-filter-reset]')!.getAttribute('aria-pressed')).toBe('false'); document.querySelector('[data-filter-reset]')!.click(); const search = document.querySelector('[data-filter-search]')!; search.value = 'high'; search.dispatchEvent(new document.defaultView!.Event('input')); expect(visible()).toEqual(['Synthetic high']); expect(document.querySelector('[data-filter-count]')!.textContent).toBe('1 / 2'); }); it('keeps every finding readable when the script does not run', async () => { const { JSDOM } = await import('jsdom'); const { document } = new JSDOM(buildManagementOverviewHtml(projection({ priorities: [{ domainId: 'identity', domainLabel: 'Identity & Account Hygiene', deviations: 1, sources: [{ toolId: 'purple-knight', basis: { kind: 'severity' }, ranked: true, shown: [finding()], total: 1 }] }], findings: [finding(), finding({ resultProjectionId: 'synthetic-2', severity: 'high' })] }))).window; expect(document.querySelector('[data-filter-toolbar]')!.hidden).toBe(true); expect([...document.querySelectorAll('[data-filter-item]')].every(row => !row.hidden)).toBe(true); }); it('clamps an out-of-range score instead of drawing past the ring', async () => { const { JSDOM } = await import('jsdom'); const { document } = new JSDOM(buildManagementOverviewHtml(projection({ overallScore: 140 }))).window; const [drawn, gap] = document.querySelector('circle.donut__value')! .getAttribute('stroke-dasharray')!.split(' ').map(Number); expect(gap).toBeCloseTo(0, 3); expect(drawn).toBeCloseTo(2 * Math.PI * 54, 2); }); });