127 lines
8.4 KiB
TypeScript
127 lines
8.4 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { GpoAutomaticAnalysisService } from '../../server/gpo/gpo-automatic-analysis-service';
|
|
import { ingestGpoBackupContainer } from '../../server/gpo/gpo-backup-ingestion';
|
|
import { syntheticGpoBackupZip } from './gpo-synthetic-fixture';
|
|
import type { AssessorRepository } from '../../src/persistence/assessor-repository';
|
|
import type { GpoPolicySnapshotService } from '../../server/gpo/gpo-policy-snapshot-service';
|
|
import type { GpoPolicyEvaluationService } from '../../server/gpo/gpo-policy-evaluation-service';
|
|
import type { GpoPolicyFindingService } from '../../server/gpo/gpo-policy-finding-service';
|
|
|
|
const timestamp = '2026-09-06T10:00:00Z';
|
|
function setup() {
|
|
const instances = ingestGpoBackupContainer(syntheticGpoBackupZip(), {
|
|
projectId: 'project', sourceArtifactId: 'artifact', discoveredAt: timestamp
|
|
}).backupInstances.map(instance => ({ processingRunId: 'processing', instance }));
|
|
const environment = { observations: [{ id: 'dc-os', state: 'observed', property: 'operating_system.name',
|
|
originalValue: 'Windows 2019', subject: { type: 'computer-information.domain-controllers' } }] };
|
|
const repository = {
|
|
listGpoPolicyAnalyses: vi.fn().mockResolvedValue([{ run: { id: 'analysis', sourceProcessingRunId: 'processing', artifactId: 'artifact', completedAt: timestamp } }]),
|
|
listImportArtifacts: vi.fn().mockResolvedValue([{ id: 'artifact', assessmentId: 'slot' }]),
|
|
getEnvironmentSnapshot: vi.fn().mockResolvedValue(environment),
|
|
listGpoBackupInstances: vi.fn().mockResolvedValue(instances),
|
|
listMicrosoftSecurityBaselines: vi.fn().mockResolvedValue([{ id: 'baseline', status: 'eligible', metadata: {
|
|
productFamily: 'Windows Server', productVersion: '2019', roles: ['Domain Controller'], scopes: ['computer'] } }]),
|
|
listMicrosoftSecurityBaselineAnalyses: vi.fn().mockResolvedValue([{ analysis: { id: 'baseline-analysis', baselineId: 'baseline' } }])
|
|
};
|
|
const snapshots = { createEnvironmentProfile: vi.fn().mockResolvedValue({ id: 'profile' }),
|
|
aggregate: vi.fn().mockResolvedValue({ run: { id: 'aggregation' } }) };
|
|
const evaluations = { evaluate: vi.fn().mockResolvedValue({ run: { id: 'evaluation', status: 'partial' } }) };
|
|
const findings = { create: vi.fn().mockResolvedValue({}) };
|
|
const service = new GpoAutomaticAnalysisService(repository as unknown as AssessorRepository,
|
|
snapshots as unknown as GpoPolicySnapshotService, evaluations as unknown as GpoPolicyEvaluationService,
|
|
findings as unknown as GpoPolicyFindingService);
|
|
return { service, repository, snapshots, evaluations, findings, environment, instances };
|
|
}
|
|
|
|
describe('automatic GPO analysis', () => {
|
|
it('uses timeslot-local observed context and the matching persisted baseline', async () => {
|
|
const test = setup();
|
|
const results = await Promise.all([test.service.run('project', 'processing'), test.service.run('project', 'processing')]);
|
|
expect(results).toEqual([{ state: 'review', evaluationRunId: 'evaluation' }, { state: 'review', evaluationRunId: 'evaluation' }]);
|
|
expect(test.repository.getEnvironmentSnapshot).toHaveBeenCalledWith({ projectId: 'project', assessmentId: 'slot' });
|
|
expect(test.snapshots.createEnvironmentProfile).toHaveBeenCalledWith('project', expect.objectContaining({
|
|
contexts: expect.arrayContaining([
|
|
expect.objectContaining({ productVersion: '2019', role: 'Domain Controller', sourceObservationIds: ['dc-os'] }),
|
|
expect.objectContaining({ id: 'gpo-baseline-comparison', scope: 'mixed' })
|
|
])
|
|
}));
|
|
expect(test.snapshots.aggregate).toHaveBeenCalledWith('project', { environmentProfileId: 'profile', sourceAnalysisRunIds: ['analysis'] });
|
|
expect(test.evaluations.evaluate).toHaveBeenCalledTimes(1);
|
|
expect(test.evaluations.evaluate).toHaveBeenCalledWith('project', { aggregationRunId: 'aggregation', baselineAnalysisIds: ['baseline-analysis'] });
|
|
});
|
|
it('evaluates imported baselines without requiring an inferred operating-system context', async () => {
|
|
const test = setup();
|
|
test.environment.observations[0].property = 'domain.functional_level';
|
|
expect(await test.service.run('project', 'processing')).toEqual({ state: 'review', evaluationRunId: 'evaluation' });
|
|
expect(test.snapshots.createEnvironmentProfile).toHaveBeenCalledWith('project', expect.objectContaining({
|
|
contexts: [expect.objectContaining({ id: 'gpo-baseline-comparison', scope: 'mixed' })]
|
|
}));
|
|
expect(test.evaluations.evaluate).toHaveBeenCalledWith('project', {
|
|
aggregationRunId: 'aggregation', baselineAnalysisIds: ['baseline-analysis']
|
|
});
|
|
});
|
|
it('does not make the neutral comparison depend on a global server-role choice', async () => {
|
|
const test = setup();
|
|
test.repository.getEnvironmentSnapshot.mockResolvedValue({ observations: [
|
|
...test.environment.observations,
|
|
{ id: 'inventory-os', state: 'observed', property: 'operating_system.name', originalValue: 'Windows Server 2019',
|
|
subject: { type: 'computer-information.operating-systems', identity: 'server-row' } },
|
|
{ id: 'inventory-count', state: 'observed', property: 'operating_system.count', originalValue: 2,
|
|
subject: { type: 'computer-information.operating-systems', identity: 'server-row' } }
|
|
] });
|
|
expect(await test.service.run('project', 'processing')).toEqual({ state: 'review', evaluationRunId: 'evaluation' });
|
|
expect(test.evaluations.evaluate).toHaveBeenCalledTimes(1);
|
|
});
|
|
it('keeps incompatible snapshot groups in review without failing the parsed import', async () => {
|
|
const test = setup();
|
|
test.instances.push({ ...test.instances[0], instance: { ...test.instances[0].instance,
|
|
id: 'other-backup', backupSet: { ...test.instances[0].instance.backupSet, id: 'other-backup-set' },
|
|
domain: { status: 'unknown' }, policyObservations: [] } });
|
|
expect(await test.service.run('project', 'processing')).toEqual({ state: 'review', diagnosticCode: 'policy-aggregation-multiple-snapshots' });
|
|
expect(test.evaluations.evaluate).not.toHaveBeenCalled();
|
|
});
|
|
it('compares independent active baseline analyses instead of treating them as ambiguous', async () => {
|
|
const test = setup();
|
|
test.repository.listMicrosoftSecurityBaselineAnalyses.mockResolvedValue([
|
|
{ analysis: { id: 'one', baselineId: 'baseline' } }, { analysis: { id: 'two', baselineId: 'baseline' } }
|
|
]);
|
|
expect(await test.service.run('project', 'processing')).toEqual({ state: 'review', evaluationRunId: 'evaluation' });
|
|
expect(test.evaluations.evaluate).toHaveBeenCalledWith('project', {
|
|
aggregationRunId: 'aggregation', baselineAnalysisIds: ['one', 'two']
|
|
});
|
|
});
|
|
it('evaluates every eligible imported baseline as a separate benchmark', async () => {
|
|
const test = setup();
|
|
test.repository.listMicrosoftSecurityBaselines.mockResolvedValue([
|
|
{ id: 'baseline', status: 'eligible', metadata: { productFamily: 'Windows Server', productVersion: '2019', roles: ['Any'], scopes: ['computer'] } },
|
|
{ id: 'unrelated', status: 'eligible', metadata: { productFamily: 'Windows 11', productVersion: '24H2', roles: ['Any'], scopes: ['computer'] } }
|
|
]);
|
|
test.repository.listMicrosoftSecurityBaselineAnalyses.mockResolvedValue([
|
|
{ analysis: { id: 'baseline-analysis', baselineId: 'baseline' } },
|
|
{ analysis: { id: 'unrelated-analysis', baselineId: 'unrelated' } }
|
|
]);
|
|
|
|
await test.service.run('project', 'processing');
|
|
expect(test.evaluations.evaluate).toHaveBeenCalledWith('project', {
|
|
aggregationRunId: 'aggregation', baselineAnalysisIds: ['baseline-analysis', 'unrelated-analysis']
|
|
});
|
|
});
|
|
|
|
it('excludes baseline packages whose metadata is not eligible', async () => {
|
|
const test = setup();
|
|
test.repository.listMicrosoftSecurityBaselines.mockResolvedValue([
|
|
{ id: 'baseline', status: 'eligible', metadata: { productFamily: 'Windows Server', productVersion: '2019', roles: ['Any'], scopes: ['computer'] } },
|
|
{ id: 'unclassified', status: 'metadata-required', metadata: { roles: ['Any'], scopes: ['mixed'] } }
|
|
]);
|
|
test.repository.listMicrosoftSecurityBaselineAnalyses.mockResolvedValue([
|
|
{ analysis: { id: 'baseline-analysis', baselineId: 'baseline' } },
|
|
{ analysis: { id: 'unclassified-analysis', baselineId: 'unclassified' } }
|
|
]);
|
|
|
|
await test.service.run('project', 'processing');
|
|
expect(test.evaluations.evaluate).toHaveBeenCalledWith('project', {
|
|
aggregationRunId: 'aggregation', baselineAnalysisIds: ['baseline-analysis']
|
|
});
|
|
});
|
|
});
|