Assessor_2.0/tests/server/gpo-automatic-analysis-serv...

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