| | import { renderHook, act } from '@testing-library/react'; |
| | import { useQueryClient } from '@tanstack/react-query'; |
| | import { useHealthCheck } from '../connection'; |
| | import { QueryKeys, Time, dataService } from 'librechat-data-provider'; |
| |
|
| | |
| | jest.mock('@tanstack/react-query'); |
| | jest.mock('librechat-data-provider', () => ({ |
| | QueryKeys: { health: 'health' }, |
| | Time: { TEN_MINUTES: 600000, FIVE_MINUTES: 300000 }, |
| | dataService: { healthCheck: jest.fn() }, |
| | })); |
| |
|
| | jest.mock('~/utils', () => ({ |
| | logger: { log: jest.fn() }, |
| | })); |
| |
|
| | |
| | jest.useFakeTimers(); |
| |
|
| | const mockQueryClient = { |
| | fetchQuery: jest.fn(), |
| | getQueryState: jest.fn(), |
| | getQueryData: jest.fn(), |
| | invalidateQueries: jest.fn(), |
| | } as any; |
| |
|
| | const mockUseQueryClient = useQueryClient as jest.MockedFunction<typeof useQueryClient>; |
| |
|
| | describe('useHealthCheck', () => { |
| | let addEventListenerSpy: jest.SpyInstance; |
| | let removeEventListenerSpy: jest.SpyInstance; |
| |
|
| | beforeEach(() => { |
| | jest.clearAllMocks(); |
| | jest.clearAllTimers(); |
| | mockUseQueryClient.mockReturnValue(mockQueryClient); |
| |
|
| | addEventListenerSpy = jest.spyOn(window, 'addEventListener'); |
| | removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); |
| |
|
| | mockQueryClient.fetchQuery.mockResolvedValue({}); |
| | mockQueryClient.getQueryState.mockReturnValue(null); |
| | }); |
| |
|
| | afterEach(() => { |
| | addEventListenerSpy.mockRestore(); |
| | removeEventListenerSpy.mockRestore(); |
| | }); |
| |
|
| | describe('when not authenticated', () => { |
| | it('should not start health check', () => { |
| | renderHook(() => useHealthCheck(false)); |
| |
|
| | |
| | act(() => { |
| | jest.advanceTimersByTime(1000); |
| | }); |
| |
|
| | expect(mockQueryClient.fetchQuery).not.toHaveBeenCalled(); |
| | expect(addEventListenerSpy).not.toHaveBeenCalled(); |
| | }); |
| | }); |
| |
|
| | describe('when authenticated', () => { |
| | it('should start health check after delay', async () => { |
| | renderHook(() => useHealthCheck(true)); |
| |
|
| | |
| | expect(mockQueryClient.fetchQuery).not.toHaveBeenCalled(); |
| |
|
| | |
| | await act(async () => { |
| | jest.advanceTimersByTime(500); |
| | }); |
| |
|
| | expect(mockQueryClient.fetchQuery).toHaveBeenCalledWith( |
| | [QueryKeys.health], |
| | expect.any(Function), |
| | { |
| | retry: false, |
| | cacheTime: 0, |
| | staleTime: 0, |
| | }, |
| | ); |
| | }); |
| |
|
| | it('should set up 10-minute interval', async () => { |
| | renderHook(() => useHealthCheck(true)); |
| |
|
| | await act(async () => { |
| | jest.advanceTimersByTime(500); |
| | }); |
| |
|
| | |
| | mockQueryClient.fetchQuery.mockClear(); |
| |
|
| | |
| | await act(async () => { |
| | jest.advanceTimersByTime(Time.TEN_MINUTES); |
| | }); |
| |
|
| | expect(mockQueryClient.fetchQuery).toHaveBeenCalledTimes(1); |
| | }); |
| |
|
| | it('should run health check continuously every 10 minutes', async () => { |
| | renderHook(() => useHealthCheck(true)); |
| |
|
| | await act(async () => { |
| | jest.advanceTimersByTime(500); |
| | }); |
| |
|
| | |
| | mockQueryClient.fetchQuery.mockClear(); |
| |
|
| | |
| | for (let i = 1; i <= 5; i++) { |
| | await act(async () => { |
| | jest.advanceTimersByTime(Time.TEN_MINUTES); |
| | }); |
| |
|
| | expect(mockQueryClient.fetchQuery).toHaveBeenCalledTimes(i); |
| | } |
| |
|
| | |
| | expect(mockQueryClient.fetchQuery).toHaveBeenCalledTimes(5); |
| |
|
| | |
| | await act(async () => { |
| | jest.advanceTimersByTime(Time.TEN_MINUTES * 3); |
| | }); |
| |
|
| | |
| | expect(mockQueryClient.fetchQuery).toHaveBeenCalledTimes(8); |
| | }); |
| |
|
| | it('should add window focus event listener', async () => { |
| | renderHook(() => useHealthCheck(true)); |
| |
|
| | await act(async () => { |
| | jest.advanceTimersByTime(500); |
| | }); |
| |
|
| | expect(addEventListenerSpy).toHaveBeenCalledWith('focus', expect.any(Function)); |
| | }); |
| |
|
| | it('should handle window focus correctly when no previous check', async () => { |
| | renderHook(() => useHealthCheck(true)); |
| |
|
| | await act(async () => { |
| | jest.advanceTimersByTime(500); |
| | }); |
| |
|
| | |
| | const focusHandler = addEventListenerSpy.mock.calls[0][1]; |
| |
|
| | |
| | mockQueryClient.getQueryState.mockReturnValue(null); |
| | mockQueryClient.fetchQuery.mockClear(); |
| |
|
| | |
| | await act(async () => { |
| | focusHandler(); |
| | }); |
| |
|
| | expect(mockQueryClient.fetchQuery).toHaveBeenCalledTimes(1); |
| | }); |
| |
|
| | it('should handle window focus correctly when check is recent', async () => { |
| | renderHook(() => useHealthCheck(true)); |
| |
|
| | await act(async () => { |
| | jest.advanceTimersByTime(500); |
| | }); |
| |
|
| | |
| | const focusHandler = addEventListenerSpy.mock.calls[0][1]; |
| |
|
| | |
| | mockQueryClient.getQueryState.mockReturnValue({ |
| | dataUpdatedAt: Date.now() - 300000, |
| | }); |
| | mockQueryClient.fetchQuery.mockClear(); |
| |
|
| | |
| | await act(async () => { |
| | focusHandler(); |
| | }); |
| |
|
| | expect(mockQueryClient.fetchQuery).not.toHaveBeenCalled(); |
| | }); |
| |
|
| | it('should handle window focus correctly when check is old', async () => { |
| | renderHook(() => useHealthCheck(true)); |
| |
|
| | await act(async () => { |
| | jest.advanceTimersByTime(500); |
| | }); |
| |
|
| | |
| | const focusHandler = addEventListenerSpy.mock.calls[0][1]; |
| |
|
| | |
| | mockQueryClient.getQueryState.mockReturnValue({ |
| | dataUpdatedAt: Date.now() - 700000, |
| | }); |
| | mockQueryClient.fetchQuery.mockClear(); |
| |
|
| | |
| | await act(async () => { |
| | focusHandler(); |
| | }); |
| |
|
| | expect(mockQueryClient.fetchQuery).toHaveBeenCalledTimes(1); |
| | }); |
| |
|
| | it('should prevent multiple initializations', async () => { |
| | const { rerender } = renderHook(({ auth }) => useHealthCheck(auth), { |
| | initialProps: { auth: true }, |
| | }); |
| |
|
| | await act(async () => { |
| | jest.advanceTimersByTime(500); |
| | }); |
| |
|
| | const initialCallCount = addEventListenerSpy.mock.calls.length; |
| |
|
| | |
| | rerender({ auth: true }); |
| |
|
| | await act(async () => { |
| | jest.advanceTimersByTime(500); |
| | }); |
| |
|
| | |
| | expect(addEventListenerSpy).toHaveBeenCalledTimes(initialCallCount); |
| | }); |
| |
|
| | it('should handle API errors gracefully', async () => { |
| | const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); |
| | mockQueryClient.fetchQuery.mockRejectedValue(new Error('API Error')); |
| |
|
| | renderHook(() => useHealthCheck(true)); |
| |
|
| | await act(async () => { |
| | jest.advanceTimersByTime(500); |
| | }); |
| |
|
| | expect(consoleSpy).toHaveBeenCalledWith('Health check failed:', expect.any(Error)); |
| | consoleSpy.mockRestore(); |
| | }); |
| | }); |
| |
|
| | describe('cleanup', () => { |
| | it('should clear intervals on unmount', async () => { |
| | const { unmount } = renderHook(() => useHealthCheck(true)); |
| |
|
| | await act(async () => { |
| | jest.advanceTimersByTime(500); |
| | }); |
| |
|
| | const clearIntervalSpy = jest.spyOn(global, 'clearInterval'); |
| |
|
| | unmount(); |
| |
|
| | expect(clearIntervalSpy).toHaveBeenCalled(); |
| | clearIntervalSpy.mockRestore(); |
| | }); |
| |
|
| | it('should remove event listeners on unmount', async () => { |
| | const { unmount } = renderHook(() => useHealthCheck(true)); |
| |
|
| | await act(async () => { |
| | jest.advanceTimersByTime(500); |
| | }); |
| |
|
| | unmount(); |
| |
|
| | expect(removeEventListenerSpy).toHaveBeenCalledWith('focus', expect.any(Function)); |
| | }); |
| |
|
| | it('should clear timeout on unmount before initialization', () => { |
| | const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout'); |
| | const { unmount } = renderHook(() => useHealthCheck(true)); |
| |
|
| | |
| | unmount(); |
| |
|
| | expect(clearTimeoutSpy).toHaveBeenCalled(); |
| | clearTimeoutSpy.mockRestore(); |
| | }); |
| | }); |
| |
|
| | describe('authentication state changes', () => { |
| | it('should start health check when authentication becomes true', async () => { |
| | const { rerender } = renderHook(({ auth }) => useHealthCheck(auth), { |
| | initialProps: { auth: false }, |
| | }); |
| |
|
| | |
| | act(() => { |
| | jest.advanceTimersByTime(1000); |
| | }); |
| | expect(mockQueryClient.fetchQuery).not.toHaveBeenCalled(); |
| |
|
| | |
| | rerender({ auth: true }); |
| |
|
| | await act(async () => { |
| | jest.advanceTimersByTime(500); |
| | }); |
| |
|
| | expect(mockQueryClient.fetchQuery).toHaveBeenCalled(); |
| | }); |
| | }); |
| | }); |
| |
|