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.

360 lines (298 loc) 11.4 kB
import { act, renderHook, waitFor } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import { modelsService } from '@/services/models'; import { userService } from '@/services/user'; import { useUserStore } from '@/store/user'; import { ProviderConfig } from '@/types/user/settings'; import { settingsSelectors } from '../settings/selectors'; import { CustomModelCardDispatch } from './reducers/customModelCard'; import { modelProviderSelectors } from './selectors'; // Mock userService vi.mock('@/services/user', () => ({ userService: { updateUserSettings: vi.fn(), resetUserSettings: vi.fn(), }, })); vi.mock('zustand/traditional'); describe('LLMSettingsSliceAction', () => { describe('setModelProviderConfig', () => { it('should set OpenAI configuration', async () => { const { result } = renderHook(() => useUserStore()); const openAIConfig: Partial<ProviderConfig> = { fetchOnClient: true }; // Perform the action await act(async () => { await result.current.setModelProviderConfig('openai', openAIConfig); }); // Assert that updateUserSettings was called with the correct OpenAI configuration expect(userService.updateUserSettings).toHaveBeenCalledWith( { languageModel: { openai: openAIConfig } }, expect.any(AbortSignal), ); }); }); describe('dispatchCustomModelCards', () => { it('should return early when prevState does not exist', async () => { const { result } = renderHook(() => useUserStore()); const provider = 'openai'; const payload: CustomModelCardDispatch = { type: 'add', modelCard: { id: 'test-id' } }; // Mock the selector to return undefined vi.spyOn(settingsSelectors, 'providerConfig').mockReturnValueOnce(() => undefined); vi.spyOn(result.current, 'setModelProviderConfig'); await act(async () => { await result.current.dispatchCustomModelCards(provider, payload); }); // Assert that setModelProviderConfig was not called expect(result.current.setModelProviderConfig).not.toHaveBeenCalled(); }); }); describe('refreshDefaultModelProviderList', () => { it('default', async () => { const { result } = renderHook(() => useUserStore()); act(() => { useUserStore.setState({ serverLanguageModel: { azure: { serverModelCards: [{ id: 'abc', deploymentName: 'abc' }] }, }, }); }); act(() => { result.current.refreshDefaultModelProviderList(); }); // Assert that setModelProviderConfig was not called const azure = result.current.defaultModelProviderList.find((m) => m.id === 'azure'); expect(azure?.chatModels).toEqual([{ id: 'abc', deploymentName: 'abc' }]); }); it('openai', () => { const { result } = renderHook(() => useUserStore()); act(() => { useUserStore.setState({ serverLanguageModel: { openai: { enabled: true, enabledModels: ['gpt-4-0125-preview', 'gpt-4-turbo-2024-04-09'], serverModelCards: [ { displayName: 'ChatGPT-4', functionCall: true, id: 'gpt-4-0125-preview', contextWindowTokens: 128000, enabled: true, }, { displayName: 'ChatGPT-4 Vision', functionCall: true, id: 'gpt-4-turbo-2024-04-09', contextWindowTokens: 128000, vision: true, enabled: true, }, ], }, }, }); }); act(() => { result.current.refreshDefaultModelProviderList(); }); // Assert that setModelProviderConfig was not called const openai = result.current.defaultModelProviderList.find((m) => m.id === 'openai'); expect(openai?.chatModels).toEqual([ { displayName: 'ChatGPT-4', enabled: true, functionCall: true, id: 'gpt-4-0125-preview', contextWindowTokens: 128000, }, { displayName: 'ChatGPT-4 Vision', enabled: true, functionCall: true, id: 'gpt-4-turbo-2024-04-09', contextWindowTokens: 128000, vision: true, }, ]); }); }); describe('refreshModelProviderList', () => { it('visible', async () => { const { result } = renderHook(() => useUserStore()); act(() => { useUserStore.setState({ settings: { languageModel: { ollama: { enabledModels: ['llava'] }, }, }, }); }); act(() => { result.current.refreshModelProviderList(); }); const ollamaList = result.current.modelProviderList.find((r) => r.id === 'ollama'); // Assert that setModelProviderConfig was not called const model = ollamaList?.chatModels.find((c) => c.id === 'llava'); expect(model).toMatchSnapshot(); }); it('modelProviderListForModelSelect should return only enabled providers', () => { const { result } = renderHook(() => useUserStore()); act(() => { useUserStore.setState({ settings: { languageModel: { perplexity: { enabled: true }, azure: { enabled: false }, }, }, }); }); act(() => { result.current.refreshModelProviderList(); }); const enabledProviders = modelProviderSelectors.modelProviderListForModelSelect( result.current, ); expect(enabledProviders).toHaveLength(3); expect(enabledProviders.at(-1)!.id).toBe('perplexity'); }); }); describe('removeEnabledModels', () => { it('should remove the specified model from enabledModels', async () => { const { result } = renderHook(() => useUserStore()); const model = 'gpt-3.5-turbo'; const spyOn = vi.spyOn(userService, 'updateUserSettings'); act(() => { useUserStore.setState({ settings: { languageModel: { azure: { enabledModels: ['gpt-3.5-turbo', 'gpt-4'] }, }, }, }); }); await act(async () => { console.log(JSON.stringify(result.current.settings)); await result.current.removeEnabledModels('azure', model); }); expect(spyOn).toHaveBeenCalledWith( { languageModel: { azure: { enabledModels: ['gpt-4'] } } }, expect.any(AbortSignal), ); }); }); describe('toggleEditingCustomModelCard', () => { it('should update editingCustomCardModel when params are provided', () => { const { result } = renderHook(() => useUserStore()); act(() => { result.current.toggleEditingCustomModelCard({ id: 'test-id', provider: 'openai' }); }); expect(result.current.editingCustomCardModel).toEqual({ id: 'test-id', provider: 'openai' }); }); it('should reset editingCustomCardModel when no params are provided', () => { const { result } = renderHook(() => useUserStore()); act(() => { result.current.toggleEditingCustomModelCard(); }); expect(result.current.editingCustomCardModel).toBeUndefined(); }); }); describe('toggleProviderEnabled', () => { it('should enable the provider', async () => { const { result } = renderHook(() => useUserStore()); await act(async () => { await result.current.toggleProviderEnabled('minimax', true); }); expect(userService.updateUserSettings).toHaveBeenCalledWith( { languageModel: { minimax: { enabled: true } } }, expect.any(AbortSignal), ); }); it('should disable the provider', async () => { const { result } = renderHook(() => useUserStore()); const provider = 'openai'; await act(async () => { await result.current.toggleProviderEnabled(provider, false); }); expect(userService.updateUserSettings).toHaveBeenCalledWith( { languageModel: { openai: { enabled: false } } }, expect.any(AbortSignal), ); }); }); describe('updateEnabledModels', () => { // TODO: 有待 updateEnabledModels 实现的同步改造 it('should add new custom model to customModelCards', async () => { const { result } = renderHook(() => useUserStore()); const provider = 'openai'; const modelKeys = ['gpt-3.5-turbo', 'custom-model']; const options = [{ value: 'gpt-3.5-turbo' }, {}]; await act(async () => { await result.current.updateEnabledModels(provider, modelKeys, options); }); expect(userService.updateUserSettings).toHaveBeenCalledWith( { languageModel: { openai: { customModelCards: [{ id: 'custom-model' }], // TODO:目标单测中需要包含下面这一行 // enabledModels: ['gpt-3.5-turbo', 'custom-model'], }, }, }, expect.any(AbortSignal), ); }); it('should not add removed model to customModelCards', async () => { const { result } = renderHook(() => useUserStore()); const provider = 'openai'; const modelKeys = ['gpt-3.5-turbo']; const options = [{ value: 'gpt-3.5-turbo' }]; act(() => { useUserStore.setState({ settings: { languageModel: { openai: { enabledModels: ['gpt-3.5-turbo', 'gpt-4'] }, }, }, }); }); await act(async () => { await result.current.updateEnabledModels(provider, modelKeys, options); }); expect(userService.updateUserSettings).toHaveBeenCalledWith( { languageModel: { openai: { enabledModels: ['gpt-3.5-turbo'] } }, }, expect.any(AbortSignal), ); }); }); describe('useFetchProviderModelList', () => { it('should fetch data when enabledAutoFetch is true', async () => { const { result } = renderHook(() => useUserStore()); const provider = 'openai'; const enabledAutoFetch = true; const spyOn = vi.spyOn(result.current, 'refreshDefaultModelProviderList'); vi.spyOn(modelsService, 'getModels').mockResolvedValueOnce([]); renderHook(() => result.current.useFetchProviderModelList(provider, enabledAutoFetch)); await waitFor(() => { expect(spyOn).toHaveBeenCalled(); }); // expect(result.current.settings.languageModel.openai?.latestFetchTime).toBeDefined(); // expect(result.current.settings.languageModel.openai?.remoteModelCards).toBeDefined(); }); it('should not fetch data when enabledAutoFetch is false', async () => { const { result } = renderHook(() => useUserStore()); const provider = 'openai'; const enabledAutoFetch = false; const spyOn = vi.spyOn(result.current, 'refreshDefaultModelProviderList'); vi.spyOn(modelsService, 'getModels').mockResolvedValueOnce([]); renderHook(() => result.current.useFetchProviderModelList(provider, enabledAutoFetch)); await waitFor(() => { expect(spyOn).not.toHaveBeenCalled(); }); }); }); });