156 lines
10 KiB
TypeScript
156 lines
10 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import type { UnifiedResultProjection } from '../../src/domain/unified-result';
|
|
import type { GpoPolicyValue } from '../../src/domain/gpo-policy';
|
|
import { POLICY_EVALUATION_ENGINE, type PolicyEvaluationResult, type StoredPolicyEvaluation } from '../../src/domain/policy-evaluation';
|
|
import { buildPolicyWorkResults, countPolicyWorkResults } from '../../src/evaluation/policy-work-results';
|
|
import { appendPolicyObservationsToDataset } from '../../src/features/project-import/policy-observations-to-dataset';
|
|
import { filterAndSortRecords } from '../../src/features/search-filter/filter-records';
|
|
import { genericProfile } from '../../src/profiles/generic/generic-profile';
|
|
|
|
function observation(id: string, backupId: string, value: number | 'delete'): UnifiedResultProjection {
|
|
return {
|
|
sourceKind: 'policy-observation', sourceResultId: id, resultProjectionId: `policy-observation:${id}`,
|
|
projectId: 'synthetic', assessmentId: 'slot', importArtifactId: 'import', title: 'Synthetic mode',
|
|
importedAt: '2026-09-17T10:00:00Z', policyScope: 'computer', policyDeliveryMechanism: 'registry-policy',
|
|
policyIdentityStatus: 'resolved', policyIdentityKey: 'synthetic-mode', policyBackupInstanceId: backupId,
|
|
policyState: value === 'delete' ? 'instruction' : 'configured',
|
|
policyValues: [{ kind: 'registry-policy', key: 'Software\\Policies\\Synthetic', valueName: 'Mode',
|
|
operation: value === 'delete' ? 'delete-value' : 'set-value',
|
|
...(value === 'delete' ? {} : { typeCode: 4, data: { kind: 'uint32', value } }) }]
|
|
} as unknown as UnifiedResultProjection;
|
|
}
|
|
|
|
function evaluation(
|
|
observations: UnifiedResultProjection[], customerState: PolicyEvaluationResult['customerState'],
|
|
patch: Partial<PolicyEvaluationResult> = {}
|
|
): StoredPolicyEvaluation {
|
|
const decision = customerState === 'configured' ? 'all-pass' : customerState === 'conflicted' ? 'all-fail' : 'unavailable';
|
|
return {
|
|
version: 1,
|
|
run: { id: 'evaluation', projectId: 'synthetic', aggregationRunId: 'aggregation', evaluator: POLICY_EVALUATION_ENGINE,
|
|
completedAt: '2026-09-17T10:01:00Z' },
|
|
results: [{
|
|
id: 'comparison', observationIds: observations.map(source => source.sourceResultId),
|
|
backupInstanceIds: [...new Set(observations.map(source => source.policyBackupInstanceId))],
|
|
baselineId: 'baseline', baselineLabel: 'Synthetic baseline', baselineState: 'explicit-recommendation',
|
|
identity: { id: 'synthetic-mode', scope: 'computer', target: { kind: 'registry-value', hive: 'HKEY_LOCAL_MACHINE',
|
|
key: 'Software\\Policies\\Synthetic', valueName: 'Mode' } },
|
|
customerState, classification: decision === 'all-pass' ? 'configured_match'
|
|
: decision === 'all-fail' ? 'confirmed_configured_deviation' : 'review_required',
|
|
comparator: { version: 1, comparator: { kind: 'equality' }, relation: decision === 'all-pass' ? 'match'
|
|
: decision === 'all-fail' ? 'different' : 'incomparable', baseline: { kind: 'integer', value: '1' }, diagnosticCodes: [] },
|
|
baselineVariantAssessment: { version: 1, scope: 'backup-configuration', decision,
|
|
actualValues: customerState === 'configured' ? [observations.at(-1)!.policyValues![0]] : observations.flatMap(source => source.policyValues!),
|
|
variants: [], diagnosticCodes: [] },
|
|
diagnosticCodes: customerState === 'conflicted' ? ['policy-evaluation-customer-conflict'] : [], ...patch
|
|
}], evidence: []
|
|
} as unknown as StoredPolicyEvaluation;
|
|
}
|
|
|
|
function uncertainObservation(id: string, backupId: string, operation = 'unsupported-special'): UnifiedResultProjection {
|
|
const source = observation(id, backupId, 0);
|
|
return { ...source, policyState: 'instruction', policyValues: source.policyValues!.map(value => ({
|
|
...value as object, operation
|
|
})) } as UnifiedResultProjection;
|
|
}
|
|
|
|
describe('GPO operation diagnostics in evaluated Work settings', () => {
|
|
it('keeps set/delete evidence from four GPOs in both Deviation and Conflicts without multiplying the verdict', () => {
|
|
const sources = [observation('a', 'backup-a', 1), observation('b', 'backup-b', 'delete'),
|
|
observation('c', 'backup-c', 1), observation('d', 'backup-d', 1)];
|
|
const saved = evaluation(sources, 'conflicted');
|
|
const before = JSON.stringify([sources, saved]);
|
|
const results = buildPolicyWorkResults(sources, [saved]);
|
|
expect(results).toHaveLength(1);
|
|
expect(results[0]).toMatchObject({ status: 'Fail', conflict: true, duplicate: false, sources });
|
|
expect(countPolicyWorkResults(results)).toMatchObject({ Fail: 1, Conflicts: 1, Duplicates: 0 });
|
|
const dataset = appendPolicyObservationsToDataset(null, 'Synthetic', sources, [saved])!;
|
|
const sheet = dataset.sheets[0];
|
|
const options = { activeProfile: genericProfile, dataset, search: '', sortColumn: '', sortDirection: 'asc' as const,
|
|
actionOnly: false, reviews: {} };
|
|
const deviation = filterAndSortRecords(sheet, { ...options, statusFilter: 'deviation' });
|
|
const conflicts = filterAndSortRecords(sheet, { ...options, statusFilter: 'conflicts' });
|
|
expect(deviation).toHaveLength(1);
|
|
expect(conflicts).toEqual(deviation);
|
|
expect(filterAndSortRecords(sheet, { ...options, statusFilter: 'duplicates' })).toEqual([]);
|
|
expect(JSON.stringify([sources, saved])).toBe(before);
|
|
});
|
|
|
|
it('does not turn earlier instructions within one GPO into a conflict or duplicate after authoritative reduction', () => {
|
|
const sources = [observation('first', 'one-backup', 0), observation('delete', 'one-backup', 'delete'),
|
|
observation('last', 'one-backup', 1)];
|
|
const results = buildPolicyWorkResults(sources, [evaluation(sources, 'configured')]);
|
|
expect(results).toHaveLength(1);
|
|
expect(results[0]).toMatchObject({ status: 'Pass', conflict: false, duplicate: false, sources });
|
|
expect(countPolicyWorkResults(results)).toMatchObject({ Pass: 1, Conflicts: 0, Duplicates: 0 });
|
|
});
|
|
|
|
it('counts identical reduced contributions from two GPOs as duplicate despite earlier differing instructions', () => {
|
|
const sources = [observation('first', 'backup-a', 0), observation('delete', 'backup-a', 'delete'),
|
|
observation('last-a', 'backup-a', 1), observation('last-b', 'backup-b', 1)];
|
|
const results = buildPolicyWorkResults(sources, [evaluation(sources, 'configured')]);
|
|
expect(results).toHaveLength(1);
|
|
expect(results[0]).toMatchObject({ status: 'Pass', conflict: false, duplicate: true });
|
|
});
|
|
|
|
it('does not infer a conflict from unreduced raw instructions when the retained customer state is indeterminate', () => {
|
|
const sources = [observation('first', 'one-backup', 0), observation('last', 'one-backup', 1)];
|
|
const results = buildPolicyWorkResults(sources, [evaluation(sources, 'indeterminate')]);
|
|
expect(results).toHaveLength(1);
|
|
expect(results[0]).toMatchObject({ status: 'Review required', conflict: false, duplicate: false });
|
|
});
|
|
|
|
it.each(['unsupported-special', 'set-value-if-absent'])('retains a proved two-GPO conflict beside another GPO with %s', operation => {
|
|
const sources = [observation('a', 'backup-a', 0), observation('b', 'backup-b', 1),
|
|
uncertainObservation('c', 'backup-c', operation)];
|
|
const saved = evaluation(sources, 'indeterminate');
|
|
const before = JSON.stringify(saved);
|
|
const results = buildPolicyWorkResults(sources, [saved]);
|
|
expect(results).toHaveLength(1);
|
|
expect(results[0]).toMatchObject({ status: 'Review required', conflict: true, duplicate: false });
|
|
expect(countPolicyWorkResults(results)).toMatchObject({ 'Review required': 1, Conflicts: 1, Fail: 0 });
|
|
const dataset = appendPolicyObservationsToDataset(null, 'Synthetic', sources, [saved])!;
|
|
const record = dataset.sheets[0].analysis.records[0];
|
|
expect(genericProfile.matchesStatus(record, 'conflicts')).toBe(true);
|
|
expect(genericProfile.matchesStatus(record, 'deviation')).toBe(false);
|
|
expect(JSON.stringify(saved)).toBe(before);
|
|
});
|
|
|
|
it('does not resurrect a prior same-GPO value absent from the retained compared values', () => {
|
|
const sources = [observation('first', 'backup-a', 0), observation('last', 'backup-a', 1),
|
|
uncertainObservation('unknown', 'backup-b')];
|
|
const saved = evaluation(sources, 'indeterminate');
|
|
saved.results[0].baselineVariantAssessment!.actualValues = sources.slice(1).flatMap(source => source.policyValues!) as GpoPolicyValue[];
|
|
const results = buildPolicyWorkResults(sources, [saved]);
|
|
expect(results).toHaveLength(1);
|
|
expect(results[0]).toMatchObject({ status: 'Review required', conflict: false, duplicate: false });
|
|
});
|
|
|
|
it('excludes the whole uncertain backup even when it retains several earlier comparable writes', () => {
|
|
const sources = [observation('first', 'backup-a', 0), observation('second', 'backup-a', 1),
|
|
uncertainObservation('unknown', 'backup-a'), observation('known', 'backup-b', 1)];
|
|
const results = buildPolicyWorkResults(sources, [evaluation(sources, 'indeterminate')]);
|
|
expect(results).toHaveLength(1);
|
|
expect(results[0]).toMatchObject({ status: 'Review required', conflict: false, duplicate: false });
|
|
});
|
|
|
|
it('does not derive a new indeterminate conflict without retained compared-value evidence', () => {
|
|
const sources = [observation('a', 'backup-a', 0), observation('b', 'backup-b', 1), uncertainObservation('c', 'backup-c')];
|
|
const saved = evaluation(sources, 'indeterminate');
|
|
saved.results[0].baselineVariantAssessment!.actualValues = [];
|
|
const results = buildPolicyWorkResults(sources, [saved]);
|
|
expect(results[0]).toMatchObject({ status: 'Review required', conflict: false, duplicate: false });
|
|
});
|
|
|
|
it('does not call distinct child settings duplicate GPO contributions in a registry-key requirement', () => {
|
|
const sources = [observation('a', 'backup-a', 1), observation('b', 'backup-b', 1)];
|
|
const saved = evaluation(sources, 'configured', { identity: {
|
|
id: 'synthetic-key', scope: 'computer', target: { kind: 'registry-key', hive: 'HKEY_LOCAL_MACHINE',
|
|
key: 'Software\\Policies\\Synthetic' }
|
|
} as PolicyEvaluationResult['identity'] });
|
|
const results = buildPolicyWorkResults(sources, [saved]);
|
|
expect(results).toHaveLength(1);
|
|
expect(results[0]).toMatchObject({ status: 'Pass', conflict: false, duplicate: false });
|
|
});
|
|
});
|