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.

331 lines (262 loc) 11.4 kB
import { act, renderHook, waitFor } from '@testing-library/react'; import { mutate } from 'swr'; import { describe, expect, it, vi } from 'vitest'; import { INBOX_SESSION_ID } from '@/const/session'; import { globalService } from '@/services/global'; import { sessionService } from '@/services/session'; import { useAgentStore } from '@/store/agent'; import { agentSelectors } from '@/store/agent/selectors'; import { useSessionStore } from '@/store/session'; vi.mock('zustand/traditional'); vi.mock('swr', async (importOriginal) => { const origin = await importOriginal(); return { ...(origin as any), mutate: vi.fn(), }; }); describe('AgentSlice', () => { describe('removePlugin', () => { it('should call togglePlugin with the provided id and false', async () => { const { result } = renderHook(() => useAgentStore()); const pluginId = 'plugin-id'; const togglePluginMock = vi .spyOn(result.current, 'togglePlugin') .mockResolvedValue(undefined); await act(async () => { await result.current.removePlugin(pluginId); }); expect(togglePluginMock).toHaveBeenCalledWith(pluginId, false); togglePluginMock.mockRestore(); }); }); describe('togglePlugin', () => { it('should add plugin id to plugins array if not present and open is true or undefined', async () => { const { result } = renderHook(() => useAgentStore()); const pluginId = 'plugin-id'; const updateAgentConfigMock = vi .spyOn(result.current, 'updateAgentConfig') .mockResolvedValue(undefined); // 模拟当前配置不包含插件 ID vi.spyOn(agentSelectors, 'currentAgentConfig').mockReturnValue({ plugins: [] } as any); await act(async () => { await result.current.togglePlugin(pluginId); }); expect(updateAgentConfigMock).toHaveBeenCalledWith( expect.objectContaining({ plugins: [pluginId] }), ); updateAgentConfigMock.mockRestore(); }); it('should remove plugin id from plugins array if present and open is false', async () => { const { result } = renderHook(() => useAgentStore()); const pluginId = 'plugin-id'; const updateAgentConfigMock = vi .spyOn(result.current, 'updateAgentConfig') .mockResolvedValue(undefined); // 模拟当前配置包含插件 ID vi.spyOn(agentSelectors, 'currentAgentConfig').mockReturnValue({ plugins: [pluginId], } as any); await act(async () => { await result.current.togglePlugin(pluginId, false); }); expect(updateAgentConfigMock).toHaveBeenCalledWith(expect.objectContaining({ plugins: [] })); updateAgentConfigMock.mockRestore(); }); it('should not modify plugins array if plugin id is not present and open is false', async () => { const { result } = renderHook(() => useAgentStore()); const pluginId = 'plugin-id'; const updateAgentConfigMock = vi .spyOn(result.current, 'updateAgentConfig') .mockResolvedValue(undefined); // 模拟当前配置不包含插件 ID vi.spyOn(agentSelectors, 'currentAgentConfig').mockReturnValue({ plugins: [] } as any); await act(async () => { await result.current.togglePlugin(pluginId, false); }); expect(updateAgentConfigMock).toHaveBeenCalledWith(expect.objectContaining({ plugins: [] })); updateAgentConfigMock.mockRestore(); }); }); describe('updateAgentConfig', () => { it('should update global config if current session is inbox session', async () => { const { result } = renderHook(() => useAgentStore()); const config = { model: 'gpt-3.5-turbo' }; const updateSessionConfigMock = vi .spyOn(sessionService, 'updateSessionConfig') .mockResolvedValue(undefined); const refreshMock = vi.spyOn(result.current, 'internal_refreshAgentConfig'); await act(async () => { await result.current.updateAgentConfig(config); }); expect(updateSessionConfigMock).toHaveBeenCalledWith( 'inbox', config, expect.any(AbortSignal), ); expect(refreshMock).toHaveBeenCalled(); updateSessionConfigMock.mockRestore(); refreshMock.mockRestore(); }); it('should update session config if current session is not inbox session', async () => { const { result } = renderHook(() => useAgentStore()); const config = { model: 'gpt-3.5-turbo' }; const updateSessionConfigMock = vi .spyOn(sessionService, 'updateSessionConfig') .mockResolvedValue(undefined); const refreshMock = vi.spyOn(result.current, 'internal_refreshAgentConfig'); // 模拟当前会话不是收件箱会话 act(() => { useAgentStore.setState({ activeId: 'session-id', }); }); await act(async () => { await result.current.updateAgentConfig(config); }); expect(updateSessionConfigMock).toHaveBeenCalledWith( 'session-id', config, expect.any(AbortSignal), ); expect(refreshMock).toHaveBeenCalled(); updateSessionConfigMock.mockRestore(); refreshMock.mockRestore(); }); it('should not update config if there is no current session', async () => { const { result } = renderHook(() => useAgentStore()); const config = { model: 'gpt-3.5-turbo' }; const updateSessionConfigMock = vi.spyOn(sessionService, 'updateSessionConfig'); // 模拟没有当前会话 act(() => { useAgentStore.setState({ activeId: null as any }); }); await act(async () => { await result.current.updateAgentConfig(config); }); expect(updateSessionConfigMock).not.toHaveBeenCalled(); updateSessionConfigMock.mockRestore(); }); }); describe('useFetchAgentConfig', () => { it('should update agentConfig and isAgentConfigInit when data changes and isAgentConfigInit is false', async () => { const { result } = renderHook(() => useAgentStore()); // act(() => { // result.current.agentMap = {}; // }); vi.spyOn(sessionService, 'getSessionConfig').mockResolvedValueOnce({ model: 'gpt-4' } as any); renderHook(() => result.current.useFetchAgentConfig(true, 'test-session-id')); await waitFor(() => { expect(result.current.agentMap['test-session-id']).toEqual({ model: 'gpt-4' }); // expect(result.current.isAgentConfigInit).toBe(true); }); }); it('should not update state when data is the same and isAgentConfigInit is true', async () => { const { result } = renderHook(() => useAgentStore()); act(() => { useAgentStore.setState({ agentMap: { 'test-session-id': { model: 'gpt-3.5-turbo' }, }, }); }); vi.spyOn(useSessionStore, 'setState'); vi.spyOn(sessionService, 'getSessionConfig').mockResolvedValueOnce({ model: 'gpt-3.5-turbo', } as any); renderHook(() => result.current.useFetchAgentConfig(true, 'test-session-id')); await waitFor(() => { expect(result.current.agentMap['test-session-id']).toEqual({ model: 'gpt-3.5-turbo' }); expect(useSessionStore.setState).not.toHaveBeenCalled(); }); }); }); describe('useFetchInboxAgentConfig', () => { it('should merge DEFAULT_AGENT_CONFIG and update defaultAgentConfig and isDefaultAgentConfigInit on success', async () => { const { result } = renderHook(() => useAgentStore()); vi.spyOn(sessionService, 'getSessionConfig').mockResolvedValue({ model: 'gemini-pro', } as any); renderHook(() => result.current.useInitInboxAgentStore(true)); await waitFor(async () => { expect(result.current.agentMap[INBOX_SESSION_ID]).toEqual({ model: 'gemini-pro' }); expect(result.current.isInboxAgentConfigInit).toBe(true); }); }); it('should not modify state if user not logged in', async () => { const { result } = renderHook(() => useAgentStore()); vi.spyOn(sessionService, 'getSessionConfig').mockResolvedValue({ model: 'gemini-pro', } as any); renderHook(() => result.current.useInitInboxAgentStore(false)); await waitFor(async () => { expect(result.current.agentMap[INBOX_SESSION_ID]).toBeUndefined(); expect(result.current.isInboxAgentConfigInit).toBe(false); }); }); it('should not modify state on failure', async () => { const { result } = renderHook(() => useAgentStore()); vi.spyOn(globalService, 'getDefaultAgentConfig').mockRejectedValueOnce(new Error()); renderHook(() => result.current.useInitInboxAgentStore(true)); await waitFor(async () => { expect(result.current.agentMap[INBOX_SESSION_ID]).toBeUndefined(); expect(result.current.isInboxAgentConfigInit).toBe(false); }); }); }); describe('internal_updateAgentConfig', () => { it('should call sessionService.updateSessionConfig', async () => { const { result } = renderHook(() => useAgentStore()); const updateSessionConfigMock = vi .spyOn(sessionService, 'updateSessionConfig') .mockResolvedValue(undefined); await act(async () => { await result.current.internal_updateAgentConfig('test-session-id', { foo: 'bar' } as any); }); expect(updateSessionConfigMock).toHaveBeenCalledWith( 'test-session-id', { foo: 'bar' }, undefined, ); }); it('should trigger internal_refreshAgentConfig', async () => { const { result } = renderHook(() => useAgentStore()); const refreshMock = vi.spyOn(result.current, 'internal_refreshAgentConfig'); await act(async () => { await result.current.internal_updateAgentConfig('test-session-id', {}); }); expect(refreshMock).toHaveBeenCalledWith('test-session-id'); }); it('should trigger useSessionStore.refreshSessions when model changes', async () => { const { result } = renderHook(() => useAgentStore()); vi.spyOn(agentSelectors, 'currentAgentModel').mockReturnValueOnce('gpt-3.5-turbo'); const refreshSessionsMock = vi.spyOn(useSessionStore.getState(), 'refreshSessions'); await act(async () => { await result.current.internal_updateAgentConfig('test-session-id', { model: 'gpt-4' }); }); expect(refreshSessionsMock).toHaveBeenCalled(); }); }); describe('internal_refreshAgentConfig', () => { it('should call mutate with correct key', async () => { const { result } = renderHook(() => useAgentStore()); await act(async () => { await result.current.internal_refreshAgentConfig('test-session-id'); }); expect(mutate).toHaveBeenCalledWith(['FETCH_AGENT_CONFIG', 'test-session-id']); }); }); describe('edge cases', () => { it('should not update config if activeId is null', async () => { const { result } = renderHook(() => useAgentStore()); act(() => { useAgentStore.setState({ activeId: null } as any); }); const updateMock = vi.spyOn(result.current, 'internal_updateAgentConfig'); await act(async () => { await result.current.updateAgentConfig({}); }); expect(updateMock).not.toHaveBeenCalled(); }); }); });