92 lines
10 KiB
TypeScript
92 lines
10 KiB
TypeScript
/** Non-production native HTTP acceptance. Recovery fixtures never dispatch an upstream request. */
|
|
import assert from 'node:assert/strict'
|
|
import { randomUUID } from 'node:crypto'
|
|
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
import { PrismaClient } from '@prisma/client'
|
|
import { aiModelVerificationContext } from '../lib/ai-model-verification-state'
|
|
import { getAiModelStageProtocol } from '../lib/ai-model-capabilities'
|
|
import type { AiModelAdminState } from '../lib/ai-model-types'
|
|
const db = new URL(process.env.DATABASE_URL || '')
|
|
assert.equal(db.hostname, '127.0.0.1'); assert.equal(db.port, '55441'); assert.equal(db.pathname, '/ai_model_test')
|
|
const base = 'http://localhost:3125', output = 'D:/AI Code/gongxiangp2/output/ai-workflow-20261014', phase = process.argv[2] || 'recovery'
|
|
const prisma = new PrismaClient({ log: [] })
|
|
async function save(name: string, value: unknown) { await mkdir(output, { recursive: true }); await writeFile(`${output}/${name}.json`, JSON.stringify(value, null, 2)) }
|
|
async function session() {
|
|
const jar = new Map<string, string>()
|
|
const store = (response: Response) => { for (const cookie of response.headers.getSetCookie()) { const value = cookie.split(';')[0], at = value.indexOf('='); jar.set(value.slice(0, at), value.slice(at + 1)) } }
|
|
const csrf = await fetch(base + '/api/auth/csrf'); store(csrf)
|
|
const { csrfToken } = await csrf.json()
|
|
const cookie = () => Array.from(jar).map(([key, value]) => `${key}=${value}`).join('; ')
|
|
const signed = await fetch(base + '/api/auth/callback/credentials', { method: 'POST', redirect: 'manual', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Origin: base, Cookie: cookie() }, body: new URLSearchParams({ csrfToken, email: 'ai-admin-0@example.invalid', password: 'AiAdminLocalTest-20261012', callbackUrl: base + '/admin', json: 'true' }) })
|
|
store(signed); assert.equal(signed.status, 200); return cookie()
|
|
}
|
|
async function main() {
|
|
const cookie = await session(), actor = await prisma.user.findUniqueOrThrow({ where: { email: 'ai-admin-0@example.invalid' } })
|
|
assert.ok(actor.isSuperAdmin && actor.isActive)
|
|
async function request(path = '', method = 'GET', body?: unknown) {
|
|
const response = await fetch(base + '/api/admin/ai-models' + path, { method, headers: { Cookie: cookie, 'Content-Type': 'application/json' }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), signal: AbortSignal.timeout(30000) })
|
|
const raw = await response.text(); assert.ok(!process.env.DETAIL_GENERATOR_API_KEY || !raw.includes(process.env.DETAIL_GENERATOR_API_KEY), 'Credential leaked')
|
|
return { status: response.status, data: JSON.parse(raw) }
|
|
}
|
|
let state = (await request()).data as AiModelAdminState
|
|
const provider = state.providers.find(value => value.credentialSource === 'environment')!; assert.ok(provider)
|
|
if (phase === 'query-real') {
|
|
const model = provider.models.find(value => value.id === 'nano-banana-2')!, result = model?.verification.image
|
|
assert.ok(result?.id && result.taskId && result.status === 'passed')
|
|
const starts = await prisma.$queryRawUnsafe<Array<{ count: number }>>(`SELECT COUNT(*)::int AS count FROM ai_model_audit WHERE action='model.verification-start' AND details->>'verificationId'=$1`, result.id)
|
|
await save('real-query-checkpoint', { verificationId: result.id, taskId: result.taskId, simulation: 'QA-only interrupted result checkpoint; original successful upstream task retained' })
|
|
await prisma.$executeRawUnsafe(`UPDATE ai_model_verifications SET status='unknown',message='隔离验收:模拟结果落库中断,查询原上游任务' WHERE id=$1 AND actor_id=$2 AND model_key=$3 AND status='passed' AND task_id=$4`, result.id, actor.id, model.key, result.taskId)
|
|
const recovered = await request(`/providers/${provider.id}/verifications/${result.id}/refresh`, 'POST'); assert.equal(recovered.status, 200)
|
|
const after = recovered.data.providers.find((value: any) => value.id === provider.id).models.find((value: any) => value.key === model.key).verification.image
|
|
assert.equal(after.id, result.id); assert.equal(after.taskId, result.taskId); assert.equal(after.status, 'passed')
|
|
const finalStarts = await prisma.$queryRawUnsafe<Array<{ count: number }>>(`SELECT COUNT(*)::int AS count FROM ai_model_audit WHERE action='model.verification-start' AND details->>'verificationId'=$1`, result.id)
|
|
assert.equal(finalStarts[0].count, starts[0].count)
|
|
const evidence = { nativeHttp: true, sameVerificationId: result.id, sameUpstreamTaskId: result.taskId, status: after.status, newGenerationAdmissions: 0, productionTouched: false }
|
|
await save('real-query-acceptance', evidence); console.log(JSON.stringify(evidence)); return
|
|
}
|
|
if (phase === 'rename') {
|
|
if (provider.name === '现有详情页上游') { const result = await request(`/providers/${provider.id}`, 'PATCH', { name: '平台模型服务', revision: provider.revision }); assert.equal(result.status, 200); state = result.data }
|
|
await save('provider-name', { name: state.providers.find(value => value.id === provider.id)!.name, productionTouched: false }); return
|
|
}
|
|
if (phase === 'cleanup') {
|
|
const receipt = JSON.parse(await readFile(`${output}/recovery-fixture.json`, 'utf8'))
|
|
const models = await prisma.$queryRawUnsafe<Array<{ id: string; model_id: string }>>('SELECT id,model_id FROM ai_model_catalog WHERE id=$1 AND provider_id=$2', receipt.modelKey, provider.id)
|
|
assert.equal(models[0]?.model_id, receipt.modelId); assert.ok(receipt.modelId.startsWith('qa-workflow-'))
|
|
await prisma.$transaction(async tx => {
|
|
await tx.$executeRawUnsafe('DELETE FROM ai_model_verifications WHERE model_key=$1 AND actor_id=$2', receipt.modelKey, actor.id)
|
|
await tx.$executeRawUnsafe('DELETE FROM ai_model_catalog WHERE id=$1 AND model_id=$2', receipt.modelKey, receipt.modelId)
|
|
}); await save('recovery-fixture-cleanup', { modelKey: receipt.modelKey, productionTouched: false }); return
|
|
}
|
|
assert.equal(phase, 'recovery')
|
|
const source = await prisma.$queryRawUnsafe<Array<{ capability_metadata: unknown }>>('SELECT capability_metadata FROM ai_model_catalog WHERE provider_id=$1 AND model_id=$2', provider.id, 'gemini-3-flash')
|
|
assert.ok(source[0]); const metadata = source[0].capability_metadata, modelKey = randomUUID(), modelId = `qa-workflow-${randomUUID()}`
|
|
const protocol = getAiModelStageProtocol(metadata, 'analysis'); assert.ok(protocol)
|
|
const contextHash = aiModelVerificationContext(provider.baseUrl, modelId, metadata, process.env.DETAIL_GENERATOR_API_KEY!, protocol)
|
|
await prisma.$executeRawUnsafe(`INSERT INTO ai_model_catalog (id,provider_id,model_id,enabled,stages,present,capability_metadata) VALUES ($1,$2,$3,FALSE,ARRAY[]::text[],TRUE,$4::jsonb)`, modelKey, provider.id, modelId, JSON.stringify(metadata))
|
|
await save('recovery-fixture', { modelKey, modelId, providerId: provider.id, actorId: actor.id })
|
|
const before = await prisma.$queryRawUnsafe<Array<{ count: number }>>('SELECT COUNT(*)::int AS count FROM ai_model_verifications')
|
|
const absent = { stage: 'analysis', idempotencyKey: randomUUID() }
|
|
const checked = await request(`/providers/${provider.id}/models/${modelKey}/submission`, 'POST', absent)
|
|
assert.equal(checked.status, 200); assert.equal(checked.data.submissionCheck.received, false)
|
|
const late = await request(`/providers/${provider.id}/models/${modelKey}/verify`, 'POST', { ...absent, revision: provider.revision })
|
|
assert.equal(late.status, 409); assert.equal(late.data.code, 'verification_submission_closed')
|
|
const after = await prisma.$queryRawUnsafe<Array<{ count: number }>>('SELECT COUNT(*)::int AS count FROM ai_model_verifications'); assert.equal(after[0].count, before[0].count)
|
|
const id = randomUUID(), submissionKey = randomUUID()
|
|
await prisma.$executeRawUnsafe(`INSERT INTO ai_model_verifications (id,model_key,stage,context_hash,idempotency_key,actor_id,status) VALUES ($1,$2,'analysis',$3,$4,$5,'unknown')`, id, modelKey, contextHash, submissionKey, actor.id)
|
|
const known = await request(`/providers/${provider.id}/models/${modelKey}/submission`, 'POST', { stage: 'analysis', idempotencyKey: submissionKey }); assert.equal(known.data.submissionCheck.received, true)
|
|
const blocked = await request(`/providers/${provider.id}/models/${modelKey}/verify`, 'POST', { stage: 'copy', idempotencyKey: randomUUID(), revision: provider.revision }); assert.equal(blocked.data.code, 'verification_unresolved')
|
|
const proof = { outcome: 'failed', reference: 'qa-native-log-001', note: '隔离环境核查用例:原请求未调用上游,已结束。', confirmed: true }
|
|
const bad = await request(`/providers/${provider.id}/verifications/${id}/reconcile`, 'POST', { ...proof, confirmed: false }); assert.equal(bad.status, 400)
|
|
const ended = await request(`/providers/${provider.id}/verifications/${id}/reconcile`, 'POST', proof); assert.equal(ended.status, 200)
|
|
const model = ended.data.providers.find((value: any) => value.id === provider.id).models.find((value: any) => value.key === modelKey)
|
|
assert.equal(model.verification.analysis.status, 'failed'); assert.equal(model.verification.analysis.reconciled, true); assert.equal(model.enabled, false)
|
|
const cannotEnable = await request('/configuration', 'PUT', { revision: state.revision, defaults: { analysis: null, copy: null, image: null }, providers: [{ id: provider.id, revision: provider.revision, models: [{ key: modelKey, enabled: true, stages: ['analysis'] }] }] }); assert.equal(cannotEnable.data.code, 'model_verification_required')
|
|
const deniedState = await prisma.$queryRawUnsafe<Array<{ enabled: boolean }>>('SELECT enabled FROM ai_model_catalog WHERE id=$1', modelKey); assert.equal(deniedState[0].enabled, false)
|
|
await prisma.$executeRawUnsafe(`UPDATE ai_model_verifications SET status='unknown',evidence='{}'::jsonb,task_id='qa-queryable-original' WHERE id=$1`, id)
|
|
const queryOnly = await request(`/providers/${provider.id}/verifications/${id}/reconcile`, 'POST', proof); assert.equal(queryOnly.data.code, 'verification_query_required')
|
|
await prisma.$executeRawUnsafe(`UPDATE ai_model_verifications SET task_id=NULL WHERE id=$1`, id)
|
|
const evidence = { nativeHttp: true, productionTouched: false, lateSubmission: 409, differentStageBypass: 409, insufficientEvidence: 400, queryableTaskCannotClose: 409, manualRecoveryDoesNotPass: true, enableWithoutPass: 400, paidModelCalls: 0 }
|
|
await save('native-recovery-acceptance', evidence); console.log(JSON.stringify(evidence))
|
|
}
|
|
main().catch(error => { console.error(error instanceof Error ? error.message : 'Acceptance failed'); process.exitCode = 1 }).finally(() => prisma.$disconnect())
|