Assessor_2.0/tests/server/gpo-policy-snapshot-http.te...

147 lines
6.3 KiB
TypeScript

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