import { describe, expect, it } from 'vitest'; import { normalizeStatusDimensionsFor } from '../../src/importers/status-normalization'; import { deriveExposureState, deriveIsIoe, deriveResultType } from '../../src/domain/result-contract-mapping'; describe('status normalization', () => { // Purple Knight meldet einen gefundenen Expositionsindikator als "IOEs found" // bzw. "IOE Found" - nie als "failed". Genau diese Werte sind die echten // Befunde und muessen als fail in der Result-Tabelle ankommen, damit sie auch // als is_ioe gelten. it('maps the full Purple Knight result vocabulary', () => { const cases: Array<[string, { legacy: string; outcome: string; technical: string }]> = [ ['IOEs found', { legacy: 'failed', outcome: 'fail', technical: 'completed' }], ['IOE Found', { legacy: 'failed', outcome: 'fail', technical: 'completed' }], ['Pass', { legacy: 'passed', outcome: 'pass', technical: 'completed' }], ['Passed', { legacy: 'passed', outcome: 'pass', technical: 'completed' }], ['Not Relevant', { legacy: 'not-applicable', outcome: 'not_applicable', technical: 'completed' }], ['Not selected', { legacy: 'not-run', outcome: 'not_run', technical: 'not-run' }], ['Canceled', { legacy: 'not-run', outcome: 'not_run', technical: 'not-run' }], ['Failed to run', { legacy: 'error', outcome: 'execution_error', technical: 'failed' }] ]; cases.forEach(([source, expected]) => { expect(normalizeStatusDimensionsFor('purple-knight', source), source).toMatchObject(expected); }); }); it('never leaves a known Purple Knight result value as unknown', () => { ['IOEs found', 'IOE Found', 'Pass', 'Not Relevant', 'Not selected', 'Canceled', 'Failed to run'] .forEach(source => { expect(normalizeStatusDimensionsFor('purple-knight', source).outcome, source).not.toBe('unknown'); }); }); it('keeps the previously working Maester and PingCastle behaviour', () => { expect(normalizeStatusDimensionsFor('maester', 'Failed')).toMatchObject({ legacy: 'failed', outcome: 'fail' }); expect(normalizeStatusDimensionsFor('maester', 'Passed')).toMatchObject({ legacy: 'passed', outcome: 'pass' }); expect(normalizeStatusDimensionsFor('maester', 'Not run')).toMatchObject({ legacy: 'not-run', outcome: 'not_run' }); // Ein unspezifischer Treffertext ohne bestanden/fehlgeschlagen-Semantik // bleibt ein Hinweis - auch bei PingCastle. expect(normalizeStatusDimensionsFor('pingcastle', 'detected')).toMatchObject({ legacy: 'open', outcome: 'warning' }); }); // PingCastle listet ausschliesslich ausgeloeste RiskRules und meldet sie mit // dem Status "Matched". Zuvor griff dafuer die generische Rueckfallregel // (open/warning), womit die Befunde als blosse Hinweise galten. it('reads a matched PingCastle rule as a factual failure', () => { expect(normalizeStatusDimensionsFor('pingcastle', 'Matched')) .toMatchObject({ legacy: 'failed', outcome: 'fail', technical: 'completed' }); }); // Regression fuer das Symptom "PingCastle-Bericht zeigt keine IOEs": die // vollstaendige Kette vom Quellstatus bis is_ioe, nicht nur ihr erstes Glied. it('turns a matched PingCastle rule into an indicator of exposure', () => { const outcome = normalizeStatusDimensionsFor('pingcastle', 'Matched').outcome; const resultType = deriveResultType('pingcastle'); const exposureState = deriveExposureState(resultType, outcome); expect(resultType).toBe('exposure_finding'); expect(exposureState).toBe('detected'); expect(deriveIsIoe(resultType, exposureState).isIoe).toBe(true); }); it('does not read a negated status as its positive counterpart', () => { expect(normalizeStatusDimensionsFor(undefined, 'Not passed')).toMatchObject({ legacy: 'failed', outcome: 'fail' }); expect(normalizeStatusDimensionsFor(undefined, 'nicht bestanden')).toMatchObject({ legacy: 'failed', outcome: 'fail' }); // "failed to run" ist ein technischer Abbruch, kein fachliches Scheitern. expect(normalizeStatusDimensionsFor(undefined, 'Failed to run')).toMatchObject({ outcome: 'execution_error' }); }); it('treats an empty or unrecognised status as unknown', () => { expect(normalizeStatusDimensionsFor('purple-knight', '')).toMatchObject({ legacy: 'unknown', outcome: 'unknown' }); expect(normalizeStatusDimensionsFor(undefined, 'völlig unbekannt')).toMatchObject({ legacy: 'unknown', outcome: 'unknown' }); }); });