File size: 5,016 Bytes
dcedb25 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | import { describe, expect, it } from 'vitest'
import {
MODEL_PICKER_MIN_MODELS,
resolveScopeUpdate,
scopeCandidates,
shouldOfferModelPicker,
type ScopeCandidate,
} from './model-scope-selection'
import type { FallbackEntry } from './routing'
function entry(platform: string, modelId: string, extra: Partial<FallbackEntry> = {}): FallbackEntry {
return {
modelDbId: 1,
priority: 1,
effectivePriority: 1,
penalty: 0,
rateLimitHits: 0,
enabled: true,
platform,
modelId,
displayName: modelId.toUpperCase(),
intelligenceRank: 50,
speedRank: 50,
sizeLabel: 'Large',
rpmLimit: null,
rpdLimit: null,
monthlyTokenBudget: '',
supportsVision: false,
supportsTools: true,
keyCount: 1,
...extra,
}
}
function candidates(...ids: string[]): ScopeCandidate[] {
return ids.map(modelId => ({ modelId, displayName: modelId, sizeLabel: null, contextWindow: null }))
}
describe('scopeCandidates (#657 post-add picker)', () => {
const entries = [
entry('groq', 'llama-3.3-70b'),
entry('cerebras', 'qwen-3-32b'),
entry('groq', 'kimi-k2', { displayName: 'Kimi K2', sizeLabel: 'Frontier', contextWindow: 262144 }),
]
it('keeps only the named platform, in server order', () => {
expect(scopeCandidates(entries, 'groq').map(c => c.modelId)).toEqual(['llama-3.3-70b', 'kimi-k2'])
})
it('carries the display name, tier and context the picker badges', () => {
expect(scopeCandidates(entries, 'groq')[1]).toEqual({
modelId: 'kimi-k2',
displayName: 'Kimi K2',
sizeLabel: 'Frontier',
contextWindow: 262144,
})
})
it('falls back to the model id when the catalog has no display name', () => {
const rows = scopeCandidates([entry('groq', 'bare', { displayName: '' })], 'groq')
expect(rows[0].displayName).toBe('bare')
})
it('normalises a missing tier/context to null', () => {
const rows = scopeCandidates([entry('groq', 'x', { sizeLabel: '', contextWindow: undefined })], 'groq')
expect(rows[0]).toMatchObject({ sizeLabel: null, contextWindow: null })
})
it('collapses duplicate model ids — modelScope matches on id alone', () => {
const dupes = [entry('groq', 'dup'), entry('groq', 'dup'), entry('groq', 'other')]
expect(scopeCandidates(dupes, 'groq').map(c => c.modelId)).toEqual(['dup', 'other'])
})
it('offers nothing for custom endpoints or an unnamed platform', () => {
expect(scopeCandidates([entry('custom', 'local-qwen')], 'custom')).toEqual([])
expect(scopeCandidates(entries, '')).toEqual([])
})
it('offers nothing when the model list has not loaded', () => {
expect(scopeCandidates([], 'groq')).toEqual([])
})
})
describe('shouldOfferModelPicker', () => {
it('stays out of the way for a small catalog', () => {
expect(shouldOfferModelPicker(candidates('a', 'b', 'c', 'd', 'e'))).toBe(false)
})
it('offers the picker once the catalog passes five models', () => {
expect(shouldOfferModelPicker(candidates('a', 'b', 'c', 'd', 'e', 'f'))).toBe(true)
})
it('never offers an empty list', () => {
expect(shouldOfferModelPicker([])).toBe(false)
})
it('draws the line at six', () => {
expect(MODEL_PICKER_MIN_MODELS).toBe(6)
})
})
describe('resolveScopeUpdate', () => {
const all = ['a', 'b', 'c']
it('saves nothing when every model is ticked — the scope stays null', () => {
// A stored full list would freeze the key at today's catalog; null keeps
// tomorrow's models flowing through it.
expect(resolveScopeUpdate(all, new Set(all))).toEqual({ patch: false, reason: 'all' })
})
it('patches exactly the ticked ids for a subset', () => {
expect(resolveScopeUpdate(all, new Set(['a', 'c']))).toEqual({ patch: true, modelScope: ['a', 'c'] })
})
it('emits the ids in catalog order, not tick order', () => {
const update = resolveScopeUpdate(all, new Set(['c', 'a']))
expect(update).toEqual({ patch: true, modelScope: ['a', 'c'] })
})
it('patches a single ticked model', () => {
expect(resolveScopeUpdate(all, new Set(['b']))).toEqual({ patch: true, modelScope: ['b'] })
})
it('refuses to submit an empty selection', () => {
// The server maps `modelScope: []` back to NULL = "serves everything", the
// opposite of an empty tick list — so this never becomes a request. The
// dialog also disables Confirm here.
expect(resolveScopeUpdate(all, new Set())).toEqual({ patch: false, reason: 'empty' })
})
it('ignores ticks for ids no longer on offer', () => {
expect(resolveScopeUpdate(all, new Set(['a', 'gone']))).toEqual({ patch: true, modelScope: ['a'] })
// …including when the leftovers would otherwise fake a full selection.
expect(resolveScopeUpdate(all, new Set(['a', 'b', 'c', 'gone']))).toEqual({ patch: false, reason: 'all' })
})
it('treats an empty catalog as nothing to save', () => {
expect(resolveScopeUpdate([], new Set(['a']))).toEqual({ patch: false, reason: 'empty' })
})
})
|