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([]); }); });