Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion src/Service/BaseConfig.service.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { S3 } from 'aws-sdk';

import DependencyAwareClass from '../DependencyInjection/DependencyAware.class';

import LambdaTermination from '../Wrapper/LambdaTermination';
/**
* Error.code for S3 404 errors
*/
Expand All @@ -16,6 +16,15 @@ export const ServiceStates = {
INDEFINITELY_PAUSED: 'UNDEFINITELY_PAUSED',
};

/**
* Maps service states to HTTP codes
*/
export const ServiceStatesHttpCodes = {
[ServiceStates.OK]: 200,
[ServiceStates.TEMPORARY_PAUSED]: 409,
[ServiceStates.INDEFINITELY_PAUSED]: 409,
};

/**
* BaseConfigService class
*
Expand Down Expand Up @@ -159,4 +168,37 @@ export default class BaseConfigService extends DependencyAwareClass {
...partialConfig,
});
}

/**
* Performs a health check
* given the currentConfig.
*
* If currentConfig is not supplied
* it uses `getOrCreate` to fetch it.
*
* @param currentConfig
*/
async healthCheck(currentConfig = null) {
const config = currentConfig || await this.getOrCreate();

return ServiceStatesHttpCodes[config.state] || 500;
}

/**
* Ensures that the application is healthy
* or throws a LambdaTermination
*
* @param currentConfig
*/
async ensureHealthy(currentConfig = null) {
const statusCode = await this.healthCheck(currentConfig);

if (statusCode < 400) {
return statusCode;
}

const message = 'Application is not healthy.';

throw new LambdaTermination(message, statusCode, message, message);
}
}
70 changes: 69 additions & 1 deletion tests/unit/Service/BaseConfig.service.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { S3 } from 'aws-sdk';

import DependencyInjection from '../../../src/DependencyInjection/DependencyInjection.class';
import BaseConfigService, { S3_NO_SUCH_KEY_ERROR_CODE } from '../../../src/Service/BaseConfig.service';
import BaseConfigService, { S3_NO_SUCH_KEY_ERROR_CODE, ServiceStates, ServiceStatesHttpCodes } from '../../../src/Service/BaseConfig.service';

const createAsyncMock = (returnValue) => {
const mockedValue = returnValue instanceof Error
Expand Down Expand Up @@ -182,6 +182,74 @@ const BaseConfigUnitTests = (serviceGenerator: (...args) => BaseConfigService) =
await expect(service.patch({ b: 1 })).rejects.toThrowErrorMatchingSnapshot();
});
});

describe('healthCheck', () => {
Object.values(ServiceStates).forEach((state) => {
describe(state, () => {
it('Returns the expected HTTP code with the given config', async () => {
const config = { state };
const service = serviceGenerator();
const statusCode = await service.healthCheck(config);
const expected = ServiceStatesHttpCodes[state];

expect(statusCode).toEqual(expected);
});

it('Returns the expected HTTP code with the existing config', async () => {
const config = { state };
const service = serviceGenerator({ getObject: { Body: JSON.stringify(config) } });
const statusCode = await service.healthCheck();
const expected = ServiceStatesHttpCodes[state];

expect(statusCode).toEqual(expected);
});
});
});

describe('Unknown state', () => {
it('Returns 500 with the given config', async () => {
const config = { state: 'Unknown' };
const service = serviceGenerator();
const statusCode = await service.healthCheck(config);
const expected = 500;

expect(statusCode).toEqual(expected);
});

it('Returns 500 with the existing config', async () => {
const config = { state: 'Unknown' };
const service = serviceGenerator({ getObject: { Body: JSON.stringify(config) } });
const statusCode = await service.healthCheck();
const expected = 500;

expect(statusCode).toEqual(expected);
});
});
});

describe('ensureHealthy', () => {
[200, 201, 202, 204, 300, 301, 399].forEach((statusCode) => {
describe(statusCode, () => {
it('is healthy', async () => {
const service = serviceGenerator();
jest.spyOn(service, 'healthCheck').mockImplementation(() => Promise.resolve(statusCode));

await expect(service.ensureHealthy()).resolves.toEqual(statusCode);
});
});
});

[400, 401, 403, 404, 409, 499, 500, 501, 502, 503, 504, 'Dante Alighieri'].forEach((statusCode) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nice touch :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I try to spread Dante Alighieri everywhere in our test cases! 😄

describe(statusCode, () => {
it('throws a LambdaTermination', async () => {
const service = serviceGenerator();
jest.spyOn(service, 'healthCheck').mockImplementation(() => Promise.resolve(statusCode));

await expect(service.ensureHealthy()).rejects.toThrowErrorMatchingSnapshot();
});
});
});
});
};

describe('Service/BaseConfigService', () => {
Expand Down
24 changes: 24 additions & 0 deletions tests/unit/Service/__snapshots__/BaseConfig.service.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Service/BaseConfigService ensureHealthy 400 throws a LambdaTermination 1`] = `"Application is not healthy."`;

exports[`Service/BaseConfigService ensureHealthy 401 throws a LambdaTermination 1`] = `"Application is not healthy."`;

exports[`Service/BaseConfigService ensureHealthy 403 throws a LambdaTermination 1`] = `"Application is not healthy."`;

exports[`Service/BaseConfigService ensureHealthy 404 throws a LambdaTermination 1`] = `"Application is not healthy."`;

exports[`Service/BaseConfigService ensureHealthy 409 throws a LambdaTermination 1`] = `"Application is not healthy."`;

exports[`Service/BaseConfigService ensureHealthy 499 throws a LambdaTermination 1`] = `"Application is not healthy."`;

exports[`Service/BaseConfigService ensureHealthy 500 throws a LambdaTermination 1`] = `"Application is not healthy."`;

exports[`Service/BaseConfigService ensureHealthy 501 throws a LambdaTermination 1`] = `"Application is not healthy."`;

exports[`Service/BaseConfigService ensureHealthy 502 throws a LambdaTermination 1`] = `"Application is not healthy."`;

exports[`Service/BaseConfigService ensureHealthy 503 throws a LambdaTermination 1`] = `"Application is not healthy."`;

exports[`Service/BaseConfigService ensureHealthy 504 throws a LambdaTermination 1`] = `"Application is not healthy."`;

exports[`Service/BaseConfigService ensureHealthy Dante Alighieri throws a LambdaTermination 1`] = `"Application is not healthy."`;

exports[`Service/BaseConfigService get propagates the 404 1`] = `"404"`;

exports[`Service/BaseConfigService get refuses empty configurations 1`] = `"Configuration file is empty"`;
Expand Down