Files
2026-07-13 13:20:22 +08:00

255 lines
9.0 KiB
TypeScript

/**
* @license
* Copyright 2025 AionUi (aionui.com)
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect } from 'vitest';
import {
parseApiKeys,
maskApiKey,
normalizeBaseUrl,
removeApiPathSuffix,
guessProtocolFromUrl,
guessProtocolFromKey,
identifyProviderFromKey,
getProtocolDisplayName,
getRecommendedPlatform,
} from '@/common/utils/protocolDetector';
describe('protocolDetector', () => {
describe('parseApiKeys', () => {
it('parses comma-separated keys', () => {
expect(parseApiKeys('key1,key2,key3')).toEqual(['key1', 'key2', 'key3']);
});
it('parses newline-separated keys', () => {
expect(parseApiKeys('key1\nkey2\nkey3')).toEqual(['key1', 'key2', 'key3']);
});
it('parses mixed separators', () => {
expect(parseApiKeys('key1,key2\nkey3')).toEqual(['key1', 'key2', 'key3']);
});
it('trims whitespace from keys', () => {
expect(parseApiKeys(' key1 , key2 \n key3 ')).toEqual(['key1', 'key2', 'key3']);
});
it('filters empty strings', () => {
expect(parseApiKeys('key1,,key2,,')).toEqual(['key1', 'key2']);
expect(parseApiKeys('key1\n\nkey2')).toEqual(['key1', 'key2']);
});
it('returns empty array for empty input', () => {
expect(parseApiKeys('')).toEqual([]);
expect(parseApiKeys(null as any)).toEqual([]);
expect(parseApiKeys(undefined as any)).toEqual([]);
});
it('handles single key', () => {
expect(parseApiKeys('single-key')).toEqual(['single-key']);
});
});
describe('maskApiKey', () => {
it('masks long API keys showing first and last 4 characters', () => {
expect(maskApiKey('sk-abc123def456ghi789')).toBe('sk-a...i789');
const geminiKey = 'AIza' + 'a'.repeat(35);
expect(maskApiKey(geminiKey)).toBe('AIza...aaaa');
});
it('returns *** for short keys (8 chars or less)', () => {
expect(maskApiKey('short')).toBe('***');
expect(maskApiKey('12345678')).toBe('***');
});
it('handles exact 9 character key', () => {
expect(maskApiKey('123456789')).toBe('1234...6789');
});
it('handles empty string', () => {
expect(maskApiKey('')).toBe('***');
});
});
describe('normalizeBaseUrl', () => {
it('removes trailing slashes', () => {
expect(normalizeBaseUrl('https://api.example.com/')).toBe('https://api.example.com');
expect(normalizeBaseUrl('https://api.example.com///')).toBe('https://api.example.com');
});
it('trims whitespace', () => {
expect(normalizeBaseUrl(' https://api.example.com ')).toBe('https://api.example.com');
});
it('preserves path', () => {
expect(normalizeBaseUrl('https://api.example.com/v1')).toBe('https://api.example.com/v1');
expect(normalizeBaseUrl('https://api.example.com/v1/')).toBe('https://api.example.com/v1');
});
it('returns empty string for empty input', () => {
expect(normalizeBaseUrl('')).toBe('');
expect(normalizeBaseUrl(null as any)).toBe('');
expect(normalizeBaseUrl(undefined as any)).toBe('');
});
});
describe('removeApiPathSuffix', () => {
it('removes OpenAI path suffixes', () => {
expect(removeApiPathSuffix('https://api.example.com/v1/chat/completions')).toBe('https://api.example.com');
expect(removeApiPathSuffix('https://api.example.com/v1/models')).toBe('https://api.example.com');
});
it('removes Gemini path suffixes', () => {
expect(removeApiPathSuffix('https://generativelanguage.googleapis.com/v1beta/models')).toBe(
'https://generativelanguage.googleapis.com'
);
});
it('removes Anthropic path suffixes', () => {
expect(removeApiPathSuffix('https://api.anthropic.com/v1/messages')).toBe('https://api.anthropic.com');
});
it('matches longest suffix first', () => {
expect(removeApiPathSuffix('https://api.example.com/v1/chat/completions')).toBe('https://api.example.com');
});
it('returns null when no suffix matches', () => {
expect(removeApiPathSuffix('https://api.example.com/custom/endpoint')).toBeNull();
expect(removeApiPathSuffix('https://api.example.com')).toBeNull();
});
it('handles case-insensitive matching', () => {
expect(removeApiPathSuffix('https://api.example.com/V1/MODELS')).toBe('https://api.example.com');
});
it('returns null for empty input', () => {
expect(removeApiPathSuffix('')).toBeNull();
expect(removeApiPathSuffix(null as any)).toBeNull();
});
});
describe('guessProtocolFromUrl', () => {
it('detects Gemini URLs', () => {
expect(guessProtocolFromUrl('https://generativelanguage.googleapis.com/v1')).toBe('gemini');
expect(guessProtocolFromUrl('https://aiplatform.googleapis.com/v1')).toBe('gemini');
});
it('detects OpenAI URLs', () => {
expect(guessProtocolFromUrl('https://api.openai.com/v1')).toBe('openai');
expect(guessProtocolFromUrl('https://api.deepseek.com/v1')).toBe('openai');
expect(guessProtocolFromUrl('https://openrouter.ai/v1')).toBe('openai');
});
it('detects Anthropic URLs', () => {
expect(guessProtocolFromUrl('https://api.anthropic.com/v1')).toBe('anthropic');
});
it('returns null for unknown URLs', () => {
expect(guessProtocolFromUrl('https://unknown.example.com/v1')).toBeNull();
});
it('handles case-insensitive matching', () => {
expect(guessProtocolFromUrl('HTTPS://API.OPENAI.COM/V1')).toBe('openai');
});
it('detects localhost URLs as OpenAI', () => {
expect(guessProtocolFromUrl('http://localhost:8000/v1')).toBe('openai');
expect(guessProtocolFromUrl('http://127.0.0.1:8000/v1')).toBe('openai');
});
});
describe('guessProtocolFromKey', () => {
it('detects OpenAI key format', () => {
expect(guessProtocolFromKey('sk-abc123def456ghi789jkl012')).toBe('openai');
expect(guessProtocolFromKey('sk-proj-abc123def456ghi789jkl012')).toBe('openai');
});
it('detects Gemini key format', () => {
const geminiKey = 'AIza' + 'a'.repeat(35);
expect(guessProtocolFromKey(geminiKey)).toBe('gemini');
});
it('detects Anthropic key format (matched as openai due to pattern order)', () => {
const anthropicKey = 'sk-ant-' + 'a'.repeat(80);
// Current implementation: OpenAI pattern matches first (sk-[A-Za-z0-9-_]{20,})
expect(guessProtocolFromKey(anthropicKey)).toBe('openai');
});
it('detects Groq key format', () => {
const groqKey = 'gsk_' + 'a'.repeat(52);
expect(guessProtocolFromKey(groqKey)).toBe('openai');
});
it('detects Perplexity key format', () => {
const pplxKey = 'pplx-' + 'a'.repeat(48);
expect(guessProtocolFromKey(pplxKey)).toBe('openai');
});
it('returns null for unknown key format', () => {
expect(guessProtocolFromKey('unknown-key-format')).toBeNull();
expect(guessProtocolFromKey('abc123')).toBeNull();
});
it('returns null for empty input', () => {
expect(guessProtocolFromKey('')).toBeNull();
});
});
describe('identifyProviderFromKey', () => {
it('identifies OpenAI/Compatible provider', () => {
expect(identifyProviderFromKey('sk-abc123def456ghi789jkl012')).toBe('OpenAI/Compatible');
});
it('identifies Google/Gemini provider', () => {
const geminiKey = 'AIza' + 'a'.repeat(35);
expect(identifyProviderFromKey(geminiKey)).toBe('Google/Gemini');
});
it('identifies Anthropic provider (matched as OpenAI/Compatible due to pattern order)', () => {
const anthropicKey = 'sk-ant-' + 'a'.repeat(80);
// Current implementation: OpenAI pattern matches first in THIRD_PARTY_KEY_PATTERNS
expect(identifyProviderFromKey(anthropicKey)).toBe('OpenAI/Compatible');
});
it('identifies Groq provider', () => {
const groqKey = 'gsk_' + 'a'.repeat(52);
expect(identifyProviderFromKey(groqKey)).toBe('Groq');
});
it('identifies Perplexity provider', () => {
const pplxKey = 'pplx-' + 'a'.repeat(48);
expect(identifyProviderFromKey(pplxKey)).toBe('Perplexity');
});
it('returns null for unknown key', () => {
expect(identifyProviderFromKey('unknown-key')).toBeNull();
});
});
describe('getProtocolDisplayName', () => {
it('returns display names for known protocols', () => {
expect(getProtocolDisplayName('openai')).toBe('OpenAI');
expect(getProtocolDisplayName('gemini')).toBe('Gemini');
expect(getProtocolDisplayName('anthropic')).toBe('Anthropic');
expect(getProtocolDisplayName('unknown')).toBe('Unknown');
});
it('returns protocol string for unmapped values', () => {
expect(getProtocolDisplayName('custom' as any)).toBe('custom');
});
});
describe('getRecommendedPlatform', () => {
it('returns recommended platforms', () => {
expect(getRecommendedPlatform('gemini')).toBe('gemini');
expect(getRecommendedPlatform('anthropic')).toBe('Anthropic');
});
it('returns null for protocols without recommendation', () => {
expect(getRecommendedPlatform('openai')).toBeNull();
expect(getRecommendedPlatform('unknown')).toBeNull();
});
});
});