diff --git a/frontend/src/api/dynamicVariables/__tests__/getFieldKeys.test.ts b/frontend/src/api/dynamicVariables/__tests__/getFieldKeys.test.ts new file mode 100644 index 000000000000..da2a3cef0670 --- /dev/null +++ b/frontend/src/api/dynamicVariables/__tests__/getFieldKeys.test.ts @@ -0,0 +1,114 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +import { ApiBaseInstance } from 'api'; + +import { getFieldKeys } from '../getFieldKeys'; + +// Mock the API instance +jest.mock('api', () => ({ + ApiBaseInstance: { + get: jest.fn(), + }, +})); + +describe('getFieldKeys API', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const mockSuccessResponse = { + data: { + status: 'success', + data: { + keys: { + 'service.name': [], + 'http.status_code': [], + }, + complete: true, + }, + }, + }; + + it('should call API with correct parameters when no args provided', async () => { + // Mock successful API response + (ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce(mockSuccessResponse); + + // Call function with no parameters + await getFieldKeys(); + + // Verify API was called correctly with empty params object + expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/keys', { + params: {}, + }); + }); + + it('should call API with signal parameter when provided', async () => { + // Mock successful API response + (ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce(mockSuccessResponse); + + // Call function with signal parameter + await getFieldKeys('traces'); + + // Verify API was called with signal parameter + expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/keys', { + params: { signal: 'traces' }, + }); + }); + + it('should call API with name parameter when provided', async () => { + // Mock successful API response + (ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({ + data: { + status: 'success', + data: { + keys: { service: [] }, + complete: false, + }, + }, + }); + + // Call function with name parameter + await getFieldKeys(undefined, 'service'); + + // Verify API was called with name parameter + expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/keys', { + params: { name: 'service' }, + }); + }); + + it('should call API with both signal and name when provided', async () => { + // Mock successful API response + (ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({ + data: { + status: 'success', + data: { + keys: { service: [] }, + complete: false, + }, + }, + }); + + // Call function with both parameters + await getFieldKeys('logs', 'service'); + + // Verify API was called with both parameters + expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/keys', { + params: { signal: 'logs', name: 'service' }, + }); + }); + + it('should return properly formatted response', async () => { + // Mock API to return our response + (ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce(mockSuccessResponse); + + // Call the function + const result = await getFieldKeys('traces'); + + // Verify the returned structure matches our expected format + expect(result).toEqual({ + statusCode: 200, + error: null, + message: 'success', + payload: mockSuccessResponse.data.data, + }); + }); +}); diff --git a/frontend/src/api/dynamicVariables/__tests__/getFieldValues.test.ts b/frontend/src/api/dynamicVariables/__tests__/getFieldValues.test.ts new file mode 100644 index 000000000000..7c65cc4c5ceb --- /dev/null +++ b/frontend/src/api/dynamicVariables/__tests__/getFieldValues.test.ts @@ -0,0 +1,209 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +import { ApiBaseInstance } from 'api'; + +import { getFieldValues } from '../getFieldValues'; + +// Mock the API instance +jest.mock('api', () => ({ + ApiBaseInstance: { + get: jest.fn(), + }, +})); + +describe('getFieldValues API', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call the API with correct parameters (no options)', async () => { + // Mock API response + (ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({ + data: { + status: 'success', + data: { + values: { + stringValues: ['frontend', 'backend'], + }, + complete: true, + }, + }, + }); + + // Call function without parameters + await getFieldValues(); + + // Verify API was called correctly with empty params + expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/values', { + params: {}, + }); + }); + + it('should call the API with signal parameter', async () => { + // Mock API response + (ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({ + data: { + status: 'success', + data: { + values: { + stringValues: ['frontend', 'backend'], + }, + complete: true, + }, + }, + }); + + // Call function with signal parameter + await getFieldValues('traces'); + + // Verify API was called with signal parameter + expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/values', { + params: { signal: 'traces' }, + }); + }); + + it('should call the API with name parameter', async () => { + // Mock API response + (ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({ + data: { + status: 'success', + data: { + values: { + stringValues: ['frontend', 'backend'], + }, + complete: true, + }, + }, + }); + + // Call function with name parameter + await getFieldValues(undefined, 'service.name'); + + // Verify API was called with name parameter + expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/values', { + params: { name: 'service.name' }, + }); + }); + + it('should call the API with value parameter', async () => { + // Mock API response + (ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({ + data: { + status: 'success', + data: { + values: { + stringValues: ['frontend'], + }, + complete: false, + }, + }, + }); + + // Call function with value parameter + await getFieldValues(undefined, 'service.name', 'front'); + + // Verify API was called with value parameter + expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/values', { + params: { name: 'service.name', value: 'front' }, + }); + }); + + it('should call the API with time range parameters', async () => { + // Mock API response + (ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({ + data: { + status: 'success', + data: { + values: { + stringValues: ['frontend', 'backend'], + }, + complete: true, + }, + }, + }); + + // Call function with time range parameters + const startUnixMilli = 1625097600000000; // Note: nanoseconds + const endUnixMilli = 1625184000000000; + await getFieldValues( + 'logs', + 'service.name', + undefined, + startUnixMilli, + endUnixMilli, + ); + + // Verify API was called with time range parameters (converted to milliseconds) + expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/values', { + params: { + signal: 'logs', + name: 'service.name', + startUnixMilli: '1625097600', // Should be converted to seconds (divided by 1000000) + endUnixMilli: '1625184000', // Should be converted to seconds (divided by 1000000) + }, + }); + }); + + it('should normalize the response values', async () => { + // Mock API response with multiple value types + const mockResponse = { + data: { + status: 'success', + data: { + values: { + stringValues: ['frontend', 'backend'], + numberValues: [200, 404], + boolValues: [true, false], + }, + complete: true, + }, + }, + }; + + (ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce(mockResponse); + + // Call the function + const result = await getFieldValues('traces', 'mixed.values'); + + // Verify the response has normalized values array + expect(result.payload?.normalizedValues).toContain('frontend'); + expect(result.payload?.normalizedValues).toContain('backend'); + expect(result.payload?.normalizedValues).toContain('200'); + expect(result.payload?.normalizedValues).toContain('404'); + expect(result.payload?.normalizedValues).toContain('true'); + expect(result.payload?.normalizedValues).toContain('false'); + expect(result.payload?.normalizedValues?.length).toBe(6); + }); + + it('should return a properly formatted success response', async () => { + // Create mock response + const mockApiResponse = { + data: { + status: 'success', + data: { + values: { + stringValues: ['frontend', 'backend'], + }, + complete: true, + }, + }, + }; + + // Mock API to return our response + (ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce(mockApiResponse); + + // Call the function + const result = await getFieldValues('traces', 'service.name'); + + // Verify the returned structure + expect(result).toEqual({ + statusCode: 200, + error: null, + message: 'success', + payload: expect.objectContaining({ + values: expect.any(Object), + normalizedValues: expect.any(Array), + complete: true, + }), + }); + }); +}); diff --git a/frontend/src/hooks/dashboard/__test__/useGetDynamicVariables.test.tsx b/frontend/src/hooks/dashboard/__test__/useGetDynamicVariables.test.tsx new file mode 100644 index 000000000000..584b89a4679b --- /dev/null +++ b/frontend/src/hooks/dashboard/__test__/useGetDynamicVariables.test.tsx @@ -0,0 +1,224 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { useDashboard } from 'providers/Dashboard/Dashboard'; +import { ReactNode } from 'react'; +import { QueryClient, QueryClientProvider, useQuery } from 'react-query'; + +import { useGetDynamicVariables } from '../useGetDynamicVariables'; + +// Mock the dependencies +jest.mock('react-query', () => ({ + ...jest.requireActual('react-query'), + useQuery: jest.fn(), +})); + +jest.mock('providers/Dashboard/Dashboard', () => ({ + useDashboard: jest.fn(), +})); + +// Sample dashboard data with variables +const mockDashboardData = { + data: { + title: 'Test Dashboard', + variables: { + var1: { + id: 'var1', + name: 'service', + type: 'DYNAMIC', + dynamicVariablesAttribute: 'service.name', + dynamicVariablesSource: 'Traces', + selectedValue: 'frontend', + multiSelect: false, + showALLOption: false, + allSelected: false, + description: '', + sort: 'DISABLED', + }, + var2: { + id: 'var2', + name: 'status', + type: 'DYNAMIC', + dynamicVariablesAttribute: 'http.status_code', + dynamicVariablesSource: 'Traces', + selectedValue: '200', + multiSelect: false, + showALLOption: false, + allSelected: false, + description: '', + sort: 'DISABLED', + }, + var3: { + id: 'var3', + name: 'interval', + type: 'CUSTOM', // Not DYNAMIC - should be filtered out + customValue: '5m', + multiSelect: false, + showALLOption: false, + allSelected: false, + description: '', + sort: 'DISABLED', + }, + }, + }, + uuid: 'dashboard-123', + loading: false, + error: null, +}; + +// Mock refetch function +const mockRefetch = jest.fn(); + +// Constants +const DASHBOARD_ID = 'dashboard-123'; + +// Create a wrapper for the renderHook function with the QueryClientProvider +const createWrapper = (): React.FC<{ children: ReactNode }> => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }); + + // Define as function declaration to fix linter error + function Wrapper({ children }: { children: ReactNode }): JSX.Element { + return ( + {children} + ); + } + + return Wrapper; +}; + +describe('useGetDynamicVariables', () => { + beforeEach(() => { + jest.clearAllMocks(); + + // Mock the useDashboard hook + (useDashboard as jest.Mock).mockReturnValue({ + dashboardId: DASHBOARD_ID, + }); + + // Mock the useQuery hook for successful response + (useQuery as jest.Mock).mockReturnValue({ + data: mockDashboardData, + isLoading: false, + isError: false, + refetch: mockRefetch, + }); + }); + + it('should return dynamic variables from the dashboard', async () => { + const { result } = renderHook(() => useGetDynamicVariables(), { + wrapper: createWrapper(), + }); + + await waitFor(() => { + expect(result.current.dynamicVariables).toHaveLength(2); // Only DYNAMIC type variables + expect(result.current.dynamicVariables[0].name).toBe('service'); + expect(result.current.dynamicVariables[1].name).toBe('status'); + expect(result.current.isLoading).toBe(false); + expect(result.current.isError).toBe(false); + }); + + // Verify each dynamic variable has dashboard info + expect(result.current.dynamicVariables[0].dashboardName).toBe( + 'Test Dashboard', + ); + expect(result.current.dynamicVariables[0].dashboardId).toBe(DASHBOARD_ID); + }); + + it('should use dashboardId from props if provided', async () => { + const customDashboardId = 'custom-dashboard-id'; + renderHook(() => useGetDynamicVariables({ dashboardId: customDashboardId }), { + wrapper: createWrapper(), + }); + + // Check that useQuery was called with the custom dashboardId + expect(useQuery).toHaveBeenCalledWith( + expect.objectContaining({ + queryKey: expect.arrayContaining(['DASHBOARD_BY_ID', customDashboardId]), + }), + ); + }); + + it('should return empty array when dashboard has no variables', async () => { + // Mock no variables in dashboard + (useQuery as jest.Mock).mockReturnValue({ + data: { + data: { title: 'Empty Dashboard' }, + uuid: 'dashboard-empty', + loading: false, + error: null, + }, + isLoading: false, + isError: false, + refetch: mockRefetch, + }); + + const { result } = renderHook(() => useGetDynamicVariables(), { + wrapper: createWrapper(), + }); + + expect(result.current.dynamicVariables).toHaveLength(0); + }); + + it('should return empty array when dashboard is null', async () => { + // Mock null dashboard data + (useQuery as jest.Mock).mockReturnValue({ + data: null, + isLoading: false, + isError: false, + refetch: mockRefetch, + }); + + const { result } = renderHook(() => useGetDynamicVariables(), { + wrapper: createWrapper(), + }); + + expect(result.current.dynamicVariables).toHaveLength(0); + }); + + it('should handle loading state', async () => { + // Mock loading state + (useQuery as jest.Mock).mockReturnValue({ + data: null, + isLoading: true, + isError: false, + refetch: mockRefetch, + }); + + const { result } = renderHook(() => useGetDynamicVariables(), { + wrapper: createWrapper(), + }); + + expect(result.current.isLoading).toBe(true); + expect(result.current.dynamicVariables).toHaveLength(0); + }); + + it('should handle error state', async () => { + // Mock error state + (useQuery as jest.Mock).mockReturnValue({ + data: null, + isLoading: false, + isError: true, + refetch: mockRefetch, + }); + + const { result } = renderHook(() => useGetDynamicVariables(), { + wrapper: createWrapper(), + }); + + expect(result.current.isError).toBe(true); + expect(result.current.dynamicVariables).toHaveLength(0); + }); + + it('should call refetch when returned function is called', async () => { + const { result } = renderHook(() => useGetDynamicVariables(), { + wrapper: createWrapper(), + }); + + result.current.refetch(); + expect(mockRefetch).toHaveBeenCalledTimes(1); + }); +});