mirror of https://github.com/grafana/grafana.git
727 lines
32 KiB
TypeScript
727 lines
32 KiB
TypeScript
import 'whatwg-fetch'; // fetch polyfill needed for PhantomJs rendering
|
|
import { Observable, of } from 'rxjs';
|
|
import { delay } from 'rxjs/operators';
|
|
import { AppEvents } from '@grafana/data';
|
|
|
|
import {
|
|
BackendSrv,
|
|
getBackendSrv,
|
|
isContentTypeApplicationJson,
|
|
parseBody,
|
|
parseHeaders,
|
|
parseInitFromOptions,
|
|
parseUrlFromOptions,
|
|
} from '../services/backend_srv';
|
|
import { Emitter } from '../utils/emitter';
|
|
import { ContextSrv, User } from '../services/context_srv';
|
|
import { CoreEvents } from '../../types';
|
|
|
|
const getTestContext = (overides?: object) => {
|
|
const defaults = {
|
|
data: { test: 'hello world' },
|
|
ok: true,
|
|
status: 200,
|
|
statusText: 'Ok',
|
|
isSignedIn: true,
|
|
orgId: 1337,
|
|
redirected: false,
|
|
type: 'basic',
|
|
url: 'http://localhost:3000/api/some-mock',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
};
|
|
const props = { ...defaults, ...overides };
|
|
const textMock = jest.fn().mockResolvedValue(JSON.stringify(props.data));
|
|
const fromFetchMock = jest.fn().mockImplementation(() => {
|
|
const mockedResponse = {
|
|
ok: props.ok,
|
|
status: props.status,
|
|
statusText: props.statusText,
|
|
text: textMock,
|
|
redirected: false,
|
|
type: 'basic',
|
|
url: 'http://localhost:3000/api/some-mock',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
};
|
|
return of(mockedResponse);
|
|
});
|
|
const appEventsMock: Emitter = ({
|
|
emit: jest.fn(),
|
|
} as any) as Emitter;
|
|
const user: User = ({
|
|
isSignedIn: props.isSignedIn,
|
|
orgId: props.orgId,
|
|
} as any) as User;
|
|
const contextSrvMock: ContextSrv = ({
|
|
user,
|
|
} as any) as ContextSrv;
|
|
const logoutMock = jest.fn();
|
|
const parseRequestOptionsMock = jest.fn().mockImplementation(options => options);
|
|
const parseDataSourceRequestOptionsMock = jest.fn().mockImplementation(options => options);
|
|
|
|
const backendSrv = new BackendSrv({
|
|
fromFetch: fromFetchMock,
|
|
appEvents: appEventsMock,
|
|
contextSrv: contextSrvMock,
|
|
logout: logoutMock,
|
|
});
|
|
|
|
backendSrv['parseRequestOptions'] = parseRequestOptionsMock;
|
|
backendSrv['parseDataSourceRequestOptions'] = parseDataSourceRequestOptionsMock;
|
|
|
|
const expectCallChain = (options: any) => {
|
|
expect(fromFetchMock).toHaveBeenCalledTimes(1);
|
|
};
|
|
|
|
const expectRequestCallChain = (options: any) => {
|
|
expect(parseRequestOptionsMock).toHaveBeenCalledTimes(1);
|
|
expect(parseRequestOptionsMock).toHaveBeenCalledWith(options, 1337);
|
|
expectCallChain(options);
|
|
};
|
|
|
|
const expectDataSourceRequestCallChain = (options: any) => {
|
|
expect(parseDataSourceRequestOptionsMock).toHaveBeenCalledTimes(1);
|
|
expect(parseDataSourceRequestOptionsMock).toHaveBeenCalledWith(options, 1337, undefined);
|
|
expectCallChain(options);
|
|
};
|
|
|
|
return {
|
|
backendSrv,
|
|
fromFetchMock,
|
|
appEventsMock,
|
|
contextSrvMock,
|
|
textMock,
|
|
logoutMock,
|
|
parseRequestOptionsMock,
|
|
parseDataSourceRequestOptionsMock,
|
|
expectRequestCallChain,
|
|
expectDataSourceRequestCallChain,
|
|
};
|
|
};
|
|
|
|
describe('backendSrv', () => {
|
|
describe('parseRequestOptions', () => {
|
|
it.each`
|
|
retry | url | orgId | expected
|
|
${undefined} | ${'http://localhost:3000/api/dashboard'} | ${undefined} | ${{ retry: 0, url: 'http://localhost:3000/api/dashboard' }}
|
|
${1} | ${'http://localhost:3000/api/dashboard'} | ${1} | ${{ retry: 1, url: 'http://localhost:3000/api/dashboard' }}
|
|
${undefined} | ${'api/dashboard'} | ${undefined} | ${{ retry: 0, url: 'api/dashboard' }}
|
|
${undefined} | ${'/api/dashboard'} | ${undefined} | ${{ retry: 0, url: 'api/dashboard' }}
|
|
${undefined} | ${'/api/dashboard/'} | ${undefined} | ${{ retry: 0, url: 'api/dashboard' }}
|
|
${1} | ${'/api/dashboard/'} | ${undefined} | ${{ retry: 1, url: 'api/dashboard' }}
|
|
${undefined} | ${'/api/dashboard/'} | ${1} | ${{ retry: 0, url: 'api/dashboard', headers: { 'X-Grafana-Org-Id': 1 } }}
|
|
${1} | ${'/api/dashboard/'} | ${1} | ${{ retry: 1, url: 'api/dashboard', headers: { 'X-Grafana-Org-Id': 1 } }}
|
|
`(
|
|
"when called with retry: '$retry', url: '$url' and orgId: '$orgId' then result should be '$expected'",
|
|
({ retry, url, orgId, expected }) => {
|
|
expect(getBackendSrv()['parseRequestOptions']({ retry, url }, orgId)).toEqual(expected);
|
|
}
|
|
);
|
|
});
|
|
|
|
describe('parseDataSourceRequestOptions', () => {
|
|
it.each`
|
|
retry | url | headers | orgId | noBackendCache | expected
|
|
${undefined} | ${'http://localhost:3000/api/dashboard'} | ${undefined} | ${undefined} | ${undefined} | ${{ retry: 0, url: 'http://localhost:3000/api/dashboard' }}
|
|
${1} | ${'http://localhost:3000/api/dashboard'} | ${{ Authorization: 'Some Auth' }} | ${1} | ${true} | ${{ retry: 1, url: 'http://localhost:3000/api/dashboard', headers: { Authorization: 'Some Auth' } }}
|
|
${undefined} | ${'api/dashboard'} | ${undefined} | ${undefined} | ${undefined} | ${{ retry: 0, url: 'api/dashboard' }}
|
|
${undefined} | ${'/api/dashboard'} | ${undefined} | ${undefined} | ${undefined} | ${{ retry: 0, url: 'api/dashboard' }}
|
|
${undefined} | ${'/api/dashboard/'} | ${undefined} | ${undefined} | ${undefined} | ${{ retry: 0, url: 'api/dashboard/' }}
|
|
${undefined} | ${'/api/dashboard/'} | ${{ Authorization: 'Some Auth' }} | ${undefined} | ${undefined} | ${{ retry: 0, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth' } }}
|
|
${undefined} | ${'/api/dashboard/'} | ${{ Authorization: 'Some Auth' }} | ${1} | ${undefined} | ${{ retry: 0, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth', 'X-Grafana-Org-Id': 1 } }}
|
|
${undefined} | ${'/api/dashboard/'} | ${{ Authorization: 'Some Auth' }} | ${1} | ${true} | ${{ retry: 0, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth', 'X-Grafana-Org-Id': 1, 'X-Grafana-NoCache': 'true' } }}
|
|
${1} | ${'/api/dashboard/'} | ${undefined} | ${undefined} | ${undefined} | ${{ retry: 1, url: 'api/dashboard/' }}
|
|
${1} | ${'/api/dashboard/'} | ${{ Authorization: 'Some Auth' }} | ${undefined} | ${undefined} | ${{ retry: 1, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth' } }}
|
|
${1} | ${'/api/dashboard/'} | ${{ Authorization: 'Some Auth' }} | ${1} | ${undefined} | ${{ retry: 1, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth', 'X-Grafana-Org-Id': 1 } }}
|
|
${1} | ${'/api/dashboard/'} | ${{ Authorization: 'Some Auth' }} | ${1} | ${true} | ${{ retry: 1, url: 'api/dashboard/', headers: { 'X-DS-Authorization': 'Some Auth', 'X-Grafana-Org-Id': 1, 'X-Grafana-NoCache': 'true' } }}
|
|
`(
|
|
"when called with retry: '$retry', url: '$url', headers: '$headers', orgId: '$orgId' and noBackendCache: '$noBackendCache' then result should be '$expected'",
|
|
({ retry, url, headers, orgId, noBackendCache, expected }) => {
|
|
expect(
|
|
getBackendSrv()['parseDataSourceRequestOptions']({ retry, url, headers }, orgId, noBackendCache)
|
|
).toEqual(expected);
|
|
}
|
|
);
|
|
});
|
|
|
|
describe('request', () => {
|
|
describe('when making a successful call and conditions for showSuccessAlert are not favorable', () => {
|
|
it('then it should return correct result and not emit anything', async () => {
|
|
const { backendSrv, appEventsMock, expectRequestCallChain } = getTestContext({
|
|
data: { message: 'A message' },
|
|
});
|
|
const url = '/api/dashboard/';
|
|
const result = await backendSrv.request({ url, method: 'DELETE', showSuccessAlert: false });
|
|
expect(result).toEqual({ message: 'A message' });
|
|
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
|
expectRequestCallChain({ url, method: 'DELETE', showSuccessAlert: false });
|
|
});
|
|
});
|
|
|
|
describe('when making a successful call and conditions for showSuccessAlert are favorable', () => {
|
|
it('then it should emit correct message', async () => {
|
|
const { backendSrv, appEventsMock, expectRequestCallChain } = getTestContext({
|
|
data: { message: 'A message' },
|
|
});
|
|
const url = '/api/dashboard/';
|
|
const result = await backendSrv.request({ url, method: 'DELETE', showSuccessAlert: true });
|
|
expect(result).toEqual({ message: 'A message' });
|
|
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
|
expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvents.alertSuccess, ['A message']);
|
|
expectRequestCallChain({ url, method: 'DELETE', showSuccessAlert: true });
|
|
});
|
|
});
|
|
|
|
describe('when called with the same requestId twice', () => {
|
|
it('then it should cancel the first call and the first call should be unsubscribed', async () => {
|
|
const url = '/api/dashboard/';
|
|
const { backendSrv, fromFetchMock } = getTestContext({ url });
|
|
const unsubscribe = jest.fn();
|
|
const slowData = { message: 'Slow Request' };
|
|
const slowFetch = new Observable(subscriber => {
|
|
subscriber.next({
|
|
ok: true,
|
|
status: 200,
|
|
statusText: 'Ok',
|
|
text: () => Promise.resolve(JSON.stringify(slowData)),
|
|
headers: {
|
|
map: {
|
|
'content-type': 'application/json',
|
|
},
|
|
},
|
|
redirected: false,
|
|
type: 'basic',
|
|
url,
|
|
});
|
|
return unsubscribe;
|
|
}).pipe(delay(10000));
|
|
const fastData = { message: 'Fast Request' };
|
|
const fastFetch = of({
|
|
ok: true,
|
|
status: 200,
|
|
statusText: 'Ok',
|
|
text: () => Promise.resolve(JSON.stringify(fastData)),
|
|
headers: {
|
|
map: {
|
|
'content-type': 'application/json',
|
|
},
|
|
},
|
|
redirected: false,
|
|
type: 'basic',
|
|
url,
|
|
});
|
|
fromFetchMock.mockImplementationOnce(() => slowFetch);
|
|
fromFetchMock.mockImplementation(() => fastFetch);
|
|
const options = {
|
|
url,
|
|
method: 'GET',
|
|
requestId: 'A',
|
|
};
|
|
const slowRequest = backendSrv.request(options);
|
|
const fastResponse = await backendSrv.request(options);
|
|
expect(fastResponse).toEqual({ message: 'Fast Request' });
|
|
|
|
const result = await slowRequest;
|
|
expect(result).toEqual([]);
|
|
expect(unsubscribe).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('when making an unsuccessful call and conditions for retry are favorable and loginPing does not throw', () => {
|
|
it('then it should retry', async () => {
|
|
jest.useFakeTimers();
|
|
const url = '/api/dashboard/';
|
|
const { backendSrv, appEventsMock, logoutMock, expectRequestCallChain } = getTestContext({
|
|
ok: false,
|
|
status: 401,
|
|
statusText: 'UnAuthorized',
|
|
data: { message: 'UnAuthorized' },
|
|
url,
|
|
});
|
|
backendSrv.loginPing = jest
|
|
.fn()
|
|
.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', data: { message: 'Ok' } });
|
|
await backendSrv
|
|
.request({ url, method: 'GET', retry: 0 })
|
|
.catch(error => {
|
|
expect(error.status).toBe(401);
|
|
expect(error.statusText).toBe('UnAuthorized');
|
|
expect(error.data).toEqual({ message: 'UnAuthorized' });
|
|
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
|
expect(logoutMock).not.toHaveBeenCalled();
|
|
expect(backendSrv.loginPing).toHaveBeenCalledTimes(1);
|
|
expectRequestCallChain({ url, method: 'GET', retry: 0 });
|
|
jest.advanceTimersByTime(50);
|
|
})
|
|
.catch(error => {
|
|
expect(error).toEqual({ message: 'UnAuthorized' });
|
|
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
|
expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvents.alertWarning, ['UnAuthorized', '']);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when making an unsuccessful call and conditions for retry are favorable and retry throws', () => {
|
|
it('then it throw error', async () => {
|
|
jest.useFakeTimers();
|
|
const { backendSrv, appEventsMock, logoutMock, expectRequestCallChain } = getTestContext({
|
|
ok: false,
|
|
status: 401,
|
|
statusText: 'UnAuthorized',
|
|
data: { message: 'UnAuthorized' },
|
|
});
|
|
backendSrv.loginPing = jest
|
|
.fn()
|
|
.mockRejectedValue({ status: 403, statusText: 'Forbidden', data: { message: 'Forbidden' } });
|
|
const url = '/api/dashboard/';
|
|
await backendSrv
|
|
.request({ url, method: 'GET', retry: 0 })
|
|
.catch(error => {
|
|
expect(error.status).toBe(403);
|
|
expect(error.statusText).toBe('Forbidden');
|
|
expect(error.data).toEqual({ message: 'Forbidden' });
|
|
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
|
expect(backendSrv.loginPing).toHaveBeenCalledTimes(1);
|
|
expect(logoutMock).not.toHaveBeenCalled();
|
|
expectRequestCallChain({ url, method: 'GET', retry: 0 });
|
|
jest.advanceTimersByTime(50);
|
|
})
|
|
.catch(error => {
|
|
expect(error).toEqual({ message: 'Forbidden' });
|
|
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
|
expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvents.alertWarning, ['Forbidden', '']);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when making an unsuccessful 422 call', () => {
|
|
it('then it should emit Validation failed message', async () => {
|
|
jest.useFakeTimers();
|
|
const { backendSrv, appEventsMock, logoutMock, expectRequestCallChain } = getTestContext({
|
|
ok: false,
|
|
status: 422,
|
|
statusText: 'Unprocessable Entity',
|
|
data: { message: 'Unprocessable Entity' },
|
|
});
|
|
const url = '/api/dashboard/';
|
|
await backendSrv
|
|
.request({ url, method: 'GET' })
|
|
.catch(error => {
|
|
expect(error.status).toBe(422);
|
|
expect(error.statusText).toBe('Unprocessable Entity');
|
|
expect(error.data).toEqual({ message: 'Unprocessable Entity' });
|
|
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
|
expect(logoutMock).not.toHaveBeenCalled();
|
|
expectRequestCallChain({ url, method: 'GET' });
|
|
jest.advanceTimersByTime(50);
|
|
})
|
|
.catch(error => {
|
|
expect(error).toEqual({ message: 'Unprocessable Entity' });
|
|
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
|
expect(appEventsMock.emit).toHaveBeenCalledWith(AppEvents.alertWarning, [
|
|
'Validation failed',
|
|
'Unprocessable Entity',
|
|
]);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when making an unsuccessful call and we handle the error', () => {
|
|
it('then it should not emit message', async () => {
|
|
jest.useFakeTimers();
|
|
const { backendSrv, appEventsMock, logoutMock, expectRequestCallChain } = getTestContext({
|
|
ok: false,
|
|
status: 404,
|
|
statusText: 'Not found',
|
|
data: { message: 'Not found' },
|
|
});
|
|
const url = '/api/dashboard/';
|
|
await backendSrv.request({ url, method: 'GET' }).catch(error => {
|
|
expect(error.status).toBe(404);
|
|
expect(error.statusText).toBe('Not found');
|
|
expect(error.data).toEqual({ message: 'Not found' });
|
|
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
|
expect(logoutMock).not.toHaveBeenCalled();
|
|
expectRequestCallChain({ url, method: 'GET' });
|
|
error.isHandled = true;
|
|
jest.advanceTimersByTime(50);
|
|
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('datasourceRequest', () => {
|
|
describe('when making a successful call and silent is true', () => {
|
|
it('then it should not emit message', async () => {
|
|
const url = 'http://localhost:3000/api/some-mock';
|
|
const { backendSrv, appEventsMock, expectDataSourceRequestCallChain } = getTestContext({ url });
|
|
const result = await backendSrv.datasourceRequest({ url, method: 'GET', silent: true });
|
|
expect(result).toEqual({
|
|
data: { test: 'hello world' },
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
ok: true,
|
|
redirected: false,
|
|
status: 200,
|
|
statusText: 'Ok',
|
|
type: 'basic',
|
|
url,
|
|
request: {
|
|
url,
|
|
method: 'GET',
|
|
body: undefined,
|
|
headers: {
|
|
map: {
|
|
'content-type': 'application/json',
|
|
accept: 'application/json, text/plain, */*',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
expect(appEventsMock.emit).not.toHaveBeenCalled();
|
|
expectDataSourceRequestCallChain({ url, method: 'GET', silent: true });
|
|
});
|
|
});
|
|
|
|
describe('when making a successful call and silent is not defined', () => {
|
|
it('then it should not emit message', async () => {
|
|
const url = 'http://localhost:3000/api/some-mock';
|
|
const { backendSrv, appEventsMock, expectDataSourceRequestCallChain } = getTestContext({ url });
|
|
const result = await backendSrv.datasourceRequest({ url, method: 'GET' });
|
|
const expectedResult = {
|
|
data: { test: 'hello world' },
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
ok: true,
|
|
redirected: false,
|
|
status: 200,
|
|
statusText: 'Ok',
|
|
type: 'basic',
|
|
url,
|
|
request: {
|
|
url,
|
|
method: 'GET',
|
|
body: undefined as any,
|
|
headers: {
|
|
map: {
|
|
'content-type': 'application/json',
|
|
accept: 'application/json, text/plain, */*',
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
expect(result).toEqual(expectedResult);
|
|
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
|
expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestResponse, expectedResult);
|
|
expectDataSourceRequestCallChain({ url, method: 'GET' });
|
|
});
|
|
});
|
|
|
|
describe('when called with the same requestId twice', () => {
|
|
it('then it should cancel the first call and the first call should be unsubscribed', async () => {
|
|
const url = '/api/dashboard/';
|
|
const { backendSrv, fromFetchMock } = getTestContext({ url });
|
|
const unsubscribe = jest.fn();
|
|
const slowData = { message: 'Slow Request' };
|
|
const slowFetch = new Observable(subscriber => {
|
|
subscriber.next({
|
|
ok: true,
|
|
status: 200,
|
|
statusText: 'Ok',
|
|
text: () => Promise.resolve(JSON.stringify(slowData)),
|
|
headers: {
|
|
map: {
|
|
'content-type': 'application/json',
|
|
},
|
|
},
|
|
redirected: false,
|
|
type: 'basic',
|
|
url,
|
|
});
|
|
return unsubscribe;
|
|
}).pipe(delay(10000));
|
|
const fastData = { message: 'Fast Request' };
|
|
const fastFetch = of({
|
|
ok: true,
|
|
status: 200,
|
|
statusText: 'Ok',
|
|
text: () => Promise.resolve(JSON.stringify(fastData)),
|
|
headers: {
|
|
map: {
|
|
'content-type': 'application/json',
|
|
},
|
|
},
|
|
redirected: false,
|
|
type: 'basic',
|
|
url,
|
|
});
|
|
fromFetchMock.mockImplementationOnce(() => slowFetch);
|
|
fromFetchMock.mockImplementation(() => fastFetch);
|
|
const options = {
|
|
url,
|
|
method: 'GET',
|
|
requestId: 'A',
|
|
};
|
|
const slowRequest = backendSrv.datasourceRequest(options);
|
|
const fastResponse = await backendSrv.datasourceRequest(options);
|
|
expect(fastResponse).toEqual({
|
|
data: { message: 'Fast Request' },
|
|
headers: {
|
|
map: {
|
|
'content-type': 'application/json',
|
|
},
|
|
},
|
|
ok: true,
|
|
redirected: false,
|
|
status: 200,
|
|
statusText: 'Ok',
|
|
type: 'basic',
|
|
url: '/api/dashboard/',
|
|
request: {
|
|
url: '/api/dashboard/',
|
|
method: 'GET',
|
|
body: undefined,
|
|
headers: {
|
|
map: {
|
|
'content-type': 'application/json',
|
|
accept: 'application/json, text/plain, */*',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const result = await slowRequest;
|
|
expect(result).toEqual({
|
|
data: [],
|
|
status: -1,
|
|
statusText: 'Request was aborted',
|
|
request: {
|
|
url: '/api/dashboard/',
|
|
method: 'GET',
|
|
body: undefined,
|
|
headers: {
|
|
map: {
|
|
'content-type': 'application/json',
|
|
accept: 'application/json, text/plain, */*',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
expect(unsubscribe).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('when making an unsuccessful call and conditions for retry are favorable and loginPing does not throw', () => {
|
|
it('then it should retry', async () => {
|
|
const { backendSrv, appEventsMock, logoutMock, expectDataSourceRequestCallChain } = getTestContext({
|
|
ok: false,
|
|
status: 401,
|
|
statusText: 'UnAuthorized',
|
|
data: { message: 'UnAuthorized' },
|
|
});
|
|
backendSrv.loginPing = jest
|
|
.fn()
|
|
.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', data: { message: 'Ok' } });
|
|
const url = '/api/dashboard/';
|
|
await backendSrv.datasourceRequest({ url, method: 'GET', retry: 0 }).catch(error => {
|
|
expect(error.status).toBe(401);
|
|
expect(error.statusText).toBe('UnAuthorized');
|
|
expect(error.data).toEqual({ message: 'UnAuthorized' });
|
|
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
|
expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestError, {
|
|
data: { message: 'UnAuthorized' },
|
|
status: 401,
|
|
statusText: 'UnAuthorized',
|
|
});
|
|
expect(backendSrv.loginPing).toHaveBeenCalledTimes(1);
|
|
expect(logoutMock).not.toHaveBeenCalled();
|
|
expectDataSourceRequestCallChain({ url, method: 'GET', retry: 0 });
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when making an unsuccessful call and conditions for retry are favorable and retry throws', () => {
|
|
it('then it throw error', async () => {
|
|
const { backendSrv, appEventsMock, logoutMock, expectDataSourceRequestCallChain } = getTestContext({
|
|
ok: false,
|
|
status: 401,
|
|
statusText: 'UnAuthorized',
|
|
data: { message: 'UnAuthorized' },
|
|
});
|
|
backendSrv.loginPing = jest
|
|
.fn()
|
|
.mockRejectedValue({ status: 403, statusText: 'Forbidden', data: { message: 'Forbidden' } });
|
|
const url = '/api/dashboard/';
|
|
await backendSrv.datasourceRequest({ url, method: 'GET', retry: 0 }).catch(error => {
|
|
expect(error.status).toBe(403);
|
|
expect(error.statusText).toBe('Forbidden');
|
|
expect(error.data).toEqual({ message: 'Forbidden' });
|
|
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
|
expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestError, {
|
|
data: { message: 'Forbidden' },
|
|
status: 403,
|
|
statusText: 'Forbidden',
|
|
});
|
|
expect(backendSrv.loginPing).toHaveBeenCalledTimes(1);
|
|
expect(logoutMock).not.toHaveBeenCalled();
|
|
expectDataSourceRequestCallChain({ url, method: 'GET', retry: 0 });
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when making an Internal Error call', () => {
|
|
it('then it should throw cancelled error', async () => {
|
|
const { backendSrv, appEventsMock, logoutMock, expectDataSourceRequestCallChain } = getTestContext({
|
|
ok: false,
|
|
status: 500,
|
|
statusText: 'Internal Server Error',
|
|
data: 'Internal Server Error',
|
|
});
|
|
const url = '/api/dashboard/';
|
|
await backendSrv.datasourceRequest({ url, method: 'GET' }).catch(error => {
|
|
expect(error).toEqual({
|
|
status: 500,
|
|
statusText: 'Internal Server Error',
|
|
data: {
|
|
error: 'Internal Server Error',
|
|
response: 'Internal Server Error',
|
|
message: 'Internal Server Error',
|
|
},
|
|
});
|
|
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
|
expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestError, {
|
|
status: 500,
|
|
statusText: 'Internal Server Error',
|
|
data: {
|
|
error: 'Internal Server Error',
|
|
response: 'Internal Server Error',
|
|
message: 'Internal Server Error',
|
|
},
|
|
});
|
|
expect(logoutMock).not.toHaveBeenCalled();
|
|
expectDataSourceRequestCallChain({ url, method: 'GET' });
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when formatting prometheus error', () => {
|
|
it('then it should throw cancelled error', async () => {
|
|
const { backendSrv, appEventsMock, logoutMock, expectDataSourceRequestCallChain } = getTestContext({
|
|
ok: false,
|
|
status: 403,
|
|
statusText: 'Forbidden',
|
|
data: { error: 'Forbidden' },
|
|
});
|
|
const url = '/api/dashboard/';
|
|
await backendSrv.datasourceRequest({ url, method: 'GET' }).catch(error => {
|
|
expect(error).toEqual({
|
|
status: 403,
|
|
statusText: 'Forbidden',
|
|
data: {
|
|
error: 'Forbidden',
|
|
message: 'Forbidden',
|
|
},
|
|
});
|
|
expect(appEventsMock.emit).toHaveBeenCalledTimes(1);
|
|
expect(appEventsMock.emit).toHaveBeenCalledWith(CoreEvents.dsRequestError, {
|
|
status: 403,
|
|
statusText: 'Forbidden',
|
|
data: {
|
|
error: 'Forbidden',
|
|
message: 'Forbidden',
|
|
},
|
|
});
|
|
expect(logoutMock).not.toHaveBeenCalled();
|
|
expectDataSourceRequestCallChain({ url, method: 'GET' });
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('parseUrlFromOptions', () => {
|
|
it.each`
|
|
params | url | expected
|
|
${undefined} | ${'api/dashboard'} | ${'api/dashboard'}
|
|
${{ key: 'value' }} | ${'api/dashboard'} | ${'api/dashboard?key=value'}
|
|
${{ key: undefined }} | ${'api/dashboard'} | ${'api/dashboard'}
|
|
${{ firstKey: 'first value', secondValue: 'second value' }} | ${'api/dashboard'} | ${'api/dashboard?firstKey=first%20value&secondValue=second%20value'}
|
|
${{ firstKey: 'first value', secondValue: undefined }} | ${'api/dashboard'} | ${'api/dashboard?firstKey=first%20value'}
|
|
${{ id: [1, 2, 3] }} | ${'api/dashboard'} | ${'api/dashboard?id=1&id=2&id=3'}
|
|
${{ id: [] }} | ${'api/dashboard'} | ${'api/dashboard'}
|
|
`(
|
|
"when called with params: '$params' and url: '$url' then result should be '$expected'",
|
|
({ params, url, expected }) => {
|
|
expect(parseUrlFromOptions({ params, url })).toEqual(expected);
|
|
}
|
|
);
|
|
});
|
|
|
|
describe('parseInitFromOptions', () => {
|
|
it.each`
|
|
method | expected
|
|
${undefined} | ${{ method: undefined, headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
|
|
${'GET'} | ${{ method: 'GET', headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
|
|
${'POST'} | ${{ method: 'POST', headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
|
|
${'monkey'} | ${{ method: 'monkey', headers: { map: { 'content-type': 'application/json', accept: 'application/json, text/plain, */*' } }, body: '{"id":"0"}' }}
|
|
`(
|
|
"when called with method: '$method', headers: '$headers' and data: '$data' then result should be '$expected'",
|
|
({ method, expected }) => {
|
|
expect(parseInitFromOptions({ method, data: { id: '0' }, url: '' })).toEqual(expected);
|
|
}
|
|
);
|
|
});
|
|
|
|
describe('parseHeaders', () => {
|
|
it.each`
|
|
options | expected
|
|
${undefined} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
|
${{ propKey: 'some prop value' }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
|
${{ headers: { 'content-type': 'application/json' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
|
${{ headers: { 'cOnTent-tYpe': 'application/json' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json' } }}
|
|
${{ headers: { 'content-type': 'AppLiCatIon/JsOn' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'AppLiCatIon/JsOn' } }}
|
|
${{ headers: { 'cOnTent-tYpe': 'AppLiCatIon/JsOn' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'AppLiCatIon/JsOn' } }}
|
|
${{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/x-www-form-urlencoded' } }}
|
|
${{ headers: { Accept: 'text/plain' } }} | ${{ map: { accept: 'text/plain', 'content-type': 'application/json' } }}
|
|
${{ headers: { Auth: 'Basic asdasdasd' } }} | ${{ map: { accept: 'application/json, text/plain, */*', 'content-type': 'application/json', auth: 'Basic asdasdasd' } }}
|
|
`("when called with options: '$options' then the result should be '$expected'", ({ options, expected }) => {
|
|
expect(parseHeaders(options)).toEqual(expected);
|
|
});
|
|
});
|
|
|
|
describe('isContentTypeApplicationJson', () => {
|
|
it.each`
|
|
headers | expected
|
|
${undefined} | ${false}
|
|
${new Headers({ 'cOnTent-tYpe': 'application/json' })} | ${true}
|
|
${new Headers({ 'content-type': 'AppLiCatIon/JsOn' })} | ${true}
|
|
${new Headers({ 'cOnTent-tYpe': 'AppLiCatIon/JsOn' })} | ${true}
|
|
${new Headers({ 'content-type': 'application/x-www-form-urlencoded' })} | ${false}
|
|
${new Headers({ auth: 'Basic akdjasdkjalksdjasd' })} | ${false}
|
|
`("when called with headers: 'headers' then the result should be '$expected'", ({ headers, expected }) => {
|
|
expect(isContentTypeApplicationJson(headers)).toEqual(expected);
|
|
});
|
|
});
|
|
|
|
describe('parseBody', () => {
|
|
it.each`
|
|
options | isAppJson | expected
|
|
${undefined} | ${false} | ${undefined}
|
|
${undefined} | ${true} | ${undefined}
|
|
${{ data: undefined }} | ${false} | ${undefined}
|
|
${{ data: undefined }} | ${true} | ${undefined}
|
|
${{ data: 'some data' }} | ${false} | ${'some data'}
|
|
${{ data: 'some data' }} | ${true} | ${'some data'}
|
|
${{ data: { id: '0' } }} | ${false} | ${new URLSearchParams({ id: '0' })}
|
|
${{ data: { id: '0' } }} | ${true} | ${'{"id":"0"}'}
|
|
`(
|
|
"when called with options: '$options' and isAppJson: '$isAppJson' then the result should be '$expected'",
|
|
({ options, isAppJson, expected }) => {
|
|
expect(parseBody(options, isAppJson)).toEqual(expected);
|
|
}
|
|
);
|
|
});
|