Files
hermes-ui/tests/server/provider-model-discovery.test.ts
T
toor 3ca960f388
Build / build (push) Canceled after 0s
Playwright / e2e (push) Canceled after 0s
优化页面交互并增强 Provider 配置能力
- 表单下拉框不再用 label 包裹,改为 div + aria-label,避免点击后
  下拉面板瞬间关闭
- Provider 卡片新增「从上游获取模型」和「修改」入口,支持编辑
  base URL、API Key 与 api_mode,凭据写入对应的环境变量
- 服务端抽出 provider-model-discovery 统一不同 api_mode 的模型发现,
  旧的 custom_providers 与新的 providers 字典结构都能正确更新
- Agent Bridge 改为后台启动,HTTP 接口在 Python 运行时就绪前即可用;
  worker 连接被拒时按类型重试,不重放可能已产生副作用的请求
- 断线重连后恢复 profiles、models 与会话列表,健康检查改为自调度
  的 setTimeout,离线时 2 秒后重试
- 批量删除确认文案改用 Popconfirm content,避免变成侧栏触发器文本
2026-10-22 14:29:48 +08:00

40 lines
2.4 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest'
import { discoverProviderModels } from '../../packages/server/src/services/hermes/provider-model-discovery'
afterEach(() => vi.unstubAllGlobals())
describe('live provider model discovery', () => {
it('uses Anthropic authentication and collects every page before returning the catalog', async () => {
const fetch = vi.fn()
.mockResolvedValueOnce({ ok: true, json: async () => ({ data: [{ id: 'MiniMax-M3' }], has_more: true, last_id: 'MiniMax-M3' }) })
.mockResolvedValueOnce({ ok: true, json: async () => ({ data: [{ id: 'MiniMax-M2.7' }], has_more: false }) })
vi.stubGlobal('fetch', fetch)
await expect(discoverProviderModels('https://proxy.invalid/anthropic/v1/', 'test-key', true)).resolves.toEqual(['MiniMax-M2.7', 'MiniMax-M3'])
expect(fetch).toHaveBeenNthCalledWith(1, 'https://proxy.invalid/anthropic/v1/models', expect.objectContaining({
headers: { 'x-api-key': 'test-key', 'anthropic-version': '2023-06-01' },
}))
expect(fetch.mock.calls[1][0]).toContain('after_id=MiniMax-M3')
})
it('uses Bearer authentication for OpenAI-compatible endpoints and normalizes model IDs', async () => {
const fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ data: [{ id: 'b' }, { id: '' }, { id: 'a' }, { id: 'b' }] }) })
vi.stubGlobal('fetch', fetch)
await expect(discoverProviderModels('https://proxy.invalid', 'test-key')).resolves.toEqual(['a', 'b'])
expect(fetch).toHaveBeenCalledWith('https://proxy.invalid/v1/models', expect.objectContaining({ headers: { Authorization: 'Bearer test-key' } }))
})
it('rejects a failed later page instead of returning a partial list', async () => {
vi.stubGlobal('fetch', vi.fn()
.mockResolvedValueOnce({ ok: true, json: async () => ({ data: [{ id: 'first' }], has_more: true, last_id: 'first' }) })
.mockResolvedValueOnce({ ok: false, status: 503 }))
await expect(discoverProviderModels('https://proxy.invalid/v1', 'test-key', true)).rejects.toThrow('HTTP 503')
})
it('rejects a repeated cursor instead of looping', async () => {
const fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ data: [{ id: 'first' }], has_more: true, last_id: 'first' }) })
vi.stubGlobal('fetch', fetch)
await expect(discoverProviderModels('https://proxy.invalid/v1', 'test-key', true)).rejects.toThrow('invalid model pagination')
expect(fetch).toHaveBeenCalledTimes(2)
})
})