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

Create HealthIndicatorService to simplify custom health indicators #2510

Merged
merged 3 commits into from
Jan 23, 2025
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
1 change: 1 addition & 0 deletions .github/workflows/build-samples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ jobs:
# FIXME: Remove the `--legacy-peer-deps` flag once all dependencies are updated
- run: npm ci --legacy-peer-deps
- run: npm run build:all
- run: npm run test:samples
env:
CI: true
6 changes: 6 additions & 0 deletions lib/errors/connection-not-found.error.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
/* eslint-disable deprecation/deprecation */
import { CONNECTION_NOT_FOUND } from './messages.constant';
import { HealthCheckError } from '../health-check/health-check.error';

/**
* Error which gets thrown when the connection
* instance was not found in the application context
* @publicApi
*
* @deprecated
* This class has been deprecated and will be removed in the next major release.
* Instead utilise the `HealthIndicatorService` to indicate the health of your health indicator.
*
*/
export class ConnectionNotFoundError extends HealthCheckError {
/**
Expand Down
5 changes: 5 additions & 0 deletions lib/errors/storage-exceeded.error.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
/* eslint-disable deprecation/deprecation */
import { STORAGE_EXCEEDED } from './messages.constant';
import { HealthCheckError } from '../health-check/health-check.error';

/**
* Error which gets thrown when the given storage threshold
* has exceeded.
* @publicApi
*
* @deprecated
* This class has been deprecated and will be removed in the next major release.
* Instead utilise the `HealthIndicatorService` to indicate the health of your health indicator.
*/
export class StorageExceededError extends HealthCheckError {
/**
Expand Down
5 changes: 5 additions & 0 deletions lib/errors/timeout-error.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
/* eslint-disable deprecation/deprecation */
import { TIMEOUT_EXCEEDED } from './messages.constant';
import { HealthCheckError } from '../health-check/health-check.error';

/**
* Gets thrown when the timeout of the health check exceeds
* @publicApi
*
* @deprecated
* This class has been deprecated and will be removed in the next major release.
* Instead utilise the `HealthIndicatorService` to indicate the health of your health indicator.
*/
export class TimeoutError extends HealthCheckError {
/**
Expand Down
5 changes: 5 additions & 0 deletions lib/errors/unhealthy-response-code.error.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
/* eslint-disable deprecation/deprecation */
import { UNHEALTHY_RESPONSE_CODE } from './messages.constant';
import { HealthCheckError } from '../health-check/health-check.error';

/**
* Error which gets thrown when the terminus client receives
* an unhealthy response code from the server.
* @publicApi
*
* @deprecated
* This class has been deprecated and will be removed in the next major release.
* Instead utilise the `HealthIndicatorService` to indicate the health of your health indicator.
*/
export class UnhealthyResponseCodeError extends HealthCheckError {
/**
Expand Down
161 changes: 151 additions & 10 deletions lib/health-check/health-check-executor.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,78 @@
import { Test } from '@nestjs/testing';
import { HealthCheckExecutor } from './health-check-executor.service';
import { HealthIndicatorResult } from '../health-indicator';
import {
HealthIndicatorResult,
HealthIndicatorService,
} from '../health-indicator';
import { HealthCheckResult } from './health-check-result.interface';
import { HealthCheckError } from '../health-check/health-check.error';

const healthyCheck = async (): Promise<HealthIndicatorResult> => {
////////////////////////////////////////////////////////////////

const healthIndicator = async (h: HealthIndicatorService) =>
h.check('healthy').up();

const unhealthyHealthIndicator = async (h: HealthIndicatorService) =>
h.check('unhealthy').down();

const unhealthyHealthIndicatorSync = (h: HealthIndicatorService) =>
h.check('unhealthy').down();

// Legacy health indicator functions

const legacyHealthyIndicator = async (): Promise<HealthIndicatorResult> => {
return {
healthy: {
status: 'up',
},
};
};

const unhealthyCheck = async (): Promise<HealthIndicatorResult> => {
const legacyUnhealthyIndicator = async (): Promise<HealthIndicatorResult> => {
throw new HealthCheckError('error', {
unhealthy: {
status: 'down',
},
});
};

const unhealthyCheckSync = () => {
const legacyUnhealthyIndicatorSync = () => {
throw new HealthCheckError('error', {
unhealthy: {
status: 'down',
},
});
};

const legacyUnhealthyIndicatorWithoutError =
async (): Promise<HealthIndicatorResult> => {
return {
unhealthy: {
status: 'down',
},
};
};

////////////////////////////////////////////////////////////////

describe('HealthCheckExecutorService', () => {
let healthCheckExecutor: HealthCheckExecutor;
let h: HealthIndicatorService;

beforeEach(async () => {
const module = Test.createTestingModule({
providers: [HealthCheckExecutor],
providers: [HealthCheckExecutor, HealthIndicatorService],
});
const context = await module.compile();
healthCheckExecutor = context.get(HealthCheckExecutor);
h = context.get(HealthIndicatorService);
});

describe('execute', () => {
it('should return a result object without errors', async () => {
const result = await healthCheckExecutor.execute([() => healthyCheck()]);
const result = await healthCheckExecutor.execute([
() => healthIndicator(h),
]);
expect(result).toEqual<HealthCheckResult>({
status: 'ok',
info: {
Expand All @@ -60,7 +91,7 @@ describe('HealthCheckExecutorService', () => {

it('should return a result object with errors', async () => {
const result = await healthCheckExecutor.execute([
() => unhealthyCheck(),
() => unhealthyHealthIndicator(h),
]);
expect(result).toEqual<HealthCheckResult>({
status: 'error',
Expand All @@ -80,7 +111,7 @@ describe('HealthCheckExecutorService', () => {

it('should return a result object with errors with sync indicator function', async () => {
const result = await healthCheckExecutor.execute([
() => unhealthyCheckSync(),
() => unhealthyHealthIndicatorSync(h),
]);
expect(result).toEqual<HealthCheckResult>({
status: 'error',
Expand All @@ -100,8 +131,8 @@ describe('HealthCheckExecutorService', () => {

it('should return a result object without errors and with errors', async () => {
const result = await healthCheckExecutor.execute([
() => unhealthyCheck(),
() => healthyCheck(),
() => unhealthyHealthIndicator(h),
() => healthIndicator(h),
]);
expect(result).toEqual<HealthCheckResult>({
status: 'error',
Expand All @@ -125,5 +156,115 @@ describe('HealthCheckExecutorService', () => {
},
});
});

describe('backwards compatibility', () => {
it('should return a result object without errors', async () => {
const result = await healthCheckExecutor.execute([
() => legacyHealthyIndicator(),
]);
expect(result).toEqual<HealthCheckResult>({
status: 'ok',
info: {
healthy: {
status: 'up',
},
},
error: {},
details: {
healthy: {
status: 'up',
},
},
});
});

it('should return a result object with errors', async () => {
const result = await healthCheckExecutor.execute([
() => legacyUnhealthyIndicator(),
]);
expect(result).toEqual<HealthCheckResult>({
status: 'error',
info: {},
error: {
unhealthy: {
status: 'down',
},
},
details: {
unhealthy: {
status: 'down',
},
},
});
});

it('should return a result object with errors with sync indicator function', async () => {
const result = await healthCheckExecutor.execute([
() => legacyUnhealthyIndicatorSync(),
]);
expect(result).toEqual<HealthCheckResult>({
status: 'error',
info: {},
error: {
unhealthy: {
status: 'down',
},
},
details: {
unhealthy: {
status: 'down',
},
},
});
});

it('should return a result object without errors and with errors', async () => {
const result = await healthCheckExecutor.execute([
() => legacyUnhealthyIndicator(),
() => legacyHealthyIndicator(),
]);
expect(result).toEqual<HealthCheckResult>({
status: 'error',
info: {
healthy: {
status: 'up',
},
},
error: {
unhealthy: {
status: 'down',
},
},
details: {
healthy: {
status: 'up',
},
unhealthy: {
status: 'down',
},
},
});
});

it('should return a result object with errors when error is not an instance of HealthCheckError', async () => {
const result = await healthCheckExecutor.execute([
() => legacyUnhealthyIndicatorWithoutError(),
]);
expect(result).toEqual<HealthCheckResult>({
status: 'error',
info: {},
error: {
unhealthy: {
status: 'down',
},
},
details: {
unhealthy: {
status: 'down',
},
},
});
});
});
});
});
11 changes: 9 additions & 2 deletions lib/health-check/health-check-executor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,21 @@ export class HealthCheckExecutor implements BeforeApplicationShutdown {

result.forEach((res) => {
if (res.status === 'fulfilled') {
results.push(res.value);
Object.entries(res.value).forEach(([key, value]) => {
if (value.status === 'up') {
results.push({ [key]: value });
} else if (value.status === 'down') {
errors.push({ [key]: value });
}
});
} else {
const error = res.reason;
// Is not an expected error. Throw further!
if (!isHealthCheckError(error)) {
throw error;
}
// Is a expected health check error

// eslint-disable-next-line deprecation/deprecation
errors.push((error as HealthCheckError).causes);
}
});
Expand Down
5 changes: 5 additions & 0 deletions lib/health-check/health-check.error.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* @deprecated
* This class has been deprecated and will be removed in the next major release.
* Instead utilise the `HealthIndicatorService` to indicate the health of your health indicator.
*/
export class HealthCheckError extends Error {
causes: any;
isHealthCheckError = true;
Expand Down
Loading
Loading