gongxiangp2/lib/__tests__/ai-model-contract-audit.test.ts

38 lines
3.2 KiB
TypeScript

/** @jest-environment node */
import { auditAiModelContracts, renderAiModelContractAudit } from '@/lib/ai-model-contract-audit'
jest.mock('next-auth', () => ({ getServerSession: jest.fn() }))
jest.mock('@/lib/auth', () => ({ authOptions: {} }))
jest.mock('@/lib/department-permission', () => ({ isSuperAdmin: jest.fn() }))
jest.mock('@/lib/prisma', () => ({ prisma: {} }))
function fixture() {
return { code: 0, data: { schema_version: '2.2', sections: [{ id: 'chat', models: [{ id: 'arbitrary-name', type: 'model', modality: 'multimodal', available: true, status: 'available', default_service_scope_id: 'scope-one', default_route_option_id: 'route-one', service_offers: [{ service_scope: { id: 'scope-one' }, public_operation_id: 'openai.chat_completions.create', route_options: [{ id: 'route-one', invocation: { method: 'POST', path: '/v1/chat/completions', content_type: 'application/json', headers: { 'X-Relay-Route-Option': 'route-one' }, input_modes: ['text', 'image'], output_modes: ['text'], request_example: { api_key: 'MUST-NOT-COPY' }, supported_fields: [{ name: 'model', type: 'string', state: 'typed' }, { name: 'temperature', type: 'number', state: 'passthrough' }], input_schema: { type: 'object', properties: { model: { type: 'string' }, temperature: { type: 'number' }, max_tokens: { type: 'integer', maximum: 4096 } } } } }] }] }] }] } }
}
test('inventory preserves declared limits and forwarding state without turning them into verified features', () => {
const result = auditAiModelContracts(fixture())
expect(result.models[0]).toMatchObject({ id: 'arbitrary-name', production: null, adaptedStages: ['analysis', 'copy'] })
expect(result.models[0].contracts[0].parameters).toEqual(expect.arrayContaining([
{ name: 'temperature', state: 'passthrough', declaredType: 'number', constraints: [{ type: 'number' }] },
{ name: 'max_tokens', state: 'schema-only', declaredType: null, constraints: [{ type: 'integer', maximum: 4096 }] },
]))
expect(JSON.stringify(result)).not.toContain('MUST-NOT-COPY')
const markdown = renderAiModelContractAudit(result, '2026-10-16')
expect(markdown).toContain('不等于真实产品')
expect(markdown).toContain('透传')
expect(markdown).toContain('max_tokens')
expect(markdown).not.toContain('分析:通过')
})
test('production evidence is joined by exact model ID; absent legacy models stay explicit', () => {
const report = auditAiModelContracts(fixture(), { providers: [{ models: [{ id: 'arbitrary-name', enabled: true, stages: ['analysis'], verification: { analysis: { status: 'passed' }, copy: { status: 'unknown' } } }, { id: 'old-version', enabled: false, stages: [] }] }] })
expect(report.models[0].production?.verification.copy.status).toBe('unknown')
expect(report.nativeOnly).toEqual([{ id: 'old-version', note: expect.stringContaining('不在本次公开目录') }])
expect(renderAiModelContractAudit(report, '2026-10-16')).toContain('分析:通过;文案:待核查')
})
test('a public directory with duplicate IDs cannot silently combine conflicting contracts', () => {
const value = fixture(); value.data.sections[0].models.push(value.data.sections[0].models[0])
expect(() => auditAiModelContracts(value)).toThrow('重复')
})