147 lines
6.3 KiB
TypeScript
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 } }]
|
|
});
|
|
});
|
|
});
|
|
|