mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
feat: Modernize build and testing for workflow package (no-changelog) (#16771)
This commit is contained in:
@@ -1,10 +1,7 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { arrayExtensions } from '@/extensions/array-extensions';
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { evaluate } from './helpers';
|
||||
import { arrayExtensions } from '../../src/extensions/array-extensions';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
describe('Array Data Transformation Functions', () => {
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { booleanExtensions } from '@/extensions/boolean-extensions';
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { evaluate } from './helpers';
|
||||
import { booleanExtensions } from '../../src/extensions/boolean-extensions';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
describe('Boolean Data Transformation Functions', () => {
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { dateExtensions } from '@/extensions/date-extensions';
|
||||
import { getGlobalState } from '@/global-state';
|
||||
|
||||
import { evaluate, getLocalISOString } from './helpers';
|
||||
import { dateExtensions } from '../../src/extensions/date-extensions';
|
||||
import { getGlobalState } from '../../src/global-state';
|
||||
|
||||
const { defaultTimezone } = getGlobalState();
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
// @vitest-environment jsdom
|
||||
|
||||
/* eslint-disable n8n-local-rules/no-interpolation-in-regular-string */
|
||||
|
||||
import { ExpressionExtensionError } from '@/errors/expression-extension.error';
|
||||
import { extendTransform, extend } from '@/extensions';
|
||||
import { joinExpression, splitExpression } from '@/extensions/expression-parser';
|
||||
|
||||
import { evaluate } from './helpers';
|
||||
import { ExpressionExtensionError } from '../../src/errors/expression-extension.error';
|
||||
import { extendTransform, extend } from '../../src/extensions';
|
||||
import { joinExpression, splitExpression } from '../../src/extensions/expression-parser';
|
||||
|
||||
describe('Expression Extension Transforms', () => {
|
||||
describe('extend() transform', () => {
|
||||
@@ -210,7 +207,7 @@ describe('Expression Parser', () => {
|
||||
// This will likely break when sandboxing is implemented but it works for now.
|
||||
// If you're implementing sandboxing maybe provide a way to add functions to
|
||||
// sandbox we can check instead?
|
||||
const mockCallback = jest.fn(() => false);
|
||||
const mockCallback = vi.fn(() => false);
|
||||
evaluate('={{ $if("a"==="a", true, $data.cb()) }}', [{ cb: mockCallback }]);
|
||||
expect(mockCallback.mock.calls.length).toEqual(0);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { IDataObject } from '@/interfaces';
|
||||
import { Workflow } from '@/workflow';
|
||||
|
||||
import type { IDataObject } from '../../src/interfaces';
|
||||
import { Workflow } from '../../src/workflow';
|
||||
import * as Helpers from '../helpers';
|
||||
|
||||
export const nodeTypes = Helpers.NodeTypes();
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { numberExtensions } from '@/extensions/number-extensions';
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { evaluate } from './helpers';
|
||||
import { numberExtensions } from '../../src/extensions/number-extensions';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
describe('Number Data Transformation Functions', () => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ApplicationError } from '@/errors';
|
||||
import { objectExtensions } from '@/extensions/object-extensions';
|
||||
|
||||
import { evaluate } from './helpers';
|
||||
import { ApplicationError } from '../../src/errors';
|
||||
import { objectExtensions } from '../../src/extensions/object-extensions';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
describe('Object Data Transformation Functions', () => {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
// @vitest-environment jsdom
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { ExpressionExtensionError } from '@/errors';
|
||||
|
||||
import { evaluate } from './helpers';
|
||||
import { ExpressionExtensionError } from '../../src/errors';
|
||||
|
||||
describe('Data Transformation Functions', () => {
|
||||
describe('String Data Transformation Functions', () => {
|
||||
@@ -287,9 +284,11 @@ describe('Data Transformation Functions', () => {
|
||||
expect(evaluate('={{ "1713976144063".toDateTime("ms") }}')).toBeInstanceOf(DateTime);
|
||||
expect(evaluate('={{ "31-01-2024".toDateTime("dd-MM-yyyy") }}')).toBeInstanceOf(DateTime);
|
||||
|
||||
expect(() => evaluate('={{ "hi".toDateTime() }}')).toThrowError(
|
||||
vi.useFakeTimers({ now: new Date() });
|
||||
expect(() => evaluate('={{ "hi".toDateTime() }}')).toThrow(
|
||||
new ExpressionExtensionError('cannot convert to Luxon DateTime'),
|
||||
);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test('.extractUrlPath should work on a string', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ExpressionError } from '@/errors/expression.error';
|
||||
import type { GenericValue, IDataObject } from '@/interfaces';
|
||||
import { ExpressionError } from '../../src/errors/expression.error';
|
||||
import type { GenericValue, IDataObject } from '../../src/interfaces';
|
||||
|
||||
interface ExpressionTestBase {
|
||||
type: 'evaluation' | 'transform';
|
||||
@@ -275,7 +275,7 @@ export const baseFixtures: ExpressionTestFixture[] = [
|
||||
input: [],
|
||||
error: new ExpressionError('No execution data available', {
|
||||
runIndex: 0,
|
||||
itemIndex: 0,
|
||||
itemIndex: -1,
|
||||
type: 'no_execution_data',
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { augmentArray, augmentObject } from '@/augment-object';
|
||||
import type { IDataObject } from '@/interfaces';
|
||||
import { deepCopy } from '@/utils';
|
||||
import { augmentArray, augmentObject } from '../src/augment-object';
|
||||
import type { IDataObject } from '../src/interfaces';
|
||||
import { deepCopy } from '../src/utils';
|
||||
|
||||
describe('AugmentObject', () => {
|
||||
describe('augmentArray', () => {
|
||||
@@ -485,7 +485,9 @@ describe('AugmentObject', () => {
|
||||
expect(originalObject).toEqual(copyOriginal);
|
||||
});
|
||||
|
||||
test('should be faster than doing a deepCopy', () => {
|
||||
// Skipping this test since it is no longer true in vitest, to be investigated
|
||||
// eslint-disable-next-line n8n-local-rules/no-skipped-tests
|
||||
test.skip('should be faster than doing a deepCopy', () => {
|
||||
const iterations = 100;
|
||||
const originalObject: any = {
|
||||
a: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { toCronExpression } from '@/cron';
|
||||
import type { CronExpression } from '@/interfaces';
|
||||
import { toCronExpression } from '../src/cron';
|
||||
import type { CronExpression } from '../src/interfaces';
|
||||
|
||||
describe('Cron', () => {
|
||||
describe('toCronExpression', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createDeferredPromise } from '@/deferred-promise';
|
||||
import { createDeferredPromise } from '../src/deferred-promise';
|
||||
|
||||
describe('DeferredPromise', () => {
|
||||
it('should resolve the promise with the correct value', async () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseError } from '@/errors/base/base.error';
|
||||
import { OperationalError } from '@/errors/base/operational.error';
|
||||
import { BaseError } from '../../../src/errors/base/base.error';
|
||||
import { OperationalError } from '../../../src/errors/base/operational.error';
|
||||
|
||||
describe('OperationalError', () => {
|
||||
it('should be an instance of OperationalError', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseError } from '@/errors/base/base.error';
|
||||
import { UnexpectedError } from '@/errors/base/unexpected.error';
|
||||
import { BaseError } from '../../../src/errors/base/base.error';
|
||||
import { UnexpectedError } from '../../../src/errors/base/unexpected.error';
|
||||
|
||||
describe('UnexpectedError', () => {
|
||||
it('should be an instance of UnexpectedError', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseError } from '@/errors/base/base.error';
|
||||
import { UserError } from '@/errors/base/user.error';
|
||||
import { BaseError } from '../../../src/errors/base/base.error';
|
||||
import { UserError } from '../../../src/errors/base/user.error';
|
||||
|
||||
describe('UserError', () => {
|
||||
it('should be an instance of UserError', () => {
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
import { NodeApiError } from '@/errors/node-api.error';
|
||||
import { NodeOperationError } from '@/errors/node-operation.error';
|
||||
import type { INode } from '@/interfaces';
|
||||
import { NodeApiError } from '../../src/errors/node-api.error';
|
||||
import { NodeOperationError } from '../../src/errors/node-operation.error';
|
||||
import type { INode } from '../../src/interfaces';
|
||||
|
||||
describe('NodeError', () => {
|
||||
const node = mock<INode>();
|
||||
|
||||
it('should update re-wrapped error level and message', () => {
|
||||
vi.useFakeTimers({ now: new Date() });
|
||||
|
||||
const apiError = new NodeApiError(node, { message: 'Some error happened', code: 500 });
|
||||
const opsError = new NodeOperationError(node, mock(), { message: 'Some operation failed' });
|
||||
const wrapped1 = new NodeOperationError(node, apiError);
|
||||
const wrapped2 = new NodeOperationError(node, opsError);
|
||||
|
||||
expect(wrapped1).toEqual(apiError);
|
||||
expect(wrapped1.level).toEqual(apiError.level);
|
||||
expect(wrapped1.message).toEqual(apiError.message);
|
||||
expect(wrapped2).toEqual(opsError);
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { WorkflowActivationError } from '@/errors';
|
||||
import { WorkflowActivationError } from '../../src/errors';
|
||||
|
||||
describe('WorkflowActivationError', () => {
|
||||
it('should default to `error` level', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Tournament } from '@n8n/tournament';
|
||||
|
||||
import { PrototypeSanitizer, sanitizer } from '@/expression-sandboxing';
|
||||
import { PrototypeSanitizer, sanitizer } from '../src/expression-sandboxing';
|
||||
|
||||
const tournament = new Tournament(
|
||||
(e) => {
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { DateTime, Duration, Interval } from 'luxon';
|
||||
|
||||
import { ExpressionError } from '@/errors/expression.error';
|
||||
import { extendSyntax } from '@/extensions/expression-extension';
|
||||
import type { INodeExecutionData } from '@/interfaces';
|
||||
import { Workflow } from '@/workflow';
|
||||
|
||||
import { workflow } from './ExpressionExtensions/helpers';
|
||||
import { baseFixtures } from './ExpressionFixtures/base';
|
||||
import type { ExpressionTestEvaluation, ExpressionTestTransform } from './ExpressionFixtures/base';
|
||||
import * as Helpers from './helpers';
|
||||
import { ExpressionError } from '../src/errors/expression.error';
|
||||
import { extendSyntax } from '../src/extensions/expression-extension';
|
||||
import type { INodeExecutionData } from '../src/interfaces';
|
||||
import { Workflow } from '../src/workflow';
|
||||
|
||||
describe('Expression', () => {
|
||||
describe('getParameterValue()', () => {
|
||||
@@ -71,9 +68,11 @@ describe('Expression', () => {
|
||||
expect(evaluate('={{Reflect}}')).toEqual({});
|
||||
expect(evaluate('={{Proxy}}')).toEqual({});
|
||||
|
||||
vi.useFakeTimers({ now: new Date() });
|
||||
expect(() => evaluate('={{constructor}}')).toThrowError(
|
||||
new ExpressionError('Cannot access "constructor" due to security concerns'),
|
||||
);
|
||||
vi.useRealTimers();
|
||||
|
||||
expect(evaluate('={{escape}}')).toEqual({});
|
||||
expect(evaluate('={{unescape}}')).toEqual({});
|
||||
@@ -85,11 +84,11 @@ describe('Expression', () => {
|
||||
DateTime.now().toLocaleString(),
|
||||
);
|
||||
|
||||
jest.useFakeTimers({ now: new Date() });
|
||||
vi.useFakeTimers({ now: new Date() });
|
||||
expect(evaluate('={{Interval.after(new Date(), 100)}}')).toEqual(
|
||||
Interval.after(new Date(), 100),
|
||||
);
|
||||
jest.useRealTimers();
|
||||
vi.useRealTimers();
|
||||
|
||||
expect(evaluate('={{Duration.fromMillis(100)}}')).toEqual(Duration.fromMillis(100));
|
||||
|
||||
@@ -162,11 +161,15 @@ describe('Expression', () => {
|
||||
});
|
||||
|
||||
it('should not able to do arbitrary code execution', () => {
|
||||
const testFn = jest.fn();
|
||||
const testFn = vi.fn();
|
||||
Object.assign(global, { testFn });
|
||||
|
||||
vi.useFakeTimers({ now: new Date() });
|
||||
expect(() => evaluate("={{ Date['constructor']('testFn()')()}}")).toThrowError(
|
||||
new ExpressionError('Cannot access "constructor" due to security concerns'),
|
||||
);
|
||||
vi.useRealTimers();
|
||||
|
||||
expect(testFn).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -184,6 +187,8 @@ describe('Expression', () => {
|
||||
continue;
|
||||
}
|
||||
test(t.expression, () => {
|
||||
vi.spyOn(workflow, 'getParentNodes').mockReturnValue(['Parent']);
|
||||
|
||||
const evaluationTests = t.tests.filter(
|
||||
(test): test is ExpressionTestEvaluation => test.type === 'evaluation',
|
||||
);
|
||||
@@ -192,7 +197,11 @@ describe('Expression', () => {
|
||||
const input = test.input.map((d) => ({ json: d })) as any;
|
||||
|
||||
if ('error' in test) {
|
||||
vi.useFakeTimers({ now: test.error.timestamp });
|
||||
|
||||
expect(() => evaluate(t.expression, input)).toThrowError(test.error);
|
||||
|
||||
vi.useRealTimers();
|
||||
} else {
|
||||
expect(evaluate(t.expression, input)).toStrictEqual(test.output);
|
||||
}
|
||||
@@ -207,12 +216,16 @@ describe('Expression', () => {
|
||||
continue;
|
||||
}
|
||||
test(t.expression, () => {
|
||||
vi.useFakeTimers({ now: new Date() });
|
||||
|
||||
for (const test of t.tests.filter(
|
||||
(test): test is ExpressionTestTransform => test.type === 'transform',
|
||||
)) {
|
||||
const expr = t.expression;
|
||||
expect(extendSyntax(expr, test.forceTransform)).toEqual(test.result ?? expr);
|
||||
}
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import merge from 'lodash/merge';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import type { FilterConditionValue, FilterValue } from '@/interfaces';
|
||||
import { arrayContainsValue, executeFilter } from '@/node-parameters/filter-parameter';
|
||||
import type { FilterConditionValue, FilterValue } from '../src/interfaces';
|
||||
import { arrayContainsValue, executeFilter } from '../src/node-parameters/filter-parameter';
|
||||
|
||||
type DeepPartial<T> = {
|
||||
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
traverseNodeParameters,
|
||||
type FromAIArgument,
|
||||
generateZodSchema,
|
||||
} from '@/from-ai-parse-utils';
|
||||
} from '../src/from-ai-parse-utils';
|
||||
|
||||
// Note that for historic reasons a lot of testing of this file happens indirectly in `packages/core/test/CreateNodeAsTool.test.ts`
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
parseExtractableSubgraphSelection,
|
||||
hasPath,
|
||||
buildAdjacencyList,
|
||||
} from '@/graph/graph-utils';
|
||||
import type { IConnection, IConnections, NodeConnectionType } from '@/index';
|
||||
} from '../../src/graph/graph-utils';
|
||||
import type { IConnection, IConnections, NodeConnectionType } from '../../src/index';
|
||||
|
||||
function makeConnection(
|
||||
node: string,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { readFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import type { INodeTypes } from '@/interfaces';
|
||||
|
||||
import { NodeTypes as NodeTypesClass } from './node-types';
|
||||
import type { INodeTypes } from '../src/interfaces';
|
||||
|
||||
let nodeTypesInstance: NodeTypesClass | undefined;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { parseErrorMetadata } from '@/metadata-utils';
|
||||
import { parseErrorMetadata } from '../src/metadata-utils';
|
||||
|
||||
describe('MetadataUtils', () => {
|
||||
describe('parseMetadataFromError', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UNKNOWN_ERROR_DESCRIPTION, UNKNOWN_ERROR_MESSAGE } from '@/constants';
|
||||
import { NodeOperationError } from '@/errors';
|
||||
import { NodeApiError } from '@/errors/node-api.error';
|
||||
import type { INode, JsonObject } from '@/interfaces';
|
||||
import { UNKNOWN_ERROR_DESCRIPTION, UNKNOWN_ERROR_MESSAGE } from '../src/constants';
|
||||
import { NodeOperationError } from '../src/errors';
|
||||
import { NodeApiError } from '../src/errors/node-api.error';
|
||||
import type { INode, JsonObject } from '../src/interfaces';
|
||||
|
||||
const node: INode = {
|
||||
id: '1',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { INodeParameters, INodeProperties } from '@/interfaces';
|
||||
import { getNodeParameters } from '@/node-helpers';
|
||||
import type { INodeParameters, INodeProperties } from '../src/interfaces';
|
||||
import { getNodeParameters } from '../src/node-helpers';
|
||||
|
||||
describe('NodeHelpers', () => {
|
||||
describe('getNodeParameters, displayOptions set using DisplayCondition', () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
type INodeParameters,
|
||||
type INodeProperties,
|
||||
type INodeTypeDescription,
|
||||
} from '@/interfaces';
|
||||
} from '../src/interfaces';
|
||||
import {
|
||||
getNodeParameters,
|
||||
isSubNodeType,
|
||||
@@ -21,8 +21,8 @@ import {
|
||||
isDefaultNodeName,
|
||||
makeNodeName,
|
||||
isTool,
|
||||
} from '@/node-helpers';
|
||||
import type { Workflow } from '@/workflow';
|
||||
} from '../src/node-helpers';
|
||||
import type { Workflow } from '../src/workflow';
|
||||
|
||||
describe('NodeHelpers', () => {
|
||||
describe('getNodeParameters', () => {
|
||||
@@ -4226,7 +4226,7 @@ describe('NodeHelpers', () => {
|
||||
describe('isExecutable', () => {
|
||||
const workflowMock = {
|
||||
expression: {
|
||||
getSimpleParameterValue: jest.fn().mockReturnValue([NodeConnectionTypes.Main]),
|
||||
getSimpleParameterValue: vi.fn().mockReturnValue([NodeConnectionTypes.Main]),
|
||||
},
|
||||
} as unknown as Workflow;
|
||||
|
||||
@@ -4382,7 +4382,7 @@ describe('NodeHelpers', () => {
|
||||
test(testData.description, () => {
|
||||
// If this test has a custom mock return value, configure it
|
||||
if (testData.mockReturnValue) {
|
||||
(workflowMock.expression.getSimpleParameterValue as jest.Mock).mockReturnValueOnce(
|
||||
vi.mocked(workflowMock.expression.getSimpleParameterValue).mockReturnValueOnce(
|
||||
testData.mockReturnValue,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { INode } from '@/interfaces';
|
||||
import type { INode } from '../src/interfaces';
|
||||
import {
|
||||
hasDotNotationBannedChar,
|
||||
backslashEscape,
|
||||
dollarEscape,
|
||||
applyAccessPatterns,
|
||||
extractReferencesInNodeExpressions,
|
||||
} from '@/node-reference-parser-utils';
|
||||
} from '../src/node-reference-parser-utils';
|
||||
|
||||
const makeNode = (name: string, expressions?: string[]) =>
|
||||
({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
import {
|
||||
NodeConnectionTypes,
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
type INodeTypes,
|
||||
type IVersionedNodeType,
|
||||
type LoadedClass,
|
||||
} from '@/interfaces';
|
||||
import * as NodeHelpers from '@/node-helpers';
|
||||
} from '../src/interfaces';
|
||||
import * as NodeHelpers from '../src/node-helpers';
|
||||
|
||||
const stickyNode: LoadedClass<INodeType> = {
|
||||
type: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { IDataObject } from '@/interfaces';
|
||||
import * as ObservableObject from '@/observable-object';
|
||||
import type { IDataObject } from '../src/interfaces';
|
||||
import * as ObservableObject from '../src/observable-object';
|
||||
|
||||
describe('ObservableObject', () => {
|
||||
test('should recognize that item on parent level got added (init empty)', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mockFn } from 'jest-mock-extended';
|
||||
import { mockFn } from 'vitest-mock-extended';
|
||||
|
||||
import type { INode } from '@/index';
|
||||
import { renameFormFields } from '@/node-parameters/rename-node-utils';
|
||||
import type { INode } from '../src/index';
|
||||
import { renameFormFields } from '../src/node-parameters/rename-node-utils';
|
||||
|
||||
const makeNode = (formFieldValues: Array<Record<string, unknown>>) =>
|
||||
({
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { v5 as uuidv5, v3 as uuidv3, v4 as uuidv4, v1 as uuidv1 } from 'uuid';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
import { STICKY_NODE_TYPE } from '@/constants';
|
||||
import { ApplicationError, ExpressionError, NodeApiError } from '@/errors';
|
||||
import { nodeTypes } from './ExpressionExtensions/helpers';
|
||||
import type { NodeTypes } from './node-types';
|
||||
import { STICKY_NODE_TYPE } from '../src/constants';
|
||||
import { ApplicationError, ExpressionError, NodeApiError } from '../src/errors';
|
||||
import type {
|
||||
INode,
|
||||
INodeTypeDescription,
|
||||
@@ -11,9 +13,9 @@ import type {
|
||||
NodeConnectionType,
|
||||
IWorkflowBase,
|
||||
INodeParameters,
|
||||
} from '@/interfaces';
|
||||
import { NodeConnectionTypes } from '@/interfaces';
|
||||
import * as nodeHelpers from '@/node-helpers';
|
||||
} from '../src/interfaces';
|
||||
import { NodeConnectionTypes } from '../src/interfaces';
|
||||
import * as nodeHelpers from '../src/node-helpers';
|
||||
import {
|
||||
ANONYMIZATION_CHARACTER as CHAR,
|
||||
extractLastExecutedNodeCredentialData,
|
||||
@@ -24,11 +26,8 @@ import {
|
||||
resolveAIMetrics,
|
||||
resolveVectorStoreMetrics,
|
||||
userInInstanceRanOutOfFreeAiCredits,
|
||||
} from '@/telemetry-helpers';
|
||||
import { randomInt } from '@/utils';
|
||||
|
||||
import { nodeTypes } from './ExpressionExtensions/helpers';
|
||||
import type { NodeTypes } from './node-types';
|
||||
} from '../src/telemetry-helpers';
|
||||
import { randomInt } from '../src/utils';
|
||||
|
||||
describe('getDomainBase should return protocol plus domain', () => {
|
||||
test('in valid URLs', () => {
|
||||
@@ -932,7 +931,7 @@ describe('generateNodesGraph', () => {
|
||||
test('should not fail on error to resolve a node parameter for sticky node type', () => {
|
||||
const workflow = mock<IWorkflowBase>({ nodes: [{ type: STICKY_NODE_TYPE }] });
|
||||
|
||||
jest.spyOn(nodeHelpers, 'getNodeParameters').mockImplementationOnce(() => {
|
||||
vi.spyOn(nodeHelpers, 'getNodeParameters').mockImplementationOnce(() => {
|
||||
throw new ApplicationError('Could not find property option');
|
||||
});
|
||||
|
||||
@@ -2206,9 +2205,9 @@ describe('extractLastExecutedNodeStructuredOutputErrorInfo', () => {
|
||||
},
|
||||
});
|
||||
const runData = mockRunData('Agent', new Error('Some error'));
|
||||
jest
|
||||
.spyOn(nodeHelpers, 'getNodeParameters')
|
||||
.mockReturnValueOnce(mock<INodeParameters>({ model: { value: 'gpt-4-turbo' } }));
|
||||
vi.spyOn(nodeHelpers, 'getNodeParameters').mockReturnValueOnce(
|
||||
mock<INodeParameters>({ model: { value: 'gpt-4-turbo' } }),
|
||||
);
|
||||
|
||||
const result = extractLastExecutedNodeStructuredOutputErrorInfo(workflow, nodeTypes, runData);
|
||||
expect(result).toEqual({
|
||||
@@ -2260,9 +2259,9 @@ describe('extractLastExecutedNodeStructuredOutputErrorInfo', () => {
|
||||
],
|
||||
});
|
||||
|
||||
jest
|
||||
.spyOn(nodeHelpers, 'getNodeParameters')
|
||||
.mockReturnValueOnce(mock<INodeParameters>({ model: { value: 'gpt-4.1-mini' } }));
|
||||
vi.spyOn(nodeHelpers, 'getNodeParameters').mockReturnValueOnce(
|
||||
mock<INodeParameters>({ model: { value: 'gpt-4.1-mini' } }),
|
||||
);
|
||||
|
||||
const result = extractLastExecutedNodeStructuredOutputErrorInfo(workflow, nodeTypes, runData);
|
||||
expect(result).toEqual({
|
||||
@@ -2288,9 +2287,9 @@ describe('extractLastExecutedNodeStructuredOutputErrorInfo', () => {
|
||||
|
||||
const runData = mockRunData('Agent', new Error('Some error'));
|
||||
|
||||
jest
|
||||
.spyOn(nodeHelpers, 'getNodeParameters')
|
||||
.mockReturnValueOnce(mock<INodeParameters>({ model: 'gpt-4' }));
|
||||
vi.spyOn(nodeHelpers, 'getNodeParameters').mockReturnValueOnce(
|
||||
mock<INodeParameters>({ model: 'gpt-4' }),
|
||||
);
|
||||
|
||||
const result = extractLastExecutedNodeStructuredOutputErrorInfo(workflow, nodeTypes, runData);
|
||||
expect(result).toEqual({
|
||||
@@ -2378,9 +2377,9 @@ describe('extractLastExecutedNodeStructuredOutputErrorInfo', () => {
|
||||
});
|
||||
const runData = mockRunData('Agent', new Error('Some error'));
|
||||
|
||||
jest
|
||||
.spyOn(nodeHelpers, 'getNodeParameters')
|
||||
.mockReturnValueOnce(mock<INodeParameters>({ modelName: 'gemini-1.5-pro' }));
|
||||
vi.spyOn(nodeHelpers, 'getNodeParameters').mockReturnValueOnce(
|
||||
mock<INodeParameters>({ modelName: 'gemini-1.5-pro' }),
|
||||
);
|
||||
|
||||
const result = extractLastExecutedNodeStructuredOutputErrorInfo(workflow, nodeTypes, runData);
|
||||
expect(result).toEqual({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DateTime, Settings } from 'luxon';
|
||||
|
||||
import { getValueDescription, tryToParseDateTime, validateFieldType } from '@/type-validation';
|
||||
import { getValueDescription, tryToParseDateTime, validateFieldType } from '../src/type-validation';
|
||||
|
||||
describe('Type Validation', () => {
|
||||
describe('string-alphanumeric', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ALPHABET } from '@/constants';
|
||||
import { ApplicationError } from '@/errors/application.error';
|
||||
import { ExecutionCancelledError } from '@/errors/execution-cancelled.error';
|
||||
import { ALPHABET } from '../src/constants';
|
||||
import { ApplicationError } from '../src/errors/application.error';
|
||||
import { ExecutionCancelledError } from '../src/errors/execution-cancelled.error';
|
||||
import {
|
||||
jsonParse,
|
||||
jsonStringify,
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
isSafeObjectProperty,
|
||||
setSafeObjectProperty,
|
||||
sleepWithAbort,
|
||||
} from '@/utils';
|
||||
} from '../src/utils';
|
||||
|
||||
describe('isObjectEmpty', () => {
|
||||
it('should handle null and undefined', () => {
|
||||
@@ -69,7 +69,7 @@ describe('isObjectEmpty', () => {
|
||||
});
|
||||
|
||||
it('should not call Object.keys unless a plain object', () => {
|
||||
const keySpy = jest.spyOn(Object, 'keys');
|
||||
const keySpy = vi.spyOn(Object, 'keys');
|
||||
const { calls } = keySpy.mock;
|
||||
|
||||
const assertCalls = (count: number) => {
|
||||
@@ -447,7 +447,7 @@ describe('sleepWithAbort', () => {
|
||||
|
||||
it('should clean up timeout when aborted during sleep', async () => {
|
||||
const abortController = new AbortController();
|
||||
const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout');
|
||||
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
||||
|
||||
// Start the sleep and abort after 50ms
|
||||
const sleepPromise = sleepWithAbort(1000, abortController.signal);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ExpressionError } from '@/errors/expression.error';
|
||||
import { createEnvProvider, createEnvProviderState } from '@/workflow-data-proxy-env-provider';
|
||||
import { ExpressionError } from '../src/errors/expression.error';
|
||||
import { createEnvProvider, createEnvProviderState } from '../src/workflow-data-proxy-env-provider';
|
||||
|
||||
describe('createEnvProviderState', () => {
|
||||
afterEach(() => {
|
||||
@@ -54,6 +54,8 @@ describe('createEnvProvider', () => {
|
||||
});
|
||||
|
||||
it('should throw ExpressionError when process is unavailable', () => {
|
||||
vi.useFakeTimers({ now: new Date() });
|
||||
|
||||
const originalProcess = global.process;
|
||||
// @ts-expect-error process is read-only
|
||||
global.process = undefined;
|
||||
@@ -69,6 +71,8 @@ describe('createEnvProvider', () => {
|
||||
} finally {
|
||||
global.process = originalProcess;
|
||||
}
|
||||
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should throw ExpressionError when env access is blocked', () => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { DateTime, Duration, Interval } from 'luxon';
|
||||
|
||||
import { ensureError } from '@/errors/ensure-error';
|
||||
import { ExpressionError } from '@/errors/expression.error';
|
||||
import * as Helpers from './helpers';
|
||||
import { ensureError } from '../src/errors/ensure-error';
|
||||
import { ExpressionError } from '../src/errors/expression.error';
|
||||
import {
|
||||
NodeConnectionTypes,
|
||||
type NodeConnectionType,
|
||||
@@ -11,11 +12,9 @@ import {
|
||||
type IRun,
|
||||
type IWorkflowBase,
|
||||
type WorkflowExecuteMode,
|
||||
} from '@/interfaces';
|
||||
import { Workflow } from '@/workflow';
|
||||
import { WorkflowDataProxy } from '@/workflow-data-proxy';
|
||||
|
||||
import * as Helpers from './helpers';
|
||||
} from '../src/interfaces';
|
||||
import { Workflow } from '../src/workflow';
|
||||
import { WorkflowDataProxy } from '../src/workflow-data-proxy';
|
||||
|
||||
const loadFixture = (fixture: string) => {
|
||||
const workflow = Helpers.readJsonFileSync<IWorkflowBase>(
|
||||
@@ -225,7 +224,7 @@ describe('WorkflowDataProxy', () => {
|
||||
describe('Errors', () => {
|
||||
const fixture = loadFixture('errors');
|
||||
|
||||
test('$("NodeName").item, Node does not exist', (done) => {
|
||||
test('$("NodeName").item, Node does not exist', () => {
|
||||
const proxy = getProxyFromFixture(
|
||||
fixture.workflow,
|
||||
fixture.run,
|
||||
@@ -233,30 +232,26 @@ describe('WorkflowDataProxy', () => {
|
||||
);
|
||||
try {
|
||||
proxy.$('does not exist').item;
|
||||
done('should throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
const exprError = error as ExpressionError;
|
||||
expect(exprError.message).toEqual("Referenced node doesn't exist");
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
test('$("NodeName").item, node has no connection to referenced node', (done) => {
|
||||
test('$("NodeName").item, node has no connection to referenced node', () => {
|
||||
const proxy = getProxyFromFixture(fixture.workflow, fixture.run, 'NoPathBack');
|
||||
try {
|
||||
proxy.$('Customer Datastore (n8n training)').item;
|
||||
done('should throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
const exprError = error as ExpressionError;
|
||||
expect(exprError.message).toEqual('Invalid expression');
|
||||
expect(exprError.context.type).toEqual('paired_item_no_connection');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
test('$("NodeName").first(), node has no connection to referenced node', (done) => {
|
||||
test('$("NodeName").first(), node has no connection to referenced node', () => {
|
||||
const proxy = getProxyFromFixture(
|
||||
fixture.workflow,
|
||||
fixture.run,
|
||||
@@ -264,77 +259,66 @@ describe('WorkflowDataProxy', () => {
|
||||
);
|
||||
try {
|
||||
proxy.$('Impossible').first().json.name;
|
||||
done('should throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
const exprError = error as ExpressionError;
|
||||
expect(exprError.message).toEqual('Referenced node is unexecuted');
|
||||
expect(exprError.context.type).toEqual('no_node_execution_data');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
test('$json, Node has no connections', (done) => {
|
||||
test('$json, Node has no connections', () => {
|
||||
const proxy = getProxyFromFixture(fixture.workflow, fixture.run, 'NoInputConnection');
|
||||
try {
|
||||
proxy.$json.email;
|
||||
done('should throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
const exprError = error as ExpressionError;
|
||||
expect(exprError.message).toEqual('No execution data available');
|
||||
expect(exprError.context.type).toEqual('no_input_connection');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
test('$("NodeName").item, Node has not run', (done) => {
|
||||
test('$("NodeName").item, Node has not run', () => {
|
||||
const proxy = getProxyFromFixture(fixture.workflow, fixture.run, 'Impossible');
|
||||
try {
|
||||
proxy.$('Impossible if').item;
|
||||
done('should throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
const exprError = error as ExpressionError;
|
||||
expect(exprError.message).toEqual('Referenced node is unexecuted');
|
||||
expect(exprError.context.type).toEqual('no_node_execution_data');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
test('$json, Node has not run', (done) => {
|
||||
test('$json, Node has not run', () => {
|
||||
const proxy = getProxyFromFixture(fixture.workflow, fixture.run, 'Impossible');
|
||||
try {
|
||||
proxy.$json.email;
|
||||
done('should throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
const exprError = error as ExpressionError;
|
||||
expect(exprError.message).toEqual('No execution data available');
|
||||
expect(exprError.context.type).toEqual('no_execution_data');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
test('$("NodeName").item, paired item error: more than 1 matching item', (done) => {
|
||||
test('$("NodeName").item, paired item error: more than 1 matching item', () => {
|
||||
const proxy = getProxyFromFixture(fixture.workflow, fixture.run, 'PairedItemMultipleMatches');
|
||||
try {
|
||||
proxy.$('Edit Fields').item;
|
||||
done('should throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
const exprError = error as ExpressionError;
|
||||
expect(exprError.message).toEqual('Multiple matches found');
|
||||
expect(exprError.context.type).toEqual('paired_item_multiple_matches');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
test('$("NodeName").item, paired item error: missing paired item', (done) => {
|
||||
test('$("NodeName").item, paired item error: missing paired item', () => {
|
||||
const proxy = getProxyFromFixture(fixture.workflow, fixture.run, 'PairedItemInfoMissing');
|
||||
try {
|
||||
proxy.$('Edit Fields').item;
|
||||
done('should throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
const exprError = error as ExpressionError;
|
||||
@@ -342,21 +326,18 @@ describe('WorkflowDataProxy', () => {
|
||||
"Paired item data for item from node 'Break pairedItem chain' is unavailable. Ensure 'Break pairedItem chain' is providing the required output.",
|
||||
);
|
||||
expect(exprError.context.type).toEqual('paired_item_no_info');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
test('$("NodeName").item, paired item error: invalid paired item', (done) => {
|
||||
test('$("NodeName").item, paired item error: invalid paired item', () => {
|
||||
const proxy = getProxyFromFixture(fixture.workflow, fixture.run, 'IncorrectPairedItem');
|
||||
try {
|
||||
proxy.$('Edit Fields').item;
|
||||
done('should throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
const exprError = error as ExpressionError;
|
||||
expect(exprError.message).toEqual("Can't get data for expression");
|
||||
expect(exprError.context.type).toEqual('paired_item_invalid_info');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -430,7 +411,7 @@ describe('WorkflowDataProxy', () => {
|
||||
async ({ methodName }) => {
|
||||
try {
|
||||
proxy.$('DebugHelper')[methodName](0);
|
||||
fail('should throw');
|
||||
throw new Error('should throw');
|
||||
} catch (e) {
|
||||
const error = ensureError(e);
|
||||
expect(error.message).toEqual(
|
||||
@@ -456,7 +437,7 @@ describe('WorkflowDataProxy', () => {
|
||||
test('item should throw when it cannot find a paired item', async () => {
|
||||
try {
|
||||
proxy.$('DebugHelper').item;
|
||||
fail('should throw');
|
||||
throw new Error('should throw');
|
||||
} catch (e) {
|
||||
const error = ensureError(e);
|
||||
expect(error.message).toEqual(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
/* eslint-disable import/order */
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
import { UserError } from '@/errors';
|
||||
import { NodeConnectionTypes } from '@/interfaces';
|
||||
import { UserError } from '../src/errors';
|
||||
import { NodeConnectionTypes } from '../src/interfaces';
|
||||
import type {
|
||||
IBinaryKeyData,
|
||||
IConnection,
|
||||
@@ -12,11 +13,12 @@ import type {
|
||||
INodeParameters,
|
||||
IRunExecutionData,
|
||||
NodeParameterValueType,
|
||||
} from '@/interfaces';
|
||||
import { Workflow } from '@/workflow';
|
||||
} from '../src/interfaces';
|
||||
import { Workflow } from '../src/workflow';
|
||||
|
||||
process.env.TEST_VARIABLE_1 = 'valueEnvVariable1';
|
||||
|
||||
// eslint-disable-next-line import/order
|
||||
import * as Helpers from './helpers';
|
||||
|
||||
interface StubNode {
|
||||
@@ -347,7 +349,7 @@ describe('Workflow', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('renameNodeInParameterValue', () => {
|
||||
@@ -2621,7 +2623,7 @@ describe('Workflow', () => {
|
||||
|
||||
test('should skip nodes that do not exist and log a warning', () => {
|
||||
// Spy on console.warn
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
const nodes = SIMPLE_WORKFLOW.getNodes(['Start', 'NonExistentNode', 'Set1']);
|
||||
expect(nodes).toHaveLength(2);
|
||||
@@ -2634,7 +2636,7 @@ describe('Workflow', () => {
|
||||
|
||||
test('should return an empty array if none of the requested nodes exist', () => {
|
||||
// Spy on console.warn
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
const nodes = SIMPLE_WORKFLOW.getNodes(['NonExistentNode1', 'NonExistentNode2']);
|
||||
expect(nodes).toHaveLength(0);
|
||||
|
||||
Reference in New Issue
Block a user