UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

1,140 lines (965 loc) • 35.9 kB
// @vitest-environment node import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { LobeOpenAICompatibleRuntime } from '../../core/BaseAI'; import { testProvider } from '../../providerTestUtils'; import { LobeSearch1API, params } from './index'; testProvider({ provider: 'search1api', defaultBaseURL: 'https://api.search1api.com/v1', chatModel: 'gpt-4o-mini', Runtime: LobeSearch1API, chatDebugEnv: 'DEBUG_SEARCH1API_CHAT_COMPLETION', }); // Mock the console.error to avoid polluting test output vi.spyOn(console, 'error').mockImplementation(() => {}); let instance: LobeOpenAICompatibleRuntime; beforeEach(() => { instance = new LobeSearch1API({ apiKey: 'test' }); // Use vi.spyOn to mock chat.completions.create method vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue( new ReadableStream() as any, ); }); afterEach(() => { vi.clearAllMocks(); }); describe('LobeSearch1API - custom features', () => { describe('Debug Configuration', () => { it('should disable debug by default', () => { delete process.env.DEBUG_SEARCH1API_CHAT_COMPLETION; const result = params.debug.chatCompletion(); expect(result).toBe(false); }); it('should enable debug when env is set', () => { process.env.DEBUG_SEARCH1API_CHAT_COMPLETION = '1'; const result = params.debug.chatCompletion(); expect(result).toBe(true); delete process.env.DEBUG_SEARCH1API_CHAT_COMPLETION; }); }); describe('handlePayload', () => { describe('presence_penalty handling', () => { it('should use presence_penalty when it is non-zero', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: 0.5, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ presence_penalty: 0.5, }), expect.anything(), ); }); it('should use presence_penalty when it is negative', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: -0.5, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ presence_penalty: -0.5, }), expect.anything(), ); }); it('should use presence_penalty when it is 1', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: 1, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ presence_penalty: 1, }), expect.anything(), ); }); it('should not include frequency_penalty when presence_penalty is non-zero', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: 0.5, frequency_penalty: 0.8, }); const call = (instance['client'].chat.completions.create as any).mock.calls[0][0]; expect(call.presence_penalty).toBe(0.5); expect(call.frequency_penalty).toBeUndefined(); }); }); describe('frequency_penalty handling', () => { it('should use frequency_penalty when presence_penalty is 0', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: 0, frequency_penalty: 0.8, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ frequency_penalty: 0.8, }), expect.anything(), ); }); it('should use default frequency_penalty of 1 when presence_penalty is 0 and frequency_penalty is not provided', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: 0, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ frequency_penalty: 1, }), expect.anything(), ); }); it('should use default frequency_penalty of 1 when presence_penalty is undefined and frequency_penalty is not provided', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', }); const call = (instance['client'].chat.completions.create as any).mock.calls[0][0]; // presence_penalty is undefined (not 0), so no frequency_penalty is set expect(call.presence_penalty).toBeUndefined(); }); it('should use frequency_penalty of 0 when explicitly set with presence_penalty 0', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: 0, frequency_penalty: 0, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ frequency_penalty: 1, // 0 is falsy, so default to 1 }), expect.anything(), ); }); it('should not include presence_penalty when presence_penalty is 0', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: 0, frequency_penalty: 0.8, }); const call = (instance['client'].chat.completions.create as any).mock.calls[0][0]; expect(call.frequency_penalty).toBe(0.8); expect(call.presence_penalty).toBeUndefined(); }); }); describe('temperature handling', () => { it('should preserve temperature when it is less than 2', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', temperature: 0.7, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ temperature: 0.7, }), expect.anything(), ); }); it('should preserve temperature when it is 0', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', temperature: 0, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ temperature: 0, }), expect.anything(), ); }); it('should preserve temperature when it is 1', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', temperature: 1, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ temperature: 1, }), expect.anything(), ); }); it('should preserve temperature when it is 1.99', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', temperature: 1.99, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ temperature: 1.99, }), expect.anything(), ); }); it('should set temperature to undefined when it is 2', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', temperature: 2, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ temperature: undefined, }), expect.anything(), ); }); it('should set temperature to undefined when it is greater than 2', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', temperature: 2.5, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ temperature: undefined, }), expect.anything(), ); }); it('should set temperature to undefined when it is not provided', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', }); const call = (instance['client'].chat.completions.create as any).mock.calls[0][0]; expect(call.temperature).toBeUndefined(); }); }); describe('stream handling', () => { it('should default stream to true when not specified', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ stream: true, }), expect.anything(), ); }); it('should preserve stream value when explicitly set to false', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', stream: false, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ stream: false, }), expect.anything(), ); }); it('should preserve stream value when explicitly set to true', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', stream: true, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ stream: true, }), expect.anything(), ); }); }); describe('other properties preservation', () => { it('should preserve other payload properties', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', max_tokens: 100, top_p: 0.9, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', max_tokens: 100, top_p: 0.9, }), expect.anything(), ); }); it('should preserve tools in payload', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', tools: [ { type: 'function' as const, function: { name: 'tool1', description: '', parameters: {} }, }, ], }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ tools: [ { type: 'function' as const, function: { name: 'tool1', description: '', parameters: {} }, }, ], }), expect.anything(), ); }); it('should preserve messages with multiple roles', async () => { const messages = [ { content: 'Hello', role: 'user' as const }, { content: 'Hi there', role: 'assistant' as const }, { content: 'How are you?', role: 'user' as const }, ]; await instance.chat({ messages, model: 'gpt-4o-mini', }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ messages, }), expect.anything(), ); }); }); describe('combined parameter scenarios', () => { it('should handle presence_penalty with temperature < 2', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: 0.5, temperature: 0.8, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ presence_penalty: 0.5, temperature: 0.8, }), expect.anything(), ); }); it('should handle presence_penalty with temperature >= 2', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: 0.5, temperature: 2.5, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ presence_penalty: 0.5, temperature: undefined, }), expect.anything(), ); }); it('should handle frequency_penalty with temperature < 2', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: 0, frequency_penalty: 0.8, temperature: 0.7, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ frequency_penalty: 0.8, temperature: 0.7, }), expect.anything(), ); }); it('should handle all parameters together', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: 0.5, frequency_penalty: 0.8, // Should be ignored temperature: 1.5, max_tokens: 200, top_p: 0.95, stream: false, }); const call = (instance['client'].chat.completions.create as any).mock.calls[0][0]; expect(call.presence_penalty).toBe(0.5); expect(call.frequency_penalty).toBeUndefined(); expect(call.temperature).toBe(1.5); expect(call.max_tokens).toBe(200); expect(call.top_p).toBe(0.95); expect(call.stream).toBe(false); }); }); }); describe('handlePayload edge cases', () => { it('should handle negative temperature values', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', temperature: -1, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ temperature: -1, }), expect.anything(), ); }); it('should handle very large temperature values', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', temperature: 100, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ temperature: undefined, }), expect.anything(), ); }); it('should handle edge case temperature exactly at 2', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', temperature: 2.0, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ temperature: undefined, }), expect.anything(), ); }); it('should handle negative presence_penalty', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: -2, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ presence_penalty: -2, }), expect.anything(), ); }); it('should handle negative frequency_penalty', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: 0, frequency_penalty: -1, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ frequency_penalty: -1, }), expect.anything(), ); }); it('should handle very small positive presence_penalty', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', presence_penalty: 0.001, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ presence_penalty: 0.001, }), expect.anything(), ); }); it('should handle empty messages array', async () => { await instance.chat({ messages: [], model: 'gpt-4o-mini', }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ messages: [], }), expect.anything(), ); }); it('should handle system messages', async () => { await instance.chat({ messages: [ { content: 'You are a helpful assistant', role: 'system' }, { content: 'Hello', role: 'user' }, ], model: 'gpt-4o-mini', }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ messages: [ { content: 'You are a helpful assistant', role: 'system' }, { content: 'Hello', role: 'user' }, ], }), expect.anything(), ); }); it('should handle response_format parameter', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', response_format: { type: 'json_object' }, }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ response_format: { type: 'json_object' }, }), expect.anything(), ); }); it('should handle seed parameter', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', seed: 12345, } as any); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ seed: 12345, }), expect.anything(), ); }); it('should handle stop parameter as string', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', stop: 'STOP', } as any); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ stop: 'STOP', }), expect.anything(), ); }); it('should handle stop parameter as array', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', stop: ['STOP', 'END'], } as any); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ stop: ['STOP', 'END'], }), expect.anything(), ); }); it('should handle logit_bias parameter', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', logit_bias: { '50256': -100 }, } as any); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ logit_bias: { '50256': -100 }, }), expect.anything(), ); }); it('should handle tool_choice parameter', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', tools: [ { type: 'function' as const, function: { name: 'get_weather', description: '', parameters: {} }, }, ], tool_choice: 'auto', }); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ tool_choice: 'auto', }), expect.anything(), ); }); it('should handle parallel_tool_calls parameter', async () => { await instance.chat({ messages: [{ content: 'Hello', role: 'user' }], model: 'gpt-4o-mini', tools: [ { type: 'function' as const, function: { name: 'tool1', description: '', parameters: {} }, }, ], parallel_tool_calls: false, } as any); expect(instance['client'].chat.completions.create).toHaveBeenCalledWith( expect.objectContaining({ parallel_tool_calls: false, }), expect.anything(), ); }); }); describe('models', () => { const mockClient = { models: { list: vi.fn(), }, }; beforeEach(() => { vi.clearAllMocks(); }); it('should fetch and process models from API', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: 'gpt-4o-mini' }, { id: 'gpt-4o' }, { id: 'claude-3-5-sonnet-20241022' }], }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(3); expect(models[0]).toMatchObject({ id: 'gpt-4o-mini', }); expect(models[1]).toMatchObject({ id: 'gpt-4o', }); expect(models[2]).toMatchObject({ id: 'claude-3-5-sonnet-20241022', }); }); it('should merge with known model list for all properties', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: 'gpt-4o-mini' }], }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(1); // Should have properties from LOBE_DEFAULT_MODEL_LIST expect(models[0].displayName).toBeDefined(); expect(models[0].contextWindowTokens).toBeDefined(); expect(models[0].functionCall).toBe(true); expect(models[0].vision).toBe(true); // Check that enabled is defined expect(models[0].enabled).toBeDefined(); }); it('should handle case-insensitive model matching', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: 'GPT-4O-MINI' }], }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(1); expect(models[0].id).toBe('GPT-4O-MINI'); // Should match with lowercase in LOBE_DEFAULT_MODEL_LIST expect(models[0].displayName).toBeDefined(); expect(models[0].enabled).toBeDefined(); }); it('should handle models not in known model list', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: 'unknown-custom-model' }], }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(1); expect(models[0]).toMatchObject({ id: 'unknown-custom-model', displayName: undefined, enabled: false, contextWindowTokens: undefined, functionCall: false, vision: false, reasoning: false, }); }); it('should handle empty model list', async () => { mockClient.models.list.mockResolvedValue({ data: [], }); const models = await params.models({ client: mockClient as any }); expect(models).toEqual([]); }); it('should preserve all known model abilities', async () => { mockClient.models.list.mockResolvedValue({ data: [ { id: 'gpt-4o-mini' }, { id: 'claude-3-5-sonnet-20241022' }, { id: 'deepseek-chat' }, ], }); const models = await params.models({ client: mockClient as any }); expect(models.length).toBe(3); models.forEach((model) => { expect(model).toHaveProperty('functionCall'); expect(model).toHaveProperty('vision'); expect(model).toHaveProperty('reasoning'); expect(model).toHaveProperty('contextWindowTokens'); expect(model).toHaveProperty('displayName'); expect(model).toHaveProperty('enabled'); }); }); it('should handle mix of known and unknown models', async () => { mockClient.models.list.mockResolvedValue({ data: [ { id: 'gpt-4o-mini' }, { id: 'unknown-model-1' }, { id: 'claude-3-5-sonnet-20241022' }, { id: 'unknown-model-2' }, ], }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(4); // Known models should have displayName expect(models[0].displayName).toBeDefined(); expect(models[2].displayName).toBeDefined(); // Unknown models should have undefined displayName expect(models[1].displayName).toBeUndefined(); expect(models[3].displayName).toBeUndefined(); }); it('should preserve model id exactly as returned from API', async () => { mockClient.models.list.mockResolvedValue({ data: [ { id: 'Model-With-Mixed-CASE' }, { id: 'model-with-dashes' }, { id: 'model_with_underscores' }, ], }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(3); expect(models[0].id).toBe('Model-With-Mixed-CASE'); expect(models[1].id).toBe('model-with-dashes'); expect(models[2].id).toBe('model_with_underscores'); }); it('should handle models with special characters in id', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: 'model:v1' }, { id: 'model@latest' }, { id: 'model/variant' }], }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(3); expect(models[0].id).toBe('model:v1'); expect(models[1].id).toBe('model@latest'); expect(models[2].id).toBe('model/variant'); }); it('should return models with correct structure', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: 'test-model' }], }); const models = await params.models({ client: mockClient as any }); expect(models[0]).toHaveProperty('id'); expect(models[0]).toHaveProperty('contextWindowTokens'); expect(models[0]).toHaveProperty('displayName'); expect(models[0]).toHaveProperty('enabled'); expect(models[0]).toHaveProperty('functionCall'); expect(models[0]).toHaveProperty('vision'); expect(models[0]).toHaveProperty('reasoning'); }); it('should filter out falsy values', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: 'valid-model' }, null, undefined, { id: 'another-valid-model' }], }); const models = await params.models({ client: mockClient as any }); // Should only include valid models expect(models.length).toBeGreaterThan(0); models.forEach((model) => { expect(model).toBeTruthy(); expect(model.id).toBeTruthy(); }); }); it('should handle vision models from known list', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: 'gpt-4o' }], }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(1); expect(models[0].vision).toBe(true); }); it('should handle reasoning models from known list', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: 'deepseek-reasoner' }], }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(1); // Check if the model has reasoning capability based on known list expect(models[0]).toHaveProperty('reasoning'); }); it('should filter out models without id', async () => { mockClient.models.list.mockResolvedValue({ data: [ { id: 'valid-model' }, { id: '' }, // Empty id { id: null }, // Null id { id: undefined }, // Undefined id { name: 'no-id-model' }, // Missing id { id: 'another-valid-model' }, ], }); const models = await params.models({ client: mockClient as any }); // Should only include models with valid ids expect(models.length).toBe(2); expect(models[0].id).toBe('valid-model'); expect(models[1].id).toBe('another-valid-model'); }); it('should handle API errors gracefully', async () => { mockClient.models.list.mockRejectedValue(new Error('Network error')); // Should throw the error (no error handling in the implementation) await expect(params.models({ client: mockClient as any })).rejects.toThrow('Network error'); }); it('should handle malformed API response', async () => { mockClient.models.list.mockResolvedValue({ data: null, }); // This will throw an error when trying to access .filter on null await expect(params.models({ client: mockClient as any })).rejects.toThrow(); }); it('should handle API response without data field', async () => { mockClient.models.list.mockResolvedValue({}); // This will throw an error when trying to access .data await expect(params.models({ client: mockClient as any })).rejects.toThrow(); }); it('should preserve model order from API', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: 'model-z' }, { id: 'model-a' }, { id: 'model-m' }], }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(3); expect(models[0].id).toBe('model-z'); expect(models[1].id).toBe('model-a'); expect(models[2].id).toBe('model-m'); }); it('should handle large number of models', async () => { const largeModelList = Array.from({ length: 100 }, (_, i) => ({ id: `model-${i}` })); mockClient.models.list.mockResolvedValue({ data: largeModelList, }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(100); expect(models[0].id).toBe('model-0'); expect(models[99].id).toBe('model-99'); }); it('should handle models with only id field', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: 'minimal-model' }], }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(1); expect(models[0]).toMatchObject({ id: 'minimal-model', displayName: undefined, enabled: false, contextWindowTokens: undefined, functionCall: false, vision: false, reasoning: false, }); }); it('should handle function call models correctly', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: 'gpt-4o-mini' }], }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(1); expect(models[0].functionCall).toBe(true); }); it('should handle models with whitespace in id', async () => { mockClient.models.list.mockResolvedValue({ data: [ { id: ' model-with-spaces ' }, { id: '\tmodel-with-tab\t' }, { id: '\nmodel-with-newline\n' }, ], }); const models = await params.models({ client: mockClient as any }); // Should preserve the whitespace in the id expect(models).toHaveLength(3); expect(models[0].id).toBe(' model-with-spaces '); expect(models[1].id).toBe('\tmodel-with-tab\t'); expect(models[2].id).toBe('\nmodel-with-newline\n'); }); it('should handle models with numeric ids', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: '12345' }, { id: '67890' }], }); const models = await params.models({ client: mockClient as any }); expect(models).toHaveLength(2); expect(models[0].id).toBe('12345'); expect(models[1].id).toBe('67890'); }); it('should handle duplicate model ids', async () => { mockClient.models.list.mockResolvedValue({ data: [{ id: 'duplicate-model' }, { id: 'duplicate-model' }, { id: 'unique-model' }], }); const models = await params.models({ client: mockClient as any }); // Should include duplicates (no deduplication in implementation) expect(models).toHaveLength(3); expect(models[0].id).toBe('duplicate-model'); expect(models[1].id).toBe('duplicate-model'); expect(models[2].id).toBe('unique-model'); }); }); describe('Runtime instantiation', () => { it('should create runtime instance with apiKey', () => { const runtime = new LobeSearch1API({ apiKey: 'test-key' }); expect(runtime).toBeDefined(); expect(runtime).toBeInstanceOf(LobeSearch1API); }); it('should create runtime instance with baseURL', () => { const runtime = new LobeSearch1API({ apiKey: 'test-key', baseURL: 'https://custom.api.com/v1', }); expect(runtime).toBeDefined(); }); it('should create runtime instance with all options', () => { const runtime = new LobeSearch1API({ apiKey: 'test-key', baseURL: 'https://custom.api.com/v1', dangerouslyAllowBrowser: true, }); expect(runtime).toBeDefined(); }); }); describe('Provider configuration', () => { it('should have correct provider ID', () => { expect(params.provider).toBe('search1api'); }); it('should have correct baseURL', () => { expect(params.baseURL).toBe('https://api.search1api.com/v1'); }); it('should export params object', () => { expect(params).toBeDefined(); expect(params).toHaveProperty('baseURL'); expect(params).toHaveProperty('chatCompletion'); expect(params).toHaveProperty('debug'); expect(params).toHaveProperty('models'); expect(params).toHaveProperty('provider'); }); it('should have chatCompletion.handlePayload function', () => { expect(params.chatCompletion.handlePayload).toBeDefined(); expect(typeof params.chatCompletion.handlePayload).toBe('function'); }); it('should have debug.chatCompletion function', () => { expect(params.debug.chatCompletion).toBeDefined(); expect(typeof params.debug.chatCompletion).toBe('function'); }); it('should have models function', () => { expect(params.models).toBeDefined(); expect(typeof params.models).toBe('function'); }); }); });