import { renderHook } from '@testing-library/react-hooks'; import React from 'react'; import * as reactRedux from 'react-redux'; import { DataSourceJsonData, DataSourceSettings } from '@grafana/data'; import { config } from '@grafana/runtime'; import { AlertmanagerChoice, AlertManagerDataSourceJsonData } from 'app/plugins/datasource/alertmanager/types'; import { mockDataSource, mockDataSourcesStore, mockStore } from '../mocks'; import { useExternalAmSelector, useExternalDataSourceAlertmanagers } from './useExternalAmSelector'; const useSelectorMock = jest.spyOn(reactRedux, 'useSelector'); describe('useExternalAmSelector', () => { beforeEach(() => { useSelectorMock.mockClear(); }); it('should have one in pending', () => { useSelectorMock.mockImplementation((callback) => { return callback(createMockStoreState([], [], ['some/url/to/am'])); }); const alertmanagers = useExternalAmSelector(); expect(alertmanagers).toEqual([ { url: 'some/url/to/am', status: 'pending', actualUrl: '', }, ]); }); it('should have one active, one pending', () => { useSelectorMock.mockImplementation((callback) => { return callback( createMockStoreState([{ url: 'some/url/to/am/api/v2/alerts' }], [], ['some/url/to/am', 'some/url/to/am1']) ); }); const alertmanagers = useExternalAmSelector(); expect(alertmanagers).toEqual([ { url: 'some/url/to/am', actualUrl: 'some/url/to/am/api/v2/alerts', status: 'active', }, { url: 'some/url/to/am1', actualUrl: '', status: 'pending', }, ]); }); it('should have two active', () => { useSelectorMock.mockImplementation((callback) => { return callback( createMockStoreState( [{ url: 'some/url/to/am/api/v2/alerts' }, { url: 'some/url/to/am1/api/v2/alerts' }], [], ['some/url/to/am', 'some/url/to/am1'] ) ); }); const alertmanagers = useExternalAmSelector(); expect(alertmanagers).toEqual([ { url: 'some/url/to/am', actualUrl: 'some/url/to/am/api/v2/alerts', status: 'active', }, { url: 'some/url/to/am1', actualUrl: 'some/url/to/am1/api/v2/alerts', status: 'active', }, ]); }); it('should have one active, one dropped, one pending', () => { useSelectorMock.mockImplementation((callback) => { return callback( createMockStoreState( [{ url: 'some/url/to/am/api/v2/alerts' }], [{ url: 'some/dropped/url/api/v2/alerts' }], ['some/url/to/am', 'some/url/to/am1'] ) ); }); const alertmanagers = useExternalAmSelector(); expect(alertmanagers).toEqual([ { url: 'some/url/to/am', actualUrl: 'some/url/to/am/api/v2/alerts', status: 'active', }, { url: 'some/url/to/am1', actualUrl: '', status: 'pending', }, { url: 'some/dropped/url', actualUrl: 'some/dropped/url/api/v2/alerts', status: 'dropped', }, ]); }); it('The number of alert managers should match config entries when there are multiple entries of the same url', () => { useSelectorMock.mockImplementation((callback) => { return callback( createMockStoreState( [ { url: 'same/url/to/am/api/v2/alerts' }, { url: 'same/url/to/am/api/v2/alerts' }, { url: 'same/url/to/am/api/v2/alerts' }, ], [], ['same/url/to/am', 'same/url/to/am', 'same/url/to/am'] ) ); }); const alertmanagers = useExternalAmSelector(); expect(alertmanagers.length).toBe(3); expect(alertmanagers).toEqual([ { url: 'same/url/to/am', actualUrl: 'same/url/to/am/api/v2/alerts', status: 'active', }, { url: 'same/url/to/am', actualUrl: 'same/url/to/am/api/v2/alerts', status: 'active', }, { url: 'same/url/to/am', actualUrl: 'same/url/to/am/api/v2/alerts', status: 'active', }, ]); }); }); describe('useExternalDataSourceAlertmanagers', () => { beforeEach(() => { useSelectorMock.mockRestore(); }); it('Should merge data sources information from config and api responses', () => { // Arrange const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'http://grafana.com' }); config.datasources = { 'External Alertmanager': dsInstanceSettings, }; const store = mockDataSourcesStore({ dataSources: [dsSettings], }); const wrapper: React.FC = ({ children }) => {children}; // Act const { result: { current }, } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper }); // Assert expect(current).toHaveLength(1); expect(current[0].dataSource.uid).toBe('1'); expect(current[0].url).toBe('http://grafana.com'); }); it('Should have active state if available in the activeAlertManagers', () => { // Arrange const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'http://grafana.com' }); config.datasources = { 'External Alertmanager': dsInstanceSettings, }; const store = mockStore((state) => { state.dataSources.dataSources = [dsSettings]; state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result = { data: { activeAlertManagers: [{ url: 'http://grafana.com/api/v2/alerts' }], droppedAlertManagers: [], }, }; }); const wrapper: React.FC = ({ children }) => {children}; // Act const { result: { current }, } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper }); // Assert expect(current).toHaveLength(1); expect(current[0].status).toBe('active'); expect(current[0].statusInconclusive).toBe(false); }); it('Should have dropped state if available in the droppedAlertManagers', () => { // Arrange const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'http://grafana.com' }); config.datasources = { 'External Alertmanager': dsInstanceSettings, }; const store = mockStore((state) => { state.dataSources.dataSources = [dsSettings]; state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result = { data: { activeAlertManagers: [], droppedAlertManagers: [{ url: 'http://grafana.com/api/v2/alerts' }], }, }; }); const wrapper: React.FC = ({ children }) => {children}; // Act const { result: { current }, } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper }); // Assert expect(current).toHaveLength(1); expect(current[0].status).toBe('dropped'); expect(current[0].statusInconclusive).toBe(false); }); it('Should have pending state if not available neither in dropped nor in active alertManagers', () => { // Arrange const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource(); config.datasources = { 'External Alertmanager': dsInstanceSettings, }; const store = mockStore((state) => { state.dataSources.dataSources = [dsSettings]; state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result = { data: { activeAlertManagers: [], droppedAlertManagers: [], }, }; }); const wrapper: React.FC = ({ children }) => {children}; // Act const { result: { current }, } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper }); // Assert expect(current).toHaveLength(1); expect(current[0].status).toBe('pending'); expect(current[0].statusInconclusive).toBe(false); }); it('Should match Alertmanager url when datasource url does not have protocol specified', () => { // Arrange const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'localhost:9093' }); config.datasources = { 'External Alertmanager': dsInstanceSettings, }; const store = mockStore((state) => { state.dataSources.dataSources = [dsSettings]; state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result = { data: { activeAlertManagers: [{ url: 'http://localhost:9093/api/v2/alerts' }], droppedAlertManagers: [], }, }; }); const wrapper: React.FC = ({ children }) => {children}; // Act const { result: { current }, } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper }); // Assert expect(current).toHaveLength(1); expect(current[0].status).toBe('active'); expect(current[0].url).toBe('localhost:9093'); }); it('Should have inconclusive state when there are many Alertmanagers of the same URL', () => { // Arrange const { dsSettings, dsInstanceSettings } = setupAlertmanagerDataSource({ url: 'http://grafana.com' }); config.datasources = { 'External Alertmanager': dsInstanceSettings, }; const store = mockStore((state) => { state.dataSources.dataSources = [dsSettings]; state.unifiedAlerting.externalAlertmanagers.discoveredAlertmanagers.result = { data: { activeAlertManagers: [ { url: 'http://grafana.com/api/v2/alerts' }, { url: 'http://grafana.com/api/v2/alerts' }, ], droppedAlertManagers: [], }, }; }); const wrapper: React.FC = ({ children }) => {children}; // Act const { result: { current }, } = renderHook(() => useExternalDataSourceAlertmanagers(), { wrapper }); // Assert expect(current).toHaveLength(1); expect(current[0].status).toBe('active'); expect(current[0].statusInconclusive).toBe(true); }); }); function setupAlertmanagerDataSource(partialDsSettings?: Partial>) { const dsCommonConfig = { uid: '1', name: 'External Alertmanager', type: 'alertmanager', jsonData: { handleGrafanaManagedAlerts: true } as AlertManagerDataSourceJsonData, }; const dsInstanceSettings = mockDataSource(dsCommonConfig); const dsSettings = mockApiDataSource({ ...dsCommonConfig, ...partialDsSettings, }); return { dsSettings, dsInstanceSettings }; } function mockApiDataSource(partial: Partial> = {}) { const dsSettings: DataSourceSettings = { uid: '1', id: 1, name: '', url: '', type: '', access: '', orgId: 1, typeLogoUrl: '', typeName: '', user: '', database: '', basicAuth: false, isDefault: false, basicAuthUser: '', jsonData: { handleGrafanaManagedAlerts: true } as AlertManagerDataSourceJsonData, secureJsonFields: {}, readOnly: false, withCredentials: false, ...partial, }; return dsSettings; } const createMockStoreState = ( activeAlertmanagers: Array<{ url: string }>, droppedAlertmanagers: Array<{ url: string }>, alertmanagerConfig: string[] ) => { return { unifiedAlerting: { externalAlertmanagers: { discoveredAlertmanagers: { result: { data: { activeAlertManagers: activeAlertmanagers, droppedAlertManagers: droppedAlertmanagers, }, }, dispatched: false, loading: false, }, alertmanagerConfig: { result: { alertmanagers: alertmanagerConfig, alertmanagersChoice: AlertmanagerChoice.All, }, dispatched: false, loading: false, }, }, }, }; };