diff --git a/packages/cli/src/evaluation/test-runner/__tests__/create-pin-data.ee.test.ts b/packages/cli/src/evaluation/test-runner/__tests__/create-pin-data.ee.test.ts index 6da88f9c20..685c15552b 100644 --- a/packages/cli/src/evaluation/test-runner/__tests__/create-pin-data.ee.test.ts +++ b/packages/cli/src/evaluation/test-runner/__tests__/create-pin-data.ee.test.ts @@ -13,7 +13,9 @@ const executionDataJson = JSON.parse( describe('createPinData', () => { test('should create pin data from past execution data', () => { - const pinData = createPinData(wfUnderTestJson, executionDataJson); + const mockedNodes = ['When clicking ‘Test workflow’'].map((name) => ({ name })); + + const pinData = createPinData(wfUnderTestJson, mockedNodes, executionDataJson); expect(pinData).toEqual( expect.objectContaining({ @@ -21,4 +23,34 @@ describe('createPinData', () => { }), ); }); + + test('should not create pin data for non-existing mocked nodes', () => { + const mockedNodes = ['Non-existing node'].map((name) => ({ name })); + + const pinData = createPinData(wfUnderTestJson, mockedNodes, executionDataJson); + + expect(pinData).toEqual({}); + }); + + test('should create pin data for all mocked nodes', () => { + const mockedNodes = ['When clicking ‘Test workflow’', 'Edit Fields', 'Code'].map((name) => ({ + name, + })); + + const pinData = createPinData(wfUnderTestJson, mockedNodes, executionDataJson); + + expect(pinData).toEqual( + expect.objectContaining({ + 'When clicking ‘Test workflow’': expect.anything(), + 'Edit Fields': expect.anything(), + Code: expect.anything(), + }), + ); + }); + + test('should return empty object if no mocked nodes are provided', () => { + const pinData = createPinData(wfUnderTestJson, [], executionDataJson); + + expect(pinData).toEqual({}); + }); }); diff --git a/packages/cli/src/evaluation/test-runner/__tests__/test-runner.service.ee.test.ts b/packages/cli/src/evaluation/test-runner/__tests__/test-runner.service.ee.test.ts index cdb8e848d9..e03fb8dc47 100644 --- a/packages/cli/src/evaluation/test-runner/__tests__/test-runner.service.ee.test.ts +++ b/packages/cli/src/evaluation/test-runner/__tests__/test-runner.service.ee.test.ts @@ -163,6 +163,7 @@ describe('TestRunnerService', () => { mock({ workflowId: 'workflow-under-test-id', evaluationWorkflowId: 'evaluation-workflow-id', + mockedNodes: [], }), ); @@ -219,6 +220,7 @@ describe('TestRunnerService', () => { mock({ workflowId: 'workflow-under-test-id', evaluationWorkflowId: 'evaluation-workflow-id', + mockedNodes: [{ name: 'When clicking ‘Test workflow’' }], }), ); diff --git a/packages/cli/src/evaluation/test-runner/test-runner.service.ee.ts b/packages/cli/src/evaluation/test-runner/test-runner.service.ee.ts index 92f5e4394e..11903581cd 100644 --- a/packages/cli/src/evaluation/test-runner/test-runner.service.ee.ts +++ b/packages/cli/src/evaluation/test-runner/test-runner.service.ee.ts @@ -11,7 +11,7 @@ import { Service } from 'typedi'; import { ActiveExecutions } from '@/active-executions'; import type { ExecutionEntity } from '@/databases/entities/execution-entity'; -import type { TestDefinition } from '@/databases/entities/test-definition.ee'; +import type { MockedNodeItem, TestDefinition } from '@/databases/entities/test-definition.ee'; import type { User } from '@/databases/entities/user'; import type { WorkflowEntity } from '@/databases/entities/workflow-entity'; import { ExecutionRepository } from '@/databases/repositories/execution.repository'; @@ -30,9 +30,7 @@ import { createPinData, getPastExecutionTriggerNode } from './utils.ee'; * past executions, creates pin data from them, * and runs the workflow-under-test with the pin data. * After the workflow-under-test finishes, it runs the evaluation workflow - * with the original and new run data. - * TODO: Node pinning - * TODO: Collect metrics + * with the original and new run data, and collects the metrics. */ @Service() export class TestRunnerService { @@ -52,10 +50,11 @@ export class TestRunnerService { private async runTestCase( workflow: WorkflowEntity, pastExecutionData: IRunExecutionData, + mockedNodes: MockedNodeItem[], userId: string, ): Promise { // Create pin data from the past execution data - const pinData = createPinData(workflow, pastExecutionData); + const pinData = createPinData(workflow, mockedNodes, pastExecutionData); // Determine the start node of the past execution const pastExecutionStartNode = getPastExecutionTriggerNode(pastExecutionData); @@ -196,7 +195,12 @@ export class TestRunnerService { const executionData = parse(pastExecution.executionData.data) as IRunExecutionData; // Run the test case and wait for it to finish - const testCaseExecution = await this.runTestCase(workflow, executionData, user.id); + const testCaseExecution = await this.runTestCase( + workflow, + executionData, + test.mockedNodes, + user.id, + ); // In case of a permission check issue, the test case execution will be undefined. // Skip them and continue with the next test case diff --git a/packages/cli/src/evaluation/test-runner/utils.ee.ts b/packages/cli/src/evaluation/test-runner/utils.ee.ts index c2ef68a5dc..e608ad6b4a 100644 --- a/packages/cli/src/evaluation/test-runner/utils.ee.ts +++ b/packages/cli/src/evaluation/test-runner/utils.ee.ts @@ -1,21 +1,29 @@ import type { IRunExecutionData, IPinData } from 'n8n-workflow'; +import type { MockedNodeItem } from '@/databases/entities/test-definition.ee'; import type { WorkflowEntity } from '@/databases/entities/workflow-entity'; /** * Extracts the execution data from the past execution * and creates a pin data object from it for the given workflow. - * For now, it only pins trigger nodes. + * It uses a list of mocked nodes defined in a test definition + * to decide which nodes to pin. */ -export function createPinData(workflow: WorkflowEntity, executionData: IRunExecutionData) { - const triggerNodes = workflow.nodes.filter((node) => /trigger$/i.test(node.type)); - +export function createPinData( + workflow: WorkflowEntity, + mockedNodes: MockedNodeItem[], + executionData: IRunExecutionData, +) { const pinData = {} as IPinData; - for (const triggerNode of triggerNodes) { - const triggerData = executionData.resultData.runData[triggerNode.name]; - if (triggerData?.[0]?.data?.main?.[0]) { - pinData[triggerNode.name] = triggerData[0]?.data?.main?.[0]; + const workflowNodeNames = new Set(workflow.nodes.map((node) => node.name)); + + for (const mockedNode of mockedNodes) { + if (workflowNodeNames.has(mockedNode.name)) { + const nodeData = executionData.resultData.runData[mockedNode.name]; + if (nodeData?.[0]?.data?.main?.[0]) { + pinData[mockedNode.name] = nodeData[0]?.data?.main?.[0]; + } } }