90 lines
4.4 KiB
TypeScript
90 lines
4.4 KiB
TypeScript
import { renderToStaticMarkup } from 'react-dom/server';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { makeSheet } from '../../src/importers/sheet-factory';
|
|
import { genericProfile } from '../../src/profiles/generic/generic-profile';
|
|
import { securityDomainCatalog } from '../../src/features/dashboard/security-domains';
|
|
import { summarizeSecurityDomains, toggleSecurityDomainSelection } from '../../src/features/sidebar/SidebarSecurityDomains';
|
|
import { deriveDynamicFacets } from '../../src/features/search-filter/dynamic-facets';
|
|
import { compactSeveritySelection, DynamicFilterControls } from '../../src/features/search-filter/DynamicFilterControls';
|
|
|
|
const rows = [
|
|
['Title', 'Severity', 'Result'],
|
|
['Inactive account', 'Critical', 'Failed'],
|
|
['Privileged administrator', 'High', 'Failed'],
|
|
['MFA disabled', 'High', 'Failed'],
|
|
['Certificate expired', 'Medium', 'Failed'],
|
|
['Firewall disabled', 'Low', 'Failed'],
|
|
['GPO baseline deviation', 'High', 'Failed'],
|
|
['Cloud application exposed', 'High', 'Failed'],
|
|
['Backup unavailable', 'Low', 'Failed'],
|
|
['Unmatched finding', 'Info', 'Failed'],
|
|
['Account passed', 'Medium', 'Passed']
|
|
];
|
|
|
|
function options(statusFilter = 'deviation', facetFilters = {}) {
|
|
return {
|
|
sheet: makeSheet('Synthetic findings', rows, '', 'synthetic.csv'),
|
|
dataset: null,
|
|
activeProfile: genericProfile,
|
|
reviews: {},
|
|
statusFilter,
|
|
facetFilters,
|
|
search: '',
|
|
actionOnly: false
|
|
};
|
|
}
|
|
|
|
describe('sidebar security domains', () => {
|
|
it('uses the exact Client Info catalog order and adds Unclassified only when present', () => {
|
|
const summary = summarizeSecurityDomains(options());
|
|
expect(summary?.domains.map(domain => domain.label)).toEqual([
|
|
...securityDomainCatalog.map(domain => domain.label),
|
|
'Unclassified'
|
|
]);
|
|
expect(summary?.domains.map(domain => domain.count)).toEqual([1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
|
});
|
|
|
|
it('keeps all eight catalog domains selectable at zero hits and follows active filters', () => {
|
|
const summary = summarizeSecurityDomains(options('passed'));
|
|
expect(summary?.domains.slice(0, 8).map(domain => domain.id)).toEqual(securityDomainCatalog.map(domain => domain.id));
|
|
expect(summary?.domains.find(domain => domain.id === 'identity')?.count).toBe(1);
|
|
expect(summary?.domains.filter(domain => domain.id !== 'identity').every(domain => domain.count === 0)).toBe(true);
|
|
});
|
|
|
|
it('uses single selection and clears the active domain on a second click', () => {
|
|
expect(toggleSecurityDomainSelection([], 'identity')).toEqual(['identity']);
|
|
expect(toggleSecurityDomainSelection(['identity'], 'gpo')).toEqual(['gpo']);
|
|
expect(toggleSecurityDomainSelection(['gpo'], 'gpo')).toEqual([]);
|
|
});
|
|
|
|
it('does not render a rack without an active sheet', () => {
|
|
expect(summarizeSecurityDomains({ ...options(), sheet: undefined })).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('Work filter controls retained beside the sidebar domain filter', () => {
|
|
it('renders a compact multi-severity label in the filter control', () => {
|
|
const base = options();
|
|
const severityFacet = deriveDynamicFacets(base.sheet, null, genericProfile, {})
|
|
.find(facet => facet.severityScale);
|
|
if (!severityFacet) throw new Error('Synthetic severity facet missing');
|
|
const html = renderToStaticMarkup(<DynamicFilterControls facets={[severityFacet]}
|
|
filters={{ [severityFacet.id]: { values: ['info', 'low', 'medium', 'high', 'critical'] } }}
|
|
statusFilter="deviation" onStatusFilter={() => undefined} onFilter={() => undefined}/>);
|
|
expect(compactSeveritySelection(['info', 'critical', 'medium'])).toBe('Crit-Med-Inf');
|
|
expect(html).toContain('Crit-High-Med-Low-Inf');
|
|
expect(html).toContain('value="__severity_multi__" selected=""');
|
|
});
|
|
|
|
it('keeps Source: Scan as the final right-edge filter control', () => {
|
|
const scanSheet = makeSheet('Synthetic findings', [
|
|
['Title', 'Severity', 'Source: Scan'], ['One', 'High', 'Scanner A'], ['Two', 'Low', 'Scanner B']
|
|
], '', 'synthetic.csv');
|
|
const facets = deriveDynamicFacets(scanSheet, null, genericProfile, {});
|
|
const html = renderToStaticMarkup(<DynamicFilterControls facets={facets} filters={{}} statusFilter="all"
|
|
onStatusFilter={() => undefined} onFilter={() => undefined}/>);
|
|
expect(facets.at(-1)?.label).toBe('Source: Scan');
|
|
expect(html).toContain('dynamic-facet-select is-source-scan');
|
|
});
|
|
});
|