| 1 | import { describe, expect, it } from 'vitest' |
| 2 | import { |
| 3 | buildModelListRequest, |
| 4 | parseModelListResponse |
| 5 | } from '../../../src/main/config/model-list' |
| 6 | |
| 7 | describe('buildModelListRequest', () => { |
| 8 | it('openai:默认官方地址,Bearer 认证', () => { |
| 9 | const spec = buildModelListRequest('openai', '', 'sk-test') |
| 10 | expect(spec.url).toBe('https://api.openai.com/v1/models') |
| 11 | expect(spec.headers).toEqual({ Authorization: 'Bearer sk-test' }) |
| 12 | }) |
| 13 | |
| 14 | it('openai:内网地址去掉尾斜杠后拼接 /models', () => { |
| 15 | const spec = buildModelListRequest('openai', 'https://llm.corp.internal/v1/', 'key') |
| 16 | expect(spec.url).toBe('https://llm.corp.internal/v1/models') |
| 17 | }) |
| 18 | |
| 19 | it('openai-responses:剥离 /responses 后缀再拼接', () => { |
| 20 | expect(buildModelListRequest('openai-responses', 'https://llm.corp.internal/v1/responses', 'k').url).toBe( |
| 21 | 'https://llm.corp.internal/v1/models' |
| 22 | ) |
| 23 | expect(buildModelListRequest('openai-responses', 'https://llm.corp.internal/v1', 'k').url).toBe( |
| 24 | 'https://llm.corp.internal/v1/models' |
| 25 | ) |
| 26 | }) |
| 27 | |
| 28 | it('anthropic:默认官方网关,x-api-key + 版本头', () => { |
| 29 | const spec = buildModelListRequest('anthropic', '', 'sk-ant') |
| 30 | expect(spec.url).toBe('https://api.anthropic.com/v1/models?limit=1000') |
| 31 | expect(spec.headers).toEqual({ 'x-api-key': 'sk-ant', 'anthropic-version': '2023-06-01' }) |
| 32 | }) |
| 33 | |
| 34 | it('anthropic:内网网关拼接 /v1/models', () => { |
| 35 | const spec = buildModelListRequest('anthropic', 'https://gw.corp.cn/', 'k') |
| 36 | expect(spec.url).toBe('https://gw.corp.cn/v1/models?limit=1000') |
| 37 | }) |
| 38 | |
| 39 | it('google:默认 generativelanguage 地址,x-goog-api-key 头', () => { |
| 40 | const spec = buildModelListRequest('google', '', 'g-key') |
| 41 | expect(spec.url).toBe('https://generativelanguage.googleapis.com/v1beta/models?pageSize=1000') |
| 42 | expect(spec.headers).toEqual({ 'x-goog-api-key': 'g-key' }) |
| 43 | }) |
| 44 | }) |
| 45 | |
| 46 | describe('parseModelListResponse', () => { |
| 47 | it('OpenAI/Anthropic 形状:取 data[].id,去重并排序', () => { |
| 48 | const models = parseModelListResponse('openai', { |
| 49 | object: 'list', |
| 50 | data: [ |
| 51 | { id: 'deepseek-v4-flash', object: 'model' }, |
| 52 | { id: 'deepseek-v4-flash' }, |
| 53 | { id: 'glm-5.3' }, |
| 54 | { object: 'model' }, |
| 55 | null |
| 56 | ] |
| 57 | }) |
| 58 | expect(models).toEqual(['deepseek-v4-flash', 'glm-5.3']) |
| 59 | }) |
| 60 | |
| 61 | it('非 google 供应商缺失 id 时回退使用 name 字段', () => { |
| 62 | const models = parseModelListResponse('anthropic', { |
| 63 | data: [{ display_name: 'Sonnet', name: 'claude-sonnet-4-5' }] |
| 64 | }) |
| 65 | expect(models).toEqual(['claude-sonnet-4-5']) |
| 66 | }) |
| 67 | |
| 68 | it('google 形状:取 models[].name 并剥离 models/ 前缀', () => { |
| 69 | const models = parseModelListResponse('google', { |
| 70 | models: [{ name: 'models/gemini-2.0-flash' }, { name: 'models/gemini-3.1-pro' }] |
| 71 | }) |
| 72 | expect(models).toEqual(['gemini-2.0-flash', 'gemini-3.1-pro']) |
| 73 | }) |
| 74 | |
| 75 | it('结构不符合预期时抛错', () => { |
| 76 | expect(() => parseModelListResponse('openai', { data: 'oops' })).toThrow() |
| 77 | expect(() => parseModelListResponse('google', {})).toThrow() |
| 78 | expect(() => parseModelListResponse('openai', null)).toThrow() |
| 79 | }) |
| 80 | |
| 81 | it('空列表返回空数组', () => { |
| 82 | expect(parseModelListResponse('openai', { data: [] })).toEqual([]) |
| 83 | }) |
| 84 | }) |
| 85 |