Assessor_2.0/tests/features/dashboard-derivation-cost.t...

95 lines
4.7 KiB
TypeScript

import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';
import type { FindingRecord } from '../../src/domain/finding';
import { dashboardDomainAssignment, dashboardDomainRecords } from '../../src/features/dashboard/DashboardPanel';
const component = readFileSync(new URL('../../src/features/dashboard/DashboardPanel.tsx', import.meta.url), 'utf8');
const resultStateModule = readFileSync(new URL('../../src/features/dashboard/dashboard-result-state.ts', import.meta.url), 'utf8');
function record(id: string, title: string, fields: Array<[string, string]> = []): FindingRecord {
return {
id,
title,
fields: fields.map(([label, value]) => ({ label, value })),
sourceFile: 'synthetic.csv'
} as unknown as FindingRecord;
}
describe('client info derivation cost', () => {
/**
* Ein Mandantenblatt traegt mehrere tausend Datensaetze. Die Ansicht darf sie
* nicht bei jedem Rendern erneut durchlaufen, sonst haengt sie bei jeder
* Zustandsaenderung der Anwendung.
*/
it('derives every record-level view once inside a memoized hook', () => {
expect(component).toContain('function useDashboardAnalysis(');
expect(component).toContain('const analysis = useDashboardAnalysis(activeSheet, dataset, activeProfile, reviews, sourceFilter);');
// Facetten und Auswertung ueberdauern den Ansichtswechsel, sonst rechnet
// jeder Besuch der Client Info dieselben Datensaetze erneut durch.
expect(component).toContain('const analysisCache = new WeakMap<');
// Der Ergebnisstatus je Datensatz liegt seit der Auslagerung in einem eigenen
// Modul, das Client Info und Data Hub gemeinsam nutzen.
expect(resultStateModule).toContain('const recordStateCache = new WeakMap<');
expect(component).toContain("from './dashboard-result-state';");
expect(component).toContain('useMemo(() => dashboardEnvironmentDetail(environment.snapshot), [environment.snapshot])');
// Die teure Ersatzzaehlung aus Findingtexten laeuft nur, solange die
// Verarbeitungsliste der Sicherungen noch nicht geantwortet hat.
expect(component).toContain('const derivedGpoCount = useMemo(');
expect(component).toContain('gpoInventory.gpoCount ?? derivedGpoCount');
});
it('classifies all security domains in a single pass over the records', () => {
// Synthetic structured Raw Data (user decision 2026-10-04).
const records = [
record('r1', 'Inactive user account detected', [['Source: Scan', 'PingCastle · x'], ['Source Rule ID', 'A-MinPwdLen']]),
record('r2', 'Group Policy baseline deviation', [['Source: Scan', 'GPOs · x'], ['Policy type', 'security-template'], ['Policy section', 'Privilege Rights']]),
record('r3', 'Nothing classifiable here')
];
let textReads = 0;
const counted = records.map(item => ({
...item,
get fields() {
textReads += 1;
return item.fields;
}
})) as FindingRecord[];
const assignment = dashboardDomainAssignment(counted);
expect(textReads).toBe(counted.length);
expect(assignment.get('identity')?.map(item => item.id)).toEqual(['r1']);
expect(assignment.get('privilege')?.map(item => item.id)).toEqual(['r2']);
expect(assignment.get('hardening')?.map(item => item.id)).toEqual(['r3']);
});
it('assigns every record to exactly one primary domain', () => {
const records = [
record('gpo', 'Account password policy deviation', [['Source: Scan', 'GPOs · x'], ['Policy type', 'security-template'], ['Policy section', 'System Access']]),
record('privilege', 'Inactive user account', [['Source: Scan', 'PingCastle · x'], ['Source Rule ID', 'P-AdminLogin']]),
record('other', 'No matching classification')
];
const assignment = dashboardDomainAssignment(records);
const assigned = [...assignment.values()].flat();
expect(assignment.get('identity')?.map(item => item.id)).toEqual(['gpo']);
expect(assignment.get('privilege')?.map(item => item.id)).toEqual(['privilege']);
expect(assignment.get('hardening')?.map(item => item.id)).toEqual(['other']);
expect(assigned.map(item => item.id).sort()).toEqual(records.map(item => item.id).sort());
expect(new Set(assigned.map(item => item.id)).size).toBe(records.length);
});
it('keeps the single-domain lookup in agreement with the full assignment', () => {
const records = [
record('r1', 'Stale computer account'),
record('r2', 'Certificate template allows SAN'),
record('r3', 'Conditional access gap')
];
const assignment = dashboardDomainAssignment(records);
['identity', 'infrastructure', 'hardening', 'cloud'].forEach(domain => {
expect(dashboardDomainRecords(records, domain)).toEqual(assignment.get(domain) ?? []);
});
expect(dashboardDomainRecords(records, 'missing')).toEqual([]);
});
});