177 lines
8.2 KiB
TypeScript
177 lines
8.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { artifactPathCollisionKey, normalizeArtifactRelativePath } from '../../src/domain/artifact-path';
|
|
import type { GpoBackupInstance } from '../../src/domain/gpo-ingestion';
|
|
import {
|
|
gpoArtifactInventoryValidationErrors,
|
|
gpoBackupCompleteness,
|
|
gpoBackupInstanceValidationErrors
|
|
} from '../../src/domain/gpo-ingestion';
|
|
import { buildGpoAnalysisSnapshots } from '../../src/gpo/snapshot-builder';
|
|
|
|
const timestamp = '2026-07-20T12:00:00.000Z';
|
|
|
|
function instance(
|
|
id: string,
|
|
gpoId: string,
|
|
backupSetId: string,
|
|
overrides: Partial<GpoBackupInstance> = {}
|
|
): GpoBackupInstance {
|
|
return {
|
|
version: 1,
|
|
id,
|
|
projectId: 'project-synthetic',
|
|
sourceArtifactId: `artifact-${id}`,
|
|
identity: {
|
|
status: 'resolved',
|
|
gpoId,
|
|
backupId: `${id.length.toString(16).padStart(8, '0')}-1111-4111-8111-111111111111`,
|
|
evidence: 'backup-metadata'
|
|
},
|
|
domain: { status: 'resolved', key: 'domain-synthetic', evidence: 'backup-metadata' },
|
|
collection: { status: 'observed', collectedAt: timestamp, evidence: 'collection-manifest' },
|
|
backupSet: { id: backupSetId, scope: 'complete-domain-export', evidence: 'collection-manifest', expectedGpoCount: 1 },
|
|
metadataStatus: 'complete',
|
|
sections: { computer: 'empty', user: 'disabled' },
|
|
inventory: {
|
|
version: 1,
|
|
status: 'complete',
|
|
entries: [{
|
|
version: 1,
|
|
artifactId: `metadata-${id}`,
|
|
relativePath: `${id}/Backup.xml`,
|
|
role: 'backup-metadata',
|
|
scope: 'shared',
|
|
disposition: 'supported',
|
|
secretIndicatorStatus: 'not-inspected'
|
|
}],
|
|
diagnosticCodes: []
|
|
},
|
|
discoveredAt: timestamp,
|
|
...overrides
|
|
};
|
|
}
|
|
|
|
describe('GPO ingestion contracts', () => {
|
|
it('normalizes safe archive paths and rejects traversal, aliases and Windows special paths', () => {
|
|
expect(normalizeArtifactRelativePath('Domain\\Policies\\Machine\\Registry.pol'))
|
|
.toBe('Domain/Policies/Machine/Registry.pol');
|
|
expect(artifactPathCollisionKey('Domain/Backup.XML')).toBe('domain/backup.xml');
|
|
for (const unsafe of [
|
|
'../escape.xml', '/absolute.xml', '\\server\\share.xml', 'C:\\drive.xml', 'safe/../escape.xml',
|
|
'safe/file.xml:stream', 'safe/CON.txt', 'safe/name. ', 'safe//double.xml', 'safe/fi\u202ele.xml'
|
|
]) expect(normalizeArtifactRelativePath(unsafe)).toBeNull();
|
|
});
|
|
|
|
it('blocks case-colliding inventory paths and executable script treatment', () => {
|
|
const value = instance('one', '11111111-1111-4111-8111-111111111111', 'set-one');
|
|
value.inventory.entries.push({
|
|
version: 1,
|
|
artifactId: 'script-one',
|
|
relativePath: 'ONE/backup.xml',
|
|
role: 'script',
|
|
scope: 'computer',
|
|
disposition: 'supported',
|
|
secretIndicatorStatus: 'not-inspected'
|
|
});
|
|
expect(gpoArtifactInventoryValidationErrors(value.inventory)).toEqual(expect.arrayContaining([
|
|
expect.stringContaining('kollidiert'),
|
|
expect.stringContaining('Skript')
|
|
]));
|
|
});
|
|
|
|
it('keeps inventory-only scripts from authorizing set-absence conclusions', () => {
|
|
const value = instance('scripted', '55555555-5555-4555-8555-555555555555', 'set-scripted');
|
|
value.inventory.entries.push({
|
|
version: 1,
|
|
artifactId: 'script-file',
|
|
relativePath: 'scripted/Machine/Scripts/startup.ps1',
|
|
role: 'script',
|
|
scope: 'computer',
|
|
disposition: 'inventory-only',
|
|
secretIndicatorStatus: 'not-inspected'
|
|
});
|
|
expect(gpoBackupInstanceValidationErrors(value)).toEqual([]);
|
|
expect(gpoBackupCompleteness(value)).toBe('partial');
|
|
expect(buildGpoAnalysisSnapshots([value], { createdAt: timestamp })[0])
|
|
.toMatchObject({ status: 'indeterminate', permitsSetAbsenceConclusions: false });
|
|
});
|
|
|
|
it('forms a complete manifest-backed snapshot without inventing scope or effectiveness', () => {
|
|
const first = instance('first', '11111111-1111-4111-8111-111111111111', 'set-complete', {
|
|
backupSet: { id: 'set-complete', scope: 'complete-domain-export', evidence: 'collection-manifest', expectedGpoCount: 2 }
|
|
});
|
|
const second = instance('second', '22222222-2222-4222-8222-222222222222', 'set-complete', {
|
|
backupSet: { id: 'set-complete', scope: 'complete-domain-export', evidence: 'collection-manifest', expectedGpoCount: 2 }
|
|
});
|
|
const snapshots = buildGpoAnalysisSnapshots([second, first], { createdAt: timestamp });
|
|
|
|
expect(snapshots).toEqual([expect.objectContaining({
|
|
instanceIds: ['first', 'second'],
|
|
status: 'consistent',
|
|
completeness: 'complete',
|
|
permitsSetAbsenceConclusions: true,
|
|
diagnosticCodes: []
|
|
})]);
|
|
});
|
|
|
|
it('separates historical sets and blocks duplicate GPO identities inside one snapshot', () => {
|
|
const historical = buildGpoAnalysisSnapshots([
|
|
instance('old', '11111111-1111-4111-8111-111111111111', 'set-old'),
|
|
instance('new', '11111111-1111-4111-8111-111111111111', 'set-new')
|
|
], { createdAt: timestamp });
|
|
expect(historical).toHaveLength(2);
|
|
expect(historical.every(snapshot => snapshot.status === 'consistent')).toBe(true);
|
|
|
|
const duplicated = buildGpoAnalysisSnapshots([
|
|
instance('copy-a', '11111111-1111-4111-8111-111111111111', 'set-duplicate', {
|
|
backupSet: { id: 'set-duplicate', scope: 'complete-domain-export', evidence: 'collection-manifest', expectedGpoCount: 2 }
|
|
}),
|
|
instance('copy-b', '11111111-1111-4111-8111-111111111111', 'set-duplicate', {
|
|
backupSet: { id: 'set-duplicate', scope: 'complete-domain-export', evidence: 'collection-manifest', expectedGpoCount: 2 }
|
|
})
|
|
], { createdAt: timestamp });
|
|
expect(duplicated[0]).toMatchObject({ status: 'inconsistent', completeness: 'inconsistent', permitsSetAbsenceConclusions: false });
|
|
expect(duplicated[0].diagnosticCodes).toContain('gpo-duplicate-identity-in-snapshot');
|
|
});
|
|
|
|
it('splits mixed domains and keeps partial or selected sets from negative conclusions', () => {
|
|
const otherDomain = instance('other', '22222222-2222-4222-8222-222222222222', 'set-domain');
|
|
otherDomain.domain = { status: 'resolved', key: 'other-domain', evidence: 'backup-metadata' };
|
|
const split = buildGpoAnalysisSnapshots([
|
|
instance('primary', '11111111-1111-4111-8111-111111111111', 'set-domain'),
|
|
otherDomain
|
|
], { createdAt: timestamp });
|
|
expect(split).toHaveLength(2);
|
|
expect(split.every(snapshot => snapshot.status === 'inconsistent'
|
|
&& snapshot.diagnosticCodes.includes('gpo-backup-set-domain-split'))).toBe(true);
|
|
|
|
const selected = instance('selected', '33333333-3333-4333-8333-333333333333', 'set-selected', {
|
|
backupSet: { id: 'set-selected', scope: 'selected-gpos', evidence: 'user-selection' }
|
|
});
|
|
const selectedSnapshot = buildGpoAnalysisSnapshots([selected], { createdAt: timestamp })[0];
|
|
expect(selectedSnapshot).toMatchObject({ status: 'consistent', completeness: 'selected-subset', permitsSetAbsenceConclusions: false });
|
|
|
|
const selectedOtherDomain = instance('selected-other', '55555555-5555-4555-8555-555555555555', 'set-selected', {
|
|
sourceArtifactId: selected.sourceArtifactId,
|
|
domain: { status: 'resolved', key: 'other-domain', evidence: 'backup-metadata' },
|
|
backupSet: { id: 'set-selected', scope: 'selected-gpos', evidence: 'user-selection' }
|
|
});
|
|
const mixedSelected = buildGpoAnalysisSnapshots([selected, selectedOtherDomain], { createdAt: timestamp });
|
|
expect(mixedSelected).toEqual([expect.objectContaining({
|
|
status: 'indeterminate', completeness: 'selected-subset', domain: { status: 'unknown' },
|
|
instanceIds: ['selected', 'selected-other'], permitsSetAbsenceConclusions: false,
|
|
diagnosticCodes: expect.arrayContaining(['gpo-backup-set-domain-mixed'])
|
|
})]);
|
|
|
|
const incomplete = instance('incomplete', '44444444-4444-4444-8444-444444444444', 'set-incomplete', {
|
|
sections: { computer: 'missing', user: 'disabled' }
|
|
});
|
|
expect(gpoBackupCompleteness(incomplete)).toBe('partial');
|
|
expect(gpoBackupInstanceValidationErrors(incomplete)).toEqual([]);
|
|
expect(buildGpoAnalysisSnapshots([incomplete], { createdAt: timestamp })[0]).toMatchObject({
|
|
status: 'indeterminate', permitsSetAbsenceConclusions: false,
|
|
diagnosticCodes: expect.arrayContaining(['gpo-backup-instance-incomplete'])
|
|
});
|
|
});
|
|
});
|