grafana/public/app/features/variables/query/VariableQueryRunner.test.ts
Hugo Häggmark f73be970d3
Variables: Removes experimental Tags feature (#33361)
* Variables: Removes experimental Tags feature

* Refactor: adds dashboard migration

* Tests: fixes snapshots

* Docs: removes docs for experimental feature

* Refactor: dummy change

* Docs: removes reference
2021-04-27 05:57:25 +02:00

293 lines
9.4 KiB
TypeScript

import { of, throwError } from 'rxjs';
import { getDefaultTimeRange, LoadingState, VariableSupportType } from '@grafana/data';
import { delay } from 'rxjs/operators';
import { UpdateOptionsResults, VariableQueryRunner } from './VariableQueryRunner';
import { queryBuilder } from '../shared/testing/builders';
import { QueryRunner, QueryRunners } from './queryRunners';
import { toVariableIdentifier, VariableIdentifier } from '../state/types';
import { QueryVariableModel } from '../types';
import { updateVariableOptions } from './reducer';
type DoneCallback = {
(...args: any[]): any;
fail(error?: string | { message: string }): any;
};
function expectOnResults(args: {
runner: VariableQueryRunner;
identifier: VariableIdentifier;
done: DoneCallback;
expect: (results: UpdateOptionsResults[]) => void;
}) {
const { runner, identifier, done, expect: expectCallback } = args;
const results: UpdateOptionsResults[] = [];
const subscription = runner.getResponse(identifier).subscribe({
next: (value) => {
results.push(value);
if (value.state === LoadingState.Done || value.state === LoadingState.Error) {
try {
expectCallback(results);
subscription.unsubscribe();
done();
} catch (err) {
subscription.unsubscribe();
done.fail(err);
}
}
},
});
}
function getTestContext(variable?: QueryVariableModel) {
variable = variable ?? queryBuilder().withId('query').build();
const getTimeSrv = jest.fn().mockReturnValue({
timeRange: jest.fn().mockReturnValue(getDefaultTimeRange()),
});
const datasource: any = { metricFindQuery: jest.fn().mockResolvedValue([]) };
const identifier = toVariableIdentifier(variable);
const searchFilter = undefined;
const getTemplatedRegex = jest.fn().mockReturnValue('getTemplatedRegex result');
const dispatch = jest.fn().mockResolvedValue({});
const getState = jest.fn().mockReturnValue({
templating: {
transaction: {
uid: '0123456789',
},
},
variables: {
[variable.id]: variable,
},
});
const queryRunner: QueryRunner = {
type: VariableSupportType.Standard,
canRun: jest.fn().mockReturnValue(true),
getTarget: jest.fn().mockReturnValue({ refId: 'A', query: 'A query' }),
runRequest: jest.fn().mockReturnValue(of({ series: [], state: LoadingState.Done })),
};
const queryRunners = ({
getRunnerForDatasource: jest.fn().mockReturnValue(queryRunner),
} as unknown) as QueryRunners;
const getVariable = jest.fn().mockReturnValue(variable);
const runRequest = jest.fn().mockReturnValue(of({}));
const runner = new VariableQueryRunner({
getTimeSrv,
getTemplatedRegex,
dispatch,
getState,
getVariable,
queryRunners,
runRequest,
});
return {
identifier,
datasource,
runner,
searchFilter,
getTemplatedRegex,
dispatch,
getState,
queryRunner,
queryRunners,
getVariable,
runRequest,
variable,
getTimeSrv,
};
}
describe('VariableQueryRunner', () => {
describe('happy case', () => {
it('then it should work as expected', (done) => {
const {
identifier,
runner,
datasource,
getState,
getVariable,
queryRunners,
queryRunner,
dispatch,
} = getTestContext();
expectOnResults({
identifier,
runner,
expect: (results) => {
// verify that the observable works as expected
expect(results).toEqual([
{ state: LoadingState.Loading, identifier },
{ state: LoadingState.Done, identifier },
]);
// verify that mocks have been called as expected
expect(getState).toHaveBeenCalledTimes(3);
expect(getVariable).toHaveBeenCalledTimes(1);
expect(queryRunners.getRunnerForDatasource).toHaveBeenCalledTimes(1);
expect(queryRunner.getTarget).toHaveBeenCalledTimes(1);
expect(queryRunner.runRequest).toHaveBeenCalledTimes(1);
expect(datasource.metricFindQuery).not.toHaveBeenCalled();
// updateVariableOptions and validateVariableSelectionState
expect(dispatch).toHaveBeenCalledTimes(2);
expect(dispatch.mock.calls[0][0]).toEqual(
updateVariableOptions({
id: 'query',
type: 'query',
data: { results: [], templatedRegex: 'getTemplatedRegex result' },
})
);
},
done,
});
runner.queueRequest({ identifier, datasource });
});
});
describe('error cases', () => {
describe('queryRunners.getRunnerForDatasource throws', () => {
it('then it should work as expected', (done) => {
const {
identifier,
runner,
datasource,
getState,
getVariable,
queryRunners,
queryRunner,
dispatch,
} = getTestContext();
queryRunners.getRunnerForDatasource = jest.fn().mockImplementation(() => {
throw new Error('getRunnerForDatasource error');
});
expectOnResults({
identifier,
runner,
expect: (results) => {
// verify that the observable works as expected
expect(results).toEqual([
{ state: LoadingState.Loading, identifier },
{ state: LoadingState.Error, identifier, error: new Error('getRunnerForDatasource error') },
]);
// verify that mocks have been called as expected
expect(getState).toHaveBeenCalledTimes(2);
expect(getVariable).toHaveBeenCalledTimes(1);
expect(queryRunners.getRunnerForDatasource).toHaveBeenCalledTimes(1);
expect(queryRunner.getTarget).not.toHaveBeenCalled();
expect(queryRunner.runRequest).not.toHaveBeenCalled();
expect(datasource.metricFindQuery).not.toHaveBeenCalled();
expect(dispatch).not.toHaveBeenCalled();
},
done,
});
runner.queueRequest({ identifier, datasource });
});
});
describe('runRequest throws', () => {
it('then it should work as expected', (done) => {
const {
identifier,
runner,
datasource,
getState,
getVariable,
queryRunners,
queryRunner,
dispatch,
} = getTestContext();
queryRunner.runRequest = jest.fn().mockReturnValue(throwError(new Error('runRequest error')));
expectOnResults({
identifier,
runner,
expect: (results) => {
// verify that the observable works as expected
expect(results).toEqual([
{ state: LoadingState.Loading, identifier },
{ state: LoadingState.Error, identifier, error: new Error('runRequest error') },
]);
// verify that mocks have been called as expected
expect(getState).toHaveBeenCalledTimes(2);
expect(getVariable).toHaveBeenCalledTimes(1);
expect(queryRunners.getRunnerForDatasource).toHaveBeenCalledTimes(1);
expect(queryRunner.getTarget).toHaveBeenCalledTimes(1);
expect(queryRunner.runRequest).toHaveBeenCalledTimes(1);
expect(datasource.metricFindQuery).not.toHaveBeenCalled();
expect(dispatch).not.toHaveBeenCalled();
},
done,
});
runner.queueRequest({ identifier, datasource });
});
});
});
describe('cancellation cases', () => {
describe('long running request is cancelled', () => {
it('then it should work as expected', (done) => {
const { identifier, datasource, runner, queryRunner } = getTestContext();
queryRunner.runRequest = jest
.fn()
.mockReturnValue(of({ series: [], state: LoadingState.Done }).pipe(delay(10000)));
expectOnResults({
identifier,
runner,
expect: (results) => {
// verify that the observable works as expected
expect(results).toEqual([
{ state: LoadingState.Loading, identifier },
{ state: LoadingState.Loading, identifier, cancelled: true },
{ state: LoadingState.Done, identifier },
]);
},
done,
});
runner.queueRequest({ identifier, datasource });
runner.cancelRequest(identifier);
});
});
describe('an identical request is triggered before first request is finished', () => {
it('then it should work as expected', (done) => {
const { identifier, datasource, runner, queryRunner } = getTestContext();
queryRunner.runRequest = jest
.fn()
.mockReturnValueOnce(of({ series: [], state: LoadingState.Done }).pipe(delay(10000)))
.mockReturnValue(of({ series: [], state: LoadingState.Done }));
expectOnResults({
identifier,
runner,
expect: (results) => {
// verify that the observable works as expected
expect(results).toEqual([
{ state: LoadingState.Loading, identifier },
{ state: LoadingState.Loading, identifier },
{ state: LoadingState.Loading, identifier, cancelled: true },
{ state: LoadingState.Done, identifier },
]);
},
done,
});
runner.queueRequest({ identifier, datasource });
runner.queueRequest({ identifier, datasource });
});
});
});
});