import { readFileSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; import type { EnvironmentSnapshot, ScannerReportSection } from '../../src/domain/environment-context'; import { dashboardEnvironmentDetail } 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'); const tool = { id: 'synthetic', name: 'Synthetic', version: '1' }; function observation(property: string, label: string, value: string | number | boolean, state: 'observed' | 'explicitly-absent' = 'observed') { return { version: 1 as const, id: `${property}-${label}`, scanRunId: 'run-1', importFileId: 'file-1', tool, 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, source: { rowIndex: 0 }, mappingVersion: '1', attributes: {} }; } function section(sectionKey: string, label: string, tables: ScannerReportSection['tables'], state: ScannerReportSection['state'] = 'observed'): ScannerReportSection { return { version: 1, id: `section-${sectionKey}`, scanRunId: 'run-1', importFileId: 'file-1', tool, sectionKey, label, ordinal: 1, state, tables, source: { rowIndex: 0 }, extractorVersion: '1', attributes: {} }; } const snapshot = { version: 1, projectId: 'project-1', scanRunIds: ['run-1'], generatedAt: '2026-08-25T00:00:00.000Z', coverage: { observed: 6, explicitlyAbsent: 1, empty: 0, restricted: 1, incomplete: 0 }, sections: [ section('sectionPasswordPolicies', 'Password policies', [{ name: 'Password policies list', headers: ['Policy Name', 'Complexity', 'Min Password Length', 'Password History'], rows: [['Default Domain Policy " data-bs-html="true" title="" data-bs-original-title=" Linked to: DC=synthetic ">', 'False', '5', '24']] }]), section('sectionPKI', 'Certificates', [ { name: 'Certificate Templates list', headers: ['Name'], rows: [['Template A'], ['Template B']] }, { name: 'Certificates list', headers: ['Subject'], rows: [['Certificate A']] } ]), section('sectionUserInformation', 'Honey Pot', [{ name: 'Account analysis', headers: ['Nb User Accounts', 'Nb Enabled', 'Nb Disabled', 'Nb pwd never Expire'], rows: [[16, 15, 1, 15]] }], 'license-restricted'), section('sectionInfrastructure', 'Microsoft Entra Connect settings', [], 'license-restricted') ], observations: [ observation('domain.fqdn', 'Domain', 'synthetic.example'), observation('domain.netbios_name', 'Netbios Name', 'SYNTHETIC'), observation('forest.schema_version', 'Schema version', 'Windows Server 2019'), observation('domain.controller_count', 'DC count', 1), observation('domain.count', 'Number of domains', 2), observation('operating_system.name', 'Operating System', 'Synthetic Server OS'), observation('entra.configuration_detected', 'Entra ID configuration detected', false, 'explicitly-absent'), observation('purple_knight.ad.security_posture_score', 'security posture score', 65), observation('purple_knight.ad.ioes_found', 'IOEs found', 19), observation('purple_knight.ad.category_score.kerberos_security', 'Kerberos Security', 98), observation('benchmark.name', 'Benchmark', 'Synthetic CIS Benchmark'), observation('benchmark.score', 'Benchmark score', 61), observation('host.name', 'Assessed host', 'SYNTHETIC-HOST-01') ] } satisfies EnvironmentSnapshot; function fact(items: Array<{ label: string; value: string }>, label: string): string | undefined { return items.find(item => item.label === label)?.value; } describe('client info environment detail', () => { it('aggregates directory and inventory facts from the snapshot contract', () => { const detail = dashboardEnvironmentDetail(snapshot); expect(fact(detail.directory, 'Domain')).toBe('synthetic.example'); expect(fact(detail.directory, 'NetBIOS name')).toBe('SYNTHETIC'); expect(fact(detail.directory, 'Schema version')).toBe('Windows Server 2019'); expect(fact(detail.directory, 'Domains')).toBe('2'); expect(fact(detail.directory, 'Entra ID')).toBe('Not detected'); expect(fact(detail.inventory, 'User accounts')).toBe('16'); expect(fact(detail.inventory, 'Enabled / disabled')).toBe('15 / 1'); expect(fact(detail.inventory, 'Password never expires')).toBe('15'); expect(fact(detail.inventory, 'Domain controllers')).toBe('1'); expect(detail.operatingSystems.flatMap(group => group.entries)).toEqual([ expect.objectContaining({ name: 'Synthetic Server OS' }) ]); expect(detail.operatingSystems.flatMap(group => group.entries)[0].count).toBeUndefined(); expect(fact(detail.inventory, 'Assessed host')).toBe('SYNTHETIC-HOST-01'); }); it('renders unknown facts as an em dash instead of an inferred zero', () => { const empty = dashboardEnvironmentDetail(undefined); expect(new Set(empty.directory.map(item => item.value))).toEqual(new Set(['—'])); expect(new Set(empty.inventory.map(item => item.value))).toEqual(new Set(['—', '— / —'])); expect(new Set(empty.certificates.map(item => item.value))).toEqual(new Set(['—'])); expect(fact(dashboardEnvironmentDetail(snapshot).inventory, 'Trusts')).toBe('—'); }); it('does not infer a zero for present numeric facts that are empty, null, or boolean', () => { const blank = { version: 1, projectId: 'project-1', scanRunIds: ['run-1'], generatedAt: '2026-08-25T00:00:00.000Z', coverage: { observed: 4, explicitlyAbsent: 0, empty: 0, restricted: 0, incomplete: 0 }, sections: [ section('sectionUserInformation', 'User information', [{ name: 'Account analysis', headers: ['Nb User Accounts', 'Nb Enabled', 'Nb Disabled'], rows: [['', '', '']] }]) ], observations: [ observation('benchmark.score', 'Benchmark score', '' as unknown as number), observation('purple_knight.ad.ioes_found', 'IOEs found', null as unknown as number), observation('purple_knight.ad.security_posture_score', 'security posture score', false) ] } satisfies EnvironmentSnapshot; const detail = dashboardEnvironmentDetail(blank); // A present observation whose value is an empty string, explicit null, or // boolean must stay an em dash: Number('')/Number(null)/Number(false) are all 0. expect(fact(detail.toolContext, 'CIS-CAT score')).toBe('—'); expect(fact(detail.toolContext, 'Purple Knight AD IOEs')).toBe('—'); expect(fact(detail.toolContext, 'Purple Knight AD posture')).toBe('—'); // A table column whose cells are all blank must not sum to a fabricated 0. expect(fact(detail.inventory, 'User accounts')).toBe('—'); }); it('reads the password policy and PKI counts from their own report tables', () => { const detail = dashboardEnvironmentDetail(snapshot); expect(fact(detail.passwordPolicy, 'Policy')).toBe('Default Domain Policy'); expect(fact(detail.passwordPolicy, 'Complexity')).toBe('False'); expect(fact(detail.passwordPolicy, 'Minimum length')).toBe('5'); expect(fact(detail.passwordPolicy, 'History')).toBe('24'); expect(fact(detail.certificates, 'Certificate templates')).toBe('2'); expect(fact(detail.certificates, 'Certificates')).toBe('1'); expect(fact(detail.certificates, 'DC certificates')).toBe('—'); }); it('keeps native tool metrics labelled and lists restricted sections instead of counting them as zero', () => { const detail = dashboardEnvironmentDetail(snapshot); expect(fact(detail.toolContext, 'Purple Knight AD posture')).toBe('65'); expect(fact(detail.toolContext, 'Purple Knight AD IOEs')).toBe('19'); expect(fact(detail.toolContext, 'Purple Knight AD · Kerberos Security')).toBe('98'); expect(fact(detail.toolContext, 'CIS-CAT benchmark')).toBe('Synthetic CIS Benchmark'); expect(fact(detail.toolContext, 'CIS-CAT score')).toBe('61'); expect(detail.restricted).toEqual(['Honey Pot', 'Microsoft Entra Connect settings']); }); it('presents every Environment Detail group in an equal four-column tile grid', () => { expect(component).toContain('className="client-environment-detail"'); expect(component).toContain('ref={environmentDetailRef}'); expect(component).toContain('ref={overallScoreRef}'); expect(component).toContain("'--client-environment-tile-height', `${overallHeight * 0.75}px`"); expect(component).toContain('className="client-environment-grid"'); expect(component).toContain('Environment Detail'); expect(component).toContain('unit: \'/100 ASSESSOR\''); expect(component).toContain('PingCastle risk ${exactNative}/100'); expect(component).toContain('useGpoInventory'); expect(component).toContain('className="client-environment-sources"'); expect(component).toContain('Source Overview'); expect(component).toContain('title="Directory" items={environmentDetail.directory} glyph="domain"'); expect(component).toContain('title="Resilience" items={environmentDetail.resilience} glyph="shield"'); expect(component).toContain('title="Inventory" items={environmentDetail.inventory} glyph="computer"'); expect(component).toContain('title="Password policy" items={environmentDetail.passwordPolicy} glyph="lock"'); expect(component).toContain('title="Certificates & PKI" items={environmentDetail.certificates} glyph="policy"'); expect(component).toContain('title="Source metrics" items={environmentDetail.toolContext} glyph="chart"'); expect(component).toContain('icon={}'); expect(component).not.toContain('className="client-source-overview"'); expect(styles).toMatch(/\.client-environment-grid\s*\{[^}]*grid-template-columns:\s*repeat\(4, minmax\(0, 1fr\)\);[^}]*grid-auto-rows:\s*var\(--client-environment-tile-height\);/s); expect(styles).toMatch(/\.client-environment-grid > \.client-environment-sources,[\s\S]*?\.client-environment-grid > \.client-os-inventory\s*\{[^}]*height:\s*100%;[^}]*overflow:\s*auto;[^}]*background:\s*var\(--tile-surface\);/s); expect(styles).toMatch(/\.client-environment-detail\s*\{[^}]*background:\s*var\(--segment-surface\);/s); expect(styles).toMatch(/\.client-environment-sources li > div,[\s\S]*?\.client-environment-sources li > button\s*\{[^}]*background:\s*transparent;/s); expect(styles).toMatch(/\.client-env-group-title > \.client-glyph\s*\{[^}]*width:\s*var\(--space-5\);[^}]*color:\s*var\(--client-info-muted\);/s); expect(styles).toMatch(/\.client-env-group-title\s*\{[^}]*font-size:\s*var\(--type-h3-size\);[^}]*font-weight:\s*var\(--fw-bold\);/s); expect(styles).toMatch(/\.client-environment-sources > header h3\s*\{[^}]*font-size:\s*var\(--type-h3-size\);[^}]*font-weight:\s*var\(--fw-bold\);/s); expect(styles).toMatch(/\.client-command-center \.client-os-inventory > header h3\s*\{[^}]*font-size:\s*var\(--type-h3-size\);[^}]*font-weight:\s*var\(--fw-bold\);/s); expect(styles).not.toMatch(/\.client-env-group-title::after\s*\{/); expect(styles).toMatch(/\.client-env-facts\s*\{[^}]*margin:\s*var\(--space-3\) 0 var\(--space-4\);[^}]*border-top:/s); expect(styles).toMatch(/\.client-env-facts > div\s*\{[^}]*padding:\s*var\(--space-3\) var\(--space-2\);/s); expect(styles).toMatch(/\.client-env-facts dt\s*\{[^}]*font-size:\s*var\(--type-table-body-size\);/s); expect(styles).toMatch(/\.client-env-facts dd\s*\{[^}]*font-size:\s*var\(--type-body-size\);/s); expect(styles).toMatch(/\.client-environment-sources li em\s*\{[^}]*font-size:\s*var\(--type-table-body-size\);/s); expect(styles).toMatch(/\.client-command-center \.client-os-group th\s*\{[^}]*font-size:\s*var\(--type-table-body-size\);/s); expect(styles).toMatch(/\.client-command-center \.client-os-group \.client-os-source\s*\{[^}]*font-size:\s*var\(--type-table-body-size\);/s); expect(styles).toMatch(/\.floating-data-window \.client-os-groups\s*\{[^}]*grid-template-columns:\s*max-content;[^}]*width:\s*max-content;/s); expect(styles).toMatch(/\.floating-data-window \.client-os-group table\s*\{[^}]*width:\s*100%;/s); expect(styles).toMatch(/\.client-domain-score em\s*\{[^}]*color:\s*var\(--chart-color, var\(--client-info-text\)\);/s); }); });