Assessor_2.0/tests/features/dashboard-layout.test.ts

107 lines
6.4 KiB
TypeScript

import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
import type { FindingRecord } from '../../src/domain/finding';
import { 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 the reference metric strip for compliance, primary statuses and the compact summary', () => {
expect(component).toContain('className="dashboard-status-rack"');
expect(component).toContain('label="Compliance Score"');
expect(component).toContain('<DashboardStatusMetric label="Deviation"');
expect(component).toContain('<DashboardStatusMetric label="Passed"');
expect(component).toContain('<DashboardStatusMetric label="Not Run"');
expect(component).toContain('className="dashboard-status-summary"');
expect(component).toContain('<span>Not Applicable</span>');
expect(component).toContain('<span>Error</span>');
expect(component).toContain('className="dashboard-ioe-summary" type="button"');
expect(component).toContain('setPathFindingIds(explicitIoeRecordIds)');
expect(styles).toMatch(/\.dashboard-status-rack\s*\{[^}]*grid-template-columns:\s*290px repeat\(3, 210px\) 250px;/s);
expect(styles).toMatch(/\.dashboard-status-copy > strong\s*\{[^}]*display:\s*grid;[^}]*white-space:\s*normal;/s);
expect(styles).toMatch(/\.dashboard-status-copy > strong small, \.dashboard-status-copy > small\s*\{[^}]*display:\s*block;/s);
expect(styles).toContain('grid-template-columns: repeat(2, minmax(0, 290px));');
expect(component).toContain("{value.toLocaleString('en-US')}<small>{roundedPercentage}%</small>");
expect(styles).toContain('.dashboard-status-ring-track { stroke: var(--donut-track); }');
expect(styles).toContain('button.dashboard-status-metric:hover { border-color: currentColor; background: var(--tile-surface-hover); }');
});
it('uses paired segmented distributions and a compact lower dashboard row', () => {
expect(component).toContain('heading="Severity distribution"');
expect(component).toContain('heading="Scan source distribution"');
expect(component).toContain('className="dashboard-lower-grid"');
expect(styles).toMatch(/\.dashboard-distribution-grid\s*\{[^}]*grid-template-columns:\s*repeat\(2,/s);
expect(styles).toContain('.dashboard-segmented-bar');
});
it('matches the reference structure for directory coverage and risk profile', () => {
expect(component).toContain('className="dashboard-coverage-summary"');
expect(component).toContain('<small>typed observations</small>');
expect(component).toContain('className="dashboard-block dashboard-risk-profile"');
expect(component).toContain('className="dashboard-marked-summary"');
expect(component).toContain('className="dashboard-risk-tool"');
expect(component).toContain('<option value={fullRiskProfileId}>Full Risk Profile</option>');
expect(component).toContain('className="dashboard-risk-donut"');
expect(component).toContain('<span className="dashboard-donut-primary is-compact"><strong>100</strong><small>%</small></span>');
expect(styles).toContain('.dashboard-risk-donut-track { stroke: var(--donut-track); }');
expect(styles).toMatch(/\.dashboard-donut-primary\s*\{[^}]*position:\s*relative;/s);
expect(styles).toMatch(/\.dashboard-status-ring > span small\s*\{[^}]*left:\s*calc\(100% \+ var\(--space-1\)\);/s);
expect(component).toContain('categories.slice(0, 4)');
expect(component).toContain('matchesDynamicFacets(record, [sourceFacet]');
expect(component).not.toContain('dashboard-review-block');
expect(styles).toMatch(/\.dashboard-facts\s*\{[^}]*grid-template-columns:\s*repeat\(4,/s);
expect(styles).toMatch(/\.dashboard-risk-body\s*\{[^}]*grid-template-columns:/s);
});
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('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']);
});
});