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); }); });