Files
Hugo Häggmark bfd33467fb Tests: removes jest duplicate manual mock warning (#107459)
* Tests: removes jest duplicate manual mock warning

* Trigger build

* Trigger build
2025-07-02 09:25:25 +02:00

422 lines
15 KiB
TypeScript

import { lastValueFrom, of } from 'rxjs';
import { AdHocVariableFilter } from '@grafana/data';
import { BackendSrvRequest, TemplateSrv } from '@grafana/runtime';
import config from 'app/core/config';
import { queryBuilder } from '../../../features/variables/shared/testing/builders';
import { BROWSER_MODE_DISABLED_MESSAGE } from './constants';
import InfluxDatasource from './datasource';
import { getMockDSInstanceSettings, getMockInfluxDS, mockBackendService, replaceMock } from './mocks/datasource';
import { mockInfluxQueryRequest } from './mocks/request';
import { mockInfluxFetchResponse, mockMetricFindQueryResponse } from './mocks/response';
import { InfluxQuery, InfluxVersion } from './types';
const fetchMock = mockBackendService(mockInfluxFetchResponse());
describe('datasource initialization', () => {
it('should read the http method from jsonData', () => {
let ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' }));
expect(ds.httpMode).toBe('GET');
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' }));
expect(ds.httpMode).toBe('POST');
});
});
// Remove this suite when influxdbBackendMigration feature toggle removed
describe('InfluxDataSource Frontend Mode [influxdbBackendMigration=false]', () => {
beforeEach(() => {
// we want only frontend mode in this suite
config.featureToggles.influxdbBackendMigration = false;
jest.clearAllMocks();
});
describe('general checks', () => {
it('should throw an error if there is 200 response with error', async () => {
const ds = getMockInfluxDS();
fetchMock.mockImplementation(() => {
return of({
data: {
results: [
{
error: 'Query timeout',
},
],
},
});
});
try {
await lastValueFrom(ds.query(mockInfluxQueryRequest()));
} catch (err) {
if (err instanceof Error) {
expect(err.message).toBe('InfluxDB Error: Query timeout');
}
}
});
it('should throw an error when querying data when deprecated access mode', async () => {
expect.assertions(1);
const instanceSettings = getMockDSInstanceSettings();
instanceSettings.access = 'direct';
const ds = getMockInfluxDS(instanceSettings);
try {
await lastValueFrom(ds.query(mockInfluxQueryRequest()));
} catch (err) {
if (err instanceof Error) {
expect(err.message).toBe(BROWSER_MODE_DISABLED_MESSAGE);
}
}
});
});
describe('metricFindQuery', () => {
let ds: InfluxDatasource;
const query = 'SELECT max(value) FROM measurement WHERE $timeFilter';
const queryOptions = {
range: {
from: '2018-01-01T00:00:00Z',
to: '2018-01-02T00:00:00Z',
},
};
const fetchMockImpl = (req: BackendSrvRequest) => {
return of({
data: {
status: 'success',
results: [
{
series: [
{
name: 'measurement',
columns: ['name'],
values: [['cpu']],
},
],
},
],
},
});
};
beforeEach(async () => {
jest.clearAllMocks();
fetchMock.mockImplementation(fetchMockImpl);
});
it('should replace $timefilter', async () => {
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' }));
await ds.metricFindQuery({ refId: 'test', query }, queryOptions);
expect(fetchMock.mock.lastCall[0].params?.q).toMatch('time >= 1514764800000ms and time <= 1514851200000ms');
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' }));
await ds.metricFindQuery({ refId: 'test', query }, queryOptions);
expect(fetchMock.mock.lastCall[0].params?.q).toBeFalsy();
expect(fetchMock.mock.lastCall[0].data).toMatch(
'time%20%3E%3D%201514764800000ms%20and%20time%20%3C%3D%201514851200000ms'
);
});
it('should not have any data in request body if http mode is GET', async () => {
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' }));
await ds.metricFindQuery({ refId: 'test', query }, queryOptions);
expect(fetchMock.mock.lastCall[0].data).toBeNull();
});
it('should have data in request body if http mode is POST', async () => {
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' }));
await ds.metricFindQuery({ refId: 'test', query }, queryOptions);
expect(fetchMock.mock.lastCall[0].data).not.toBeNull();
expect(fetchMock.mock.lastCall[0].data).toMatch('q=SELECT');
});
it('parse response correctly', async () => {
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'GET' }));
let responseGet = await ds.metricFindQuery({ refId: 'test', query }, queryOptions);
expect(responseGet).toEqual([{ text: 'cpu' }]);
ds = getMockInfluxDS(getMockDSInstanceSettings({ httpMode: 'POST' }));
let responsePost = await ds.metricFindQuery({ refId: 'test', query }, queryOptions);
expect(responsePost).toEqual([{ text: 'cpu' }]);
});
});
// Update this after starting to use TemplateSrv from @grafana/runtime package
describe('adhoc variables', () => {
let ds = getMockInfluxDS(getMockDSInstanceSettings());
it('query should contain the ad-hoc variable', () => {
ds.query(mockInfluxQueryRequest());
expect(replaceMock.mock.calls[0][0]).toBe('adhoc_val');
});
it('should make the fetch call for adhoc filter keys', () => {
fetchMock.mockReturnValue(
of({
results: [
{
statement_id: 0,
series: [
{
name: 'cpu',
columns: ['tagKey'],
values: [['datacenter'], ['geohash'], ['source']],
},
],
},
],
})
);
ds.getTagKeys();
expect(fetchMock).toHaveBeenCalled();
const fetchReq = fetchMock.mock.calls[0][0];
expect(fetchReq).not.toBeNull();
expect(fetchReq.data).toMatch(encodeURIComponent(`SHOW TAG KEYS`));
});
it('should make the fetch call for adhoc filter values', () => {
fetchMock.mockReturnValue(
of({
results: [
{
statement_id: 0,
series: [
{
name: 'mykey',
columns: ['key', 'value'],
values: [['mykey', 'value']],
},
],
},
],
})
);
ds.getTagValues({ key: 'mykey', filters: [] });
expect(fetchMock).toHaveBeenCalled();
const fetchReq = fetchMock.mock.calls[0][0];
expect(fetchReq).not.toBeNull();
expect(fetchReq.data).toMatch(encodeURIComponent(`SHOW TAG VALUES WITH KEY = "mykey"`));
});
});
describe('datasource contract', () => {
let ds: InfluxDatasource;
const metricFindQueryMock = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
ds = getMockInfluxDS();
ds.metricFindQuery = metricFindQueryMock;
});
afterEach(() => {
jest.clearAllMocks();
});
it('should check the datasource has "getTagKeys" function defined', () => {
expect(Object.getOwnPropertyNames(Object.getPrototypeOf(ds))).toContain('getTagKeys');
});
it('should check the datasource has "getTagValues" function defined', () => {
expect(Object.getOwnPropertyNames(Object.getPrototypeOf(ds))).toContain('getTagValues');
});
it('should be able to call getTagKeys without specifying any parameter', () => {
ds.getTagKeys();
expect(metricFindQueryMock).toHaveBeenCalled();
});
it('should be able to call getTagValues without specifying anything but key', () => {
ds.getTagValues({ key: 'test', filters: [] });
expect(metricFindQueryMock).toHaveBeenCalled();
});
it('should use dbName instead of database', () => {
const instanceSettings = getMockDSInstanceSettings();
instanceSettings.database = 'should_not_be_used';
ds = getMockInfluxDS(instanceSettings);
expect(ds.database).toBe('site');
});
it('should fallback to use use database is dbName is not exist', () => {
const instanceSettings = getMockDSInstanceSettings();
instanceSettings.database = 'fallback';
instanceSettings.jsonData.dbName = undefined;
ds = getMockInfluxDS(instanceSettings);
expect(ds.database).toBe('fallback');
});
});
});
describe('InfluxDataSource Backend Mode [influxdbBackendMigration=true]', () => {
beforeEach(() => {
// we want only backend mode in this suite
config.featureToggles.influxdbBackendMigration = true;
jest.clearAllMocks();
});
describe('metric find query', () => {
let ds = getMockInfluxDS(getMockDSInstanceSettings());
it('handles multiple frames', async () => {
const fetchMockImpl = () => {
return of(mockMetricFindQueryResponse);
};
fetchMock.mockImplementation(fetchMockImpl);
const values = await ds.getTagValues({ key: 'test_id', filters: [] });
expect(fetchMock).toHaveBeenCalled();
expect(values.length).toBe(5);
expect(values[0].text).toBe('test-t2-1');
});
});
});
describe('interpolateQueryExpr', () => {
const templateSrvStub = {
replace: jest.fn().mockImplementation((...rest: unknown[]) => 'templateVarReplaced'),
} as unknown as TemplateSrv;
let ds = getMockInfluxDS(getMockDSInstanceSettings(), templateSrvStub);
it('should return the value as it is', () => {
const value = 'normalValue';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(value, variableMock, 'my query $tempVar');
const expectation = 'normalValue';
expect(result).toBe(expectation);
});
it('should return the escaped value if the value wrapped in regex', () => {
const value = '/special/path';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(
value,
variableMock,
'select atan(z/sqrt(3.14)), that where path =~ /$tempVar/'
);
const expectation = `\\/special\\/path`;
expect(result).toBe(expectation);
});
it('should return the escaped value if the value wrapped in regex 2', () => {
const value = '/special/path';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(
value,
variableMock,
'select atan(z/sqrt(3.14)), that where path !~ /^$tempVar$/'
);
const expectation = `\\/special\\/path`;
expect(result).toBe(expectation);
});
it('should return the escaped value if the value wrapped in regex 3', () => {
const value = ['env', 'env2', 'env3'];
const variableMock = queryBuilder()
.withId('tempVar')
.withName('tempVar')
.withMulti(false)
.withIncludeAll(true)
.build();
const result = ds.interpolateQueryExpr(
value,
variableMock,
'select atan(z/sqrt(3.14)), thing from path =~ /^($tempVar)$/'
);
const expectation = `(env|env2|env3)`;
expect(result).toBe(expectation);
});
it('should **not** return the escaped value if the value **is not** wrapped in regex', () => {
const value = '/special/path';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`);
const expectation = `/special/path`;
expect(result).toBe(expectation);
});
it('should **not** return the escaped value if the value **is not** wrapped in regex 2', () => {
const value = '12.2';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`);
const expectation = `12.2`;
expect(result).toBe(expectation);
});
it('should escape the value **always** if the variable is a multi-value variable', () => {
const value = [`/special/path`, `/some/other/path`];
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti().build();
const result = ds.interpolateQueryExpr(value, variableMock, `select that where path = '$tempVar'`);
const expectation = `(\\/special\\/path|\\/some\\/other\\/path)`;
expect(result).toBe(expectation);
});
it('should escape and join with the pipe even the variable is not multi-value', () => {
const variableMock = queryBuilder()
.withId('tempVar')
.withName('tempVar')
.withCurrent('All', '$__all')
.withMulti(false)
.withAllValue('')
.withIncludeAll()
.withOptions(
{
text: 'All',
value: '$__all',
},
{
text: `/special/path`,
value: `/special/path`,
},
{
text: `/some/other/path`,
value: `/some/other/path`,
}
)
.build();
const value = [`/special/path`, `/some/other/path`];
const result = ds.interpolateQueryExpr(value, variableMock, `select that where path =~ /$tempVar/`);
const expectation = `(\\/special\\/path|\\/some\\/other\\/path)`;
expect(result).toBe(expectation);
});
it('should **not** return the escaped value if the value **is not** wrapped in regex and the query is more complex (e.g. text is contained between two / but not a regex', () => {
const value = 'testmatch';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();
const result = ds.interpolateQueryExpr(
value,
variableMock,
`select value where ("tag"::tag =~ /value/) AND where other = $tempVar $timeFilter GROUP BY time($__interval) tz('Europe/London')`
);
const expectation = `testmatch`;
expect(result).toBe(expectation);
});
it('should return floating point number as it is', () => {
const variableMock = queryBuilder()
.withId('tempVar')
.withName('tempVar')
.withMulti(false)
.withOptions({
text: `1.0`,
value: `1.0`,
})
.build();
const value = `1.0`;
const result = ds.interpolateQueryExpr(value, variableMock, `select value / $tempVar from /^measurement$/`);
const expectation = `1.0`;
expect(result).toBe(expectation);
});
it('template var in adhoc', () => {
const templateVarName = '$templateVarName';
const templateVarValue = 'templateVarValue';
const templateSrvStub = {
replace: jest
.fn()
.mockImplementation((target?: string) => (target === templateVarName ? templateVarValue : target)),
} as unknown as TemplateSrv;
const ds = getMockInfluxDS(getMockDSInstanceSettings(), templateSrvStub);
ds.version = InfluxVersion.SQL;
const adhocFilter: AdHocVariableFilter[] = [{ key: 'bar', value: templateVarName, operator: '=' }];
const result = ds.applyTemplateVariables(mockInfluxQueryRequest() as unknown as InfluxQuery, {}, adhocFilter);
expect(result.tags![0].value).toBe(templateVarValue);
expect(result.adhocFilters![0].value).toBe(templateVarValue);
});
});