AIstudioProxyAPI / static /frontend /src /hooks /useModelCapabilities.test.ts
peijun1's picture
Deploy AI Studio Proxy API to Hugging Face Spaces
a5784e9
Raw
History Blame Contribute Delete
8.76 kB
/**
* useModelCapabilities Hook Tests
*
* Tests the pattern matching and capability lookup logic
*/
import { describe, it, expect } from 'vitest';
// Since the hook uses React Query, we test the pure logic separately
// by extracting and testing the matching algorithm
// Simulate the backend response structure
interface MockCapabilitiesResponse {
categories: Record<string, {
thinkingType: 'level' | 'budget' | 'none';
levels?: string[];
defaultLevel?: string;
alwaysOn?: boolean;
budgetRange?: [number, number];
defaultBudget?: number;
supportsGoogleSearch?: boolean;
}>;
matchers: Array<{ pattern: string; category: string }>;
}
// Mock backend response (matching actual backend implementation)
const mockCapabilitiesData: MockCapabilitiesResponse = {
categories: {
gemini3Flash: {
thinkingType: 'level',
levels: ['minimal', 'low', 'medium', 'high'],
defaultLevel: 'medium',
alwaysOn: true,
supportsGoogleSearch: true,
},
gemini3Pro: {
thinkingType: 'level',
levels: ['low', 'high'],
defaultLevel: 'high',
alwaysOn: true,
supportsGoogleSearch: true,
},
gemini25Pro: {
thinkingType: 'budget',
budgetRange: [128, 32768],
defaultBudget: 8192,
supportsGoogleSearch: true,
},
gemini25Flash: {
thinkingType: 'budget',
budgetRange: [0, 24576],
defaultBudget: 8192,
supportsGoogleSearch: true,
},
gemini2: {
thinkingType: 'none',
supportsGoogleSearch: false,
},
other: {
thinkingType: 'none',
supportsGoogleSearch: true,
},
},
matchers: [
{ pattern: 'gemini-3.*flash|gemini3.*flash', category: 'gemini3Flash' },
{ pattern: 'gemini-3.*pro|gemini3.*pro', category: 'gemini3Pro' },
{ pattern: 'gemini-2\\.5.*pro|gemini-2\\.5pro', category: 'gemini25Pro' },
{ pattern: 'gemini-2\\.5.*flash|gemini-flash-latest|gemini-flash-lite-latest', category: 'gemini25Flash' },
{ pattern: 'gemini-2\\.0|gemini2\\.0', category: 'gemini2' },
],
};
/**
* Pure function matching logic (extracted from the hook)
*/
function getModelCategory(modelId: string, matchers: MockCapabilitiesResponse['matchers']): string {
const modelLower = modelId.toLowerCase();
for (const matcher of matchers) {
const regex = new RegExp(matcher.pattern, 'i');
if (regex.test(modelLower)) {
return matcher.category;
}
}
return 'other';
}
function getModelCapabilities(modelId: string, data: MockCapabilitiesResponse) {
const category = getModelCategory(modelId, data.matchers);
return data.categories[category];
}
describe('Model Category Pattern Matching', () => {
describe('Gemini 3 Flash models', () => {
it('matches gemini-3-flash', () => {
expect(getModelCategory('gemini-3-flash', mockCapabilitiesData.matchers)).toBe('gemini3Flash');
});
it('matches gemini-3.0-flash', () => {
expect(getModelCategory('gemini-3.0-flash', mockCapabilitiesData.matchers)).toBe('gemini3Flash');
});
it('matches gemini-3-flash-thinking', () => {
expect(getModelCategory('gemini-3-flash-thinking', mockCapabilitiesData.matchers)).toBe('gemini3Flash');
});
it('matches case insensitively', () => {
expect(getModelCategory('GEMINI-3-FLASH', mockCapabilitiesData.matchers)).toBe('gemini3Flash');
});
});
describe('Gemini 3 Pro models', () => {
it('matches gemini-3-pro', () => {
expect(getModelCategory('gemini-3-pro', mockCapabilitiesData.matchers)).toBe('gemini3Pro');
});
it('matches gemini-3.0-pro', () => {
expect(getModelCategory('gemini-3.0-pro', mockCapabilitiesData.matchers)).toBe('gemini3Pro');
});
});
describe('Gemini 2.5 Pro models', () => {
it('matches gemini-2.5-pro', () => {
expect(getModelCategory('gemini-2.5-pro', mockCapabilitiesData.matchers)).toBe('gemini25Pro');
});
it('matches gemini-2.5-pro-latest', () => {
expect(getModelCategory('gemini-2.5-pro-latest', mockCapabilitiesData.matchers)).toBe('gemini25Pro');
});
it('matches gemini-2.5-pro-preview-0506', () => {
expect(getModelCategory('gemini-2.5-pro-preview-0506', mockCapabilitiesData.matchers)).toBe('gemini25Pro');
});
});
describe('Gemini 2.5 Flash models', () => {
it('matches gemini-2.5-flash', () => {
expect(getModelCategory('gemini-2.5-flash', mockCapabilitiesData.matchers)).toBe('gemini25Flash');
});
it('matches gemini-2.5-flash-lite', () => {
expect(getModelCategory('gemini-2.5-flash-lite', mockCapabilitiesData.matchers)).toBe('gemini25Flash');
});
it('matches gemini-flash-latest alias', () => {
expect(getModelCategory('gemini-flash-latest', mockCapabilitiesData.matchers)).toBe('gemini25Flash');
});
it('matches gemini-flash-lite-latest alias', () => {
expect(getModelCategory('gemini-flash-lite-latest', mockCapabilitiesData.matchers)).toBe('gemini25Flash');
});
});
describe('Gemini 2.0 models (no thinking)', () => {
it('matches gemini-2.0-flash', () => {
expect(getModelCategory('gemini-2.0-flash', mockCapabilitiesData.matchers)).toBe('gemini2');
});
it('matches gemini-2.0-flash-lite', () => {
expect(getModelCategory('gemini-2.0-flash-lite', mockCapabilitiesData.matchers)).toBe('gemini2');
});
});
describe('Unknown models', () => {
it('returns other for gpt-4', () => {
expect(getModelCategory('gpt-4', mockCapabilitiesData.matchers)).toBe('other');
});
it('returns other for empty string', () => {
expect(getModelCategory('', mockCapabilitiesData.matchers)).toBe('other');
});
it('returns other for claude-3-opus', () => {
expect(getModelCategory('claude-3-opus', mockCapabilitiesData.matchers)).toBe('other');
});
});
});
describe('Model Capabilities Lookup', () => {
describe('Thinking type detection', () => {
it('gemini3Flash uses level selector', () => {
const caps = getModelCapabilities('gemini-3-flash', mockCapabilitiesData);
expect(caps.thinkingType).toBe('level');
expect(caps.levels).toEqual(['minimal', 'low', 'medium', 'high']);
});
it('gemini25Pro uses budget slider', () => {
const caps = getModelCapabilities('gemini-2.5-pro', mockCapabilitiesData);
expect(caps.thinkingType).toBe('budget');
expect(caps.budgetRange).toEqual([128, 32768]);
});
it('gemini2 has no thinking', () => {
const caps = getModelCapabilities('gemini-2.0-flash', mockCapabilitiesData);
expect(caps.thinkingType).toBe('none');
});
});
describe('Always-on thinking detection', () => {
it('gemini3Flash has always-on thinking', () => {
const caps = getModelCapabilities('gemini-3-flash', mockCapabilitiesData);
expect(caps.alwaysOn).toBe(true);
});
it('gemini25Pro does not have always-on', () => {
const caps = getModelCapabilities('gemini-2.5-pro', mockCapabilitiesData);
expect(caps.alwaysOn).toBeUndefined();
});
});
describe('Google Search support', () => {
it('gemini3Flash supports Google Search', () => {
const caps = getModelCapabilities('gemini-3-flash', mockCapabilitiesData);
expect(caps.supportsGoogleSearch).toBe(true);
});
it('gemini2 does not support Google Search', () => {
const caps = getModelCapabilities('gemini-2.0-flash', mockCapabilitiesData);
expect(caps.supportsGoogleSearch).toBe(false);
});
});
describe('Default values', () => {
it('gemini3Flash has default level "medium"', () => {
const caps = getModelCapabilities('gemini-3-flash', mockCapabilitiesData);
expect(caps.defaultLevel).toBe('medium');
});
it('gemini25Flash has default budget 8192', () => {
const caps = getModelCapabilities('gemini-2.5-flash', mockCapabilitiesData);
expect(caps.defaultBudget).toBe(8192);
});
});
});
describe('Edge Cases', () => {
it('handles model ID with special characters', () => {
// Should fall through to 'other' if no match
expect(getModelCategory('gemini@3.0-flash!!!', mockCapabilitiesData.matchers)).toBe('other');
});
it('prioritizes first matching pattern', () => {
// "gemini-3-flash-pro" could match both flash and pro patterns
// The order in matchers determines priority (flash comes first)
const result = getModelCategory('gemini-3-flash-pro', mockCapabilitiesData.matchers);
expect(result).toBe('gemini3Flash');
});
it('handles very long model IDs', () => {
const longModelId = 'gemini-2.5-pro-preview-0506-experimental-thinking-enhanced-v2';
expect(getModelCategory(longModelId, mockCapabilitiesData.matchers)).toBe('gemini25Pro');
});
});