99 lines
5.5 KiB
TypeScript
99 lines
5.5 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*\{[^}]*repeat\(3,/s);
|
|
expect(styles).toContain('.dashboard-status-ring-track { stroke: var(--donut-border); }');
|
|
expect(styles).toContain('button.dashboard-status-metric:hover { border-color: currentColor; }');
|
|
});
|
|
|
|
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('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']);
|
|
});
|
|
});
|