Assessor_2.0/tests/domain/policy-analysis-actions.tes...

145 lines
6.7 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
buildPolicyAnalysisOverview,
type BuildPolicyAnalysisOverviewInput
} from '../../src/domain/policy-analysis-overview';
import { POLICY_EVALUATION_ENGINE } from '../../src/domain/policy-evaluation';
const timestamp = '2026-08-04T16:00:00.000Z';
function input(options: { aggregation?: boolean; baseline?: 'complete' | 'partial' | 'missing'; evaluation?: boolean } = {}): BuildPolicyAnalysisOverviewInput {
const aggregation = {
version: 1,
run: {
id: 'aggregation-1', environmentProfileId: 'profile-1', sourceAnalysisRunIds: ['identity-run-1'],
status: 'complete', diagnosticCodes: [], snapshotId: 'snapshot-1'
},
snapshot: { instanceIds: ['backup-1'] }, states: [], reviewHints: []
};
const evaluation = {
version: 1,
run: {
id: 'evaluation-1', aggregationRunId: 'aggregation-1', environmentProfileId: 'profile-1',
baselineAnalysisIds: ['baseline-analysis-1'], status: 'partial', diagnosticCodes: [],
evaluator: POLICY_EVALUATION_ENGINE,
completedAt: timestamp, matchCount: 0, deviationCount: 0, notApplicableCount: 0, reviewCount: 1
},
results: [{
id: 'evaluation-result-1', identity: { id: 'identity-1' }, findingEligibility: 'ineligible',
classification: 'review_required', baselineState: 'catalog-gap', diagnosticCodes: ['policy-evaluation-catalog-gap'],
observationIds: [], backupInstanceIds: []
}], evidence: []
};
return {
projectId: 'project-1', generatedAt: timestamp,
identityAnalyses: [{
run: {
id: 'identity-run-1', artifactId: 'artifact-1', sourceProcessingRunId: 'processing-1',
status: 'complete', observationCount: 0, resolvedCount: 0, diagnosticCodes: [], completedAt: timestamp
},
resolutions: []
}],
profiles: [{
id: 'profile-1', name: 'Windows Server 2025', revision: 1,
contexts: [{
id: 'context-1', productFamily: 'Windows Server', productVersion: '2025',
role: 'Member Server', scope: 'computer'
}]
}],
aggregations: options.aggregation ? [aggregation] : [],
evaluations: options.evaluation ? [evaluation] : [],
findingRuns: [],
processingRuns: [{ id: 'processing-1', artifactId: 'artifact-1', completedAt: timestamp }],
baselineRecords: options.baseline === 'missing' || !options.baseline ? [] : [{
id: 'baseline-1', status: 'eligible',
metadata: {
productContexts: [{ productFamily: 'Windows Server', productVersion: '2025', roles: ['Any'], scopes: ['mixed'] }]
}
}],
baselineAnalyses: options.baseline === 'missing' || !options.baseline ? [] : [{
analysis: {
id: 'baseline-analysis-1', baselineId: 'baseline-1', analyzedAt: timestamp, status: options.baseline
}
}]
} as unknown as BuildPolicyAnalysisOverviewInput;
}
describe('policy analysis action readiness', () => {
it('offers deterministic reprocessing and aggregation for active source and profile revisions', () => {
const overview = buildPolicyAnalysisOverview(input());
expect(overview.actions.reprocess).toEqual([expect.objectContaining({
status: 'ready', artifactId: 'artifact-1', expectedLatestProcessingRunId: 'processing-1'
})]);
expect(overview.actions.aggregate).toEqual([expect.objectContaining({
status: 'ready', environmentProfileId: 'profile-1', sourceAnalysisRunIds: ['identity-run-1']
})]);
});
it('selects one matching complete baseline analysis and offers evaluation', () => {
const overview = buildPolicyAnalysisOverview(input({ aggregation: true, baseline: 'complete' }));
expect(overview.actions.aggregate[0]).toMatchObject({ status: 'completed', existingRunId: 'aggregation-1' });
expect(overview.actions.evaluate).toEqual([expect.objectContaining({
status: 'ready', aggregationRunId: 'aggregation-1', baselineAnalysisIds: ['baseline-analysis-1']
})]);
});
it('excludes superseded profile and identity-analysis revisions from new actions', () => {
const data = input();
const oldProfile = data.profiles[0];
data.profiles.push({
...oldProfile,
id: 'profile-2',
name: 'Windows Server 2025 revised',
revision: 2,
supersedesProfileId: oldProfile.id
});
const oldAnalysis = data.identityAnalyses[0];
data.identityAnalyses.push({
...oldAnalysis,
run: {
...oldAnalysis.run,
id: 'identity-run-2',
sourceProcessingRunId: 'processing-2',
supersedesAnalysisRunId: oldAnalysis.run.id
}
});
data.processingRuns!.push({ id: 'processing-2', artifactId: 'artifact-1', completedAt: '2026-08-04T16:01:00.000Z' } as never);
const overview = buildPolicyAnalysisOverview(data);
expect(overview.actions.aggregate).toHaveLength(1);
expect(overview.actions.aggregate[0]).toMatchObject({
environmentProfileId: 'profile-2', sourceAnalysisRunIds: ['identity-run-2']
});
expect(overview.actions.reprocess[0].expectedLatestProcessingRunId).toBe('processing-2');
});
it('marks partial baseline evidence review-only and blocks missing catalog evidence', () => {
const partial = buildPolicyAnalysisOverview(input({ aggregation: true, baseline: 'partial' }));
expect(partial.actions.evaluate[0]).toMatchObject({
status: 'review-only', reasonCodes: ['policy-action-baseline-analysis-partial']
});
const missing = buildPolicyAnalysisOverview(input({ aggregation: true, baseline: 'missing' }));
expect(missing.actions.evaluate[0]).toMatchObject({ status: 'blocked' });
expect(missing.actions.evaluate[0].reasonCodes).toContain('policy-action-baseline-no-match');
});
it('keeps a partial evaluation review-only at the Finding Factory boundary', () => {
const overview = buildPolicyAnalysisOverview(input({ aggregation: true, baseline: 'complete', evaluation: true }));
expect(overview.actions.evaluate[0]).toMatchObject({ status: 'completed', existingRunId: 'evaluation-1' });
expect(overview.actions.createFindings).toEqual([expect.objectContaining({
status: 'review-only', evaluationRunId: 'evaluation-1', eligibleResultCount: 0,
reasonCodes: ['policy-action-evaluation-partial']
})]);
});
it('offers reevaluation when only an older evaluator version exists', () => {
const data = input({ aggregation: true, baseline: 'complete', evaluation: true });
data.evaluations[0].run.evaluator = { id: POLICY_EVALUATION_ENGINE.id, version: '1.1.0' } as never;
const overview = buildPolicyAnalysisOverview(data);
expect(overview.actions.evaluate[0]).toMatchObject({
status: 'ready', aggregationRunId: 'aggregation-1', baselineAnalysisIds: ['baseline-analysis-1']
});
expect(overview.actions.evaluate[0].existingRunId).toBeUndefined();
});
});