Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enterprise Search] Update enterpriseSearchRequestHandler to manage range of errors + add handleAPIErrors helper #77258

Merged
merged 8 commits into from
Sep 15, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -30,109 +30,183 @@ describe('EnterpriseSearchRequestHandler', () => {
fetchMock.mockReset();
});

it('makes an API call and returns the response', async () => {
const responseBody = {
results: [{ name: 'engine1' }],
meta: { page: { total_results: 1 } },
};
describe('createRequest()', () => {
it('makes an API call and returns the response', async () => {
const responseBody = {
results: [{ name: 'engine1' }],
meta: { page: { total_results: 1 } },
};

EnterpriseSearchAPI.mockReturn(responseBody);
EnterpriseSearchAPI.mockReturn(responseBody);

const requestHandler = enterpriseSearchRequestHandler.createRequest({
path: '/as/credentials/collection',
});
const requestHandler = enterpriseSearchRequestHandler.createRequest({
path: '/as/credentials/collection',
});

await makeAPICall(requestHandler, {
query: {
type: 'indexed',
pageIndex: 1,
},
});
await makeAPICall(requestHandler, {
query: {
type: 'indexed',
pageIndex: 1,
},
});

EnterpriseSearchAPI.shouldHaveBeenCalledWith(
'http://localhost:3002/as/credentials/collection?type=indexed&pageIndex=1',
{ method: 'GET' }
);
EnterpriseSearchAPI.shouldHaveBeenCalledWith(
'http://localhost:3002/as/credentials/collection?type=indexed&pageIndex=1',
{ method: 'GET' }
);

expect(responseMock.custom).toHaveBeenCalledWith({
body: responseBody,
statusCode: 200,
expect(responseMock.custom).toHaveBeenCalledWith({
body: responseBody,
statusCode: 200,
});
});
});

describe('request passing', () => {
it('passes route method', async () => {
const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/example' });
describe('request passing', () => {
it('passes route method', async () => {
const requestHandler = enterpriseSearchRequestHandler.createRequest({
path: '/api/example',
});

await makeAPICall(requestHandler, { route: { method: 'POST' } });
EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', {
method: 'POST',
});

await makeAPICall(requestHandler, { route: { method: 'POST' } });
EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', {
method: 'POST',
await makeAPICall(requestHandler, { route: { method: 'DELETE' } });
EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', {
method: 'DELETE',
});
});

await makeAPICall(requestHandler, { route: { method: 'DELETE' } });
EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', {
method: 'DELETE',
it('passes request body', async () => {
const requestHandler = enterpriseSearchRequestHandler.createRequest({
path: '/api/example',
});
await makeAPICall(requestHandler, { body: { bodacious: true } });

EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', {
body: '{"bodacious":true}',
});
});
});

it('passes request body', async () => {
const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/example' });
await makeAPICall(requestHandler, { body: { bodacious: true } });
it('passes custom params set by the handler, which override request params', async () => {
const requestHandler = enterpriseSearchRequestHandler.createRequest({
path: '/api/example',
params: { someQuery: true },
});
await makeAPICall(requestHandler, { query: { someQuery: false } });

EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example', {
body: '{"bodacious":true}',
EnterpriseSearchAPI.shouldHaveBeenCalledWith(
'http://localhost:3002/api/example?someQuery=true'
);
});
});

it('passes custom params set by the handler, which override request params', async () => {
const requestHandler = enterpriseSearchRequestHandler.createRequest({
path: '/api/example',
params: { someQuery: true },
describe('response passing', () => {
it('returns the response status code from Enterprise Search', async () => {
EnterpriseSearchAPI.mockReturn({}, { status: 201 });

const requestHandler = enterpriseSearchRequestHandler.createRequest({
path: '/api/example',
});
await makeAPICall(requestHandler);

EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example');
expect(responseMock.custom).toHaveBeenCalledWith({ body: {}, statusCode: 201 });
});
await makeAPICall(requestHandler, { query: { someQuery: false } });

EnterpriseSearchAPI.shouldHaveBeenCalledWith(
'http://localhost:3002/api/example?someQuery=true'
);
// TODO: It's possible we may also pass back headers at some point
// from Enterprise Search, e.g. the x-read-only mode header
});
});

describe('response passing', () => {
it('returns the response status code from Enterprise Search', async () => {
EnterpriseSearchAPI.mockReturn({}, { status: 404 });
describe('error responses', () => {
describe('handleClientError()', () => {
afterEach(() => {
EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/4xx');
expect(mockLogger.error).not.toHaveBeenCalled();
});

const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/example' });
await makeAPICall(requestHandler);
it('passes back json.error', async () => {
const error = 'some error message';
EnterpriseSearchAPI.mockReturn({ error }, { status: 404, headers: JSON_HEADER });

EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/example');
expect(responseMock.custom).toHaveBeenCalledWith({ body: {}, statusCode: 404 });
});
const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/4xx' });
await makeAPICall(requestHandler);

// TODO: It's possible we may also pass back headers at some point
// from Enterprise Search, e.g. the x-read-only mode header
});
expect(responseMock.customError).toHaveBeenCalledWith({
statusCode: 404,
body: {
message: 'some error message',
attributes: { errors: ['some error message'] },
},
});
});

describe('error handling', () => {
afterEach(() => {
expect(mockLogger.error).toHaveBeenCalledWith(
expect.stringContaining('Error connecting to Enterprise Search')
);
it('passes back json.errors', async () => {
const errors = ['one', 'two', 'three'];
EnterpriseSearchAPI.mockReturn({ errors }, { status: 400, headers: JSON_HEADER });

const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/4xx' });
await makeAPICall(requestHandler);

expect(responseMock.customError).toHaveBeenCalledWith({
statusCode: 400,
body: {
message: 'one,two,three',
attributes: { errors: ['one', 'two', 'three'] },
},
});
});

it('handles empty json', async () => {
EnterpriseSearchAPI.mockReturn({}, { status: 400, headers: JSON_HEADER });

const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/4xx' });
await makeAPICall(requestHandler);

expect(responseMock.customError).toHaveBeenCalledWith({
statusCode: 400,
body: {
message: 'Bad Request',
attributes: { errors: ['Bad Request'] },
},
});
});

it('handles blank bodies', async () => {
EnterpriseSearchAPI.mockReturn(undefined as any, { status: 404 });

const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/4xx' });
await makeAPICall(requestHandler);

expect(responseMock.customError).toHaveBeenCalledWith({
statusCode: 404,
body: {
message: 'Not Found',
attributes: { errors: ['Not Found'] },
},
});
});
});

it('returns an error when an API request fails', async () => {
EnterpriseSearchAPI.mockReturnError();
const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/failed' });
it('handleServerError()', async () => {
EnterpriseSearchAPI.mockReturn('something crashed!' as any, { status: 500 });
const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/5xx' });

await makeAPICall(requestHandler);
EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/failed');
EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/5xx');

expect(responseMock.customError).toHaveBeenCalledWith({
body: 'Error connecting to Enterprise Search: Failed',
statusCode: 502,
body: expect.stringContaining('Enterprise Search encountered an internal server error'),
});
expect(mockLogger.error).toHaveBeenCalledWith(
'Enterprise Search Server Error 500 at <http://localhost:3002/api/5xx>: "something crashed!"'
);
});

it('returns an error when `hasValidData` fails', async () => {
it('handleInvalidDataError()', async () => {
EnterpriseSearchAPI.mockReturn({ results: false });
const requestHandler = enterpriseSearchRequestHandler.createRequest({
path: '/api/invalid',
Expand All @@ -143,15 +217,29 @@ describe('EnterpriseSearchRequestHandler', () => {
EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/invalid');

expect(responseMock.customError).toHaveBeenCalledWith({
body: 'Error connecting to Enterprise Search: Invalid data received',
statusCode: 502,
body: 'Invalid data received from Enterprise Search',
});
expect(mockLogger.debug).toHaveBeenCalledWith(
expect(mockLogger.error).toHaveBeenCalledWith(
'Invalid data received from <http://localhost:3002/api/invalid>: {"results":false}'
);
});

describe('user authentication errors', () => {
it('handleConnectionError()', async () => {
EnterpriseSearchAPI.mockReturnError();
const requestHandler = enterpriseSearchRequestHandler.createRequest({ path: '/api/failed' });

await makeAPICall(requestHandler);
EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/failed');

expect(responseMock.customError).toHaveBeenCalledWith({
statusCode: 502,
body: 'Error connecting to Enterprise Search: Failed',
});
expect(mockLogger.error).toHaveBeenCalled();
});

describe('handleAuthenticationError()', () => {
afterEach(async () => {
const requestHandler = enterpriseSearchRequestHandler.createRequest({
path: '/api/unauthenticated',
Expand All @@ -160,9 +248,10 @@ describe('EnterpriseSearchRequestHandler', () => {

EnterpriseSearchAPI.shouldHaveBeenCalledWith('http://localhost:3002/api/unauthenticated');
expect(responseMock.customError).toHaveBeenCalledWith({
body: 'Error connecting to Enterprise Search: Cannot authenticate Enterprise Search user',
statusCode: 502,
body: 'Cannot authenticate Enterprise Search user',
});
expect(mockLogger.error).toHaveBeenCalled();
});

it('errors when redirected to /login', async () => {
Expand All @@ -175,7 +264,7 @@ describe('EnterpriseSearchRequestHandler', () => {
});
});

it('has a helper for checking empty objects', async () => {
it('isEmptyObj', async () => {
expect(enterpriseSearchRequestHandler.isEmptyObj({})).toEqual(true);
expect(enterpriseSearchRequestHandler.isEmptyObj({ empty: false })).toEqual(false);
});
Expand Down
Loading