70 lines
4.7 KiB
TypeScript
70 lines
4.7 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
import { describe, expect, it } from 'vitest';
|
|
import type { ImportedDataset } from '../../src/domain/dataset';
|
|
import { rowsToCsv } from '../../src/exports/csv-export';
|
|
import { defenderModelForDataset } from '../../src/features/defender-for-iot/defender-dataset-model';
|
|
import { makeSheet } from '../../src/importers/sheet-factory';
|
|
|
|
function dataset(): ImportedDataset {
|
|
const summary = [
|
|
['Name', 'IP', 'Site', 'Sensor', 'TotalCVEs', 'Critical', 'High', 'Medium', 'Low'],
|
|
['device-a', '', 'Site A', 'sensor-a', '3', '1', '2', '0', '0']
|
|
];
|
|
const observations = [
|
|
{ name: 'device-a', ip: '', site: 'Site A', sensor: 'sensor-a', cve: 'CVE-2026-1001', severity: 'Critical', description: 'Synthetic description', additionalSourceFields: {}, source: { tableIndex: 1, rowIndex: 1 } },
|
|
{ name: 'device-a', ip: '', site: 'Site A', sensor: 'sensor-b', cve: 'CVE-2026-1001', severity: 'Critical', description: 'Synthetic description', additionalSourceFields: {}, source: { tableIndex: 1, rowIndex: 2 } }
|
|
];
|
|
const detail = [
|
|
['CVE', 'Device Identity', 'Name', 'IP', 'Site', 'Reporting Sensors', 'Severity', 'Beschreibung', 'Observation Count', 'Source Observations'],
|
|
['CVE-2026-1001', 'name:device-a|site:site a', 'device-a', '', 'Site A', 'sensor-a, sensor-b', 'Critical', 'Synthetic description', '2', JSON.stringify(observations)]
|
|
];
|
|
return {
|
|
fileName: 'Synthetic project', fileSize: 1, lastModified: 0, format: 'Project · Microsoft Defender for IoT', rawText: '',
|
|
profileId: 'defender-for-iot-vulnerabilities', profileVersion: '1.0.0', scanTool: 'Microsoft Defender for IoT',
|
|
sheets: [
|
|
makeSheet('Vulnerabilities', detail, rowsToCsv(detail), 'synthetic.xlsx', { sourceTableIndex: 1, sourceSheet: 'Vulnerabilities', toolId: 'defender-for-iot-vulnerabilities' }),
|
|
makeSheet('Assets', summary, rowsToCsv(summary), 'synthetic.xlsx', { sourceTableIndex: 0, sourceSheet: 'Assets', toolId: 'defender-for-iot-vulnerabilities' })
|
|
]
|
|
};
|
|
}
|
|
|
|
describe('Defender vulnerability dashboard projection', () => {
|
|
it('reconstructs raw observations without double-counting the actionable occurrence', () => {
|
|
const model = defenderModelForDataset(dataset());
|
|
|
|
expect(model.occurrences).toHaveLength(1);
|
|
expect(model.observations).toHaveLength(2);
|
|
expect(model.devices[0].severity.critical).toMatchObject({ detail: 1, summary: 1, state: 'complete' });
|
|
expect(model.devices[0].severity.high).toMatchObject({ detail: 0, summary: 2, aggregateOnly: 2, state: 'aggregate-only' });
|
|
});
|
|
|
|
it('keeps the required drill-downs and avoids clipping or ellipsis rules', () => {
|
|
const component = readFileSync(new URL('../../src/features/defender-for-iot/DefenderVulnerabilityPanel.tsx', import.meta.url), 'utf8');
|
|
const dashboard = readFileSync(new URL('../../src/features/dashboard/DashboardPanel.tsx', import.meta.url), 'utf8');
|
|
const styles = readFileSync(new URL('../../src/styles/defender-for-iot.css', import.meta.url), 'utf8');
|
|
|
|
expect(component).toContain('CVE view');
|
|
expect(component).toContain('Device view');
|
|
expect(component).toContain('Site dashboard');
|
|
expect(component).not.toContain('<th>Source row</th>');
|
|
expect(component).toContain('aggregate-only');
|
|
expect(component).toContain('className="dashboard-page client-command-center defender-page"');
|
|
expect(component).toContain('className="client-overview-header defender-header"');
|
|
expect(component).toContain('<span>Source:</span><strong>Microsoft Defender for IoT</strong>');
|
|
expect(dashboard).toContain("['overview', 'Overview']");
|
|
expect(dashboard).toContain("['defender', 'Microsoft Defender for IoT']");
|
|
expect(dashboard).toContain('<DefenderVulnerabilityPanel dataset={dataset} navigation={clientInfoTabs} reviews={reviews}');
|
|
expect(dashboard).toContain('scopedRiskAcceptances={app.scopedRiskAcceptances} activeScanRunIds={app.activeScanRunIds}');
|
|
expect(dashboard).toContain('onScopedRiskAcceptance={app.backendStatus === \'connected\' ? write => app.recordScopedRiskAcceptance(write) : undefined}');
|
|
expect(styles).toContain('padding: var(--panel-inset)');
|
|
expect(styles).toContain('grid-template-columns: repeat(6, minmax(0, 1fr))');
|
|
expect(styles).toContain('background: var(--tile-surface)');
|
|
expect(styles).toMatch(/\.defender-drilldown \{[^}]*background: var\(--tile-surface\)/);
|
|
expect(styles).not.toMatch(/\.defender-drilldown \{[^}]*background: var\(--client-info-surface\)/);
|
|
expect(styles).toContain('outline: var(--bw-emphasis) solid var(--marking-color)');
|
|
expect(styles).toContain('outline: var(--bw-emphasis) solid var(--focus-ring)');
|
|
expect(styles).not.toContain('text-overflow: ellipsis');
|
|
expect(styles).not.toContain('line-clamp');
|
|
});
|
|
});
|