Assessor_2.0/tests/features/gpo-baseline-comparison-rea...

181 lines
14 KiB
TypeScript

// @vitest-environment jsdom
import { renderToStaticMarkup } from 'react-dom/server';
import { describe, expect, it } from 'vitest';
import type { UnifiedResultProjection } from '../../src/domain/unified-result';
import { POLICY_EVALUATION_ENGINE, type PolicyEvaluationResult, type StoredPolicyEvaluation } from '../../src/domain/policy-evaluation';
import { appendPolicyObservationsToDataset } from '../../src/features/project-import/policy-observations-to-dataset';
import { FindingReader } from '../../src/features/finding-reader/FindingReader';
import { wellKnownPrincipalNames } from '../../src/profiles/group-policy/well-known-principals';
/** Synthetic privilege-right setting compared against several baseline packages. */
function fixture(packages: number[], configured = ['*S-1-5-32-544', '*S-1-5-32-549'],
expectedFor: (index: number) => string[] = () => ['*S-1-5-32-544']) {
const actual = { kind: 'principal-set', section: 'Privilege Rights', key: 'SeSyntheticPrivilege', principals: configured } as const;
const observation = { sourceKind: 'policy-observation', sourceResultId: 'synthetic-observation', projectId: 'synthetic',
assessmentId: 'slot', policyBackupInstanceId: 'synthetic-backup', policyIdentityKey: 'synthetic-right',
policyIdentityStatus: 'resolved', policyScope: 'computer', policyState: 'configured', policyDeliveryMechanism: 'security-template',
importedAt: '2026-09-01T00:00:00Z', title: 'Privilege Rights · SeSyntheticPrivilege', policyValues: [actual],
sourceLocator: { objectPath: 'Synthetic/DomainSysvol/GPO/Machine/microsoft/windows nt/SecEdit/GptTmpl.inf#lines=1' }
} as unknown as UnifiedResultProjection;
const comparator = (baseline: string[]) => ({ version: 1, comparator: { kind: 'set', mode: 'exact' },
relation: configured.length === baseline.length && configured.every(item => baseline.includes(item)) ? 'match' : 'different',
diagnosticCodes: [], actual: { kind: 'string-set', values: configured }, baseline: { kind: 'string-set', values: baseline } });
const results = packages.map((variants, index) => {
const expected = { ...actual, principals: expectedFor(index) };
const variantList = Array.from({ length: variants }, (_, variant) => ({
settingId: `setting-${index}-${variant}`, baselineValues: [expected], comparator: comparator(expected.principals)
}));
const decision = variantList.every(item => item.comparator.relation === 'match') ? 'all-pass' : 'all-fail';
return {
id: `comparison-${index}`, baselineId: `baseline-${index}`, baselineLabel: `Synthetic baseline ${index}`,
baselineAnalysisId: `analysis-${index}`, observationIds: [observation.sourceResultId],
baselineState: 'explicit-recommendation', customerState: 'configured',
classification: decision === 'all-pass' ? 'configured_match' : 'confirmed_configured_deviation',
identity: { id: 'synthetic-right', scope: 'computer', target: { kind: 'security-template', section: 'Privilege Rights', key: 'SeSyntheticPrivilege' } },
diagnosticCodes: [], comparator: comparator(expected.principals),
baselineVariantAssessment: { version: 1, scope: 'backup-configuration', decision, diagnosticCodes: [],
actualValues: [actual], variants: variantList }
};
}) as unknown as PolicyEvaluationResult[];
const evaluation = { run: { id: 'evaluation', projectId: 'synthetic', aggregationRunId: 'aggregation',
evaluator: POLICY_EVALUATION_ENGINE, completedAt: '2026-09-02T00:00:00Z' }, results } as StoredPolicyEvaluation;
return { observation, evaluation };
}
function read(observation: UnifiedResultProjection, evaluations: StoredPolicyEvaluation[]) {
const dataset = appendPolicyObservationsToDataset(null, 'Synthetic', [observation], evaluations)!;
const record = dataset.sheets[0].analysis.records[0];
const html = renderToStaticMarkup(<FindingReader record={record} title={record.title} linkedSheets={[]} resultReferences={[]}
search="" position={0} recordCount={1} toolId="group-policy" onPrevious={() => {}} onNext={() => {}} onReview={() => {}}/>);
const container = document.createElement('div');
container.innerHTML = html;
return { record, container };
}
const count = (text: string, part: string) => text.split(part).length - 1;
describe('GPO Finding Reader baseline comparison', () => {
it('states identical variant outcomes once and keeps every stored comparison in a closed disclosure', () => {
const { observation, evaluation } = fixture([1, 2, 3]);
const { record, container } = read(observation, [evaluation]);
const section = container.querySelector('.policy-baseline-comparison')!;
expect(section.getAttribute('data-consistent')).toBe('true');
// The first block states the problem; the comparison section does not repeat it.
const problem = container.querySelector('.policy-problem')!;
expect(problem.querySelector('h4')?.textContent).toBe('Problem');
const values = Object.fromEntries([...problem.querySelectorAll('.policy-problem__values > div')]
.map(row => [row.querySelector('dt')!.textContent, row.querySelector('dd')!.textContent]));
// Red marks the member absent from every variant, green the member every variant expects;
// the separate Additional entries row is then redundant.
expect(values).toEqual({ Configured: '*S-1-5-32-544 (Administrators)*S-1-5-32-549 (Server Operators)',
Expected: 'Values: *S-1-5-32-544 (Administrators)' });
expect([...problem.querySelectorAll('.policy-problem__entry[data-state]')].map(entry => [entry.textContent, entry.getAttribute('data-state')]))
.toEqual([['*S-1-5-32-544 (Administrators)', 'aligned'], ['*S-1-5-32-549 (Server Operators)', 'deviating']]);
// The source section carries the same proof: green for the expected member, red for the extra one.
expect(container.querySelector('.policy-source-aligned-value')?.textContent?.trim()).toBe('*S-1-5-32-544 (Administrators)');
expect(container.querySelector('.policy-source-deviation-value')?.textContent?.trim()).toBe('*S-1-5-32-549 (Server Operators)');
expect(problem.querySelector('.policy-problem__context')?.textContent)
.toBe('1 GPO backup source · differs from all 6 assessed baseline variants in 3 baseline packages');
expect(problem.nextElementSibling?.matches('.policy-next-step')).toBe(true);
expect(container.querySelector('.policy-next-step')?.textContent).toContain('Expected: Values: *S-1-5-32-544');
expect(problem.compareDocumentPosition(container.querySelector('.policy-source-evidence')!) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
expect(section.querySelector('.policy-baseline-comparison__verdict')).toBeNull();
expect(section.querySelector('.policy-baseline-comparison__facts')).toBeNull();
const text = container.querySelector('.finding-reader')!.textContent!;
expect(count(text, 'target role is not established')).toBe(1);
expect(count(text, 'not effective device security')).toBe(1);
const details = section.querySelector('details')!;
expect(details.hasAttribute('open')).toBe(false);
expect(details.querySelector('summary')?.textContent).toBe('All baseline comparisons (3 packages, 6 variants)');
expect(details.querySelectorAll('.policy-baseline-package')).toHaveLength(3);
expect(details.querySelectorAll('.policy-baseline-variant')).toHaveLength(6);
// User correction 2026-09-25: every variant shows its own expected value and result, not only the package verdict.
const firstVariant = details.querySelector('.policy-baseline-variant')!;
expect(firstVariant.querySelector('.policy-baseline-variant__values dd')?.textContent).toBe('Values: *S-1-5-32-544 (Administrators)');
expect(firstVariant.querySelector('.policy-baseline-variant__result')?.textContent).toBe('Different');
expect(firstVariant.querySelector('.policy-baseline-variant__result')?.getAttribute('data-outcome')).toBe('fail');
expect(details.querySelectorAll('.policy-baseline-variant__values')).toHaveLength(6);
expect(details.textContent).not.toContain('Every variant below has the outcome shown above.');
// Expected value and remediation appear once instead of once per package.
expect(container.querySelector('.policy-value-block[data-value="expected"]')).toBeNull();
expect(container.querySelectorAll('.policy-problem__step')).toHaveLength(1);
expect(container.querySelector('.reader-remediation')).toBeNull();
expect(details.querySelector('table')).toBeNull();
for (const id of ['setting-0-0', 'setting-1-1', 'setting-2-2']) expect(details.textContent).toContain(`Setting reference: ${id}`);
// The repeated per-package fields are not rendered a second time in the reader.
expect([...container.querySelectorAll('.reader-summary h4')].map(item => item.textContent)).not.toContain('Comparison');
// Exports and search keep the complete original text.
const description = record.fields.find(field => field.label === 'Description')!.value;
expect(count(description, 'target role is not established')).toBe(3);
expect(count(description, 'Setting reference:')).toBe(6);
});
it('opens the disclosure and does not claim a shared outcome when package verdicts differ', () => {
const { observation, evaluation } = fixture([1, 1]);
const pass = evaluation.results[1];
const variant = pass.baselineVariantAssessment!.variants[0];
variant.baselineValues = observation.policyValues as never;
variant.comparator = { ...variant.comparator, relation: 'match',
baseline: { kind: 'string-set', values: ['*S-1-5-32-544', '*S-1-5-32-549'] } } as never;
pass.comparator = variant.comparator;
pass.classification = 'configured_match';
pass.baselineVariantAssessment!.decision = 'all-pass';
const { container } = read(observation, [evaluation]);
const section = container.querySelector('.policy-baseline-comparison')!;
expect(section.getAttribute('data-consistent')).toBe('false');
expect(section.querySelector('.policy-baseline-comparison__verdict')?.textContent).toMatch(/^Outcomes differ between 2 assessed baseline variants/);
expect(section.querySelector('.policy-baseline-comparison__statuses')?.textContent).toBe('Fail: 1 · Pass: 1');
expect(section.querySelectorAll('.policy-baseline-value-group')).toHaveLength(2);
expect(section.querySelector('details')?.hasAttribute('open')).toBe(true);
expect(section.querySelectorAll('.policy-baseline-variant__values')).toHaveLength(2);
expect([...section.querySelectorAll('.policy-baseline-package__header .outcome-mark')].map(item => item.getAttribute('data-outcome'))).toEqual(['fail', 'pass']);
});
it('groups differing expected values of a uniform Fail and names only the exception packages', () => {
const { observation, evaluation } = fixture([1, 1, 1, 1], ['*S-1-5-32-544', '*S-1-5-32-549'],
index => index === 3 ? ['*S-1-5-32-551'] : ['*S-1-5-32-544']);
const { record, container } = read(observation, [evaluation]);
const section = container.querySelector('.policy-baseline-comparison')!;
expect(section.getAttribute('data-uniform-outcome')).toBe('true');
expect(section.getAttribute('data-consistent')).toBe('false');
const problem = container.querySelector('.policy-problem')!;
expect(problem.querySelector('.policy-problem__context')?.textContent)
.toBe('1 GPO backup source · differs from all 4 assessed baseline variants in 4 baseline packages');
const expected = [...problem.querySelectorAll('.policy-problem__values > div')].find(row => row.querySelector('dt')?.textContent === 'Expected');
expect([...expected!.querySelectorAll('.policy-problem__entry')].map(item => item.textContent)).toEqual([
'Values: *S-1-5-32-544 (Administrators) — 3 baseline packages',
'Values: *S-1-5-32-551 (Backup Operators) — Synthetic baseline 3'
]);
// Package-specific differences have no single shared entry list.
expect(problem.textContent).not.toContain('Additional entries');
expect(section.querySelector('.policy-baseline-comparison__statuses')).toBeNull();
expect(section.querySelector('.policy-baseline-value-group')).toBeNull();
// A uniform result keeps the per-package list closed; its values stay available per variant.
expect(section.querySelector('details')?.hasAttribute('open')).toBe(false);
expect(section.querySelectorAll('.policy-baseline-variant__values')).toHaveLength(4);
expect(container.querySelector('.policy-value-block[data-value="expected"]')).toBeNull();
const next = container.querySelector('.policy-next-step')!;
expect(next.textContent).toContain('Establish the applicable baseline template');
expect([...next.querySelectorAll('.policy-problem__packages')].map(item => item.textContent))
.toEqual(['3 baseline packages', 'Synthetic baseline 3']);
expect(next.querySelector('.policy-problem__sources')?.textContent).toBe('In: GPO backup synthetic-backup');
expect(record.fields.find(field => field.label === 'Remediation')!.value.split('Computer policy:').length - 1).toBe(4);
});
it('keeps the complete stored text for a historical comparison', () => {
const { observation, evaluation } = fixture([1, 1]);
evaluation.run.evaluator = { ...POLICY_EVALUATION_ENGINE, version: '1.8.0' };
const { record, container } = read(observation, [evaluation]);
expect(record.policyBaselineComparisons).toBeUndefined();
expect(container.querySelector('.policy-baseline-comparison')).toBeNull();
});
it('names only documented well-known SIDs next to the unchanged original value', () => {
expect(wellKnownPrincipalNames('Values: *S-1-5-32-544, *S-1-5-32-549')).toEqual(['Administrators', 'Server Operators']);
// Standardized domain RIDs are named; the domain part itself stays unresolved.
expect(wellKnownPrincipalNames('*S-1-5-21-1000-2000-3000-512')).toEqual(['Domain Admins']);
expect(wellKnownPrincipalNames('*S-1-5-21-1000-2000-3000-1105')).toEqual([]);
expect(wellKnownPrincipalNames('*S-1-5-32-5440')).toEqual([]);
});
});