Assessor_2.0/tests/importers/status-normalization.test.ts

74 lines
4.4 KiB
TypeScript

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' });
});
});