import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { renderHook, waitFor, act } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import React from 'react'; // Mock the sandbox API vi.mock('../../api/sandbox', () => ({ getSandboxStatus: vi.fn(), toggleSandboxMode: vi.fn(), resetSandboxData: vi.fn(), })); import { useSandboxStatus, useToggleSandbox, useResetSandbox, } from '../useSandbox'; import * as sandboxApi from '../../api/sandbox'; // Create wrapper const createWrapper = () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); return function Wrapper({ children }: { children: React.ReactNode }) { return React.createElement(QueryClientProvider, { client: queryClient }, children); }; }; describe('useSandbox hooks', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('useSandboxStatus', () => { it('fetches sandbox status', async () => { const mockStatus = { sandbox_mode: true, sandbox_enabled: true, sandbox_schema: 'business_1_sandbox', }; vi.mocked(sandboxApi.getSandboxStatus).mockResolvedValue(mockStatus); const { result } = renderHook(() => useSandboxStatus(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); expect(sandboxApi.getSandboxStatus).toHaveBeenCalled(); expect(result.current.data).toEqual(mockStatus); }); it('returns sandbox_mode as false when in live mode', async () => { const mockStatus = { sandbox_mode: false, sandbox_enabled: true, sandbox_schema: 'business_1_sandbox', }; vi.mocked(sandboxApi.getSandboxStatus).mockResolvedValue(mockStatus); const { result } = renderHook(() => useSandboxStatus(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); expect(result.current.data?.sandbox_mode).toBe(false); }); it('handles sandbox not being enabled for business', async () => { const mockStatus = { sandbox_mode: false, sandbox_enabled: false, sandbox_schema: null, }; vi.mocked(sandboxApi.getSandboxStatus).mockResolvedValue(mockStatus); const { result } = renderHook(() => useSandboxStatus(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); expect(result.current.data?.sandbox_enabled).toBe(false); expect(result.current.data?.sandbox_schema).toBeNull(); }); it('handles API errors', async () => { vi.mocked(sandboxApi.getSandboxStatus).mockRejectedValue( new Error('Failed to fetch sandbox status') ); const { result } = renderHook(() => useSandboxStatus(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isError).toBe(true); }); expect(result.current.error).toBeInstanceOf(Error); expect(result.current.error?.message).toBe('Failed to fetch sandbox status'); }); it('configures staleTime to 30 seconds', async () => { const mockStatus = { sandbox_mode: false, sandbox_enabled: true, sandbox_schema: 'business_1_sandbox', }; vi.mocked(sandboxApi.getSandboxStatus).mockResolvedValue(mockStatus); const { result } = renderHook(() => useSandboxStatus(), { wrapper: createWrapper(), }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); // Data should be considered fresh for 30 seconds // This is configured in the hook with staleTime: 30 * 1000 expect(result.current.isStale).toBe(false); }); }); describe('useToggleSandbox', () => { let originalLocation: Location; let reloadMock: ReturnType; beforeEach(() => { // Mock window.location.reload originalLocation = window.location; reloadMock = vi.fn(); Object.defineProperty(window, 'location', { value: { ...originalLocation, reload: reloadMock }, writable: true, }); }); afterEach(() => { // Restore window.location Object.defineProperty(window, 'location', { value: originalLocation, writable: true, }); }); it('toggles sandbox mode to enabled', async () => { const mockResponse = { sandbox_mode: true, message: 'Sandbox mode enabled', }; vi.mocked(sandboxApi.toggleSandboxMode).mockResolvedValue(mockResponse); const { result } = renderHook(() => useToggleSandbox(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync(true); }); expect(sandboxApi.toggleSandboxMode).toHaveBeenCalled(); expect(vi.mocked(sandboxApi.toggleSandboxMode).mock.calls[0][0]).toBe(true); }); it('toggles sandbox mode to disabled', async () => { const mockResponse = { sandbox_mode: false, message: 'Sandbox mode disabled', }; vi.mocked(sandboxApi.toggleSandboxMode).mockResolvedValue(mockResponse); const { result } = renderHook(() => useToggleSandbox(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync(false); }); expect(sandboxApi.toggleSandboxMode).toHaveBeenCalled(); expect(vi.mocked(sandboxApi.toggleSandboxMode).mock.calls[0][0]).toBe(false); }); it('updates sandbox status in cache on success', async () => { const mockResponse = { sandbox_mode: true, message: 'Sandbox mode enabled', }; vi.mocked(sandboxApi.toggleSandboxMode).mockResolvedValue(mockResponse); const wrapper = createWrapper(); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); // Pre-populate cache with initial status queryClient.setQueryData(['sandboxStatus'], { sandbox_mode: false, sandbox_enabled: true, sandbox_schema: 'business_1_sandbox', }); const CustomWrapper = ({ children }: { children: React.ReactNode }) => { return React.createElement(QueryClientProvider, { client: queryClient }, children); }; const { result } = renderHook(() => useToggleSandbox(), { wrapper: CustomWrapper, }); await act(async () => { await result.current.mutateAsync(true); }); // Verify cache was updated const cachedData = queryClient.getQueryData(['sandboxStatus']); expect(cachedData).toEqual({ sandbox_mode: true, sandbox_enabled: true, sandbox_schema: 'business_1_sandbox', }); }); it('reloads window after successful toggle', async () => { const mockResponse = { sandbox_mode: true, message: 'Sandbox mode enabled', }; vi.mocked(sandboxApi.toggleSandboxMode).mockResolvedValue(mockResponse); const { result } = renderHook(() => useToggleSandbox(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync(true); }); expect(reloadMock).toHaveBeenCalled(); }); it('handles toggle errors without reloading', async () => { vi.mocked(sandboxApi.toggleSandboxMode).mockRejectedValue( new Error('Failed to toggle sandbox mode') ); const { result } = renderHook(() => useToggleSandbox(), { wrapper: createWrapper(), }); await act(async () => { try { await result.current.mutateAsync(true); } catch (error) { // Expected to throw } }); expect(reloadMock).not.toHaveBeenCalled(); }); it('updates cache even when old data is undefined', async () => { const mockResponse = { sandbox_mode: true, message: 'Sandbox mode enabled', }; vi.mocked(sandboxApi.toggleSandboxMode).mockResolvedValue(mockResponse); const wrapper = createWrapper(); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); const CustomWrapper = ({ children }: { children: React.ReactNode }) => { return React.createElement(QueryClientProvider, { client: queryClient }, children); }; const { result } = renderHook(() => useToggleSandbox(), { wrapper: CustomWrapper, }); await act(async () => { await result.current.mutateAsync(true); }); // Verify cache was set even with no prior data const cachedData = queryClient.getQueryData(['sandboxStatus']); expect(cachedData).toMatchObject({ sandbox_mode: true, }); }); }); describe('useResetSandbox', () => { it('resets sandbox data', async () => { const mockResponse = { message: 'Sandbox data reset successfully', sandbox_schema: 'business_1_sandbox', }; vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse); const { result } = renderHook(() => useResetSandbox(), { wrapper: createWrapper(), }); await act(async () => { await result.current.mutateAsync(); }); expect(sandboxApi.resetSandboxData).toHaveBeenCalled(); }); it('invalidates resource queries on success', async () => { const mockResponse = { message: 'Sandbox data reset successfully', sandbox_schema: 'business_1_sandbox', }; vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); const CustomWrapper = ({ children }: { children: React.ReactNode }) => { return React.createElement(QueryClientProvider, { client: queryClient }, children); }; const { result } = renderHook(() => useResetSandbox(), { wrapper: CustomWrapper, }); await act(async () => { await result.current.mutateAsync(); }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['resources'] }); }); it('invalidates event queries on success', async () => { const mockResponse = { message: 'Sandbox data reset successfully', sandbox_schema: 'business_1_sandbox', }; vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); const CustomWrapper = ({ children }: { children: React.ReactNode }) => { return React.createElement(QueryClientProvider, { client: queryClient }, children); }; const { result } = renderHook(() => useResetSandbox(), { wrapper: CustomWrapper, }); await act(async () => { await result.current.mutateAsync(); }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['events'] }); }); it('invalidates service queries on success', async () => { const mockResponse = { message: 'Sandbox data reset successfully', sandbox_schema: 'business_1_sandbox', }; vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); const CustomWrapper = ({ children }: { children: React.ReactNode }) => { return React.createElement(QueryClientProvider, { client: queryClient }, children); }; const { result } = renderHook(() => useResetSandbox(), { wrapper: CustomWrapper, }); await act(async () => { await result.current.mutateAsync(); }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['services'] }); }); it('invalidates customer queries on success', async () => { const mockResponse = { message: 'Sandbox data reset successfully', sandbox_schema: 'business_1_sandbox', }; vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); const CustomWrapper = ({ children }: { children: React.ReactNode }) => { return React.createElement(QueryClientProvider, { client: queryClient }, children); }; const { result } = renderHook(() => useResetSandbox(), { wrapper: CustomWrapper, }); await act(async () => { await result.current.mutateAsync(); }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['customers'] }); }); it('invalidates payment queries on success', async () => { const mockResponse = { message: 'Sandbox data reset successfully', sandbox_schema: 'business_1_sandbox', }; vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); const CustomWrapper = ({ children }: { children: React.ReactNode }) => { return React.createElement(QueryClientProvider, { client: queryClient }, children); }; const { result } = renderHook(() => useResetSandbox(), { wrapper: CustomWrapper, }); await act(async () => { await result.current.mutateAsync(); }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['payments'] }); }); it('invalidates all required queries on success', async () => { const mockResponse = { message: 'Sandbox data reset successfully', sandbox_schema: 'business_1_sandbox', }; vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); const CustomWrapper = ({ children }: { children: React.ReactNode }) => { return React.createElement(QueryClientProvider, { client: queryClient }, children); }; const { result } = renderHook(() => useResetSandbox(), { wrapper: CustomWrapper, }); await act(async () => { await result.current.mutateAsync(); }); // Verify all expected queries were invalidated expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['resources'] }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['events'] }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['services'] }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['customers'] }); expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['payments'] }); expect(invalidateSpy).toHaveBeenCalledTimes(5); }); it('handles reset errors', async () => { vi.mocked(sandboxApi.resetSandboxData).mockRejectedValue( new Error('Failed to reset sandbox data') ); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); const CustomWrapper = ({ children }: { children: React.ReactNode }) => { return React.createElement(QueryClientProvider, { client: queryClient }, children); }; const { result } = renderHook(() => useResetSandbox(), { wrapper: CustomWrapper, }); await act(async () => { try { await result.current.mutateAsync(); } catch (error) { // Expected to throw } }); // Verify queries were NOT invalidated on error expect(invalidateSpy).not.toHaveBeenCalled(); }); it('returns response data on success', async () => { const mockResponse = { message: 'Sandbox data reset successfully', sandbox_schema: 'business_1_sandbox', }; vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse); const { result } = renderHook(() => useResetSandbox(), { wrapper: createWrapper(), }); let response; await act(async () => { response = await result.current.mutateAsync(); }); expect(response).toEqual(mockResponse); }); }); });