mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
feat(core): Validate shutdown handlers on startup (#8260)
This commit is contained in:
parent
d37b9084b2
commit
3b996a7da0
|
@ -9,7 +9,7 @@ import { Logger } from '@/Logger';
|
|||
import config from '@/config';
|
||||
import * as Db from '@/Db';
|
||||
import * as CrashJournal from '@/CrashJournal';
|
||||
import { LICENSE_FEATURES, inTest } from '@/constants';
|
||||
import { LICENSE_FEATURES, inDevelopment, inTest } from '@/constants';
|
||||
import { initErrorHandling } from '@/ErrorReporting';
|
||||
import { ExternalHooks } from '@/ExternalHooks';
|
||||
import { NodeTypes } from '@/NodeTypes';
|
||||
|
@ -63,6 +63,12 @@ export abstract class BaseCommand extends Command {
|
|||
this.exitWithCrash('There was an error initializing DB', error),
|
||||
);
|
||||
|
||||
// This needs to happen after DB.init() or otherwise DB Connection is not
|
||||
// available via the dependency Container that services depend on.
|
||||
if (inDevelopment || inTest) {
|
||||
this.shutdownService.validate();
|
||||
}
|
||||
|
||||
await this.server?.init();
|
||||
|
||||
await Db.migrate().catch(async (error: Error) =>
|
||||
|
|
|
@ -39,6 +39,26 @@ export class ShutdownService {
|
|||
this.handlersByPriority[priority].push(handler);
|
||||
}
|
||||
|
||||
/** Validates that all the registered shutdown handlers are properly configured */
|
||||
validate() {
|
||||
const handlers = this.handlersByPriority.flat();
|
||||
|
||||
for (const { serviceClass, methodName } of handlers) {
|
||||
if (!Container.has(serviceClass)) {
|
||||
throw new ApplicationError(
|
||||
`Component "${serviceClass.name}" is not registered with the DI container. Any component using @OnShutdown() must be decorated with @Service()`,
|
||||
);
|
||||
}
|
||||
|
||||
const service = Container.get(serviceClass);
|
||||
if (!service[methodName]) {
|
||||
throw new ApplicationError(
|
||||
`Component "${serviceClass.name}" does not have a "${methodName}" method`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Signals all registered listeners that the application is shutting down */
|
||||
shutdown() {
|
||||
if (this.shutdownPromise) {
|
||||
|
|
|
@ -124,4 +124,36 @@ describe('ShutdownService', () => {
|
|||
expect(shutdownService.isShuttingDown()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validate', () => {
|
||||
it('should throw error if component is not registered with the DI container', () => {
|
||||
class UnregisteredComponent {
|
||||
onShutdown() {}
|
||||
}
|
||||
|
||||
shutdownService.register(10, {
|
||||
serviceClass: UnregisteredComponent as unknown as ServiceClass,
|
||||
methodName: 'onShutdown',
|
||||
});
|
||||
|
||||
expect(() => shutdownService.validate()).toThrow(
|
||||
'Component "UnregisteredComponent" is not registered with the DI container. Any component using @OnShutdown() must be decorated with @Service()',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error if component is missing the shutdown method', () => {
|
||||
class TestComponent {}
|
||||
|
||||
shutdownService.register(10, {
|
||||
serviceClass: TestComponent as unknown as ServiceClass,
|
||||
methodName: 'onShutdown',
|
||||
});
|
||||
|
||||
Container.set(TestComponent, new TestComponent());
|
||||
|
||||
expect(() => shutdownService.validate()).toThrow(
|
||||
'Component "TestComponent" does not have a "onShutdown" method',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue