Assessor_2.0/tests/comparison/gpo-timeslot-comparison.tes...

130 lines
7.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { PolicyWorkResult, PolicyWorkStatus } from '../../src/evaluation/policy-work-results';
import { compareGpoTimeslots } from '../../src/comparison/gpo-timeslot-comparison';
// Synthetic rolled-up GPO Work results. Only the fields the comparison reads are populated.
const observedA = '2026-01-01T10:00:00.000Z';
const observedB = '2026-02-01T10:00:00.000Z';
function setting(id: string, identity: string, status: PolicyWorkStatus, options: {
actual?: unknown; baselineId?: string; aggregationRun?: string; scope?: string;
} = {}): PolicyWorkResult {
const scope = options.scope ?? 'computer';
return {
id, status, title: identity, scope, identity, importedAt: observedA, sources: [],
settingKey: JSON.stringify(['dummy-project', options.aggregationRun ?? 'aggregation-1', scope, identity]),
comparison: {
baselineId: options.baselineId ?? 'baseline-server',
comparator: { actual: options.actual === undefined ? undefined : { kind: 'number', value: options.actual } }
} as unknown as PolicyWorkResult['comparison']
};
}
describe('GPO timeslot comparison', () => {
it('pairs rolled-up settings by scope and identity and keeps a value change neutral', () => {
const result = compareGpoTimeslots(
{ observedAt: observedA, results: [
setting('a-1', 'MinimumPasswordLength', 'Fail', { actual: 7 }),
setting('a-2', 'LockoutBadCount', 'Fail', { actual: 0 }),
setting('a-3', 'NoLMHash', 'Fail', { actual: 0 }),
setting('a-4', 'PasswordComplexity', 'Fail', { actual: 0 })
] },
{ observedAt: observedB, results: [
setting('b-1', 'MinimumPasswordLength', 'Fail', { actual: 10 }),
setting('b-2', 'LockoutBadCount', 'Fail', { actual: 0 }),
setting('b-3', 'NoLMHash', 'Pass', { actual: 1 }),
setting('b-4', 'PasswordComplexity', 'Not configured')
] }
);
const byIdentity = Object.fromEntries(result.decisions.map(item => [item.identity, item]));
expect(byIdentity.MinimumPasswordLength).toMatchObject({ state: 'confirmed-open', valueChanged: true, targetResultIds: ['b-1'] });
// Both retained values travel as Work displays them, for the Finding Reader's side-by-side reading.
expect(byIdentity.MinimumPasswordLength).toMatchObject({
baselineValues: ['{"kind":"number","value":7}'], targetValues: ['{"kind":"number","value":10}'] });
expect(byIdentity.PasswordComplexity.targetValues).toEqual([]);
expect(byIdentity.LockoutBadCount).toMatchObject({ state: 'confirmed-open', valueChanged: false });
expect(byIdentity.NoLMHash).toMatchObject({ state: 'verified-remediated', reasons: ['same-setting-explicit-pass'] });
// A setting missing from the newer backup set is not proof of a removed configuration.
expect(byIdentity.PasswordComplexity).toMatchObject({ state: 'unverified-absent', reasons: ['target-verdict-not-decisive:Not configured'] });
});
it('rolls several baseline releases of one setting up with the Work verdict order', () => {
const result = compareGpoTimeslots(
{ observedAt: observedA, results: [
setting('a-1', 'SeBackupPrivilege', 'Pass', { baselineId: 'baseline-dc' }),
setting('a-2', 'SeBackupPrivilege', 'Fail', { baselineId: 'baseline-server' })
] },
{ observedAt: observedB, results: [
setting('b-1', 'SeBackupPrivilege', 'Pass', { baselineId: 'baseline-dc' }),
setting('b-2', 'SeBackupPrivilege', 'Pass', { baselineId: 'baseline-server' })
] }
);
expect(result.decisions).toHaveLength(1);
expect(result.decisions[0]).toMatchObject({ state: 'verified-remediated', baselineResultIds: ['a-1', 'a-2'] });
});
it('separates new and regressed settings and claims nothing from undecided history', () => {
const result = compareGpoTimeslots(
{ observedAt: observedA, results: [
setting('a-1', 'SeEnableDelegationPrivilege', 'Not configured'),
setting('a-2', 'SeRestorePrivilege', 'Pass'),
setting('a-3', 'SeAuditPrivilege', 'Review required'),
setting('a-4', 'SeLoadDriverPrivilege', 'Pass', { baselineId: 'baseline-old' })
] },
{ observedAt: observedB, results: [
setting('b-1', 'SeEnableDelegationPrivilege', 'Fail'),
setting('b-2', 'SeRestorePrivilege', 'Fail'),
setting('b-3', 'SeAuditPrivilege', 'Fail'),
setting('b-4', 'SeLoadDriverPrivilege', 'Fail'),
setting('b-5', 'DnsPolicyConfig', 'Fail')
] }
);
expect(result.newDeviations.map(item => item.targetResultIds[0]).sort()).toEqual(['b-1', 'b-5']);
expect(result.regressedDeviations.map(item => item.targetResultIds[0])).toEqual(['b-2']);
});
it('requires a newer backup set, the same baselines and a GPO source in both timeslots', () => {
const fail = { observedAt: observedA, results: [setting('a-1', 'NoLMHash', 'Fail')] };
const sameTime = compareGpoTimeslots(fail, { observedAt: observedA, results: [setting('b-1', 'NoLMHash', 'Pass')] });
expect(sameTime.decisions[0]).toMatchObject({ state: 'not-comparable', reasons: ['target-observation-not-newer'] });
expect(sameTime.sharedPassRate).toBeUndefined();
// Without stated backup times (real backups state no time zone) the timeslot order decides.
const untimed = compareGpoTimeslots({ results: fail.results }, { results: [setting('b-1', 'NoLMHash', 'Pass')] });
expect(untimed.decisions[0]).toMatchObject({ state: 'verified-remediated' });
const changedBaseline = compareGpoTimeslots(fail, { observedAt: observedB, results: [setting('b-1', 'NoLMHash', 'Pass', { baselineId: 'baseline-new' })] });
expect(changedBaseline.decisions[0]).toMatchObject({ state: 'not-comparable', reasons: ['baseline-set-changed'] });
const missing = compareGpoTimeslots(fail, { observedAt: observedB, results: [] });
expect(missing).toMatchObject({ state: 'not-comparable', reasons: ['target-source-missing'] });
expect(missing.decisions[0].state).toBe('not-comparable');
});
it('marks a setting held by several aggregation runs of one timeslot as ambiguous', () => {
const result = compareGpoTimeslots(
{ observedAt: observedA, results: [
setting('a-1', 'NoLMHash', 'Fail', { aggregationRun: 'aggregation-1' }),
setting('a-2', 'NoLMHash', 'Fail', { aggregationRun: 'aggregation-2' })
] },
{ observedAt: observedB, results: [setting('b-1', 'NoLMHash', 'Pass')] }
);
expect(result.decisions[0]).toMatchObject({ state: 'not-comparable', reasons: ['gpo-setting-ambiguous'] });
});
it('computes the shared pass rate only over settings decided in both timeslots', () => {
const result = compareGpoTimeslots(
{ observedAt: observedA, results: [
setting('a-1', 'One', 'Fail'), setting('a-2', 'Two', 'Fail'), setting('a-3', 'Three', 'Pass'),
setting('a-4', 'OnlyInA', 'Fail')
] },
{ observedAt: observedB, results: [
setting('b-1', 'One', 'Pass'), setting('b-2', 'Two', 'Fail'), setting('b-3', 'Three', 'Pass'),
setting('b-5', 'OnlyInB', 'Pass')
] }
);
expect(result.sharedPassRate?.settings).toBe(3);
expect(result.sharedPassRate?.baseline).toBeCloseTo(100 / 3);
expect(result.sharedPassRate?.target).toBeCloseTo(200 / 3);
});
});