73 lines
3.3 KiB
TypeScript
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[])");
|
|
});
|
|
});
|