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'); 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. Die // Zustandsableitung je Datensatz liegt innerhalb dieses memoisierten Hooks // und wird ueber denselben analysisCache mitgetragen. expect(component).toContain('const analysisCache = new WeakMap<'); 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', () => { const records = [ record('r1', 'Inactive user account detected'), record('r2', 'Group Policy baseline deviation'), 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('gpo')?.map(item => item.id)).toEqual(['r2']); expect(assignment.get('unclassified')?.map(item => item.id)).toEqual(['r3']); }); it('assigns every record to exactly one primary domain', () => { const records = [ record('gpo', 'Account password policy deviation', [['Category', 'GPOs']]), record('identity', 'Inactive user account'), record('other', 'No matching classification') ]; const assignment = dashboardDomainAssignment(records); const assigned = [...assignment.values()].flat(); expect(assignment.get('gpo')?.map(item => item.id)).toEqual(['gpo']); expect(assignment.get('identity')?.map(item => item.id)).toEqual(['identity']); expect(assignment.get('unclassified')?.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', 'authentication', 'gpo'].forEach(domain => { expect(dashboardDomainRecords(records, domain)).toEqual(assignment.get(domain) ?? []); }); expect(dashboardDomainRecords(records, 'missing')).toEqual([]); }); });