Assessor_2.0/tests/exports/management-overview-report....

295 lines
14 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { describe, expect, it } from 'vitest';
import { buildManagementOverviewHtml } from '../../src/exports/management-overview-report';
import type { ReportProjection } from '../../src/domain/report-projection';
import { validAiOverviewResult } from '../fixtures/ai-overview';
const ai = () => ({ brief: validAiOverviewResult().managementBrief!, generatedAt: '2026-09-27T09:00:00.000Z' });
function projection(overrides: Partial<ReportProjection> = {}): 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('<!doctype html>')).toBe(true);
expect(html).not.toMatch(/<script\b[^>]*\bsrc\s*=/i);
expect(html).not.toMatch(/\son[a-z]+\s*=/i);
expect(html).not.toMatch(/https?:\/\//i);
expect(html).not.toMatch(/<link\b/i);
// The only url() is the embedded font; nothing is fetched when opened.
expect(html.match(/url\(/gi)).toHaveLength(1);
expect(html).toContain("url('data:font/woff2;base64,");
});
it('renders for customers in light mode only', () => {
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('<html lang="de">');
});
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 }],
findings: [finding()]
}));
expect(html).toContain('Overall Score');
expect(html).toContain('Security Domains');
expect(html).toContain('Deviations');
expect(html).toContain('GPO &amp; Baseline');
// German carries the prose around the English terms.
expect(html).toContain('Pass Rate je Domain');
});
it('shows no severity, because not every tool provides one', () => {
const html = buildManagementOverviewHtml(projection({
findings: [finding(), finding({ resultProjectionId: 'synthetic-2', severity: 'high' }), finding({ resultProjectionId: 'synthetic-3', toolId: 'cis-cat', 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
}
}), ai());
const visible = html.slice(html.indexOf('<main'), html.indexOf('<script>'));
expect(visible).not.toMatch(/severity|critical|kritikalität/i);
expect(visible).not.toMatch(/>(High|Medium|Low|Info)</);
// All deviations still count, whatever severity their tool provides.
expect(visible).toContain('3 aus Scans · 1 GPO Policy Settings');
});
it('gives a brief standing without listing single findings', () => {
const html = buildManagementOverviewHtml(projection({
priorities: [{
domainId: 'identity', domainLabel: 'Identity & Account Hygiene', deviations: 2,
sources: [{ toolId: 'purple-knight', basis: { kind: 'severity' }, ranked: true, shown: [finding({ title: 'Synthetic single finding' })], total: 2 }]
}],
findings: [finding({ title: 'Synthetic single finding' }), finding({ resultProjectionId: 'synthetic-2', severity: 'high' })]
}));
expect(html).not.toContain('Synthetic single finding');
expect(html).not.toContain('Priority Findings');
expect(html).not.toContain('<li class="finding"');
// The counts still carry the standing.
expect(html).toContain('2 aus Scans');
});
it('shows each domain on one meter row with singular and plural deviations', async () => {
const { JSDOM } = await import('jsdom');
const { document } = new JSDOM(buildManagementOverviewHtml(projection({
domains: [
{ domainId: 'identity', domainLabel: 'Identity & Account Hygiene', passed: 13, deviations: 1, assessed: 14, passRate: 13 / 14 },
{ domainId: 'hardening', domainLabel: 'System Hardening', passed: 0, deviations: 13, assessed: 13, passRate: 0 },
{ domainId: 'cloud', domainLabel: 'Cloud & Applications', passed: 0, deviations: 0, assessed: 0 }
]
}))).window;
const rows = [...document.querySelectorAll('.meters--detail li')].map(row => [...row.children].map(cell => cell.textContent!.trim()));
expect(rows).toEqual([
['Identity & Account Hygiene', '', '93 %', '1 Deviation von 14 bewertet'],
['System Hardening', '', '0 %', '13 Deviations von 13 bewertet'],
['Cloud & Applications', '', '—', 'nicht bewertet']
]);
});
it('keeps explanatory text to the single binding score line', () => {
const html = buildManagementOverviewHtml(projection({
coverage: {
total: 10, assessed: 7, deviations: 3, passed: 4, notRun: 2,
notApplicable: 1, executionErrors: 0, unknown: 0, perSource: [{ toolId: 'alpha', total: 10, assessed: 7, notRun: 2 }]
}
}));
const visible = html.slice(html.indexOf('<main'), html.indexOf('<script>'));
expect(visible.match(/class="(?:note|section__lead)"/g)).toHaveLength(1);
for (const filler of ['Ergebnissen wurden', 'nicht ausgeführt', 'Zerlegung des Index', 'importierten GPO-Backup-Satz', 'datenschutzbereinigten']) {
expect(visible).not.toContain(filler);
}
expect(html).not.toContain('<table');
});
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('<circle class="donut__value"');
expect(html).not.toContain('<span class="donut__unit">');
expect(html).toContain('nicht verfügbar');
expect(html).toContain('<span class="donut__number">—</span>');
});
it('makes a positive score below two percent legible without changing its proportional arc', () => {
const html = buildManagementOverviewHtml(projection({ overallScore: 1 }));
expect(html).toContain('class="donut__small-arc"');
expect(html).toContain('stroke-dasharray="3.393 335.899"');
expect(buildManagementOverviewHtml(projection({ overallScore: 0.2 }))).toContain('&lt;1<span class="donut__unit">/100</span>');
expect(buildManagementOverviewHtml(projection({ overallScore: 0 }))).not.toContain('class="donut__small-arc"');
});
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('has no separate GPO section but keeps the GPO counts', () => {
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).not.toContain('<h2 id="gpo"');
expect(html).not.toContain('Betroffene GPO-Objekte');
expect(html).toContain('0 aus Scans · 3 GPO Policy Settings');
});
it('counts GPO deviations once per policy setting, not per comparison', () => {
const html = buildManagementOverviewHtml(projection({
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('1 aus Scans · 1 GPO Policy Settings');
});
it('dates each source', () => {
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('22.07.2026');
});
it('adds the German AI brief as marked advisory content', () => {
const html = buildManagementOverviewHtml(projection(), ai());
expect(html).toContain('Empfohlene Maßnahmen');
expect(html).toContain('KI-gestützt · 27.09.2026');
expect(html).toContain('Synthetische Maßnahme');
expect(html).toContain('Sofort (0–7 Tage) · IT-Sicherheit');
expect(html).toContain('Synthetisches Ziel.');
});
it('omits the AI section when no current brief is supplied', () => {
expect(buildManagementOverviewHtml(projection())).not.toContain('Empfohlene Maßnahmen');
});
it('escapes hostile content from the AI brief', () => {
const brief = ai();
brief.brief.priorities[0].title = '<img src=x onerror=alert(1)>';
const html = buildManagementOverviewHtml(projection(), brief);
expect(html).toContain('&lt;img src=x onerror=alert(1)&gt;');
expect(html).not.toMatch(/<img/i);
});
});
describe('management overview structure', () => {
it('keeps the agreed section order and heading levels', async () => {
const { JSDOM } = await import('jsdom');
const html = buildManagementOverviewHtml(projection({
gpo: {
basis: { kind: 'equal-weight', reason: 'A baseline deviation carries no native criticality.' },
counts: { Fail: 0, Pass: 1, 'Not configured': 0, 'No baseline rule': 0, 'Not comparable': 0, 'Review required': 0 },
conflicts: 0, duplicates: 0, affectedSettings: 0, affectedBackups: 0, scopeLimited: false
},
remediation: {
selfReported: true, items: 1, byStatus: {}, withoutOwner: 0, withoutDue: 0, riskAccepted: 0
}
}), ai());
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', 'Empfohlene Maßnahmen'
]);
// 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('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);
});
});