import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { Pool } from 'pg'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { FileSystemBackendArtifactStore } from '../../server/artifacts/backend-artifact-store'; import { GpoBackupJobProcessor } from '../../server/gpo/gpo-backup-job-processor'; import { GpoBackupSubmissionService } from '../../server/gpo/gpo-backup-submission-service'; import { GpoPolicySnapshotService } from '../../server/gpo/gpo-policy-snapshot-service'; import { GpoPolicyEvaluationService } from '../../server/gpo/gpo-policy-evaluation-service'; import { GpoPolicyFindingService } from '../../server/gpo/gpo-policy-finding-service'; import { directGpoBackupWorkerExecutor, directGpoInventoryExecutor } from '../../server/gpo/gpo-inventory-executor'; import { MicrosoftSecurityBaselineService } from '../../server/gpo/microsoft-security-baseline-service'; import { runDatabaseMigrations } from '../../server/postgres/migration-runner'; import { PostgresAssessorRepository } from '../../server/postgres/postgres-assessor-repository'; import { syntheticGpoBackupZip } from '../server/gpo-synthetic-fixture'; import { syntheticMicrosoftBaselineZip } from '../server/microsoft-baseline-synthetic-fixture'; const connectionString = process.env.ASSESSOR_TEST_DATABASE_URL; const describePostgres = connectionString ? describe : describe.skip; const timestamp = '2026-08-04T10:00:00.000Z'; describePostgres('PostgreSQL policy analysis parity', () => { let pool: Pool; let repository: PostgresAssessorRepository; let directory: string; let store: FileSystemBackendArtifactStore; beforeAll(async () => { pool = new Pool({ connectionString, max: 2, connectionTimeoutMillis: 5_000 }); await runDatabaseMigrations(pool); repository = await PostgresAssessorRepository.create(pool); directory = await mkdtemp(join(tmpdir(), 'assessor-policy-analysis-pg-')); store = new FileSystemBackendArtifactStore(directory); }, 30_000); afterAll(async () => { await pool?.end(); await rm(directory, { recursive: true, force: true }); }); it('persists GPO identities and baseline catalog settings through the same repository contract as local mode', async () => { const project = { id: 'pg-policy-analysis-project', name: 'Synthetic policy analysis', createdAt: timestamp, updatedAt: timestamp, attributes: {} }; await pool.query('DELETE FROM assessor_projects WHERE id = $1', [project.id]); await repository.saveProject(project); const submitted = await new GpoBackupSubmissionService( repository, store, directGpoInventoryExecutor, 1024 * 1024 ).submit({ projectId: project.id, fileName: 'synthetic.zip', bytes: syntheticGpoBackupZip(), receivedAt: timestamp }); const processor = new GpoBackupJobProcessor(repository, store, directGpoBackupWorkerExecutor, { maximumArtifactBytes: 1024 * 1024, now: () => new Date(timestamp) }); await expect(processor.processNext('pg-policy-worker')).resolves.toMatchObject({ status: 'succeeded' }); const policyAnalyses = await repository.listGpoPolicyAnalyses(project.id); expect(policyAnalyses).toHaveLength(1); expect(policyAnalyses[0].run.resolvedCount).toBeGreaterThan(0); const resolutionCount = await pool.query<{ count: string }>( 'SELECT count(*)::text AS count FROM assessor_policy_identity_resolutions WHERE project_id = $1', [project.id] ); expect(Number(resolutionCount.rows[0].count)).toBe(policyAnalyses[0].run.observationCount); const snapshotService = new GpoPolicySnapshotService(repository, { now: () => new Date(timestamp) }); const profile = await snapshotService.createEnvironmentProfile(project.id, { name: 'Synthetic Windows Server environment', contexts: [{ id: 'windows-server-2022', kind: 'operating-system', productFamily: 'Windows Server', productVersion: '2022', role: 'Member Server', scope: 'computer' }] }); const aggregation = await snapshotService.aggregate(project.id, { environmentProfileId: profile.id, sourceAnalysisRunIds: [policyAnalyses[0].run.id] }); expect(aggregation.run).toMatchObject({ status: 'partial', permitsFindingGeneration: false }); expect(aggregation.states.length).toBeGreaterThan(0); await expect(snapshotService.aggregate(project.id, { environmentProfileId: profile.id, sourceAnalysisRunIds: [policyAnalyses[0].run.id] })).resolves.toEqual(aggregation); const profileCount = await pool.query<{ count: string }>( 'SELECT count(*)::text AS count FROM assessor_environment_profiles WHERE project_id = $1', [project.id] ); const stateCount = await pool.query<{ count: string }>( 'SELECT count(*)::text AS count FROM assessor_policy_snapshot_states WHERE project_id = $1', [project.id] ); expect(Number(profileCount.rows[0].count)).toBe(1); expect(Number(stateCount.rows[0].count)).toBe(aggregation.states.length); const baselineService = new MicrosoftSecurityBaselineService( repository, store, 1024 * 1024, directGpoBackupWorkerExecutor ); const baseline = await baselineService.submit({ fileName: 'Windows Server 2022 Security Baseline.zip', bytes: syntheticMicrosoftBaselineZip(), receivedAt: timestamp }); const analyses = await repository.listMicrosoftSecurityBaselineAnalyses(baseline.id); expect(analyses).toHaveLength(1); expect(analyses[0].analysis.settings).toHaveLength(1); const settings = await pool.query<{ count: string }>( 'SELECT count(*)::text AS count FROM assessor_microsoft_security_baseline_settings WHERE baseline_id = $1', [baseline.id] ); expect(Number(settings.rows[0].count)).toBe(1); const evaluationService = new GpoPolicyEvaluationService(repository, { now: () => new Date(timestamp) }); const evaluation = await evaluationService.evaluate(project.id, { aggregationRunId: aggregation.run.id, baselineAnalysisIds: [analyses[0].analysis.id] }); expect(evaluation.run).toMatchObject({ status: 'partial', deviationCount: 0, projectedFindingCount: 0, ruleCatalog: { id: 'policy-evaluation-rule-catalog-1.0.0', status: 'released' }, }); expect(evaluation.results).toEqual(expect.arrayContaining([ expect.objectContaining({ classification: 'review_required', baselineState: 'catalog-gap', findingEligibility: 'ineligible', effectiveState: 'unknown' }) ])); await expect(evaluationService.evaluate(project.id, { aggregationRunId: aggregation.run.id, baselineAnalysisIds: [analyses[0].analysis.id] })).resolves.toEqual(evaluation); const evaluationCounts = await pool.query<{ runs: string; results: string; evidence: string }>( `SELECT (SELECT count(*)::text FROM assessor_policy_evaluation_runs WHERE project_id = $1) AS runs, (SELECT count(*)::text FROM assessor_policy_evaluation_results r JOIN assessor_policy_evaluation_runs e ON e.id = r.evaluation_run_id WHERE e.project_id = $1) AS results, (SELECT count(*)::text FROM assessor_policy_evaluation_evidence v JOIN assessor_policy_evaluation_results r ON r.id = v.evaluation_result_id JOIN assessor_policy_evaluation_runs e ON e.id = r.evaluation_run_id WHERE e.project_id = $1) AS evidence`, [project.id] ); expect(Number(evaluationCounts.rows[0].runs)).toBe(1); expect(Number(evaluationCounts.rows[0].results)).toBe(evaluation.results.length); expect(Number(evaluationCounts.rows[0].evidence)).toBe(evaluation.evidence.length); const relationalEvaluation = await pool.query<{ rule_catalog_id: string; rule_catalog_version: string; classification: string; baseline_state: string; risk_rule_count: number; }>(`SELECT e.rule_catalog_id, e.rule_catalog_version, r.classification, r.baseline_state, cardinality(r.risk_rule_ids) AS risk_rule_count FROM assessor_policy_evaluation_runs e JOIN assessor_policy_evaluation_results r ON r.evaluation_run_id = e.id WHERE e.project_id = $1`, [project.id]); expect(relationalEvaluation.rows).toEqual(expect.arrayContaining([expect.objectContaining({ rule_catalog_id: 'policy-evaluation-rule-catalog-1.0.0', rule_catalog_version: '1.0.0', classification: 'review_required', baseline_state: 'catalog-gap', risk_rule_count: 0 })])); const findingService = new GpoPolicyFindingService(repository, { now: () => new Date(timestamp) }); const findingRun = await findingService.create(project.id, { evaluationRunId: evaluation.run.id }); expect(findingRun).toMatchObject({ run: { status: 'complete', eligibleResultCount: 0, findingCount: 0, rejectedResultCount: 0 }, occurrences: [], rejections: [] }); await expect(findingService.create(project.id, { evaluationRunId: evaluation.run.id })) .resolves.toEqual(findingRun); const factoryCounts = await pool.query<{ runs: string; occurrences: string }>( `SELECT (SELECT count(*)::text FROM assessor_policy_finding_runs WHERE project_id = $1) AS runs, (SELECT count(*)::text FROM assessor_policy_finding_occurrences o JOIN assessor_policy_finding_runs r ON r.id = o.factory_run_id WHERE r.project_id = $1) AS occurrences`, [project.id] ); expect(factoryCounts.rows[0]).toEqual({ runs: '1', occurrences: '0' }); await expect(repository.queryUnifiedResults({ projectId: project.id, sourceKind: ['policy-evaluation'] })) .resolves.toMatchObject({ total: 0, items: [] }); await repository.deleteProject(project.id, { projectId: project.id, cascade: true }); await pool.query('DELETE FROM assessor_microsoft_security_baselines WHERE id = $1', [baseline.id]); expect(submitted.status).toBe('queued'); }, 30_000); });