grafana/public/app/features/datasources/state/actions.test.ts
Ieva 9a3b2937aa
Data sources: Refactor logic for naming new data sources (#78479)
* move the name finding logic for new data sources from frontend to backend

* cleanup and fix test

* linting

* change the way the number after the ds type is incremented - keep incrementing it without adding more hyphens

* enterprise spec updates (unrelated to the PR)
2023-11-22 09:57:26 +00:00

400 lines
14 KiB
TypeScript

import { thunkTester } from 'test/core/thunk/thunkTester';
import { AppPluginMeta, DataSourceSettings, PluginMetaInfo, PluginType } from '@grafana/data';
import { FetchError } from '@grafana/runtime';
import { appEvents } from 'app/core/core';
import { ThunkResult, ThunkDispatch } from 'app/types';
import { getMockDataSource } from '../__mocks__';
import * as api from '../api';
import { DATASOURCES_ROUTES } from '../constants';
import { trackDataSourceCreated, trackDataSourceTested } from '../tracking';
import { GenericDataSourcePlugin } from '../types';
import {
InitDataSourceSettingDependencies,
testDataSource,
TestDataSourceDependencies,
initDataSourceSettings,
loadDataSource,
addDataSource,
} from './actions';
import {
initDataSourceSettingsSucceeded,
initDataSourceSettingsFailed,
testDataSourceStarting,
testDataSourceSucceeded,
testDataSourceFailed,
dataSourceLoaded,
} from './reducers';
jest.mock('../api');
jest.mock('app/core/services/backend_srv');
jest.mock('app/core/core', () => ({
...jest.requireActual('app/core/core'),
appEvents: {
publish: jest.fn(),
},
}));
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getDataSourceSrv: jest.fn().mockReturnValue({ reload: jest.fn() }),
getBackendSrv: jest.fn().mockReturnValue({ get: jest.fn() }),
}));
jest.mock('../tracking', () => ({
trackDataSourceCreated: jest.fn(),
trackDataSourceTested: jest.fn(),
}));
const getBackendSrvMock = () =>
({
get: jest.fn().mockReturnValue({
testDatasource: jest.fn().mockReturnValue({
status: '',
message: '',
}),
}),
withNoBackendCache: jest.fn().mockImplementationOnce((cb) => cb()),
}) as any;
const failDataSourceTest = async (error: object) => {
const dependencies: TestDataSourceDependencies = {
getDatasourceSrv: () =>
({
get: jest.fn().mockReturnValue({
testDatasource: jest.fn().mockImplementation(() => {
throw error;
}),
}),
}) as any,
getBackendSrv: getBackendSrvMock,
};
const state = {
testingStatus: {
message: '',
status: '',
},
};
const dispatchedActions = await thunkTester(state)
.givenThunk(testDataSource)
.whenThunkIsDispatched('Azure Monitor', DATASOURCES_ROUTES.Edit, dependencies);
return dispatchedActions;
};
describe('loadDataSource()', () => {
it('should resolve to a data-source if a UID was used for fetching', async () => {
const dataSourceMock = getMockDataSource();
const dispatch = jest.fn();
const getState = jest.fn();
(api.getDataSourceByIdOrUid as jest.Mock).mockResolvedValueOnce(dataSourceMock);
const dataSource = await loadDataSource(dataSourceMock.uid)(dispatch, getState, undefined);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith(dataSourceLoaded(dataSource));
expect(dataSource).toBe(dataSourceMock);
});
it('should resolve to an empty data-source if an ID (deprecated) was used for fetching', async () => {
const id = 123;
const uid = 'uid';
const dataSourceMock = getMockDataSource({ id, uid });
const dispatch = jest.fn();
const getState = jest.fn();
// @ts-ignore
delete window.location;
window.location = {} as Location;
(api.getDataSourceByIdOrUid as jest.Mock).mockResolvedValueOnce(dataSourceMock);
// Fetch the datasource by ID
const dataSource = await loadDataSource(id.toString())(dispatch, getState, undefined);
expect(dataSource).toEqual({});
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith(dataSourceLoaded({} as DataSourceSettings));
});
it('should redirect to a URL which uses the UID if an ID (deprecated) was used for fetching', async () => {
const id = 123;
const uid = 'uid';
const dataSourceMock = getMockDataSource({ id, uid });
const dispatch = jest.fn();
const getState = jest.fn();
// @ts-ignore
delete window.location;
window.location = {} as Location;
(api.getDataSourceByIdOrUid as jest.Mock).mockResolvedValueOnce(dataSourceMock);
// Fetch the datasource by ID
await loadDataSource(id.toString())(dispatch, getState, undefined);
expect(window.location.href).toBe(`/datasources/edit/${uid}`);
});
});
describe('initDataSourceSettings', () => {
describe('when pageId is missing', () => {
it('then initDataSourceSettingsFailed should be dispatched', async () => {
const dispatchedActions = await thunkTester({}).givenThunk(initDataSourceSettings).whenThunkIsDispatched('');
expect(dispatchedActions).toEqual([initDataSourceSettingsFailed(new Error('Invalid UID'))]);
});
});
describe('when pageId is a valid', () => {
it('then initDataSourceSettingsSucceeded should be dispatched', async () => {
const dataSource = { type: 'app' };
const dataSourceMeta = { id: 'some id' };
const dependencies: InitDataSourceSettingDependencies = {
loadDataSource: jest.fn((): ThunkResult<void> => (dispatch: ThunkDispatch, getState) => dataSource) as any,
loadDataSourceMeta: jest.fn((): ThunkResult<void> => (dispatch: ThunkDispatch, getState) => {}),
getDataSource: jest.fn().mockReturnValue(dataSource),
getDataSourceMeta: jest.fn().mockReturnValue(dataSourceMeta),
importDataSourcePlugin: jest.fn().mockReturnValue({} as GenericDataSourcePlugin),
};
const state = {
dataSourceSettings: {},
dataSources: {},
};
const dispatchedActions = await thunkTester(state)
.givenThunk(initDataSourceSettings)
.whenThunkIsDispatched(256, dependencies);
expect(dispatchedActions).toEqual([initDataSourceSettingsSucceeded({} as GenericDataSourcePlugin)]);
expect(dependencies.loadDataSource).toHaveBeenCalledTimes(1);
expect(dependencies.loadDataSource).toHaveBeenCalledWith(256);
expect(dependencies.loadDataSourceMeta).toHaveBeenCalledTimes(1);
expect(dependencies.loadDataSourceMeta).toHaveBeenCalledWith(dataSource);
expect(dependencies.getDataSource).toHaveBeenCalledTimes(1);
expect(dependencies.getDataSource).toHaveBeenCalledWith({}, 256);
expect(dependencies.getDataSourceMeta).toHaveBeenCalledTimes(1);
expect(dependencies.getDataSourceMeta).toHaveBeenCalledWith({}, 'app');
expect(dependencies.importDataSourcePlugin).toHaveBeenCalledTimes(1);
expect(dependencies.importDataSourcePlugin).toHaveBeenCalledWith(dataSourceMeta);
});
});
describe('when plugin loading fails', () => {
it('then initDataSourceSettingsFailed should be dispatched', async () => {
const dataSource = { type: 'app' };
const dependencies: InitDataSourceSettingDependencies = {
loadDataSource: jest.fn((): ThunkResult<void> => (dispatch: ThunkDispatch, getState) => dataSource) as any,
loadDataSourceMeta: jest.fn().mockImplementation(() => {
throw new Error('Error loading plugin');
}),
getDataSource: jest.fn(),
getDataSourceMeta: jest.fn(),
importDataSourcePlugin: jest.fn(),
};
const state = {
dataSourceSettings: {},
dataSources: {},
};
const dispatchedActions = await thunkTester(state)
.givenThunk(initDataSourceSettings)
.whenThunkIsDispatched(301, dependencies);
expect(dispatchedActions).toEqual([initDataSourceSettingsFailed(new Error('Error loading plugin'))]);
expect(dependencies.loadDataSource).toHaveBeenCalledTimes(1);
expect(dependencies.loadDataSource).toHaveBeenCalledWith(301);
expect(dependencies.loadDataSourceMeta).toHaveBeenCalledTimes(1);
expect(dependencies.loadDataSourceMeta).toHaveBeenCalledWith(dataSource);
});
});
});
describe('testDataSource', () => {
describe('when a datasource is tested', () => {
it('then testDataSourceStarting and testDataSourceSucceeded should be dispatched', async () => {
const dependencies: TestDataSourceDependencies = {
getDatasourceSrv: () =>
({
get: jest.fn().mockReturnValue({
testDatasource: jest.fn().mockReturnValue({
status: 'success',
message: '',
}),
type: 'cloudwatch',
uid: 'CW1234',
}),
}) as any,
getBackendSrv: getBackendSrvMock,
};
const state = {
testingStatus: {
status: 'success',
message: '',
details: {},
},
};
const dispatchedActions = await thunkTester(state)
.givenThunk(testDataSource)
.whenThunkIsDispatched('CloudWatch', DATASOURCES_ROUTES.Edit, dependencies);
expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceSucceeded(state.testingStatus)]);
expect(trackDataSourceTested).toHaveBeenCalledWith({
plugin_id: 'cloudwatch',
datasource_uid: 'CW1234',
grafana_version: '1.0',
success: true,
path: '/datasources/edit/CloudWatch',
});
});
it('then testDataSourceFailed should be dispatched', async () => {
const dependencies: TestDataSourceDependencies = {
getDatasourceSrv: () =>
({
get: jest.fn().mockReturnValue({
testDatasource: jest.fn().mockImplementation(() => {
throw new Error('Error testing datasource');
}),
type: 'azure-monitor',
uid: 'azM0nit0R',
}),
}) as any,
getBackendSrv: getBackendSrvMock,
};
const result = {
message: 'Error testing datasource',
};
const state = {
testingStatus: {
message: '',
status: '',
},
};
const dispatchedActions = await thunkTester(state)
.givenThunk(testDataSource)
.whenThunkIsDispatched('Azure Monitor', DATASOURCES_ROUTES.Edit, dependencies);
expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceFailed(result)]);
expect(trackDataSourceTested).toHaveBeenCalledWith({
plugin_id: 'azure-monitor',
datasource_uid: 'azM0nit0R',
grafana_version: '1.0',
success: false,
path: '/datasources/edit/Azure Monitor',
});
});
it('then testDataSourceFailed should be dispatched with response error message', async () => {
const result = {
message: 'Response error message',
};
const error: FetchError = {
config: {
url: '',
},
data: { message: 'Response error message' },
status: 400,
statusText: 'Bad Request',
};
const dispatchedActions = await failDataSourceTest(error);
expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceFailed(result)]);
});
it('then testDataSourceFailed should be dispatched with response data message', async () => {
const result = {
message: 'Response error message',
};
const error: FetchError = {
config: {
url: '',
},
data: { message: 'Response error message' },
status: 400,
statusText: 'Bad Request',
};
const dispatchedActions = await failDataSourceTest(error);
expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceFailed(result)]);
});
it('then testDataSourceFailed should be dispatched with response statusText', async () => {
const result = {
message: 'HTTP error Bad Request',
};
const error: FetchError = {
config: {
url: '',
},
data: {},
statusText: 'Bad Request',
status: 400,
};
const dispatchedActions = await failDataSourceTest(error);
expect(dispatchedActions).toEqual([testDataSourceStarting(), testDataSourceFailed(result)]);
});
it('publishes an app event when the test succeeds', async () => {
const dependencies: TestDataSourceDependencies = {
getDatasourceSrv: () => ({
get: jest.fn().mockReturnValue({
testDatasource: jest.fn().mockReturnValue({
status: 'success',
message: '',
}),
type: 'cloudwatch',
uid: 'CW1234',
}),
}),
getBackendSrv: getBackendSrvMock,
};
await thunkTester({})
.givenThunk(testDataSource)
.whenThunkIsDispatched('CloudWatch', DATASOURCES_ROUTES.Edit, dependencies);
expect(appEvents.publish).toHaveBeenCalledWith({ type: 'datasource-test-succeeded' });
});
it('publishes an app event when the test fails', async () => {
const error: FetchError = {
config: {
url: '',
},
data: {},
statusText: 'Bad Request',
status: 400,
};
await failDataSourceTest(error);
expect(appEvents.publish).toHaveBeenCalledWith({ type: 'datasource-test-failed' });
});
});
});
describe('addDataSource', () => {
it('it creates a datasource and calls trackDataSourceCreated ', async () => {
const meta: AppPluginMeta = {
id: 'azure-monitor',
module: '',
baseUrl: 'xxx',
info: { version: '1.2.3' } as PluginMetaInfo,
type: PluginType.datasource,
name: 'test DS',
};
const dataSourceMock = { datasource: { uid: 'azure23' }, meta };
(api.createDataSource as jest.Mock).mockResolvedValueOnce(dataSourceMock);
(api.getDataSources as jest.Mock).mockResolvedValueOnce([]);
await thunkTester({}).givenThunk(addDataSource).whenThunkIsDispatched(meta);
expect(trackDataSourceCreated).toHaveBeenCalledWith({
plugin_id: 'azure-monitor',
plugin_version: '1.2.3',
datasource_uid: 'azure23',
grafana_version: '1.0',
path: DATASOURCES_ROUTES.Edit.replace(':uid', 'azure23'),
});
});
});