Assessor_2.0/tests/importers/pingcastle-environment-aggr...

109 lines
5.7 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { EnvironmentObservationCandidate } from '../../src/domain/source-extraction';
import { derivePingCastleEnvironmentAggregates } from '../../src/importers/pingcastle-environment-aggregates';
function cell(
subjectType: string,
label: string,
value: string | number | boolean,
valueType: EnvironmentObservationCandidate['valueType'],
state: EnvironmentObservationCandidate['state'] = 'observed'
): EnvironmentObservationCandidate {
return {
version: 1,
subject: { type: subjectType, identity: 'sevenkingdoms:private', label: 'Private tenant' },
property: `pingcastle.section.tabelle.99.${label.toLowerCase().replace(/[^a-z0-9]+/g, '.')}`,
label,
originalValue: value,
normalizedValue: value,
valueType,
state,
sectionKey: 'sectionSynthetic',
source: { fileName: 'ad_hc_report.html', objectPath: 'html/section/cell' },
attributes: {}
};
}
function byProperty(observations: EnvironmentObservationCandidate[]) {
return new Map(observations.map(observation => [observation.property, observation.normalizedValue]));
}
describe('PingCastle environment aggregate derivation', () => {
it('derives stable account, inventory, policy and configuration aggregates from raw cells', () => {
const input: EnvironmentObservationCandidate[] = [
cell('user-information.account-analysis', 'Nb User Accounts', 16, 'number'),
cell('user-information.account-analysis', 'Nb Enabled', 15, 'number'),
cell('user-information.account-analysis', 'Nb Disabled', 1, 'number'),
cell('user-information.account-analysis', 'Nb Active', 15, 'number'),
cell('user-information.account-analysis', 'Nb pwd never Expire', 15, 'number'),
cell('computer-information.account-analysis', 'Nb Computer Accounts', 1, 'number'),
cell('computer-information.account-analysis', 'Nb Enabled', 1, 'number'),
cell('admin-groups.groups', 'Admin groups list row count', 14, 'number'),
cell('trusts-details.discovered-domains', 'Trusts list row count', 0, 'number'),
cell('pki.certificate-templates', 'Certificate Templates list row count', 12, 'number'),
cell('pki.certificate-templates', 'Delegations list row count', 12, 'number'),
cell('pki.certificates', 'Certificates list row count', 1, 'number'),
cell('pki.domain-controller-certificate', 'DC Certificates list row count', 1, 'number'),
cell('password-policies.password-policies', 'Complexity', false, 'boolean'),
cell('password-policies.password-policies', 'Lockout Threshold', 5, 'number'),
cell('password-policies.password-policies', 'Min Password Length', 5, 'number'),
cell('password-policies.password-policies', 'Max Password Age', '37201 day(s)', 'string'),
cell('anomalies.laps', 'Legacy LAPS installation date', 'Never', 'datetime', 'explicitly-absent'),
cell('anomalies.laps', 'Windows LAPS installation date', 'Never', 'datetime', 'explicitly-absent')
];
// The LAPS presence booleans key off the canonical installation-date facts.
input.push({
...cell('anomalies.laps', 'Legacy LAPS installation date', 'Never', 'datetime', 'explicitly-absent'),
property: 'laps.legacy.installation_date'
});
input.push({
...cell('anomalies.laps', 'Windows LAPS installation date', '2026-05-27T10:00:00Z', 'datetime', 'observed'),
property: 'laps.windows.installation_date'
});
const derived = derivePingCastleEnvironmentAggregates(input);
const values = byProperty(derived);
expect(values.get('user.account_count')).toBe(16);
expect(values.get('user.account_enabled_count')).toBe(15);
expect(values.get('user.account_disabled_count')).toBe(1);
expect(values.get('user.account_active_count')).toBe(15);
expect(values.get('user.password_never_expires_count')).toBe(15);
expect(values.get('computer.account_count')).toBe(1);
expect(values.get('computer.account_enabled_count')).toBe(1);
expect(values.get('admin_group.count')).toBe(14);
expect(values.get('trust.count')).toBe(0);
expect(values.get('pki.certificate_template_count')).toBe(12);
expect(values.get('pki.delegation_count')).toBe(12);
expect(values.get('pki.certificate_count')).toBe(1);
expect(values.get('pki.dc_certificate_count')).toBe(1);
expect(values.get('password_policy.complexity_enabled')).toBe(false);
expect(values.get('password_policy.lockout_threshold')).toBe(5);
expect(values.get('password_policy.minimum_length')).toBe(5);
expect(values.get('password_policy.maximum_age_days')).toBe(37201);
expect(values.get('laps.legacy.present')).toBe(false);
expect(values.get('laps.windows.present')).toBe(true);
// Every derived value is a plain count or boolean under the shared AD subject;
// no source identity, label text, or raw path is carried through.
derived.forEach(observation => {
expect(observation.subject.type).toBe('active-directory-environment');
expect(['number', 'boolean']).toContain(observation.valueType);
expect(observation.subject.identity).toBeUndefined();
});
expect(JSON.stringify(derived)).not.toMatch(/private|sevenkingdoms/i);
});
it('emits nothing for unrelated observations and never overwrites an existing stable property', () => {
expect(derivePingCastleEnvironmentAggregates([
cell('gpo.login', 'Some login row', 3, 'number')
])).toEqual([]);
const preexisting = derivePingCastleEnvironmentAggregates([
{ ...cell('active-directory-environment', 'User accounts', 99, 'number'), property: 'user.account_count' },
cell('user-information.account-analysis', 'Nb User Accounts', 16, 'number')
]);
expect(preexisting.some(observation => observation.property === 'user.account_count')).toBe(false);
});
});