gongxiangp2/scripts/ai-model-migration.test.mjs

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/)
})