import { describe, expect, it } from 'vitest'; import { detectHeaderRow } from '../../src/analysis/header-detector'; import { buildRecord } from '../../src/analysis/record-builder'; import { analyzeRows } from '../../src/analysis/schema-detector'; import { detectColumnType } from '../../src/analysis/type-detector'; import { normalizeNumericValue } from '../../src/shared/text'; describe('analysis primitives', () => { it('detects the actual header below a title row', () => { expect(detectHeaderRow([ ['Synthetic Security Assessment'], ['Name', 'Severity', 'Weight'], ['A', 'High', '10'], ['B', 'Low', '2'] ])).toBe(1); }); it('detects numeric and date columns', () => { expect(detectColumnType(['1', '2', '3', '4'])).toBe('Zahl'); expect(detectColumnType(['01.01.2026', '02.01.2026', '03.01.2026'])).toBe('Datum'); }); it('normalizes German, English and percent numbers without evaluating expressions', () => { expect(normalizeNumericValue('1.234,50')).toBe(1234.5); expect(normalizeNumericValue('1,234.50')).toBe(1234.5); expect(normalizeNumericValue('72,5%')).toBe(72.5); expect(normalizeNumericValue('=1+1')).toBeNull(); }); it('selects Benchmark Item as title instead of file provenance headers', () => { const record = buildRecord({ headers: ['Source: File', 'Source: Sheet', 'Source: Scan', 'Rule ID', 'Benchmark Item', 'Result'], row: [ 'WIN-9FPMF1CCPQ0-CIS_Microsoft_Windows_Server_2025_Benchmark-20260417T013626Z.html', 'Assessment Results', 'CIS-CAT Pro · 2026-07-30', '2.2.2', "2.2.2 Ensure 'Access this computer from the network' is set to 'Administrators'", 'fail' ], index: 0, sheetName: 'Test' }); expect(record.title).toBe("2.2.2 Ensure 'Access this computer from the network' is set to 'Administrators'"); }); it('selects Name as title instead of ShortName for PurpleKnight records', () => { const record = buildRecord({ headers: ['Source: File', 'Source: Sheet', 'Source: Scan', 'ShortName', 'Name', 'Status', 'Weight'], row: [ 'Security_Assessment_Report.xlsx', 'Indicators results', 'PurpleKnight · 2026-07-25', 'SI000055', 'Permission changes on AdminSDHolder object', 'IOEs found', '10' ], index: 0, sheetName: 'Test' }); expect(record.title).toBe('Permission changes on AdminSDHolder object'); }); /** * Regression: Eine einzelne Zeile hat nichts, wovon sie eine Kopfzeile sein * koennte - die Kopfzeilenerkennung waehlte sie trotzdem immer als Kopf, und * darunter blieb keine Datenzeile mehr uebrig. CIS-CAT gibt so z. B. den * Hinweis "No matching system items were found." als einzeilige * Belegtabelle aus; ohne diese Ausnahme zeigte das Ergebnisfenster ein * leeres Tabellengeruest ohne jeden sichtbaren Inhalt. */ it('behandelt eine einzelne Tabellenzeile als Datenzeile, nicht als verschluckte Kopfzeile', () => { const analysis = analyzeRows([['No matching system items were found.']], 'Test'); expect(analysis.headers).toEqual(['Hinweis']); expect(analysis.records).toHaveLength(1); expect(analysis.records[0].fields).toEqual([ { label: 'Hinweis', value: 'No matching system items were found.', index: 0 } ]); }); it('benennt mehrspaltige Einzelzeilen generisch, statt eine Kopfzeile zu verschlucken', () => { const analysis = analyzeRows([['a', 'b']], 'Test'); expect(analysis.headers).toEqual(['Feld 1', 'Feld 2']); expect(analysis.records).toHaveLength(1); expect(analysis.records[0].fields.map(field => field.value)).toEqual(['a', 'b']); }); });