import { mkdtemp, rm } from 'node:fs/promises'; import type { Server } from 'node:http'; import type { AddressInfo } from 'node:net'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { FileSystemBackendArtifactStore } from '../../server/artifacts/backend-artifact-store'; import { createAssessorHttpServer } from '../../server/assessor-http-server'; import { GpoBackupJobProcessor } from '../../server/gpo/gpo-backup-job-processor'; import { GpoBackupSubmissionService } from '../../server/gpo/gpo-backup-submission-service'; import { directGpoBackupWorkerExecutor, directGpoInventoryExecutor } from '../../server/gpo/gpo-inventory-executor'; import { GpoPolicySnapshotService } from '../../server/gpo/gpo-policy-snapshot-service'; import { GpoPolicyEvaluationService } from '../../server/gpo/gpo-policy-evaluation-service'; import { LocalAssessorRepository } from '../../src/persistence/local-assessor-repository'; import { syntheticGpoBackupZip } from './gpo-synthetic-fixture'; const timestamp = '2026-08-04T13:00:00.000Z'; const project = { id: 'policy-snapshot-http-project', name: 'Synthetic policy snapshot HTTP', createdAt: timestamp, updatedAt: timestamp, attributes: {} }; describe('policy snapshot HTTP boundary', () => { let directory: string; let server: Server; let baseUrl: string; let analysisRunId: string; beforeEach(async () => { directory = await mkdtemp(join(tmpdir(), 'assessor-policy-snapshot-http-')); const repository = new LocalAssessorRepository(); const store = new FileSystemBackendArtifactStore(join(directory, 'artifacts')); await repository.saveProject(project); await new GpoBackupSubmissionService(repository, store, directGpoInventoryExecutor, 1024 * 1024) .submit({ projectId: project.id, fileName: 'synthetic.zip', bytes: syntheticGpoBackupZip(), receivedAt: timestamp }); await new GpoBackupJobProcessor(repository, store, directGpoBackupWorkerExecutor, { maximumArtifactBytes: 1024 * 1024, now: () => new Date(timestamp) }).processNext('policy-snapshot-http-worker'); analysisRunId = (await repository.listGpoPolicyAnalyses(project.id))[0].run.id; server = createAssessorHttpServer({ repository, staticRoot: join(directory, 'dist'), now: () => new Date(timestamp), gpoPolicySnapshotService: new GpoPolicySnapshotService(repository, { now: () => new Date(timestamp) }), gpoPolicyEvaluationService: new GpoPolicyEvaluationService(repository, { now: () => new Date(timestamp) }), }); await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); }); baseUrl = 'http://127.0.0.1:' + (server.address() as AddressInfo).port; }); afterEach(async () => { await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())); await rm(directory, { recursive: true, force: true }); }); const mutate = (path: string, body: unknown) => fetch(baseUrl + path, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Assessor-Request': '1', Origin: baseUrl }, body: JSON.stringify(body) }); it('creates an immutable environment profile and a non-finding snapshot aggregation', async () => { const invalidProfileResponse = await mutate('/api/v1/projects/' + project.id + '/environment-profiles', { name: '', contexts: [] }); expect(invalidProfileResponse.status).toBe(400); const invalidEvaluationResponse = await mutate('/api/v1/projects/' + project.id + '/policy-evaluations', { aggregationRunId: '', baselineAnalysisIds: [] }); expect(invalidEvaluationResponse.status).toBe(400); const profileResponse = await mutate('/api/v1/projects/' + project.id + '/environment-profiles', { name: 'Windows Server 2022', contexts: [{ id: 'windows-server-2022', kind: 'operating-system', productFamily: 'Windows Server', productVersion: '2022', role: 'Member Server', scope: 'computer' }] }); expect(profileResponse.status).toBe(200); const profileBody = await profileResponse.json() as { record: { id: string } }; const revisionResponse = await mutate('/api/v1/projects/' + project.id + '/environment-profiles', { name: 'Windows Server 2022 domain controller', supersedesProfileId: profileBody.record.id, contexts: [{ id: 'windows-server-2022-dc', kind: 'operating-system', productFamily: 'Windows Server', productVersion: '2022', role: 'Domain Controller', scope: 'computer' }] }); await expect(revisionResponse.json()).resolves.toMatchObject({ record: { revision: 2, supersedesProfileId: profileBody.record.id } }); const aggregationResponse = await mutate('/api/v1/projects/' + project.id + '/policy-snapshot-aggregations', { environmentProfileId: profileBody.record.id, sourceAnalysisRunIds: [analysisRunId] }); expect(aggregationResponse.status).toBe(200); await expect(aggregationResponse.json()).resolves.toMatchObject({ version: 1, record: { run: { status: 'partial', permitsFindingGeneration: false, sourceAnalysisRunIds: [analysisRunId] } } }); const profiles = await fetch(baseUrl + '/api/v1/projects/' + project.id + '/environment-profiles'); const ruleCatalog = await fetch(baseUrl + '/api/v1/reference-data/policy-evaluation-rule-catalog'); await expect(ruleCatalog.json()).resolves.toMatchObject({ version: 1, catalog: { status: 'released', rules: [] } }); const aggregations = await fetch(baseUrl + '/api/v1/projects/' + project.id + '/policy-snapshot-aggregations'); await expect(profiles.json()).resolves.toMatchObject({ version: 1, projectId: project.id, records: [{ revision: 1 }, { revision: 2 }] }); const evaluations = await fetch(baseUrl + '/api/v1/projects/' + project.id + '/policy-evaluations'); await expect(evaluations.json()).resolves.toMatchObject({ version: 1, projectId: project.id, records: [] }); await expect(aggregations.json()).resolves.toMatchObject({ version: 1, projectId: project.id, records: [{ run: { permitsFindingGeneration: false } }] }); }); });