Files
grafana/public/app/features/alerting/unified/hooks/useExternalAMSelector.test.tsx
Konrad Lalik 54f2c056f5 Alerting: Configure alert manager data source as an external AM (#52081)
Co-authored-by: Jean-Philippe Quéméner <JohnnyQQQQ@users.noreply.github.com>
Co-authored-by: gotjosh <josue.abreu@gmail.com>
Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com>
2022-08-01 10:20:43 +02:00

417 lines
12 KiB
TypeScript

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 }) => <reactRedux.Provider store={store}>{children}</reactRedux.Provider>;
// 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 }) => <reactRedux.Provider store={store}>{children}</reactRedux.Provider>;
// 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 }) => <reactRedux.Provider store={store}>{children}</reactRedux.Provider>;
// 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 }) => <reactRedux.Provider store={store}>{children}</reactRedux.Provider>;
// 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 }) => <reactRedux.Provider store={store}>{children}</reactRedux.Provider>;
// 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 }) => <reactRedux.Provider store={store}>{children}</reactRedux.Provider>;
// 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<DataSourceSettings<AlertManagerDataSourceJsonData>>) {
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<DataSourceSettings<DataSourceJsonData, {}>> = {}) {
const dsSettings: DataSourceSettings<DataSourceJsonData, {}> = {
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,
},
},
},
};
};