import { describe, expect, it } from 'vitest'; import { LocalAssessorRepository } from '../../src/persistence/local-assessor-repository'; import { detectMicrosoftSecurityBaselineMetadata, selectMicrosoftSecurityBaseline, type MicrosoftSecurityBaselineRecord } from '../../src/domain/microsoft-security-baseline'; import { MicrosoftSecurityBaselineService } from '../../server/gpo/microsoft-security-baseline-service'; import type { ArtifactStoreReconciliationRequest, BackendArtifactStore, ReadArtifactRequest, StoreArtifactRequest } from '../../server/artifacts/backend-artifact-store'; import { directGpoBackupWorkerExecutor } from '../../server/gpo/gpo-inventory-executor'; import { syntheticMicrosoftBaselineZip } from './microsoft-baseline-synthetic-fixture'; import { analyzeMicrosoftSecurityBaselinePackage } from '../../server/gpo/microsoft-security-baseline-parser'; import { MICROSOFT_BASELINE_SETTINGS_ANALYZER } from '../../src/domain/microsoft-security-baseline-analysis'; class MemoryArtifactStore implements BackendArtifactStore { private readonly objects = new Map(); private sequence = 0; async put(request: StoreArtifactRequest) { const storageKey = `00000000-0000-4000-8000-${String(++this.sequence).padStart(12, '0')}`; this.objects.set(storageKey, request.bytes.slice()); const { createHash } = await import('node:crypto'); return { version: 1 as const, storageKey, sha256: createHash('sha256').update(request.bytes).digest('hex'), sizeBytes: request.bytes.byteLength }; } async read(request: ReadArtifactRequest) { return this.objects.get(request.storageKey)?.slice() ?? new Uint8Array(); } async remove(storageKey: string) { return this.objects.delete(storageKey); } async reconcile(request: ArtifactStoreReconciliationRequest) { return { version: 1 as const, referencedObjects: request.referencedStorageKeys.length, retainedUnreferencedObjects: 0, removedUnreferencedObjects: 0, removedStagingFiles: 0 }; } } function record(id: string, family: string, version: string): MicrosoftSecurityBaselineRecord { return { version: 1, id, fileName: `${family} ${version}.zip`, sha256: id.padEnd(64, 'a').slice(0, 64), sizeBytes: 4, storageKey: '00000000-0000-4000-8000-000000000001', importedAt: '2026-08-04T10:00:00.000Z', status: 'eligible', metadata: { productFamily: family, productVersion: version, roles: ['Any'], scopes: ['mixed'], source: 'detected' } }; } describe('Microsoft Security Baseline catalog', () => { it.each(['1.0.0', '1.1.0'] as const)('reprocesses a legacy %s analysis once and preserves its history', async legacyVersion => { const repository = new LocalAssessorRepository(); const artifactStore = new MemoryArtifactStore(); const bytes = syntheticMicrosoftBaselineZip(4, [ '[Service General Setting]', '"SyntheticService",2,""', '[Privilege Rights]', 'SeNetworkLogonRight=*S-1-5-32-544' ].join('\n')); const catalog = new MicrosoftSecurityBaselineService(repository, artifactStore, 1024 * 1024); const baseline = await catalog.submit({ fileName: 'Windows 11 24H2 Security Baseline.zip', bytes, receivedAt: '2026-09-08T08:00:00Z' }); const legacy = analyzeMicrosoftSecurityBaselinePackage(bytes, { baselineId: baseline.id, analyzedAt: baseline.importedAt }); legacy.id = `synthetic-legacy-${legacyVersion}`; legacy.analyzer = { ...legacy.analyzer, version: legacyVersion }; legacy.settings = legacy.settings.filter(item => item.observation.deliveryMechanism !== 'security-template'); await repository.saveMicrosoftSecurityBaselineAnalysis({ version: 1, analysis: legacy }); const service = new MicrosoftSecurityBaselineService(repository, artifactStore, 1024 * 1024, directGpoBackupWorkerExecutor); const current = await service.reprocess(baseline.id); expect(current).toMatchObject({ supersedesAnalysisId: legacy.id, analysis: { analyzer: MICROSOFT_BASELINE_SETTINGS_ANALYZER, packageSha256: baseline.sha256, status: 'partial' } }); expect(current.analysis.id).not.toBe(legacy.id); expect(current.analysis.settings).toHaveLength(2); const history = await service.listAnalyses(baseline.id); expect(history).toHaveLength(2); expect(history.find(item => item.analysis.id === legacy.id)?.analysis).toEqual(legacy); const revision = repository.revision; await expect(service.reprocess(baseline.id)).resolves.toEqual(current); await expect(service.reprocessPending()).resolves.toEqual({ analyzed: 0, failed: 0 }); expect(repository.revision).toBe(revision); }); it('detects product and version without using them as policy identity', () => { expect(detectMicrosoftSecurityBaselineMetadata('Windows 11 v24H2 Security Baseline.zip')).toMatchObject({ productFamily: 'Windows 11', productVersion: '24H2', source: 'detected' }); expect(detectMicrosoftSecurityBaselineMetadata('Microsoft 365 Apps for Enterprise 2512.zip')).toMatchObject({ productFamily: 'Microsoft 365 Apps', productVersion: '2512' }); expect(detectMicrosoftSecurityBaselineMetadata('Windows 10 Version 1809 and Windows Server 2019 Security Baseline.zip').productContexts).toEqual([ { productFamily: 'Windows 10', productVersion: '1809', roles: ['Any'], scopes: ['mixed'] }, { productFamily: 'Windows Server', productVersion: '2019', roles: ['Any'], scopes: ['mixed'] } ]); }); it('selects only one exact product/version match and fails closed on ambiguity', () => { const first = record('1', 'Windows 11', '24H2'); const second = record('2', 'Windows 11', '23H2'); expect(selectMicrosoftSecurityBaseline([first, second], { productFamily: 'Windows 11', productVersion: '24H2' })).toMatchObject({ status: 'selected', selected: { id: '1' } }); const ambiguous = selectMicrosoftSecurityBaseline([first, { ...first, id: '3', sha256: '3'.repeat(64) }], { productFamily: 'Windows 11', productVersion: '24H2' }); expect(ambiguous.status).toBe('ambiguous'); expect(ambiguous.selected).toBeUndefined(); const combined = { ...first, metadata: detectMicrosoftSecurityBaselineMetadata('Windows 10 Version 1607 and Windows Server 2016 Security Baseline.zip') }; expect(selectMicrosoftSecurityBaseline([combined], { productFamily: 'Windows Server', productVersion: '2016' })).toMatchObject({ status: 'selected', selected: { id: '1' } }); }); it('persists deterministic ADMX catalog versions and baseline settings through the worker contract', async () => { const repository = new LocalAssessorRepository(); const artifactStore = new MemoryArtifactStore(); const service = new MicrosoftSecurityBaselineService(repository, artifactStore, 1024 * 1024, directGpoBackupWorkerExecutor); const baseline = await service.submit({ fileName: 'Windows Server 2022 Security Baseline.zip', bytes: syntheticMicrosoftBaselineZip(), receivedAt: '2026-08-04T10:00:00.000Z' }); const analyses = await service.listAnalyses(baseline.id); expect(analyses).toHaveLength(1); expect(analyses[0]).toMatchObject({ analysis: { baselineId: baseline.id, status: 'complete', admxCatalog: { status: 'complete', policies: [expect.objectContaining({ technicalId: 'Synthetic.Policies#Mode' })] }, settings: [expect.objectContaining({ identity: expect.objectContaining({ status: 'resolved' }) })] } }); const revision = repository.revision; const repeated = await service.reprocess(baseline.id); expect(repeated.analysis.id).toBe(analyses[0].analysis.id); expect(repository.revision).toBe(revision); const derivedState = repository as unknown as { state: { policyEvaluations: Array<{ run: Record }>; policyFindingRuns: Array<{ run: Record }>; } }; derivedState.state.policyEvaluations.push({ run: { id: 'derived-evaluation', projectId: 'derived-project', completedAt: '2026-08-04T10:01:00.000Z', baselineAnalysisIds: [analyses[0].analysis.id] } }); derivedState.state.policyFindingRuns.push({ run: { id: 'derived-findings', projectId: 'derived-project', evaluationRunId: 'derived-evaluation', completedAt: '2026-08-04T10:02:00.000Z' } }); await expect(service.delete(baseline.id)).resolves.toMatchObject({ id: baseline.id }); await expect(service.list()).resolves.toEqual([]); await expect(service.listAnalyses(baseline.id)).resolves.toEqual([]); await expect(repository.listMicrosoftSecurityBaselineClassifications()).resolves.toEqual([]); await expect(repository.listPolicyEvaluations('derived-project')).resolves.toEqual([]); await expect(repository.listPolicyFindingRuns('derived-project')).resolves.toEqual([]); await expect(artifactStore.read({ storageKey: baseline.storageKey, expectedSha256: baseline.sha256, maximumBytes: 1024 * 1024 })).resolves.toHaveLength(0); }); it('stores ZIP originals once and persists catalog metadata', async () => { const repository = new LocalAssessorRepository(); const service = new MicrosoftSecurityBaselineService(repository, new MemoryArtifactStore(), 1024); const bytes = new Uint8Array([0x50, 0x4b, 0x03, 0x04, 0, 0]); const first = await service.submit({ fileName: 'Windows Server 2022 Security Baseline.zip', bytes, receivedAt: '2026-08-04T10:00:00.000Z' }); const second = await service.submit({ fileName: 'duplicate.zip', bytes, receivedAt: '2026-08-04T11:00:00.000Z' }); expect(second.id).toBe(first.id); await expect(service.list()).resolves.toHaveLength(1); expect(first).toMatchObject({ status: 'eligible', metadata: { productFamily: 'Windows Server', productVersion: '2022' } }); await expect(repository.listMicrosoftSecurityBaselineClassifications()).resolves.toHaveLength(1); }); });