Files
Ncurator/tests/case/unit/model/workbench-model-selection.test.ts
hy 854cde204f feat: Implement free model catalog service and update related components
- Added FreeModelCatalogService for managing free model metadata and caching.
- Introduced token management for free models, including token refresh logic.
- Updated API calls to use free model endpoints and handle authorization.
- Enhanced model selection logic to accommodate free models.
- Improved tests for free model functionality and caching behavior.
2026-10-15 16:15:04 +08:00

239 lines
7.4 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
pickFallbackWorkbenchModel,
resolveSelectedWorkbenchModel,
isPresetModelAvailableInRemoteList
} from '../../../../src/shared/workbenchModelSelection'
import type { LLMConfig, LlmRemoteModel } from '../../../../src/shared/types'
import {
toFreeModelPublicEntry,
type FreeModelCatalogEntry
} from '../../../../src/shared/freeModels'
import type { LlmPresetProvider } from '../../../../src/shared/llmPresets'
const presetConfig = (id: string): LLMConfig => ({
id,
name: '通义千问',
base_url: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
api_key: 'sk-test',
providerKind: 'openai-compatible'
})
const customConfig = (id: string): LLMConfig => ({
id,
name: '我的通义',
base_url: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
api_key: 'sk-test',
providerKind: 'openai-compatible'
})
const remote = (...ids: string[]): LlmRemoteModel[] => ids.map((id) => ({ id, label: id }))
const dashscopeCatalog: LlmPresetProvider[] = [
{
providerId: 'dashscope',
label: '通义千问',
models: [
{
id: 'qwen3.8-max',
label: 'Qwen3.8 Max',
iconUrl: 'https://www.guanzhangai.cn/llm/qwen.png',
priceTier: 'high',
description: '',
capabilities: ['text', 'vision']
},
{
id: 'qwen3.8-flash',
label: 'Qwen3.8 Flash',
iconUrl: 'https://www.guanzhangai.cn/llm/qwen.png',
priceTier: 'low',
description: '',
capabilities: ['text', 'vision']
}
]
}
]
describe('workbench model selection', () => {
it('requires exact remote id for preset availability when list is non-empty', () => {
expect(isPresetModelAvailableInRemoteList('qwen3.8-max', remote('qwen3.8-max'))).toBe(true)
expect(isPresetModelAvailableInRemoteList('qwen3.8-max', remote('qwen3.8-max-0902'))).toBe(
false
)
expect(isPresetModelAvailableInRemoteList('qwen3.8-max', [])).toBe(true)
})
it('keeps custom remote ids selected under a recommend config', () => {
const configs = [presetConfig('cfg-1')]
const lists = { 'cfg-1': remote('qwen3.8-max-0902', 'qwen3.8-max') }
expect(
resolveSelectedWorkbenchModel({
configs,
modelListsByConfigId: lists,
selected: { configId: 'cfg-1', modelName: 'qwen3.8-max-0902' }
})
).toEqual({ configId: 'cfg-1', modelName: 'qwen3.8-max-0902' })
expect(
resolveSelectedWorkbenchModel({
configs,
modelListsByConfigId: lists,
selected: { configId: 'cfg-1', modelName: 'qwen3.8-max' }
})
).toEqual({ configId: 'cfg-1', modelName: 'qwen3.8-max' })
})
it('rejects selected ids that are absent from a non-empty remote list', () => {
const configs = [presetConfig('cfg-1')]
const lists = { 'cfg-1': remote('qwen3.8-max') }
expect(
resolveSelectedWorkbenchModel({
configs,
modelListsByConfigId: lists,
selected: { configId: 'cfg-1', modelName: 'missing-model' }
})
).toBeNull()
})
it('keeps preset selection when remote list is empty and catalog is not loaded', () => {
const configs = [presetConfig('cfg-1')]
expect(
resolveSelectedWorkbenchModel({
configs,
modelListsByConfigId: { 'cfg-1': [] },
selected: { configId: 'cfg-1', modelName: 'qwen3.8-max-0902' }
})
).toEqual({ configId: 'cfg-1', modelName: 'qwen3.8-max-0902' })
})
it('when catalog is loaded, empty remote only keeps ids present in catalog', () => {
const configs = [presetConfig('cfg-1')]
expect(
resolveSelectedWorkbenchModel({
configs,
modelListsByConfigId: { 'cfg-1': [] },
selected: { configId: 'cfg-1', modelName: 'qwen3.8-max' },
presetProviders: dashscopeCatalog
})
).toEqual({ configId: 'cfg-1', modelName: 'qwen3.8-max' })
expect(
resolveSelectedWorkbenchModel({
configs,
modelListsByConfigId: { 'cfg-1': [] },
selected: { configId: 'cfg-1', modelName: 'qwen3.8-max-0902' },
presetProviders: dashscopeCatalog
})
).toBeNull()
})
it('falls back to the first available preset id instead of the first remote id', () => {
const configs = [presetConfig('cfg-1')]
const lists = {
'cfg-1': remote('qwen3.8-max-0902', 'qwen3.7-text-embedding-flash', 'qwen3.8-max')
}
expect(
pickFallbackWorkbenchModel({
configs,
modelListsByConfigId: lists,
presetProviders: dashscopeCatalog
})
).toEqual({ configId: 'cfg-1', modelName: 'qwen3.8-max' })
})
it('skips unavailable presets and uses the next available preset model', () => {
const configs = [presetConfig('cfg-1')]
const lists = {
'cfg-1': remote('qwen3.8-flash', 'qwen3.8-max-0902')
}
expect(
pickFallbackWorkbenchModel({
configs,
modelListsByConfigId: lists,
presetProviders: dashscopeCatalog
})
).toEqual({ configId: 'cfg-1', modelName: 'qwen3.8-flash' })
})
it('uses custom remote chat models only when no preset config is available', () => {
const configs = [customConfig('cfg-custom')]
const lists = {
'cfg-custom': remote('qwen3.8-max-0902', 'qwen3.7-text-embedding-flash')
}
expect(
pickFallbackWorkbenchModel({
configs,
modelListsByConfigId: lists,
presetProviders: dashscopeCatalog
})
).toEqual({ configId: 'cfg-custom', modelName: 'qwen3.8-max-0902' })
})
it('keeps free model selections and falls back to free catalog when no user configs', () => {
const freeEntry: FreeModelCatalogEntry = {
id: 'free-glm-4.7-flash',
providerKind: 'openai-compatible',
modelName: 'glm-4.7-flash',
label: 'GLM-4.7 Flash',
description: '智谱免费主力:更强工具协同与长任务,高峰可能排队。',
capabilities: ['text'],
iconUrl: 'https://www.guanzhangai.cn/llm/zhipu.png',
baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
apiKey: 'zhipu-test'
}
const freeModels = [toFreeModelPublicEntry(freeEntry)]
expect(
resolveSelectedWorkbenchModel({
configs: [],
modelListsByConfigId: {},
selected: {
configId: freeModels[0].configId,
modelName: freeModels[0].modelName
},
freeModels
})
).toEqual({
configId: freeModels[0].configId,
modelName: freeModels[0].modelName
})
expect(
pickFallbackWorkbenchModel({
configs: [],
modelListsByConfigId: {},
freeModels
})
).toEqual({
configId: freeModels[0].configId,
modelName: freeModels[0].modelName
})
})
it('clears removed free models and rejects a mismatched model name', () => {
expect(
resolveSelectedWorkbenchModel({
configs: [],
modelListsByConfigId: {},
freeModels: [],
selected: { configId: 'free:removed', modelName: 'removed' }
})
).toBeNull()
expect(
resolveSelectedWorkbenchModel({
configs: [],
modelListsByConfigId: {},
freeModels: [
{
id: 'entry',
configId: 'free:entry',
modelName: 'new-model',
providerKind: 'openai-compatible',
label: 'New',
description: '',
capabilities: ['text'],
iconUrl: ''
}
],
selected: { configId: 'free:entry', modelName: 'old-model' }
})
).toBeNull()
})
})