import { describe, expect, it } from 'vitest'; import type { FindingRecord } from '../../src/domain/finding'; import { promotedResultSheetTitlesFor, resultMessageFor, resultSectionsFor, resultSheetRowHighlightedFor, resultSheetTitleFor, resultStatusFieldFor, showResultSheetTitlesFor } from '../../src/features/linked-results/result-message'; function maesterRecord(fields: Array<[string, string]>): FindingRecord { const built = fields.map(([label, value], index) => ({ label, value, index })); return { id: 'record', index: 0, title: '', fields: built, filled: built, meta: [] }; } describe('resultMessageFor', () => { it('liest den Maester-Ergebnistext aus der Spalte "Result"', () => { const record = maesterRecord([ ['ID', 'MT.1011'], ['Status', 'Failed'], ['Result', 'No conditional access policy securing security info registration.'], ['Source: Scan', 'Maester · 2026-07-27T00:00:00.000Z'] ]); expect(resultMessageFor(record, 'maester')).toBe('No conditional access policy securing security info registration.'); }); it('liefert nichts fuer ein Toolprofil ohne resultMessageHeaders (z. B. Purple Knight)', () => { const record = maesterRecord([ ['Name', 'Some finding'], ['Result', 'IOE Found'], ['Result Message', 'Every user can register an application.'] ]); expect(resultMessageFor(record, 'purple-knight')).toBeUndefined(); }); // CIS-CAT hat keinen einzelnen werkzeuggebundenen Ergebnistext: sein Befund // steht in den Belegtabellen und in den aufgeklappten Abschnitten // (siehe resultSectionsFor), nicht in einer Ergebnisspalte. it('liefert fuer CIS-CAT keinen einzelnen Ergebnistext', () => { const record = maesterRecord([ ['CIS-CAT Details', '## Description\n\nText\n\n## Rule Result XML\n\n```xml\nfail\n```'], ['Rule Result XML', '```xml\nfail\n```'] ]); expect(resultMessageFor(record, 'cis-cat')).toBeUndefined(); }); it('liefert nichts, wenn kein Toolprofil aufgeloest werden kann', () => { const record = maesterRecord([['Result', 'Text ohne Werkzeugbezug']]); expect(resultMessageFor(record, undefined)).toBeUndefined(); }); }); describe('resultSectionsFor', () => { /** * Die Rahmenwerk-Zuordnungen hinter "More" gehoeren zu den Belegdaten und * damit in das Results-Fenster - genau wie die versteckten * Assessment-Evidence-Tabellen, die als verknuepfte Blaetter dorthin kommen. * * Das Rule Result XML ausdruecklich nicht: es bleibt ein aufklappbares * Detail im Finding (siehe `cisCatOptionalFields`), weil es nicht der Befund * ist, sondern seine maschinenlesbare Rohfassung. */ it('nimmt die Rahmenwerk-Zuordnungen auf, das Rule Result XML aber nicht', () => { const record = maesterRecord([ ['Benchmark Item', '1.1.1 Ensure the synthetic setting is enabled'], ['Description', 'Synthetic description.'], ['Control Mappings', '### CIS Controls V7.0\n\n- Control 1: Synthetic Inventory'], ['Rule Result XML', '```xml\nfail\n```'] ]); expect(resultSectionsFor(record, 'cis-cat')).toEqual([ { title: 'Control Mappings', value: '### CIS Controls V7.0\n\n- Control 1: Synthetic Inventory' } ]); }); it('behaelt den werkzeuggebundenen Ergebnistext als eigenen Abschnitt', () => { const record = maesterRecord([['Result', 'No conditional access policy securing security info registration.']]); expect(resultSectionsFor(record, 'maester')).toEqual([ { title: 'Test result', value: 'No conditional access policy securing security info registration.' } ]); }); it('liefert nichts fuer ein Toolprofil ohne Ergebnisabschnitte (z. B. Purple Knight)', () => { const record = maesterRecord([['Name', 'Some finding'], ['Result', 'IOE Found']]); expect(resultSectionsFor(record, 'purple-knight')).toEqual([]); }); }); describe('showResultSheetTitlesFor', () => { it('aktiviert fachliche Belegueberschriften fuer CIS-CAT und PingCastle', () => { expect(showResultSheetTitlesFor('cis-cat')).toBe(true); expect(showResultSheetTitlesFor('pingcastle')).toBe(true); expect(showResultSheetTitlesFor('maester')).toBe(false); expect(showResultSheetTitlesFor(undefined)).toBe(false); expect(resultSheetTitleFor('cis-cat', 'Assessment Evidence \u00b7 Complex Check')).toBe('Assessment'); expect(resultSheetTitleFor('cis-cat', 'CIS Controls V7.0 \u00b7 CIS Control Information')).toBe('CIS Controls V7.0'); expect(resultSheetTitleFor('cis-cat', 'CIS Critical Security Controls V8.0 \u00b7 CIS Control Information')).toBe('CIS Critical Security Controls V8.0'); expect(resultSheetTitleFor('cis-cat', 'Synthetic Framework V1.0 \u00b7 Synthetic Framework Information')).toBe('Synthetic Framework V1.0'); expect(resultSheetTitleFor('pingcastle', 'PingCastle \u00b7 Admin Groups → Administrators')).toBe('Admin Groups → Administrators'); expect(resultSheetTitleFor('maester', 'Assessment Evidence')).toBeUndefined(); }); }); describe('resultSheetRowHighlightedFor', () => { it('markiert den CIS-CAT-Hinweis, aber nicht denselben Text bei anderen Werkzeugen', () => { const record = maesterRecord([['Hinweis', 'No matching system items were found.']]); expect(resultSheetRowHighlightedFor('cis-cat', record)).toBe(true); expect(resultSheetRowHighlightedFor('maester', record)).toBe(false); expect(resultSheetRowHighlightedFor(undefined, record)).toBe(false); }); }); describe('resultStatusFieldFor', () => { it.each(['Passed', 'Failed'])('bevorzugt bei Purple Knight Status=%s vor einem SI-Verweis in Result', status => { const record = maesterRecord([ ['Result', 'SI000067'], ['Status', status], ['ShortName', 'SI000067'] ]); expect(resultStatusFieldFor(record, 'purple-knight')).toMatchObject({ label: 'Status', value: status }); }); it('bleibt mit Purple-Knight-Exporten kompatibel, die nur Result als Status fuehren', () => { const record = maesterRecord([['Result', 'Passed'], ['Short Name', 'SI000067']]); expect(resultStatusFieldFor(record, 'purple-knight')).toMatchObject({ label: 'Result', value: 'Passed' }); }); }); it.each([['IOE Found', 'Failed'], ['Pass', 'Passed']])('beschriftet den Purple-Knight-Rohstatus %s als %s', (source, expected) => { const record = maesterRecord([['Status', source], ['Result', 'SI000067']]); expect(resultStatusFieldFor(record, 'purple-knight')).toMatchObject({ label: 'Status', value: expected }); }); describe('promotedResultSheetTitlesFor', () => { it('ordnet mehrere Controls derselben Framework-Ueberschrift ihren Tabellen in Reihenfolge zu', () => { const sections = [{ title: 'Control Mappings', value: [ '### CIS Controls V7.0', '', '- Control 9: Limitation and Control of Network Ports, Protocols, and Services', '', '- Control 11: Secure Configuration for Network Devices', '', '### CIS Critical Security Controls V8.0', '', '- Control 4: Secure Configuration of Enterprise Assets and Software' ].join('\n') }]; const sheets = [ { id: 'v7-9', name: 'CIS Controls V7.0 \u00b7 CIS Control Information' }, { id: 'v7-11', name: 'CIS Controls V7.0 \u00b7 CIS Control Information 2' }, { id: 'v8-4', name: 'CIS Critical Security Controls V8.0 \u00b7 CIS Control Information' } ]; const promoted = promotedResultSheetTitlesFor('cis-cat', sections, sheets); expect([...promoted.tableTitles]).toEqual([ ['v7-9', 'Control 9: Limitation and Control of Network Ports, Protocols, and Services'], ['v7-11', 'Control 11: Secure Configuration for Network Devices'], ['v8-4', 'Control 4: Secure Configuration of Enterprise Assets and Software'] ]); expect([...promoted.consumedSectionIndexes]).toEqual([0]); }); it('laesst die urspruengliche Liste sichtbar, wenn eine zugehoerige Tabelle fehlt', () => { const sections = [{ title: 'Control Mappings', value: '### CIS Controls V7.0\n\n- Control 9: First\n\n- Control 11: Second' }]; const promoted = promotedResultSheetTitlesFor('cis-cat', sections, [ { id: 'only-one', name: 'CIS Controls V7.0 \u00b7 CIS Control Information' } ]); expect(promoted.tableTitles.size).toBe(0); expect(promoted.consumedSectionIndexes.size).toBe(0); }); });