Introduce a TAB to test the HTTP node (#13622)

<img width="1020" height="757" alt="Screenshot 2025-08-05 at 11 05 12"
src="https://github.com/user-attachments/assets/7cfcb216-d34d-4daa-b468-8153869c8f6a"
/>
This commit is contained in:
Félix Malfait
2025-08-06 12:12:13 +02:00
committed by GitHub
parent de802b1447
commit b7022202fa
39 changed files with 1799 additions and 169 deletions
@@ -1,113 +0,0 @@
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
describe('resolveInput', () => {
const context = {
user: {
name: 'John Doe',
age: 30,
},
settings: {
theme: 'dark',
notifications: true,
},
};
it('should return null for null input', () => {
expect(resolveInput(null, context)).toBeNull();
});
it('should return undefined for undefined input', () => {
expect(resolveInput(undefined, context)).toBeUndefined();
});
it('should resolve a simple string variable', () => {
expect(resolveInput('{{user.name}}', context)).toBe('John Doe');
});
it('should resolve multiple variables in a string', () => {
expect(
resolveInput('Name: {{user.name}}, Age: {{user.age}}', context),
).toBe('Name: John Doe, Age: 30');
});
it('should handle non-existent variables', () => {
expect(resolveInput('{{user.email}}', context)).toBe(undefined);
});
it('should resolve variables in an array', () => {
const input = ['{{user.name}}', '{{settings.theme}}', 'static'];
const expected = ['John Doe', 'dark', 'static'];
expect(resolveInput(input, context)).toEqual(expected);
});
it('should resolve variables in an object', () => {
const input = {
name: '{{user.name}}',
theme: '{{settings.theme}}',
static: 'value',
};
const expected = {
name: 'John Doe',
theme: 'dark',
static: 'value',
};
expect(resolveInput(input, context)).toEqual(expected);
});
it('should handle nested objects and arrays', () => {
const input = {
user: {
displayName: '{{user.name}}',
preferences: ['{{settings.theme}}', '{{settings.notifications}}'],
},
staticData: [1, 2, 3],
};
const expected = {
user: {
displayName: 'John Doe',
preferences: ['dark', true],
},
staticData: [1, 2, 3],
};
expect(resolveInput(input, context)).toEqual(expected);
});
it('does not wrap string variables with double quotes', () => {
expect(
resolveInput('{ {{test}}: 2 }', {
test: 'prop',
}),
).toBe('{ prop: 2 }');
});
it('does not modify static JSON', () => {
expect(resolveInput('{ "a": 2 }', {})).toBe('{ "a": 2 }');
});
it('supports variable as JSON object property name', () => {
expect(
resolveInput('{ "{{test}}": 2 }', {
test: 'prop',
}),
).toBe('{ "prop": 2 }');
});
it('supports variable as JSON number value', () => {
expect(
resolveInput('{ "a": {{test}} }', {
test: 2,
}),
).toBe('{ "a": 2 }');
});
it('supports variable as JSON string value', () => {
expect(
resolveInput('{ "a": "{{test}}" }', {
test: 'str',
}),
).toBe('{ "a": "str" }');
});
});
@@ -1,100 +0,0 @@
import { isNil, isString } from '@nestjs/common/utils/shared.utils';
import Handlebars from 'handlebars';
const VARIABLE_PATTERN = RegExp('\\{\\{(.*?)\\}\\}', 'g');
export const resolveInput = (
unresolvedInput: unknown,
context: Record<string, unknown>,
): unknown => {
if (isNil(unresolvedInput)) {
return unresolvedInput;
}
if (isString(unresolvedInput)) {
return resolveString(unresolvedInput, context);
}
if (Array.isArray(unresolvedInput)) {
return resolveArray(unresolvedInput, context);
}
if (typeof unresolvedInput === 'object' && unresolvedInput !== null) {
return resolveObject(unresolvedInput, context);
}
return unresolvedInput;
};
const resolveArray = (
input: unknown[],
context: Record<string, unknown>,
): unknown[] => {
const resolvedArray = input;
for (let i = 0; i < input.length; ++i) {
resolvedArray[i] = resolveInput(input[i], context);
}
return resolvedArray;
};
const resolveObject = (
input: object,
context: Record<string, unknown>,
): object => {
return Object.entries(input).reduce<Record<string, unknown>>(
(resolvedObject, [key, value]) => {
const resolvedKey = resolveInput(key, context);
resolvedObject[
typeof resolvedKey === 'string' ? resolvedKey : String(resolvedKey)
] = resolveInput(value, context);
return resolvedObject;
},
{},
);
};
const resolveString = (
input: string,
context: Record<string, unknown>,
): string => {
const matchedTokens = input.match(VARIABLE_PATTERN);
if (!matchedTokens || matchedTokens.length === 0) {
return input;
}
if (matchedTokens.length === 1 && matchedTokens[0] === input) {
return evalFromContext(input, context);
}
return input.replace(VARIABLE_PATTERN, (matchedToken, _) => {
const processedToken = evalFromContext(matchedToken, context);
return processedToken;
});
};
const evalFromContext = (input: string, context: Record<string, unknown>) => {
try {
Handlebars.registerHelper('json', (input: string) => JSON.stringify(input));
const inputWithHelper = input
.replace('{{', '{{{ json ')
.replace('}}', ' }}}');
const inferredInput = Handlebars.compile(inputWithHelper)(context, {
helpers: {
json: (input: string) => JSON.stringify(input),
},
});
return JSON.parse(inferredInput) ?? '';
} catch (exception) {
return undefined;
}
};
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { resolveInput } from 'twenty-shared/utils';
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
@@ -18,7 +19,6 @@ import {
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
import { isWorkflowAiAgentAction } from './guards/is-workflow-ai-agent-action.guard';
@@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common';
import { resolveInput } from 'twenty-shared/utils';
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
@@ -10,7 +12,6 @@ import {
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
import { isWorkflowCodeAction } from 'src/modules/workflow/workflow-executor/workflow-actions/code/guards/is-workflow-code-action.guard';
import { WorkflowCodeActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-input.type';
@@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common';
import { resolveInput } from 'twenty-shared/utils';
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
import {
@@ -8,7 +10,6 @@ import {
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
import { isWorkflowFilterAction } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/guards/is-workflow-filter-action.guard';
import { evaluateFilterConditions } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/utils/evaluate-filter-conditions.util';
@@ -3,6 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'class-validator';
import { Repository } from 'typeorm';
import { resolveInput } from 'twenty-shared/utils';
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
@@ -19,7 +20,6 @@ import {
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
import {
RecordCRUDActionException,
RecordCRUDActionExceptionCode,
@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'class-validator';
import { isValidUuid } from 'twenty-shared/utils';
import { isValidUuid, resolveInput } from 'twenty-shared/utils';
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
@@ -13,7 +13,6 @@ import {
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
import {
RecordCRUDActionException,
RecordCRUDActionExceptionCode,
@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
import { Entity } from '@microsoft/microsoft-graph-types';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { ObjectLiteral } from 'typeorm';
import { resolveInput } from 'twenty-shared/utils';
import {
ObjectRecordFilter,
@@ -24,7 +25,6 @@ import {
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
import {
RecordCRUDActionException,
RecordCRUDActionExceptionCode,
@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import deepEqual from 'deep-equal';
import { isDefined, isValidUuid } from 'twenty-shared/utils';
import { isDefined, isValidUuid, resolveInput } from 'twenty-shared/utils';
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
@@ -15,7 +15,6 @@ import {
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
import {
RecordCRUDActionException,
RecordCRUDActionExceptionCode,
@@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common';
import { resolveInput } from 'twenty-shared/utils';
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
import { ToolType } from 'src/engine/core-modules/tool/enums/tool-type.enum';
@@ -7,7 +9,6 @@ import { ToolRegistryService } from 'src/engine/core-modules/tool/services/tool-
import { ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@Injectable()