45 lines
3.8 KiB
JavaScript
45 lines
3.8 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { assertAiModelRegistryEvidence } from './safe-production-migrate.mjs'
|
|
import { aiModelRegistryEvidence } from './fixtures/ai-model-registry-evidence.mjs'
|
|
|
|
test('AI registry migration gate accepts the actual additive PostgreSQL schema', () => {
|
|
assert.doesNotThrow(() => assertAiModelRegistryEvidence(aiModelRegistryEvidence()))
|
|
})
|
|
|
|
test('AI registry gate rejects missing capability evidence and wrong credential column types', () => {
|
|
const evidence = aiModelRegistryEvidence()
|
|
assert.throws(() => assertAiModelRegistryEvidence({ ...evidence, columns: evidence.columns.filter(row => row.column_name !== 'capability_metadata') }), /ai_model_catalog.capability_metadata/)
|
|
for (const [name, change] of [
|
|
['secret_nonce', { data_type: 'jsonb' }], ['secret_ciphertext', { is_nullable: 'NO' }],
|
|
['stages', { udt_name: '_int4' }], ['enabled', { column_default: 'true' }],
|
|
]) assert.throws(() => assertAiModelRegistryEvidence({ ...evidence, columns: evidence.columns.map(row => row.column_name === name ? { ...row, ...change } : row) }), /Database (column|default) mismatch/)
|
|
})
|
|
|
|
test('AI registry gate preserves catalog and original connection uniqueness', () => {
|
|
const evidence = aiModelRegistryEvidence()
|
|
for (const name of ['ai_model_providers_legacy_binding_key', 'ai_model_catalog_provider_model_key']) {
|
|
assert.throws(() => assertAiModelRegistryEvidence({ ...evidence, indexes: evidence.indexes.filter(row => row.indexname !== name) }), /index/)
|
|
assert.throws(() => assertAiModelRegistryEvidence({ ...evidence, indexes: evidence.indexes.map(row => row.indexname === name ? { ...row, indisvalid: false } : row) }), /index/)
|
|
}
|
|
assert.throws(() => assertAiModelRegistryEvidence({ ...evidence, indexes: evidence.indexes.map(row => row.indexname === 'ai_model_providers_legacy_binding_key' ? { ...row, predicate: '' } : row) }), /predicate mismatch/)
|
|
})
|
|
|
|
test('AI registry gate refuses missing, weakened or cascading credential and model constraints', () => {
|
|
const evidence = aiModelRegistryEvidence()
|
|
for (const name of ['ai_model_credential_complete', 'ai_model_enabled_stages', 'ai_model_catalog_stages_check', 'ai_model_verifications_status_check', 'ai_model_verifications_stage_check', 'ai_model_verifications_context_hash_check']) {
|
|
assert.throws(() => assertAiModelRegistryEvidence({ ...evidence, constraints: evidence.constraints.filter(row => row.constraint_name !== name) }), /constraint mismatch/)
|
|
assert.throws(() => assertAiModelRegistryEvidence({ ...evidence, constraints: evidence.constraints.map(row => row.constraint_name === name ? { ...row, expression: 'true' } : row) }), /expression mismatch/)
|
|
}
|
|
assert.throws(() => assertAiModelRegistryEvidence({ ...evidence, constraints: evidence.constraints.map(row => row.contype === 'f' ? { ...row, delete_action: 'c' } : row) }), /deletion mismatch/)
|
|
})
|
|
|
|
test('verification gate refuses lost idempotency and weakened unresolved-request protection', () => {
|
|
const evidence = aiModelRegistryEvidence()
|
|
for (const name of ['ai_model_verifications_idempotency_key', 'ai_model_verifications_unresolved_key', 'ai_model_verifications_model_stage_idx']) {
|
|
assert.throws(() => assertAiModelRegistryEvidence({ ...evidence, indexes: evidence.indexes.filter(row => row.indexname !== name) }), /index/)
|
|
}
|
|
assert.throws(() => assertAiModelRegistryEvidence({ ...evidence, indexes: evidence.indexes.map(row => row.indexname === 'ai_model_verifications_unresolved_key' ? { ...row, predicate: "status = 'running'::text" } : row) }), /predicate mismatch/)
|
|
assert.throws(() => assertAiModelRegistryEvidence({ ...evidence, columns: evidence.columns.filter(row => row.table_name !== 'ai_model_verifications' || row.column_name !== 'task_id') }), /ai_model_verifications.task_id/)
|
|
})
|