diff --git a/frontend/src/container/InfraMonitoringHosts/__tests__/HostsEmptyOrIncorrectMetrics.test.tsx b/frontend/src/container/InfraMonitoringHosts/__tests__/HostsEmptyOrIncorrectMetrics.test.tsx
new file mode 100644
index 000000000000..0b2aa28531d0
--- /dev/null
+++ b/frontend/src/container/InfraMonitoringHosts/__tests__/HostsEmptyOrIncorrectMetrics.test.tsx
@@ -0,0 +1,43 @@
+import { render, screen } from '@testing-library/react';
+
+import HostsEmptyOrIncorrectMetrics from '../HostsEmptyOrIncorrectMetrics';
+
+describe('HostsEmptyOrIncorrectMetrics', () => {
+ it('shows no data message when noData is true', () => {
+ render();
+ expect(
+ screen.getByText('No host metrics data received yet.'),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText(/Infrastructure monitoring requires the/),
+ ).toBeInTheDocument();
+ });
+
+ it('shows incorrect data message when incorrectData is true', () => {
+ render();
+ expect(
+ screen.getByText(
+ 'To see host metrics, upgrade to the latest version of SigNoz k8s-infra chart. Please contact support if you need help.',
+ ),
+ ).toBeInTheDocument();
+ });
+
+ it('does not show no data message when noData is false', () => {
+ render();
+ expect(
+ screen.queryByText('No host metrics data received yet.'),
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByText(/Infrastructure monitoring requires the/),
+ ).not.toBeInTheDocument();
+ });
+
+ it('does not show incorrect data message when incorrectData is false', () => {
+ render();
+ expect(
+ screen.queryByText(
+ 'To see host metrics, upgrade to the latest version of SigNoz k8s-infra chart. Please contact support if you need help.',
+ ),
+ ).not.toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/container/InfraMonitoringHosts/__tests__/HostsList.test.tsx b/frontend/src/container/InfraMonitoringHosts/__tests__/HostsList.test.tsx
new file mode 100644
index 000000000000..270b2a8cd660
--- /dev/null
+++ b/frontend/src/container/InfraMonitoringHosts/__tests__/HostsList.test.tsx
@@ -0,0 +1,166 @@
+/* eslint-disable react/button-has-type */
+import { render } from '@testing-library/react';
+import ROUTES from 'constants/routes';
+import * as useGetHostListHooks from 'hooks/infraMonitoring/useGetHostList';
+import * as appContextHooks from 'providers/App/App';
+import * as timezoneHooks from 'providers/Timezone';
+import { QueryClient, QueryClientProvider } from 'react-query';
+import { Provider } from 'react-redux';
+import { MemoryRouter } from 'react-router-dom';
+import store from 'store';
+import { LicenseEvent } from 'types/api/licensesV3/getActive';
+
+import HostsList from '../HostsList';
+
+jest.mock('lib/getMinMax', () => ({
+ __esModule: true,
+ default: jest.fn().mockImplementation(() => ({
+ minTime: 1713734400000,
+ maxTime: 1713738000000,
+ isValidTimeFormat: jest.fn().mockReturnValue(true),
+ })),
+}));
+jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
+ __esModule: true,
+ default: ({ onSelect, selectedTime, selectedValue }: any): JSX.Element => (
+
+
+
+ ),
+}));
+
+const queryClient = new QueryClient();
+
+jest.mock('uplot', () => {
+ const paths = {
+ spline: jest.fn(),
+ bars: jest.fn(),
+ };
+ const uplotMock = jest.fn(() => ({
+ paths,
+ }));
+ return {
+ paths,
+ default: uplotMock,
+ };
+});
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'),
+ useSelector: (): any => ({
+ globalTime: {
+ selectedTime: {
+ startTime: 1713734400000,
+ endTime: 1713738000000,
+ },
+ maxTime: 1713738000000,
+ minTime: 1713734400000,
+ },
+ }),
+}));
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useLocation: jest.fn().mockReturnValue({
+ pathname: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
+ }),
+}));
+jest.mock('react-router-dom-v5-compat', () => {
+ const actual = jest.requireActual('react-router-dom-v5-compat');
+ return {
+ ...actual,
+ useSearchParams: jest
+ .fn()
+ .mockReturnValue([
+ { get: jest.fn(), entries: jest.fn().mockReturnValue([]) },
+ jest.fn(),
+ ]),
+ useNavigationType: (): any => 'PUSH',
+ };
+});
+jest.mock('hooks/useSafeNavigate', () => ({
+ useSafeNavigate: (): any => ({
+ safeNavigate: jest.fn(),
+ }),
+}));
+
+jest.spyOn(timezoneHooks, 'useTimezone').mockReturnValue({
+ timezone: {
+ offset: 0,
+ },
+ browserTimezone: {
+ offset: 0,
+ },
+} as any);
+jest.spyOn(useGetHostListHooks, 'useGetHostList').mockReturnValue({
+ data: {
+ payload: {
+ data: {
+ records: [
+ {
+ hostName: 'test-host',
+ active: true,
+ cpu: 0.75,
+ memory: 0.65,
+ wait: 0.03,
+ },
+ ],
+ isSendingK8SAgentMetrics: false,
+ sentAnyHostMetricsData: true,
+ },
+ },
+ },
+ isLoading: false,
+ isError: false,
+} as any);
+jest.spyOn(appContextHooks, 'useAppContext').mockReturnValue({
+ user: {
+ role: 'admin',
+ },
+ activeLicenseV3: {
+ event_queue: {
+ created_at: '0',
+ event: LicenseEvent.NO_EVENT,
+ scheduled_at: '0',
+ status: '',
+ updated_at: '0',
+ },
+ license: {
+ license_key: 'test-license-key',
+ license_type: 'trial',
+ org_id: 'test-org-id',
+ plan_id: 'test-plan-id',
+ plan_name: 'test-plan-name',
+ plan_type: 'trial',
+ plan_version: 'test-plan-version',
+ },
+ },
+} as any);
+
+describe('HostsList', () => {
+ it('renders hosts list table', () => {
+ const { container } = render(
+
+
+
+
+
+
+ ,
+ );
+ expect(container.querySelector('.hosts-list-table')).toBeInTheDocument();
+ });
+
+ it('renders filters', () => {
+ const { container } = render(
+
+
+
+
+
+
+ ,
+ );
+ expect(container.querySelector('.filters')).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListControl.test.tsx b/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListControl.test.tsx
new file mode 100644
index 000000000000..d5bad4c81f8f
--- /dev/null
+++ b/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListControl.test.tsx
@@ -0,0 +1,37 @@
+import { render, screen } from '@testing-library/react';
+
+import HostsListControls from '../HostsListControls';
+
+jest.mock('container/QueryBuilder/filters/QueryBuilderSearch', () => ({
+ __esModule: true,
+ default: (): JSX.Element => (
+ Search
+ ),
+}));
+
+jest.mock('container/TopNav/DateTimeSelectionV2', () => ({
+ __esModule: true,
+ default: (): JSX.Element => (
+ Date Time
+ ),
+}));
+
+describe('HostsListControls', () => {
+ const mockHandleFiltersChange = jest.fn();
+ const mockFilters = {
+ items: [],
+ op: 'AND',
+ };
+
+ it('renders search and date time filters', () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByTestId('query-builder-search')).toBeInTheDocument();
+ expect(screen.getByTestId('date-time-selection')).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListTable.test.tsx b/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListTable.test.tsx
new file mode 100644
index 000000000000..1f71b947c51a
--- /dev/null
+++ b/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListTable.test.tsx
@@ -0,0 +1,139 @@
+/* eslint-disable react/jsx-props-no-spreading */
+import { render, screen } from '@testing-library/react';
+
+import HostsListTable from '../HostsListTable';
+
+jest.mock('uplot', () => {
+ const paths = {
+ spline: jest.fn(),
+ bars: jest.fn(),
+ };
+ const uplotMock = jest.fn(() => ({
+ paths,
+ }));
+ return {
+ paths,
+ default: uplotMock,
+ };
+});
+
+const EMPTY_STATE_CONTAINER_CLASS = '.hosts-empty-state-container';
+
+describe('HostsListTable', () => {
+ const mockHost = {
+ hostName: 'test-host-1',
+ active: true,
+ cpu: 0.75,
+ memory: 0.65,
+ wait: 0.03,
+ load15: 1.5,
+ os: 'linux',
+ };
+
+ const mockTableData = {
+ payload: {
+ data: {
+ hosts: [mockHost],
+ },
+ },
+ };
+ const mockOnHostClick = jest.fn();
+ const mockSetCurrentPage = jest.fn();
+ const mockSetOrderBy = jest.fn();
+ const mockSetPageSize = jest.fn();
+ const mockProps = {
+ isLoading: false,
+ isError: false,
+ isFetching: false,
+ tableData: mockTableData,
+ hostMetricsData: [mockHost],
+ filters: {
+ items: [],
+ op: 'AND',
+ },
+ onHostClick: mockOnHostClick,
+ currentPage: 1,
+ setCurrentPage: mockSetCurrentPage,
+ pageSize: 10,
+ setOrderBy: mockSetOrderBy,
+ setPageSize: mockSetPageSize,
+ } as any;
+
+ it('renders loading state if isLoading is true', () => {
+ const { container } = render();
+ expect(container.querySelector('.hosts-list-loading-state')).toBeTruthy();
+ });
+
+ it('renders loading state if isFetching is true', () => {
+ const { container } = render();
+ expect(container.querySelector('.hosts-list-loading-state')).toBeTruthy();
+ });
+
+ it('renders error state if isError is true', () => {
+ render();
+ expect(screen.getByText('Something went wrong')).toBeTruthy();
+ });
+
+ it('renders empty state if no hosts are found', () => {
+ const { container } = render();
+ expect(container.querySelector(EMPTY_STATE_CONTAINER_CLASS)).toBeTruthy();
+ });
+
+ it('renders empty state if sentAnyHostMetricsData is false', () => {
+ const { container } = render(
+ ,
+ );
+ expect(container.querySelector(EMPTY_STATE_CONTAINER_CLASS)).toBeTruthy();
+ });
+
+ it('renders empty state if isSendingIncorrectK8SAgentMetrics is true', () => {
+ const { container } = render(
+ ,
+ );
+ expect(container.querySelector(EMPTY_STATE_CONTAINER_CLASS)).toBeTruthy();
+ });
+
+ it('renders table data', () => {
+ const { container } = render(
+ ,
+ );
+ expect(container.querySelector('.hosts-list-table')).toBeTruthy();
+ });
+});
diff --git a/frontend/src/container/InfraMonitoringHosts/__tests__/utilts.test.tsx b/frontend/src/container/InfraMonitoringHosts/__tests__/utilts.test.tsx
new file mode 100644
index 000000000000..a38681f37256
--- /dev/null
+++ b/frontend/src/container/InfraMonitoringHosts/__tests__/utilts.test.tsx
@@ -0,0 +1,104 @@
+import { render } from '@testing-library/react';
+
+import { formatDataForTable, GetHostsQuickFiltersConfig } from '../utils';
+
+const PROGRESS_BAR_CLASS = '.progress-bar';
+
+jest.mock('uplot', () => {
+ const paths = {
+ spline: jest.fn(),
+ bars: jest.fn(),
+ };
+ const uplotMock = jest.fn(() => ({
+ paths,
+ }));
+ return {
+ paths,
+ default: uplotMock,
+ };
+});
+
+describe('InfraMonitoringHosts utils', () => {
+ describe('formatDataForTable', () => {
+ it('should format host data correctly', () => {
+ const mockData = [
+ {
+ hostName: 'test-host',
+ active: true,
+ cpu: 0.95,
+ memory: 0.85,
+ wait: 0.05,
+ load15: 2.5,
+ os: 'linux',
+ },
+ ] as any;
+
+ const result = formatDataForTable(mockData);
+
+ expect(result[0].hostName).toBe('test-host');
+ expect(result[0].wait).toBe('5%');
+ expect(result[0].load15).toBe(2.5);
+
+ // Test active tag rendering
+ const activeTag = render(result[0].active as JSX.Element);
+ expect(activeTag.container.textContent).toBe('ACTIVE');
+ expect(activeTag.container.querySelector('.active')).toBeTruthy();
+
+ // Test CPU progress bar
+ const cpuProgress = render(result[0].cpu as JSX.Element);
+ const cpuProgressBar = cpuProgress.container.querySelector(
+ PROGRESS_BAR_CLASS,
+ );
+ expect(cpuProgressBar).toBeTruthy();
+
+ // Test memory progress bar
+ const memoryProgress = render(result[0].memory as JSX.Element);
+ const memoryProgressBar = memoryProgress.container.querySelector(
+ PROGRESS_BAR_CLASS,
+ );
+ expect(memoryProgressBar).toBeTruthy();
+ });
+
+ it('should handle inactive hosts', () => {
+ const mockData = [
+ {
+ hostName: 'test-host',
+ active: false,
+ cpu: 0.3,
+ memory: 0.4,
+ wait: 0.02,
+ load15: 1.2,
+ os: 'linux',
+ cpuTimeSeries: [],
+ memoryTimeSeries: [],
+ waitTimeSeries: [],
+ load15TimeSeries: [],
+ },
+ ] as any;
+
+ const result = formatDataForTable(mockData);
+
+ const inactiveTag = render(result[0].active as JSX.Element);
+ expect(inactiveTag.container.textContent).toBe('INACTIVE');
+ expect(inactiveTag.container.querySelector('.inactive')).toBeTruthy();
+ });
+ });
+
+ describe('GetHostsQuickFiltersConfig', () => {
+ it('should return correct config when dotMetricsEnabled is true', () => {
+ const result = GetHostsQuickFiltersConfig(true);
+
+ expect(result[0].attributeKey.key).toBe('host.name');
+ expect(result[1].attributeKey.key).toBe('os.type');
+ expect(result[0].aggregateAttribute).toBe('system.cpu.load_average.15m');
+ });
+
+ it('should return correct config when dotMetricsEnabled is false', () => {
+ const result = GetHostsQuickFiltersConfig(false);
+
+ expect(result[0].attributeKey.key).toBe('host_name');
+ expect(result[1].attributeKey.key).toBe('os_type');
+ expect(result[0].aggregateAttribute).toBe('system_cpu_load_average_15m');
+ });
+ });
+});