Assessor_2.0/tests/postgres/result-contract-facets-sche...

73 lines
3.3 KiB
TypeScript

import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import { describe, expect, it } from 'vitest';
import { databaseMigrations } from '../../server/postgres/migration-catalog';
const repositoryPath = resolve(import.meta.dirname, '..', '..', 'server', 'postgres', 'postgres-assessor-repository.ts');
const migrationPath = resolve(
import.meta.dirname,
'..',
'..',
'server',
'postgres',
'migrations',
'008_result_contract_facets.sql'
);
describe('PostgreSQL RESULT_CONTRACT facets migration', () => {
it('is registered in the migration catalog', async () => {
expect(databaseMigrations.find(migration => migration.version === '008')).toMatchObject({
version: '008',
name: 'result-contract-facets'
});
});
it('adds result_type and is_ioe as additive, indexed, closed-catalog columns', async () => {
const sql = await readFile(migrationPath, 'utf8');
expect(sql).toContain('ADD COLUMN result_type text');
expect(sql).toContain('ADD COLUMN is_ioe boolean');
expect(sql).toContain('ADD COLUMN ioe_rule_id text');
expect(sql).toContain('ADD COLUMN ioe_rule_version text');
expect(sql).toContain('ADD COLUMN ioe_derivation_method text');
expect(sql).toContain('ADD COLUMN exposure_state text');
expect(sql).toContain('assessor_occurrences_result_type_check');
expect(sql).toContain('assessor_occurrences_exposure_state_check');
expect(sql).toContain('assessor_occurrences_exposure_state_type_check');
expect(sql).toContain('assessor_occurrences_ioe_default_check');
expect(sql).toContain('assessor_occurrences_result_type_idx');
expect(sql).toContain('assessor_occurrences_is_ioe_idx');
expect(sql).toContain('assessor_occurrences_ioe_severity_idx');
expect(sql).not.toMatch(/DROP\s+(TABLE|COLUMN)/i);
});
it('never defaults is_ioe to true for a non-exposure result_type', async () => {
const sql = await readFile(migrationPath, 'utf8');
expect(sql).toContain("NOT (is_ioe AND result_type NOT IN ('exposure_finding'))");
});
it('uses the versioned deviation outcomes in the PostgreSQL prefilter', async () => {
const source = await readFile(repositoryPath, 'utf8');
expect(source).toContain("o.is_ioe OR o.outcome_normalized IN ('fail', 'warning')");
expect(source).not.toContain("o.is_ioe OR o.outcome_normalized = 'warning'");
});
it('derives result_type deterministically from tool_id, never from a raw status string', async () => {
const sql = await readFile(migrationPath, 'utf8');
expect(sql).toContain("WHEN 'pingcastle' THEN 'exposure_finding'");
expect(sql).toContain("WHEN 'purple-knight' THEN 'exposure_finding'");
expect(sql).toContain("WHEN 'maester' THEN 'exposure_finding'");
expect(sql).toContain("WHEN 'cis-cat' THEN 'compliance_check'");
expect(sql).toContain("ELSE 'unknown'");
});
it('projects persisted environment observations through the same result read API', async () => {
const source = await readFile(repositoryPath, 'utf8');
expect(source).toContain('FROM assessor_environment_observations o');
expect(source).toContain('createEnvironmentUnifiedResult({');
expect(source).toContain("o.observation_state = ANY(?::text[])");
expect(source).toContain("o.property_key = ANY(?::text[])");
expect(source).toContain("o.subject_type = ANY(?::text[])");
});
});