Skip to content

Commit

Permalink
test: add e2e test for graceful shutdown (#2450)
Browse files Browse the repository at this point in the history
* test: add e2e test for graceful shutdown

Co-authored-by: François <[email protected]>

---------

Co-authored-by: François <[email protected]>
  • Loading branch information
BrunnerLivio and Lp-Francois authored Nov 27, 2023
1 parent d383828 commit 198080e
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 16 deletions.
66 changes: 66 additions & 0 deletions e2e/graceful-shutdown.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ShutdownSignal } from '@nestjs/common';
import { type NestApplicationContext } from '@nestjs/core';
import * as request from 'supertest';
import { bootstrapTestingModule } from './helper';
import { sleep } from '../lib/utils';

describe('Graceful shutdown', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('should gracefully shutdown the application', async () => {
jest.spyOn(global, 'setTimeout');
const setHealthEndpoint = bootstrapTestingModule({
gracefulShutdownTimeoutMs: 64,
}).setHealthEndpoint;

const app = await setHealthEndpoint(({ healthCheck }) =>
healthCheck.check([]),
).start();

const { status } = await request(app.getHttpServer()).get('/health');

expect(status).toBe(200);

let isClosed = false;
(app.close as NestApplicationContext['close'])(ShutdownSignal.SIGTERM).then(
() => {
isClosed = true;
},
);

await sleep(16);
// 1. setTimeout is called by the `GracefulShutdownService`
// 2. setTimeout is called above
expect(setTimeout).toHaveBeenCalledTimes(2);
expect(isClosed).toBe(false);
await sleep(16);
expect(isClosed).toBe(false);
await sleep(16);
expect(isClosed).toBe(false);
await sleep(64);
expect(isClosed).toBe(true);
});

it('should not delay the shutdown if the application if the timeout is 0', async () => {
jest.spyOn(global, 'setTimeout');
const setHealthEndpoint = bootstrapTestingModule({
gracefulShutdownTimeoutMs: 0,
}).setHealthEndpoint;

const app = await setHealthEndpoint(({ healthCheck }) =>
healthCheck.check([]),
).start();

const { status } = await request(app.getHttpServer()).get('/health');

expect(status).toBe(200);

await (app.close as NestApplicationContext['close'])(
ShutdownSignal.SIGTERM,
);

expect(setTimeout).not.toHaveBeenCalled();
});
});
9 changes: 7 additions & 2 deletions e2e/helper/bootstrap-testing-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
SequelizeHealthIndicator,
TerminusModule,
TypeOrmHealthIndicator,
type TerminusModuleOptions,
} from '../../lib';
import { type HealthCheckOptions } from '../../lib/health-check';
import { MikroOrmHealthIndicator } from '../../lib/health-indicator/database/mikro-orm.health';
Expand Down Expand Up @@ -92,8 +93,12 @@ export type DynamicHealthEndpointFn = (
): Promise<INestApplication>;
};

export function bootstrapTestingModule() {
const imports: PropType<ModuleMetadata, 'imports'> = [TerminusModule];
export function bootstrapTestingModule(
terminusModuleOptions: TerminusModuleOptions = {},
) {
const imports: PropType<ModuleMetadata, 'imports'> = [
TerminusModule.forRoot(terminusModuleOptions),
];

const setHealthEndpoint: DynamicHealthEndpointFn = (func, options = {}) => {
const testingModule = Test.createTestingModule({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class GracefulShutdownService implements BeforeApplicationShutdown {
}

async beforeApplicationShutdown(signal: string) {
this.logger.log(`Received termination signal ${signal}`);
this.logger.log(`Received termination signal ${signal || ''}`);

if (signal === 'SIGTERM') {
this.logger.log(
Expand Down
36 changes: 23 additions & 13 deletions lib/terminus.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { type DynamicModule, Module } from '@nestjs/common';
import { TERMINUS_GRACEFUL_SHUTDOWN_TIMEOUT } from './graceful-shutdown-timeout/graceful-shutdown-timeout.service';
import { type DynamicModule, Module, type Provider } from '@nestjs/common';
import {
GracefulShutdownService,
TERMINUS_GRACEFUL_SHUTDOWN_TIMEOUT,
} from './graceful-shutdown-timeout/graceful-shutdown-timeout.service';
import { HealthCheckService } from './health-check';
import { getErrorLoggerProvider } from './health-check/error-logger/error-logger.provider';
import { ERROR_LOGGERS } from './health-check/error-logger/error-loggers.provider';
Expand All @@ -9,7 +12,7 @@ import { DiskUsageLibProvider } from './health-indicator/disk/disk-usage-lib.pro
import { HEALTH_INDICATORS } from './health-indicator/health-indicators.provider';
import { type TerminusModuleOptions } from './terminus-options.interface';

const providers = [
const baseProviders: Provider[] = [
...ERROR_LOGGERS,
DiskUsageLibProvider,
HealthCheckExecutor,
Expand All @@ -26,7 +29,7 @@ const exports_ = [HealthCheckService, ...HEALTH_INDICATORS];
* @publicApi
*/
@Module({
providers: [...providers, getErrorLoggerProvider(), getLoggerProvider()],
providers: [...baseProviders, getErrorLoggerProvider(), getLoggerProvider()],
exports: exports_,
})
export class TerminusModule {
Expand All @@ -37,17 +40,24 @@ export class TerminusModule {
gracefulShutdownTimeoutMs = 0,
} = options;

const providers: Provider[] = [
...baseProviders,
getErrorLoggerProvider(errorLogStyle),
getLoggerProvider(logger),
];

if (gracefulShutdownTimeoutMs > 0) {
providers.push({
provide: TERMINUS_GRACEFUL_SHUTDOWN_TIMEOUT,
useValue: gracefulShutdownTimeoutMs,
});

providers.push(GracefulShutdownService);
}

return {
module: TerminusModule,
providers: [
...providers,
getErrorLoggerProvider(errorLogStyle),
getLoggerProvider(logger),
{
provide: TERMINUS_GRACEFUL_SHUTDOWN_TIMEOUT,
useValue: gracefulShutdownTimeoutMs,
},
],
providers,
exports: exports_,
};
}
Expand Down

0 comments on commit 198080e

Please sign in to comment.