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[])"); }); });