mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
184 lines
5.7 KiB
TypeScript
184 lines
5.7 KiB
TypeScript
import { Logger } from '@n8n/backend-common';
|
|
import { mockInstance } from '@n8n/backend-test-utils';
|
|
import { TaskRunnersConfig } from '@n8n/config';
|
|
import { mock } from 'jest-mock-extended';
|
|
import type { ChildProcess, SpawnOptions } from 'node:child_process';
|
|
|
|
import type { TaskBrokerAuthService } from '@/task-runners/task-broker/auth/task-broker-auth.service';
|
|
import { JsTaskRunnerProcess } from '@/task-runners/task-runner-process-js';
|
|
|
|
import type { TaskRunnerLifecycleEvents } from '../task-runner-lifecycle-events';
|
|
|
|
const spawnMock = jest.fn(() =>
|
|
mock<ChildProcess>({
|
|
stdout: {
|
|
pipe: jest.fn(),
|
|
},
|
|
stderr: {
|
|
pipe: jest.fn(),
|
|
},
|
|
}),
|
|
);
|
|
require('child_process').spawn = spawnMock;
|
|
|
|
describe('TaskRunnerProcess', () => {
|
|
const logger = mockInstance(Logger);
|
|
const runnerConfig = mockInstance(TaskRunnersConfig);
|
|
runnerConfig.enabled = true;
|
|
runnerConfig.mode = 'internal';
|
|
runnerConfig.insecureMode = false;
|
|
const authService = mock<TaskBrokerAuthService>();
|
|
let taskRunnerProcess = new JsTaskRunnerProcess(logger, runnerConfig, authService, mock());
|
|
|
|
afterEach(async () => {
|
|
spawnMock.mockClear();
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
it('should throw if runner mode is external', () => {
|
|
runnerConfig.mode = 'external';
|
|
|
|
expect(() => new JsTaskRunnerProcess(logger, runnerConfig, authService, mock())).toThrow();
|
|
|
|
runnerConfig.mode = 'internal';
|
|
});
|
|
|
|
it('should register listener for `runner:failed-heartbeat-check` event', () => {
|
|
const runnerLifecycleEvents = mock<TaskRunnerLifecycleEvents>();
|
|
new JsTaskRunnerProcess(logger, runnerConfig, authService, runnerLifecycleEvents);
|
|
|
|
expect(runnerLifecycleEvents.on).toHaveBeenCalledWith(
|
|
'runner:failed-heartbeat-check',
|
|
expect.any(Function),
|
|
);
|
|
});
|
|
|
|
it('should register listener for `runner:timed-out-during-task` event', () => {
|
|
const runnerLifecycleEvents = mock<TaskRunnerLifecycleEvents>();
|
|
new JsTaskRunnerProcess(logger, runnerConfig, authService, runnerLifecycleEvents);
|
|
|
|
expect(runnerLifecycleEvents.on).toHaveBeenCalledWith(
|
|
'runner:timed-out-during-task',
|
|
expect.any(Function),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('start', () => {
|
|
beforeEach(() => {
|
|
taskRunnerProcess = new JsTaskRunnerProcess(logger, runnerConfig, authService, mock());
|
|
});
|
|
|
|
test.each([
|
|
'PATH',
|
|
'NODE_FUNCTION_ALLOW_BUILTIN',
|
|
'NODE_FUNCTION_ALLOW_EXTERNAL',
|
|
'N8N_SENTRY_DSN',
|
|
'N8N_VERSION',
|
|
'ENVIRONMENT',
|
|
'DEPLOYMENT_NAME',
|
|
'NODE_PATH',
|
|
'GENERIC_TIMEZONE',
|
|
'N8N_RUNNERS_INSECURE_MODE',
|
|
])('should propagate %s from env as is', async (envVar) => {
|
|
jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken');
|
|
process.env[envVar] = 'custom value';
|
|
|
|
await taskRunnerProcess.start();
|
|
|
|
// @ts-expect-error The type is not correct
|
|
const options = spawnMock.mock.calls[0][2] as SpawnOptions;
|
|
expect(options.env).toEqual(
|
|
expect.objectContaining({
|
|
[envVar]: 'custom value',
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should pass NODE_OPTIONS env if maxOldSpaceSize is configured', async () => {
|
|
jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken');
|
|
runnerConfig.maxOldSpaceSize = '1024';
|
|
|
|
await taskRunnerProcess.start();
|
|
|
|
// @ts-expect-error The type is not correct
|
|
const options = spawnMock.mock.calls[0][2] as SpawnOptions;
|
|
expect(options.env).toEqual(
|
|
expect.objectContaining({
|
|
NODE_OPTIONS: '--max-old-space-size=1024',
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should not pass NODE_OPTIONS env if maxOldSpaceSize is not configured', async () => {
|
|
jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken');
|
|
runnerConfig.maxOldSpaceSize = '';
|
|
|
|
await taskRunnerProcess.start();
|
|
|
|
// @ts-expect-error The type is not correct
|
|
const options = spawnMock.mock.calls[0][2] as SpawnOptions;
|
|
expect(options.env).not.toHaveProperty('NODE_OPTIONS');
|
|
});
|
|
|
|
it('should pass N8N_RUNNERS_TASK_TIMEOUT if set', async () => {
|
|
jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken');
|
|
runnerConfig.taskTimeout = 123;
|
|
|
|
await taskRunnerProcess.start();
|
|
|
|
// @ts-expect-error The type is not correct
|
|
const options = spawnMock.mock.calls[0][2] as SpawnOptions;
|
|
expect(options.env).toEqual(
|
|
expect.objectContaining({
|
|
N8N_RUNNERS_TASK_TIMEOUT: '123',
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('should pass N8N_RUNNERS_HEARTBEAT_INTERVAL if set', async () => {
|
|
jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken');
|
|
runnerConfig.heartbeatInterval = 456;
|
|
|
|
await taskRunnerProcess.start();
|
|
|
|
// @ts-expect-error The type is not correct
|
|
const options = spawnMock.mock.calls[0][2] as SpawnOptions;
|
|
expect(options.env).toEqual(
|
|
expect.objectContaining({
|
|
N8N_RUNNERS_HEARTBEAT_INTERVAL: '456',
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('on secure mode, should use --disallow-code-generation-from-strings and --disable-proto=delete flags', async () => {
|
|
jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken');
|
|
|
|
await taskRunnerProcess.start();
|
|
|
|
expect(spawnMock.mock.calls[0].at(1)).toEqual([
|
|
'--disallow-code-generation-from-strings',
|
|
'--disable-proto=delete',
|
|
expect.stringContaining('/packages/@n8n/task-runner/dist/start.js'),
|
|
]);
|
|
});
|
|
|
|
it('on insecure mode, should not use --disallow-code-generation-from-strings and --disable-proto=delete flags', async () => {
|
|
jest.spyOn(authService, 'createGrantToken').mockResolvedValue('grantToken');
|
|
runnerConfig.insecureMode = true;
|
|
const insecureTaskRunnerProcess = new JsTaskRunnerProcess(
|
|
logger,
|
|
runnerConfig,
|
|
authService,
|
|
mock(),
|
|
);
|
|
|
|
await insecureTaskRunnerProcess.start();
|
|
|
|
expect(spawnMock.mock.calls[0].at(1)).toEqual([
|
|
expect.stringContaining('/packages/@n8n/task-runner/dist/start.js'),
|
|
]);
|
|
});
|
|
});
|
|
});
|