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, dashboardEnvironmentSummary, dashboardExplicitIoeCount, dashboardExplicitIoeRecordIds, dashboardPingCastleRiskCategories, dashboardRiskTools, dashboardScanSourceLabel } from '../../src/features/dashboard/DashboardPanel'; 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 exactly six compact square metrics with the existing timeslot context', () => { expect(component).toContain('className="client-info-tile-grid"'); expect(component).toContain('className="client-info-metric-grid"'); expect(component).toContain(''); expect(styles).toMatch(/\.dashboard-page\s*\{[^}]*container-type:\s*inline-size;/s); expect(styles).toContain('.dashboard-context .work-timeslot-select { margin-left: auto; }'); expect(component.match(/ { expect(component).toContain('className="client-info-card-grid"'); expect(component.match(/ expect(component).toContain(`title="${title}"`)); expect(styles).toMatch(/\.client-info-card-grid\s*\{[^}]*display:\s*contents;/s); expect(styles).toMatch(/\.client-info-card\s*\{[^}]*grid-column:\s*span 2;[^}]*grid-row:\s*span 2;[^}]*aspect-ratio:\s*1;[^}]*min-height:\s*0;[^}]*border:\s*0;[^}]*border-radius:\s*var\(--radius-sm\);[^}]*background:\s*var\(--tile-surface\);[^}]*box-shadow:\s*none;/s); expect(styles).toMatch(/\.client-info-card > header\s*\{[^}]*padding-bottom:\s*0;[^}]*border-bottom:\s*0;/s); expect(component).not.toContain('className="dashboard-status-rack"'); expect(component).not.toContain('className="dashboard-lower-grid"'); }); it('projects normalized scanner context into reusable fact, bar and donut displays', () => { expect(component).toContain('dashboardEnvironmentSummary(environment.snapshot)'); expect(component).toContain('className="client-info-donut"'); expect(component).toContain('className="client-info-bars"'); expect(component).toContain('className="client-info-facts"'); expect(component).toContain("sectionObservationCount(snapshot, 'sectionAdminGroups')"); expect(component).toContain("observationNumber(snapshot, 'risk.anomalies', 'Anomalies score')"); expect(styles).toContain('.client-info-donut-track { stroke: var(--donut-track); }'); expect(styles).not.toMatch(/\.client-info-card\s*\{[^}]*border-left:/s); expect(styles).not.toMatch(/\.client-info-card\s*\{[^}]*overflow:\s*hidden;/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.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('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']); }); });