import { readFileSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; import type { FindingRecord } from '../../src/domain/finding'; import type { EnvironmentSnapshot } from '../../src/domain/environment-context'; import { dashboardCisCatSeverityRemainder, dashboardDomainInformation, dashboardEnvironmentSummary, dashboardExplicitIoeCount, dashboardExplicitIoeRecordIds, dashboardOverallScore, dashboardPingCastleRiskCategories, dashboardRiskTools, dashboardScanSourceLabel } from '../../src/features/dashboard/DashboardPanel'; import { securityDomainCatalog } from '../../src/features/dashboard/security-domains'; const component = readFileSync(new URL('../../src/features/dashboard/DashboardPanel.tsx', import.meta.url), 'utf8'); const styles = readFileSync(new URL('../../src/styles/datahub.css', import.meta.url), 'utf8'); describe('dashboard reference layout', () => { it('uses the requested summary-to-action client information hierarchy', () => { expect(component).toContain('className="client-overview-header"'); expect(component).toContain(''); expect(component.match(/Data'); expect(component).toContain('Domain Information'); expect(component).not.toContain('className="client-assessment-gaps"'); expect(component).toContain('Gaps'); expect(component).toContain(''); expect(component).toContain(' jump(() => setPathFindingIds([recordId]))} />'); expect(styles).toMatch(/\.dashboard-page\s*\{[^}]*container-type:\s*inline-size;/s); expect(component).toContain('className={`client-posture tone-${overallTone}`}'); expect(styles).toMatch(/\.dashboard-page\.client-command-center\s*\{[^}]*--client-info-surface-raised:\s*var\(--tile-surface\);[^}]*background:\s*var\(--surface-app\);/s); expect(component).not.toContain('className="client-overview-facts"'); expect(component).toContain('className="client-data-facts"'); expect(styles).not.toContain('.client-overview-fact'); expect(styles).toMatch(/\.client-data-facts\s*\{[^}]*grid-template-columns:\s*repeat\(3, minmax\(0, 1fr\)\);/s); expect(styles).toMatch(/\.client-data-fact\s*\{[^}]*background:\s*transparent;/s); expect(styles).toMatch(/\.client-overview-main\s*\{[^}]*grid-template-columns:\s*repeat\(2, minmax\(0, 1fr\)\);[^}]*align-items:\s*stretch;/s); expect(styles).toMatch(/\.client-priority\s*\{[^}]*grid-template-columns:\s*repeat\(2, minmax\(0, 1fr\)\);[^}]*align-content:\s*stretch;[^}]*align-items:\s*stretch;/s); expect(styles).not.toContain('.client-assessment-gaps'); expect(styles).toMatch(/@container \(max-width: 760px\)[\s\S]*?\.client-priority\s*\{[^}]*grid-template-columns:\s*1fr;/s); expect(styles).toMatch(/\.client-posture\s*\{[^}]*grid-template-columns:\s*minmax\(300px, \.82fr\) minmax\(430px, 1\.18fr\);/s); expect(styles).toMatch(/\.client-risk-register-layout\s*\{[^}]*grid-template-columns:\s*clamp\(420px, 27vw, 520px\) minmax\(0, 1fr\);[^}]*height:\s*calc\(var\(--tile-content-min-height\) \* 2\.5\);/s); expect(styles).toMatch(/\.client-risk-register-navigation\s*\{[^}]*border-right:\s*var\(--bw-hairline\) solid var\(--border-default\);/s); expect(styles).toMatch(/\.client-risk-register-information\s*\{[^}]*padding:\s*var\(--space-5\) var\(--space-6\);[^}]*overflow-y:\s*auto;/s); expect(styles).toMatch(/\.client-risk-register-list button\[aria-pressed="true"\]\s*\{[^}]*box-shadow:\s*inset var\(--marker-width\) 0 0 var\(--marking-color\);/s); expect(component).toContain('onOpenFinding={recordId => jump(() => setPathFindingIds([recordId]))}'); }); it('keeps source-balanced score, coverage and critical guardrail separate', () => { expect(dashboardOverallScore([{ score: 90 }, { score: 30 }, { score: undefined }])).toBe(60); expect(dashboardOverallScore([{ score: undefined }])).toBeUndefined(); expect(component).toContain(' jump(() => undefined, false)} />'); expect(component).toContain('Assessment Coverage'); expect(component).not.toContain('className="client-overall-status'); expect(component).not.toContain('className="client-overall-critical"'); expect(component).not.toContain('className="client-overall-secondary"'); expect(component).not.toContain('
Lowest domain pass rate
'); expect(component).toContain('dashboardOverallScore(sourceScores)'); expect(component).toContain("name: 'PingCastle'"); expect(component).toContain("name: 'GPO Analysis'"); expect(component).toContain("className=\"client-posture-connectors\""); expect(styles).toMatch(/\.client-overall-coverage\s*\{[^}]*width:\s*min\(70%, var\(--tile-content-width\)\);/s); expect(component).not.toContain('Design concept'); expect(component).not.toContain('Illustrative data'); }); it('shows the six structured security domains and all available sources', () => { // User decision 2026-10-04: six domains; GPO is a delivery path, not a domain. expect(securityDomainCatalog.map(domain => domain.label)).toEqual([ 'Identity & Authentication', 'Privileged Access & Delegation', 'AD Infrastructure & PKI', 'System Hardening', 'Cloud & Collaboration', 'Monitoring & Recovery' ]); expect(component).toContain('orderedSourceScores.map'); expect(component).toContain('nativePingCastleRisk'); expect(component).toContain('dashboardEnvironmentTableNumber'); expect(component).not.toContain('className="client-source-overview"'); expect(component).toContain('className="client-environment-sources"'); expect(styles).toMatch(/\.client-environment-sources ul\s*\{[^}]*list-style:\s*none;/s); expect(styles).toMatch(/\.client-environment-sources li > button\s*\{[^}]*cursor:\s*pointer;/s); expect(styles).not.toContain('.client-source-overview'); expect(component).toContain('dismissResults(); setScoringOpen(true);'); expect(styles).toMatch(/\.client-overall-ring-track\s*\{\s*stroke:\s*var\(--donut-track\);/); expect(component).toContain('{domain.label}'); expect(component).toContain('className="client-domain-amount-bar" aria-hidden="true"'); expect(component).toContain('const domainDistributionTotal = displayedDomains.reduce'); expect(styles).toMatch(/\.client-domains ul\s*\{[^}]*grid-template-columns:\s*42px max-content minmax\(var\(--space-8\), 1fr\) max-content;/s); expect(styles).toMatch(/\.client-domains li\s*\{[^}]*grid-template-columns:\s*subgrid;/s); expect(styles).toMatch(/\.client-domain-score\s*\{[^}]*grid-column:\s*1;[^}]*grid-row:\s*1;/s); expect(styles).toMatch(/\.client-domain-amount-bar\s*\{[^}]*grid-column:\s*3;[^}]*height:\s*var\(--space-6\);/s); expect(styles).toMatch(/\.client-domain-amount-bar > i\s*\{[^}]*right:\s*0;[^}]*width:\s*var\(--share\);/s); expect(styles).toMatch(/\.client-domain-amount-bar > i\s*\{[^}]*background:\s*var\(--chart-color, var\(--client-info-neutral\)\);/s); expect(component).toContain('const overallTone = dashboardScoreTone(overallScore);'); expect(component).toContain('className={`client-posture tone-${overallTone}`}'); expect(styles).not.toContain('--domain-amount-color'); expect(component).not.toContain('client-domain-copy">{domain.label}'); expect(styles).toMatch(/\.client-domain-copy strong\s*\{[^}]*font-size:\s*var\(--type-h4-size\);/s); expect(styles).toMatch(/\.client-domains li > div > b\s*\{[^}]*grid-column:\s*4;[^}]*text-align:\s*right;/s); }); it('uses deviations as the finding base and shows other result states separately', () => { expect(component).toContain("records: domainRecords.filter(record => states.get(record) === 'deviation')"); // Every check has exactly one of the six domains; there is no Unclassified node any more. expect(component).not.toContain("label: 'Unclassified'"); expect(component).toContain('const displayedDomains = domains;'); expect(component).not.toContain('total deviations'); expect(component).not.toContain("{reviewOnly ? 'Review required' : 'Deviations'}"); expect(component).toContain(""); expect(component).toContain('className="client-priority-context"'); expect(component).toContain('className="datahub-reference-knob client-priority-total"'); expect(component).toContain('percentage={share(deviations, total)} tone="deviation"'); expect(component.indexOf('className="client-priority-findings"')).toBeLessThan(component.indexOf('className="client-domain-information"')); expect(component).not.toContain('criticalSeverity'); expect(component).not.toContain('highSeverity'); expect(styles).toMatch(/\.client-priority-findings > header\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\);/s); expect(styles).toMatch(/\.client-priority-total\s*\{[^}]*--donut-ring-size:\s*var\(--tile-compact-width\);[^}]*width:\s*var\(--tile-compact-width\);[^}]*min-height:\s*0;[^}]*padding:\s*0;[^}]*cursor:\s*pointer;/s); expect(styles).toMatch(/\.client-priority-total\s*\{[^}]*border-color:\s*var\(--segment-border\);/s); expect(styles).not.toContain('.client-priority-total .donut-metric-card__plot'); expect(styles).not.toMatch(/\.client-priority-total\s*\{[^}]*(?:background|border-radius|box-shadow):/s); expect(styles).not.toContain('.client-priority-total .donut-metric-card__title'); expect(component).not.toContain('
    {priorityRecords.length'); expect(component).not.toContain('No critical or high deviations'); expect(component).not.toContain('View findings →'); ['Passed', 'Not configured', 'Duplicates', 'Conflicts'] .forEach(label => expect(component).toContain(`'${label}'`)); expect(styles).toMatch(/\.client-priority-context\s*\{[^}]*grid-template-columns:\s*repeat\(5, minmax\(0, 1fr\)\);/s); expect(component.indexOf('className="client-data-facts"')).toBeGreaterThan(component.indexOf('className="client-priority-context"')); expect((component.match(/ header h2\s*\{[^}]*font-size:\s*var\(--type-h4-size\);/s); expect(styles).toMatch(/\.client-domain-information dl > div:first-child\s*\{[^}]*grid-column:\s*1 \/ -1;/s); expect(styles).toMatch(/\.client-domain-information dt\s*\{[^}]*font-size:\s*var\(--type-table-body-size\);/s); expect(styles).toMatch(/\.client-env-facts dt\s*\{[^}]*font-weight:\s*var\(--fw-bold\);/s); }); it('derives card values only from normalized environment snapshot contracts', () => { const observation = (property: string, label: string, value: string | number | boolean, sectionKey?: string) => ({ version: 1 as const, id: `${property}-${label}`, scanRunId: 'run-1', importFileId: 'file-1', tool: { id: 'synthetic', name: 'Synthetic', version: '1' }, subject: { type: 'directory' }, property, label, originalValue: value, normalizedValue: value, valueType: typeof value === 'number' ? 'number' as const : typeof value === 'boolean' ? 'boolean' as const : 'string' as const, state: 'observed' as const, sectionKey, source: { rowIndex: 0 }, mappingVersion: '1', attributes: {} }); const snapshot = { version: 1, projectId: 'project-1', scanRunIds: ['run-1'], generatedAt: '2026-08-25T00:00:00.000Z', coverage: { observed: 4, explicitlyAbsent: 1, empty: 2, restricted: 3, incomplete: 0 }, sections: [{ version: 1, id: 'section-1', scanRunId: 'run-1', importFileId: 'file-1', tool: { id: 'synthetic', name: 'Synthetic', version: '1' }, sectionKey: 'sectionAdminGroups', label: 'Groups', ordinal: 1, state: 'license-restricted', tables: [], source: { rowIndex: 0 }, extractorVersion: '1', attributes: {} }], observations: [ observation('domain.fqdn', 'Domain', 'example.local'), observation('risk.anomalies', 'Anomalies score', 42), observation('admin.name', 'Name', 'Admin A', 'sectionAdminGroups') ] } satisfies EnvironmentSnapshot; const summary = dashboardEnvironmentSummary(snapshot); expect(summary.directoryFacts[0]).toEqual({ label: 'Domain', value: 'example.local' }); expect(summary.directoryFacts.map(item => item.label)).not.toContain('PingCastle'); expect(summary.coverage.map(item => item.value)).toEqual([4, 2, 1, 3, 0]); expect(summary.identity[0]).toMatchObject({ label: 'Admin groups', value: 1, restricted: true }); expect(summary.risk[1]).toMatchObject({ label: 'Anomalies', value: 42 }); }); it('shows source-backed domain facts beside priority findings without inferred zeroes', () => { const observation = (property: string, label: string, value: string | number) => ({ version: 1 as const, id: `${property}-${label}`, scanRunId: 'run-1', importFileId: 'file-1', tool: { id: 'synthetic', name: 'Synthetic', version: '1' }, subject: { type: 'directory' }, property, label, originalValue: value, normalizedValue: value, valueType: typeof value === 'number' ? 'number' as const : 'string' as const, state: 'observed' as const, source: { rowIndex: 0 }, mappingVersion: '1', attributes: {} }); const snapshot = { version: 1, projectId: 'project-1', scanRunIds: ['run-1'], generatedAt: '2026-09-20T00:00:00.000Z', coverage: { observed: 3, explicitlyAbsent: 0, empty: 0, restricted: 0, incomplete: 0 }, sections: [], observations: [ observation('domain.fqdn', 'Domain', 'example.local'), observation('domain.netbios_name', 'Netbios Name', 'EXAMPLE'), observation('forest.name', 'Forest', 'example.local'), observation('domain.functional_level', 'Domain Functional Level', 'Windows Server 2016'), observation('domain.controller_count', 'DC count', 3), observation('forest.functional_level', 'Forest Functional Level', 'Windows Server 2016'), observation('forest.schema_version', 'Schema version', 'Windows Server 2019'), observation('domain.created_at', 'Creation date', '2026-05-27 10:24:53Z'), observation('entra.configuration_detected', 'Entra ID configuration detected', 'Not detected') ] } satisfies EnvironmentSnapshot; expect(dashboardDomainInformation(snapshot)).toEqual([ { label: 'Domain', value: 'example.local' }, { label: 'NetBIOS name', value: 'EXAMPLE' }, { label: 'Forest', value: 'example.local' }, { label: 'Domains', value: '1' }, { label: 'Domain controllers', value: '3' }, { label: 'Domain functional level', value: 'Windows Server 2016' }, { label: 'Forest functional level', value: 'Windows Server 2016' }, { label: 'DC OS', value: '—' }, { label: 'Schema version', value: 'Windows Server 2019' }, { label: 'Domain created', value: '2026-05-27 10:24:53Z' }, { label: 'Entra ID', value: 'Not detected' } ]); }); it('shows DC OS only from observed domain-controller rows and retains distinct DC versions', () => { const os = (id: string, type: string, value: string, state: 'observed' | 'unknown' = 'observed') => ({ version: 1 as const, id, scanRunId: 'run-1', importFileId: 'file-1', tool: { id: 'synthetic', name: 'Synthetic', version: '1' }, subject: { type }, property: 'operating_system.name', label: 'Operating System', originalValue: value, normalizedValue: value, valueType: 'string' as const, state, source: { rowIndex: 0 }, mappingVersion: '1', attributes: {} }); const base = { version: 1 as const, projectId: 'project-1', scanRunIds: ['run-1'], generatedAt: '2026-09-24T00:00:00.000Z', coverage: { observed: 0, explicitlyAbsent: 0, empty: 0, restricted: 0, incomplete: 0 }, sections: [] }; const general = os('general', 'computer-information.operating-systems', 'Windows Server 2022'); const dc = os('dc', 'computer-information.domain-controllers', 'Windows 2019'); expect(dashboardDomainInformation({ ...base, observations: [general] }).find(item => item.label === 'DC OS')?.value).toBe('—'); expect(dashboardDomainInformation({ ...base, observations: [general, dc, os('dc2', dc.subject.type, 'Windows 2019'), os('dc3', dc.subject.type, 'Windows Server 2025'), os('unknown', dc.subject.type, 'Windows 2008', 'unknown')] }) .find(item => item.label === 'DC OS')?.value).toBe('Windows 2019, Windows Server 2025'); }); it('removes only a trailing scan timestamp from source labels', () => { expect(dashboardScanSourceLabel('PurpleKnight · 2026-08-22T08:15:30.000Z')).toBe('PurpleKnight'); expect(dashboardScanSourceLabel('CIS-CAT Pro · 2026-08-22')).toBe('CIS-CAT Pro'); expect(dashboardScanSourceLabel('Scanner · Production')).toBe('Scanner · Production'); }); it('groups scan runs into one selectable risk profile per tool', () => { expect(dashboardRiskTools([ { value: 'pingcastle-a', label: 'PingCastle · 2026-08-22T08:15:30.000Z', count: 20 }, { value: 'pingcastle-b', label: 'PingCastle · 2026-08-23T08:15:30.000Z', count: 24 }, { value: 'purpleknight-a', label: 'PurpleKnight · 2026-08-23T09:00:00.000Z', count: 30 } ])).toEqual([ { id: 'pingcastle', label: 'PingCastle', sourceValues: ['pingcastle-a', 'pingcastle-b'], count: 44 }, { id: 'purpleknight', label: 'PurpleKnight', sourceValues: ['purpleknight-a'], count: 30 } ]); }); it('adds only the severity-less CIS-CAT share as a neutral distribution segment', () => { expect(dashboardCisCatSeverityRemainder([ { value: 'cis-cat-run', label: 'CIS-CAT Pro · 2026-08-17', count: 411 }, { value: 'maester-run', label: 'Maester · 2026-07-28', count: 318 } ], [])).toEqual({ value: 'dashboard-cis-cat-without-severity', label: 'CIS-CAT', count: 411, sourceValues: ['cis-cat-run'] }); expect(dashboardCisCatSeverityRemainder([ { value: 'cis-cat-run', label: 'CIS-CAT Pro · 2026-08-17', count: 411 } ], [{ value: 'high', label: 'High', count: 12 }])?.count).toBe(399); expect(styles).toContain('.severity-dashboard-cis-cat-without-severity { --severity: var(--donut-not-run); }'); }); it('keeps the complete PingCastle risk catalog including zero scores', () => { expect(dashboardPingCastleRiskCategories([ { property: 'risk.anomalies', normalizedValue: 20, originalValue: '20' }, { property: 'risk.stale_objects', normalizedValue: 10, originalValue: '10' }, { property: 'risk.privileged_accounts', normalizedValue: 7, originalValue: '7' }, { property: 'risk.trusts', normalizedValue: 0, originalValue: '0' } ], [])).toEqual([ { value: 'anomalies', label: 'Anomalies', count: 20 }, { value: 'stale objects', label: 'Stale Objects', count: 10 }, { value: 'privileged accounts', label: 'Privileged Accounts', count: 7 }, { value: 'trusts', label: 'Trusts', count: 0 } ]); }); it('counts only explicit IOE-found source results for the top summary', () => { const record = (id: string, value: string): FindingRecord => ({ id, index: 0, title: id, fields: [{ index: 0, label: 'Result', value }], filled: [], meta: [] }); expect(dashboardExplicitIoeCount([ record('ioe', 'IOE Found'), record('ioes', 'IOEs found'), record('deviation', 'Failed'), record('pass', 'Passed') ])).toBe(2); expect(dashboardExplicitIoeRecordIds([ record('ioe', 'IOE Found'), record('ioes', 'IOEs found'), record('deviation', 'Failed') ])).toEqual(['ioe', 'ioes']); }); });