chore: Add node mocking logic to the Test Runner (no-changelog) (#12009)

This commit is contained in:
Eugene 2024-12-13 12:40:04 +01:00 committed by GitHub
parent c3b968acf5
commit 70b0d81604
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 61 additions and 15 deletions

View file

@ -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({});
});
});

View file

@ -163,6 +163,7 @@ describe('TestRunnerService', () => {
mock<TestDefinition>({
workflowId: 'workflow-under-test-id',
evaluationWorkflowId: 'evaluation-workflow-id',
mockedNodes: [],
}),
);
@ -219,6 +220,7 @@ describe('TestRunnerService', () => {
mock<TestDefinition>({
workflowId: 'workflow-under-test-id',
evaluationWorkflowId: 'evaluation-workflow-id',
mockedNodes: [{ name: 'When clicking Test workflow' }],
}),
);

View file

@ -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<IRun | undefined> {
// 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

View file

@ -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];
}
}
}