Skip to content

Commit

Permalink
Update/refactor EnterpriseSearchRequestHandler to manage internal end…
Browse files Browse the repository at this point in the history
…point error behavior

+ pull out / refactor all errors into their own methods instead of relying on a single 'error connecting' message
  • Loading branch information
cee-chen committed Sep 11, 2020
1 parent 52fba21 commit e57009c
Show file tree
Hide file tree
Showing 2 changed files with 280 additions and 88 deletions.
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

0 comments on commit e57009c

Please sign in to comment.