import { TaskRunnersConfig } from '@n8n/config'; import { Container } from '@n8n/di'; import { mock } from 'jest-mock-extended'; import type { IExecuteFunctions, INode, INodeExecutionData, INodeParameters, INodeTypes, IRunExecutionData, ITaskDataConnections, IWorkflowExecuteAdditionalData, WorkflowExecuteMode, } from 'n8n-workflow'; import { createEnvProviderState, NodeConnectionType, Workflow } from 'n8n-workflow'; import { LocalTaskRequester } from '@/task-runners/task-managers/local-task-requester'; import { TaskRunnerModule } from '@/task-runners/task-runner-module'; /** * Integration tests for the JS TaskRunner execution. Starts the TaskRunner * as a child process and executes tasks on it via the broker. */ describe('JS TaskRunner execution on internal mode', () => { const runnerConfig = Container.get(TaskRunnersConfig); runnerConfig.mode = 'internal'; runnerConfig.enabled = true; runnerConfig.port = 45678; const taskRunnerModule = Container.get(TaskRunnerModule); const taskRequester = Container.get(LocalTaskRequester); /** * Sets up task data that includes a workflow with manual trigger and a * code node with the given JS code. The input data is a single item: * ```json * { * "input": "item" * } * ``` */ const newTaskData = (jsCode: string) => { const taskSettings = { code: jsCode, nodeMode: 'runOnceForAllItems', workflowMode: 'manual', continueOnFail: false, }; const codeNode: INode = { parameters: { jsCode, }, type: 'n8n-nodes-base.code', typeVersion: 2, position: [200, 80], id: 'b35fd455-32e4-4d52-b840-36aa28dd1910', name: 'Code', }; const workflow = new Workflow({ id: 'testWorkflow', name: 'testWorkflow', nodes: [ { parameters: {}, type: 'n8n-nodes-base.manualTrigger', typeVersion: 1, position: [0, 0], id: 'a39a566a-283a-433e-88bc-b3857aab706f', name: 'ManualTrigger', }, codeNode, ], connections: { ManualTrigger: { main: [ [ { node: 'Code', type: NodeConnectionType.Main, index: 0, }, ], ], }, }, active: true, nodeTypes: mock(), }); const inputData: INodeExecutionData[] = [ { json: { input: 'item', }, }, ]; const inputConnections: ITaskDataConnections = { main: [inputData], }; const runExecutionData: IRunExecutionData = { startData: {}, resultData: { runData: { ManualTrigger: [ { startTime: Date.now(), executionTime: 0, executionStatus: 'success', source: [], data: { main: [inputData], }, }, ], }, lastNodeExecuted: 'ManualTrigger', }, executionData: { contextData: {}, nodeExecutionStack: [], metadata: {}, waitingExecution: {}, waitingExecutionSource: {}, }, }; return { additionalData: mock(), executeFunctions: mock(), taskSettings, codeNode, workflow, inputData, inputConnections, runExecutionData, envProviderState: createEnvProviderState(), }; }; const runTaskWithCode = async (jsCode: string) => { const { additionalData, taskSettings, codeNode, workflow, inputData, inputConnections, runExecutionData, executeFunctions, envProviderState, } = newTaskData(jsCode); return await taskRequester.startTask( additionalData, 'javascript', taskSettings, executeFunctions, inputConnections, codeNode, workflow, runExecutionData, 0, 0, codeNode.name, inputData, mock(), mock(), envProviderState, ); }; describe('Basic code execution', () => { beforeAll(async () => { await taskRunnerModule.start(); }); afterAll(async () => { await taskRunnerModule.stop(); }); it('should execute a simple JS task', async () => { // Act const result = await runTaskWithCode('return [{ hello: "world" }]'); // Assert expect(result).toEqual({ ok: true, result: [{ json: { hello: 'world' } }], }); }); }); describe('Internal and external libs', () => { beforeAll(async () => { process.env.NODE_FUNCTION_ALLOW_BUILTIN = 'crypto'; process.env.NODE_FUNCTION_ALLOW_EXTERNAL = 'moment'; await taskRunnerModule.start(); }); afterAll(async () => { await taskRunnerModule.stop(); }); it('should allow importing allowed internal module', async () => { // Act const result = await runTaskWithCode(` const crypto = require("crypto"); return [{ digest: crypto .createHmac("sha256", Buffer.from("MySecretKey")) .update("MESSAGE") .digest("base64") }] `); expect(result).toEqual({ ok: true, result: [{ json: { digest: 'T09DMv7upNDKMD3Ht36FkwzrmWSgWpPiUNlcIX9/yaI=' } }], }); }); it('should not allow importing disallowed internal module', async () => { // Act const result = await runTaskWithCode(` const fs = require("fs"); return [{ file: fs.readFileSync("test.txt") }] `); expect(result).toEqual({ ok: false, error: expect.objectContaining({ message: "Cannot find module 'fs' [line 2]", }), }); }); it('should allow importing allowed external module', async () => { // Act const result = await runTaskWithCode(` const moment = require("moment"); return [{ time: moment("1995-12-25").format("YYYY-MM-DD") }] `); expect(result).toEqual({ ok: true, result: [{ json: { time: '1995-12-25' } }], }); }); it('should not allow importing disallowed external module', async () => { // Act const result = await runTaskWithCode(` const lodash = require("lodash"); return [{ obj: lodash.cloneDeep({}) }] `); expect(result).toEqual({ ok: false, error: expect.objectContaining({ message: "Cannot find module 'lodash' [line 2]", }), }); }); }); });