Assessor_2.0/tests/domain/processing-compatibility.te...

72 lines
3.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { ImportFileRecord } from '../../src/domain/canonical-model';
import {
processingReadiness,
processingScopeIssues,
resolvedCompatibilityKey
} from '../../src/domain/processing-compatibility';
const confidence = { level: 'high' as const, score: 1, reasons: ['synthetic'] };
function file(id: string, profileId: string, profileVersion: string, mappingVersion: string): ImportFileRecord {
return {
id,
scanRunId: `run-${id}`,
fileName: `${id}.json`,
format: 'JSON',
sizeBytes: 1,
sha256: 'a'.repeat(64),
importedAt: '2026-08-23T00:00:00.000Z',
detectedTool: { id: profileId, name: profileId },
detection: confidence,
processedWith: { parserId: 'synthetic', parserVersion: '1.0.0', profileId, profileVersion, mappingVersion },
sourceArtifact: { kind: 'not-retained', reason: 'synthetic' }
};
}
describe('processing compatibility contracts', () => {
it('separates PingCastle presentation from canonical mapping semantics', () => {
const old = file('old', 'pingcastle', '1.1.0', '1.1.0');
const contextless = file('contextless', 'pingcastle', '1.2.0', '1.1.0');
const current = file('current', 'pingcastle', '1.4.0', '1.2.0');
expect(resolvedCompatibilityKey('pingcastle', old.processedWith)).toBe('pingcastle-canonical-v1.1');
expect(resolvedCompatibilityKey('pingcastle', contextless.processedWith)).toBe('pingcastle-canonical-v1.1');
expect(resolvedCompatibilityKey('pingcastle', current.processedWith)).toBe('pingcastle-canonical-v1.1');
expect(processingReadiness('pingcastle', old.processedWith)).toBe('reimport-recommended');
expect(processingReadiness('pingcastle', contextless.processedWith)).toBe('reimport-recommended');
expect(processingReadiness('pingcastle', current.processedWith)).toBe('current');
expect(processingScopeIssues([old, contextless, current])).toEqual([]);
});
it('uses the recorded profile when a caller supplies a different detected tool id', () => {
const legacy = file('defender-old', 'defender-for-iot-vulnerabilities', '1.0.0', '1.0.0');
const current = file('defender-current', 'defender-for-iot-vulnerabilities', '1.1.0', '1.0.0');
current.processedWith.normalizationVersion = '1.1.0';
current.processedWith.presentationVersion = '1.0.0';
current.processedWith.compatibilityKey = 'defender-for-iot-vulnerabilities-v2';
expect(processingReadiness('microsoft-defender-for-iot', legacy.processedWith)).toBe('incompatible');
expect(processingReadiness('microsoft-defender-for-iot', current.processedWith)).toBe('current');
expect(resolvedCompatibilityKey('microsoft-defender-for-iot', current.processedWith))
.toBe('defender-for-iot-vulnerabilities-v2');
});
it('blocks semantically incompatible versions in one scope', () => {
const legacy = file('legacy', 'pingcastle', '1.0.0', '1.0.0');
const current = file('current', 'pingcastle', '1.4.0', '1.2.0');
expect(processingReadiness('pingcastle', legacy.processedWith)).toBe('incompatible');
expect(processingScopeIssues([legacy, current])).toMatchObject([{
toolId: 'pingcastle', code: 'incompatible-processing-contracts'
}]);
});
it('fails closed for unknown persisted contracts', () => {
const unknown = file('unknown', 'pingcastle', '9.9.9', '9.9.9');
expect(processingReadiness('pingcastle', unknown.processedWith)).toBe('unknown');
expect(processingScopeIssues([unknown])).toEqual([{
toolId: 'pingcastle', code: 'unknown-processing-contract', compatibilityKeys: []
}]);
});
});