2023-02-09 00:00:29 -08:00
|
|
|
import { readFileSync, readdirSync, mkdtempSync } from 'fs';
|
2023-05-02 08:29:07 -07:00
|
|
|
import path from 'path';
|
|
|
|
import { tmpdir } from 'os';
|
2023-12-21 09:22:32 -08:00
|
|
|
import nock from 'nock';
|
2023-05-02 08:29:07 -07:00
|
|
|
import { isEmpty } from 'lodash';
|
|
|
|
import { get } from 'lodash';
|
2023-09-22 08:22:12 -07:00
|
|
|
import { BinaryDataService, Credentials, constructExecutionMetaData } from 'n8n-core';
|
|
|
|
import { Container } from 'typedi';
|
2024-05-17 09:43:50 -07:00
|
|
|
import { mock } from 'jest-mock-extended';
|
2023-05-02 01:37:19 -07:00
|
|
|
import type {
|
2023-05-02 08:29:07 -07:00
|
|
|
CredentialLoadingDetails,
|
2023-01-30 03:20:33 -08:00
|
|
|
ICredentialDataDecryptedObject,
|
2023-05-02 08:29:07 -07:00
|
|
|
ICredentialType,
|
|
|
|
ICredentialTypeData,
|
|
|
|
ICredentialTypes,
|
2023-02-07 01:27:37 -08:00
|
|
|
IDataObject,
|
2023-01-30 03:20:33 -08:00
|
|
|
IDeferredPromise,
|
2023-04-12 07:24:17 -07:00
|
|
|
IExecuteFunctions,
|
|
|
|
IGetNodeParameterOptions,
|
2023-01-30 03:20:33 -08:00
|
|
|
IHttpRequestHelper,
|
|
|
|
IHttpRequestOptions,
|
|
|
|
INode,
|
2023-06-21 00:38:28 -07:00
|
|
|
INodeCredentials,
|
2023-01-30 03:20:33 -08:00
|
|
|
INodeCredentialsDetails,
|
|
|
|
INodeType,
|
|
|
|
INodeTypeData,
|
|
|
|
INodeTypes,
|
|
|
|
IRun,
|
|
|
|
ITaskData,
|
|
|
|
IVersionedNodeType,
|
|
|
|
IWorkflowBase,
|
|
|
|
IWorkflowExecuteAdditionalData,
|
2023-05-02 08:29:07 -07:00
|
|
|
NodeLoadingDetails,
|
2023-06-21 00:38:28 -07:00
|
|
|
WorkflowTestData,
|
2023-01-30 03:20:33 -08:00
|
|
|
} from 'n8n-workflow';
|
2023-12-05 02:17:08 -08:00
|
|
|
import { ApplicationError, ICredentialsHelper, NodeHelpers, WorkflowHooks } from 'n8n-workflow';
|
2023-02-07 01:27:37 -08:00
|
|
|
import { executeWorkflow } from './ExecuteWorkflow';
|
2023-01-30 03:20:33 -08:00
|
|
|
|
2023-04-11 02:58:47 -07:00
|
|
|
import { FAKE_CREDENTIALS_DATA } from './FakeCredentialsMap';
|
|
|
|
|
2023-05-02 08:29:07 -07:00
|
|
|
const baseDir = path.resolve(__dirname, '../..');
|
|
|
|
|
2023-04-11 02:58:47 -07:00
|
|
|
const getFakeDecryptedCredentials = (
|
|
|
|
nodeCredentials: INodeCredentialsDetails,
|
|
|
|
type: string,
|
|
|
|
fakeCredentialsMap: IDataObject,
|
|
|
|
) => {
|
|
|
|
if (nodeCredentials && fakeCredentialsMap[JSON.stringify(nodeCredentials)]) {
|
|
|
|
return fakeCredentialsMap[JSON.stringify(nodeCredentials)] as ICredentialDataDecryptedObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type && fakeCredentialsMap[type]) {
|
|
|
|
return fakeCredentialsMap[type] as ICredentialDataDecryptedObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
};
|
|
|
|
|
2023-05-02 08:29:07 -07:00
|
|
|
export const readJsonFileSync = <T = any>(filePath: string) =>
|
|
|
|
JSON.parse(readFileSync(path.join(baseDir, filePath), 'utf-8')) as T;
|
|
|
|
|
|
|
|
const knownCredentials = readJsonFileSync<Record<string, CredentialLoadingDetails>>(
|
|
|
|
'dist/known/credentials.json',
|
|
|
|
);
|
|
|
|
|
|
|
|
const knownNodes = readJsonFileSync<Record<string, NodeLoadingDetails>>('dist/known/nodes.json');
|
|
|
|
|
|
|
|
class CredentialType implements ICredentialTypes {
|
|
|
|
credentialTypes: ICredentialTypeData = {};
|
|
|
|
|
|
|
|
addCredential(credentialTypeName: string, credentialType: ICredentialType) {
|
|
|
|
this.credentialTypes[credentialTypeName] = {
|
|
|
|
sourcePath: '',
|
|
|
|
type: credentialType,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
recognizes(credentialType: string): boolean {
|
|
|
|
return credentialType in this.credentialTypes;
|
|
|
|
}
|
|
|
|
|
|
|
|
getByName(credentialType: string): ICredentialType {
|
|
|
|
return this.credentialTypes[credentialType].type;
|
|
|
|
}
|
|
|
|
|
2023-11-13 03:11:16 -08:00
|
|
|
getSupportedNodes(type: string): string[] {
|
|
|
|
return knownCredentials[type]?.supportedNodes ?? [];
|
2023-05-02 08:29:07 -07:00
|
|
|
}
|
|
|
|
|
2024-05-17 09:43:50 -07:00
|
|
|
getParentTypes(_typeName: string): string[] {
|
2023-05-02 08:29:07 -07:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-17 09:43:50 -07:00
|
|
|
const credentialTypes = new CredentialType();
|
2023-05-02 08:29:07 -07:00
|
|
|
|
2024-10-16 02:23:09 -07:00
|
|
|
export class CredentialsHelper extends ICredentialsHelper {
|
2024-06-05 03:33:02 -07:00
|
|
|
getCredentialsProperties() {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2023-01-30 03:20:33 -08:00
|
|
|
async authenticate(
|
|
|
|
credentials: ICredentialDataDecryptedObject,
|
|
|
|
typeName: string,
|
|
|
|
requestParams: IHttpRequestOptions,
|
|
|
|
): Promise<IHttpRequestOptions> {
|
2024-05-17 09:43:50 -07:00
|
|
|
const credentialType = credentialTypes.getByName(typeName);
|
2023-05-02 08:29:07 -07:00
|
|
|
if (typeof credentialType.authenticate === 'function') {
|
2024-01-17 07:08:50 -08:00
|
|
|
return await credentialType.authenticate(credentials, requestParams);
|
2023-05-02 08:29:07 -07:00
|
|
|
}
|
2023-01-30 03:20:33 -08:00
|
|
|
return requestParams;
|
|
|
|
}
|
|
|
|
|
|
|
|
async preAuthentication(
|
2024-05-17 09:43:50 -07:00
|
|
|
_helpers: IHttpRequestHelper,
|
|
|
|
_credentials: ICredentialDataDecryptedObject,
|
|
|
|
_typeName: string,
|
|
|
|
_node: INode,
|
|
|
|
_credentialsExpired: boolean,
|
2023-01-30 03:20:33 -08:00
|
|
|
): Promise<ICredentialDataDecryptedObject | undefined> {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2024-05-17 09:43:50 -07:00
|
|
|
getParentTypes(_name: string): string[] {
|
2023-01-30 03:20:33 -08:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
async getDecrypted(
|
2024-05-17 09:43:50 -07:00
|
|
|
_additionalData: IWorkflowExecuteAdditionalData,
|
2023-01-30 03:20:33 -08:00
|
|
|
nodeCredentials: INodeCredentialsDetails,
|
|
|
|
type: string,
|
|
|
|
): Promise<ICredentialDataDecryptedObject> {
|
2023-04-11 02:58:47 -07:00
|
|
|
return getFakeDecryptedCredentials(nodeCredentials, type, FAKE_CREDENTIALS_DATA);
|
2023-01-30 03:20:33 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
async getCredentials(
|
2024-05-17 09:43:50 -07:00
|
|
|
_nodeCredentials: INodeCredentialsDetails,
|
|
|
|
_type: string,
|
2023-01-30 03:20:33 -08:00
|
|
|
): Promise<Credentials> {
|
2024-05-17 09:43:50 -07:00
|
|
|
return new Credentials({ id: null, name: '' }, '', '');
|
2023-01-30 03:20:33 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
async updateCredentials(
|
2024-05-17 09:43:50 -07:00
|
|
|
_nodeCredentials: INodeCredentialsDetails,
|
|
|
|
_type: string,
|
|
|
|
_data: ICredentialDataDecryptedObject,
|
2023-01-30 03:20:33 -08:00
|
|
|
): Promise<void> {}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function WorkflowExecuteAdditionalData(
|
|
|
|
waitPromise: IDeferredPromise<IRun>,
|
|
|
|
nodeExecutionOrder: string[],
|
|
|
|
): IWorkflowExecuteAdditionalData {
|
|
|
|
const hookFunctions = {
|
|
|
|
nodeExecuteAfter: [
|
2024-05-17 09:43:50 -07:00
|
|
|
async (nodeName: string, _data: ITaskData): Promise<void> => {
|
2023-01-30 03:20:33 -08:00
|
|
|
nodeExecutionOrder.push(nodeName);
|
|
|
|
},
|
|
|
|
],
|
|
|
|
workflowExecuteAfter: [
|
|
|
|
async (fullRunData: IRun): Promise<void> => {
|
|
|
|
waitPromise.resolve(fullRunData);
|
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
|
|
|
|
2024-05-17 09:43:50 -07:00
|
|
|
return mock<IWorkflowExecuteAdditionalData>({
|
|
|
|
credentialsHelper: new CredentialsHelper(),
|
|
|
|
hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', mock()),
|
2024-10-16 02:23:09 -07:00
|
|
|
// Get from node.parameters
|
|
|
|
currentNodeParameters: undefined,
|
2024-05-17 09:43:50 -07:00
|
|
|
});
|
2023-01-30 03:20:33 -08:00
|
|
|
}
|
|
|
|
|
2023-05-02 08:29:07 -07:00
|
|
|
class NodeTypes implements INodeTypes {
|
2023-01-30 03:20:33 -08:00
|
|
|
nodeTypes: INodeTypeData = {};
|
2023-05-02 01:37:19 -07:00
|
|
|
|
2023-01-30 03:20:33 -08:00
|
|
|
getByName(nodeType: string): INodeType | IVersionedNodeType {
|
|
|
|
return this.nodeTypes[nodeType].type;
|
|
|
|
}
|
|
|
|
|
|
|
|
addNode(nodeTypeName: string, nodeType: INodeType | IVersionedNodeType) {
|
|
|
|
const loadedNode = {
|
|
|
|
[nodeTypeName]: {
|
|
|
|
sourcePath: '',
|
|
|
|
type: nodeType,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
this.nodeTypes = {
|
|
|
|
...this.nodeTypes,
|
|
|
|
...loadedNode,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
|
|
|
return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType].type, version);
|
|
|
|
}
|
2024-05-17 09:43:50 -07:00
|
|
|
|
|
|
|
getKnownTypes(): IDataObject {
|
|
|
|
throw new Error('Method not implemented.');
|
|
|
|
}
|
2023-01-30 03:20:33 -08:00
|
|
|
}
|
|
|
|
|
2023-05-02 01:37:19 -07:00
|
|
|
export function createTemporaryDir(prefix = 'n8n') {
|
2023-02-10 00:53:20 -08:00
|
|
|
return mkdtempSync(path.join(tmpdir(), prefix));
|
|
|
|
}
|
|
|
|
|
2023-09-22 08:22:12 -07:00
|
|
|
export async function initBinaryDataService(mode: 'default' | 'filesystem' = 'default') {
|
|
|
|
const binaryDataService = new BinaryDataService();
|
2023-10-05 06:25:17 -07:00
|
|
|
await binaryDataService.init({
|
|
|
|
mode: 'default',
|
|
|
|
availableModes: [mode],
|
|
|
|
localStoragePath: createTemporaryDir(),
|
|
|
|
});
|
2023-09-22 08:22:12 -07:00
|
|
|
Container.set(BinaryDataService, binaryDataService);
|
2023-02-09 00:00:29 -08:00
|
|
|
}
|
|
|
|
|
2023-05-02 01:37:19 -07:00
|
|
|
export function setup(testData: WorkflowTestData[] | WorkflowTestData) {
|
2023-02-06 07:14:57 -08:00
|
|
|
if (!Array.isArray(testData)) {
|
|
|
|
testData = [testData];
|
|
|
|
}
|
|
|
|
|
2023-12-21 09:22:32 -08:00
|
|
|
if (testData.some((t) => !!t.nock)) {
|
|
|
|
beforeAll(() => {
|
|
|
|
nock.disableNetConnect();
|
|
|
|
});
|
|
|
|
|
|
|
|
afterAll(() => {
|
|
|
|
nock.restore();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-05-02 08:29:07 -07:00
|
|
|
const nodeTypes = new NodeTypes();
|
2023-02-06 07:14:57 -08:00
|
|
|
|
2023-05-02 08:29:07 -07:00
|
|
|
const nodes = [...new Set(testData.flatMap((data) => data.input.workflowData.nodes))];
|
|
|
|
const credentialNames = nodes
|
|
|
|
.filter((n) => n.credentials)
|
2023-06-21 00:38:28 -07:00
|
|
|
.flatMap(({ credentials }) => Object.keys(credentials as INodeCredentials));
|
2023-05-02 08:29:07 -07:00
|
|
|
for (const credentialName of credentialNames) {
|
|
|
|
const loadInfo = knownCredentials[credentialName];
|
|
|
|
if (!loadInfo) {
|
2023-12-05 02:17:08 -08:00
|
|
|
throw new ApplicationError(`Unknown credential type: ${credentialName}`, {
|
|
|
|
level: 'warning',
|
|
|
|
});
|
2023-05-02 08:29:07 -07:00
|
|
|
}
|
|
|
|
const sourcePath = loadInfo.sourcePath.replace(/^dist\//, './').replace(/\.js$/, '.ts');
|
|
|
|
const nodeSourcePath = path.join(baseDir, sourcePath);
|
|
|
|
const credential = new (require(nodeSourcePath)[loadInfo.className])() as ICredentialType;
|
|
|
|
credentialTypes.addCredential(credentialName, credential);
|
|
|
|
}
|
2023-02-06 07:14:57 -08:00
|
|
|
|
2023-05-02 08:29:07 -07:00
|
|
|
const nodeNames = nodes.map((n) => n.type);
|
2023-02-06 07:14:57 -08:00
|
|
|
for (const nodeName of nodeNames) {
|
|
|
|
if (!nodeName.startsWith('n8n-nodes-base.')) {
|
2023-12-05 02:17:08 -08:00
|
|
|
throw new ApplicationError(`Unknown node type: ${nodeName}`, { level: 'warning' });
|
2023-02-06 07:14:57 -08:00
|
|
|
}
|
|
|
|
const loadInfo = knownNodes[nodeName.replace('n8n-nodes-base.', '')];
|
|
|
|
if (!loadInfo) {
|
2023-12-05 02:17:08 -08:00
|
|
|
throw new ApplicationError(`Unknown node type: ${nodeName}`, { level: 'warning' });
|
2023-02-06 07:14:57 -08:00
|
|
|
}
|
2023-03-10 07:53:05 -08:00
|
|
|
const sourcePath = loadInfo.sourcePath.replace(/^dist\//, './').replace(/\.js$/, '.ts');
|
2023-05-02 08:29:07 -07:00
|
|
|
const nodeSourcePath = path.join(baseDir, sourcePath);
|
2023-03-10 07:53:05 -08:00
|
|
|
const node = new (require(nodeSourcePath)[loadInfo.className])() as INodeType;
|
2023-02-06 07:14:57 -08:00
|
|
|
nodeTypes.addNode(nodeName, node);
|
2023-01-30 03:20:33 -08:00
|
|
|
}
|
2023-02-06 07:14:57 -08:00
|
|
|
|
2023-01-30 03:20:33 -08:00
|
|
|
return nodeTypes;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getResultNodeData(result: IRun, testData: WorkflowTestData) {
|
|
|
|
return Object.keys(testData.output.nodeData).map((nodeName) => {
|
2023-05-02 08:29:07 -07:00
|
|
|
const error = result.data.resultData.error;
|
|
|
|
// If there was an error running the workflow throw it for easier debugging
|
|
|
|
// and to surface all issues
|
|
|
|
if (error?.cause) throw error.cause;
|
|
|
|
if (error) throw error;
|
|
|
|
|
2023-01-30 03:20:33 -08:00
|
|
|
if (result.data.resultData.runData[nodeName] === undefined) {
|
2023-05-02 02:44:25 -07:00
|
|
|
// log errors from other nodes
|
|
|
|
Object.keys(result.data.resultData.runData).forEach((key) => {
|
|
|
|
const error = result.data.resultData.runData[key][0]?.error;
|
|
|
|
if (error) {
|
|
|
|
console.log(`Node ${key}\n`, error);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-12-05 02:17:08 -08:00
|
|
|
throw new ApplicationError(`Data for node "${nodeName}" is missing!`, { level: 'warning' });
|
2023-01-30 03:20:33 -08:00
|
|
|
}
|
|
|
|
const resultData = result.data.resultData.runData[nodeName].map((nodeData) => {
|
|
|
|
if (nodeData.data === undefined) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-02-09 00:00:29 -08:00
|
|
|
return nodeData.data.main[0]!.map((entry) => {
|
|
|
|
if (entry.binary && isEmpty(entry.binary)) delete entry.binary;
|
|
|
|
delete entry.pairedItem;
|
|
|
|
return entry;
|
|
|
|
});
|
2023-01-30 03:20:33 -08:00
|
|
|
});
|
|
|
|
return {
|
|
|
|
nodeName,
|
|
|
|
resultData,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-02-07 01:27:37 -08:00
|
|
|
export const equalityTest = async (testData: WorkflowTestData, types: INodeTypes) => {
|
|
|
|
// execute workflow
|
|
|
|
const { result } = await executeWorkflow(testData, types);
|
|
|
|
|
|
|
|
// check if result node data matches expected test data
|
|
|
|
const resultNodeData = getResultNodeData(result, testData);
|
|
|
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
2023-05-05 08:50:10 -07:00
|
|
|
const msg = `Equality failed for "${testData.description}" at node "${nodeName}"`;
|
2023-07-18 11:07:29 -07:00
|
|
|
resultData.forEach((item) => {
|
2024-02-12 08:32:27 -08:00
|
|
|
item?.forEach(({ binary, json }) => {
|
2023-07-18 11:07:29 -07:00
|
|
|
if (binary) {
|
|
|
|
// @ts-ignore
|
|
|
|
delete binary.data.data;
|
|
|
|
delete binary.data.directory;
|
|
|
|
}
|
2024-02-12 08:32:27 -08:00
|
|
|
|
|
|
|
// Convert errors to JSON so tests can compare
|
|
|
|
if (json.error instanceof Error) {
|
|
|
|
json.error = JSON.parse(
|
|
|
|
JSON.stringify(json.error, ['message', 'name', 'description', 'context']),
|
|
|
|
);
|
|
|
|
}
|
2023-07-18 11:07:29 -07:00
|
|
|
});
|
|
|
|
});
|
2023-05-05 08:50:10 -07:00
|
|
|
return expect(resultData, msg).toEqual(testData.output.nodeData[nodeName]);
|
2023-02-07 01:27:37 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
expect(result.finished).toEqual(true);
|
|
|
|
};
|
|
|
|
|
2023-02-09 02:25:26 -08:00
|
|
|
const preparePinData = (pinData: IDataObject) => {
|
|
|
|
const returnData = Object.keys(pinData).reduce(
|
|
|
|
(acc, key) => {
|
|
|
|
const data = pinData[key] as IDataObject[];
|
2023-05-02 01:37:19 -07:00
|
|
|
acc[key] = [data];
|
2023-02-09 02:25:26 -08:00
|
|
|
return acc;
|
|
|
|
},
|
|
|
|
{} as {
|
|
|
|
[key: string]: IDataObject[][];
|
|
|
|
},
|
|
|
|
);
|
|
|
|
return returnData;
|
|
|
|
};
|
2023-02-07 01:27:37 -08:00
|
|
|
export const workflowToTests = (workflowFiles: string[]) => {
|
|
|
|
const testCases: WorkflowTestData[] = [];
|
|
|
|
for (const filePath of workflowFiles) {
|
|
|
|
const description = filePath.replace('.json', '');
|
2023-08-01 08:32:30 -07:00
|
|
|
const workflowData = readJsonFileSync<IWorkflowBase & Pick<WorkflowTestData, 'trigger'>>(
|
|
|
|
filePath,
|
|
|
|
);
|
2023-07-18 11:07:29 -07:00
|
|
|
const testDir = path.join(baseDir, path.dirname(filePath));
|
|
|
|
workflowData.nodes.forEach((node) => {
|
|
|
|
if (node.parameters) {
|
|
|
|
node.parameters = JSON.parse(
|
|
|
|
JSON.stringify(node.parameters).replace(/"C:\\\\Test\\\\(.*)"/, `"${testDir}/$1"`),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
2023-02-07 01:27:37 -08:00
|
|
|
if (workflowData.pinData === undefined) {
|
2023-12-05 02:17:08 -08:00
|
|
|
throw new ApplicationError('Workflow data does not contain pinData', { level: 'warning' });
|
2023-02-07 01:27:37 -08:00
|
|
|
}
|
2023-02-09 02:25:26 -08:00
|
|
|
|
|
|
|
const nodeData = preparePinData(workflowData.pinData);
|
2023-02-07 01:27:37 -08:00
|
|
|
delete workflowData.pinData;
|
|
|
|
|
2023-08-01 08:32:30 -07:00
|
|
|
const { trigger } = workflowData;
|
|
|
|
delete workflowData.trigger;
|
|
|
|
|
2023-02-07 01:27:37 -08:00
|
|
|
const input = { workflowData };
|
|
|
|
const output = { nodeData };
|
|
|
|
|
2023-08-01 08:32:30 -07:00
|
|
|
testCases.push({ description, input, output, trigger });
|
2023-02-07 01:27:37 -08:00
|
|
|
}
|
|
|
|
return testCases;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const testWorkflows = (workflows: string[]) => {
|
|
|
|
const tests = workflowToTests(workflows);
|
|
|
|
|
|
|
|
const nodeTypes = setup(tests);
|
|
|
|
|
|
|
|
for (const testData of tests) {
|
2024-01-17 07:08:50 -08:00
|
|
|
test(testData.description, async () => await equalityTest(testData, nodeTypes));
|
2023-02-07 01:27:37 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const getWorkflowFilenames = (dirname: string) => {
|
|
|
|
const workflows: string[] = [];
|
|
|
|
|
|
|
|
const filenames = readdirSync(dirname);
|
|
|
|
const testFolder = dirname.split(`${path.sep}nodes-base${path.sep}`)[1];
|
|
|
|
filenames.forEach((file) => {
|
2023-05-02 08:29:07 -07:00
|
|
|
if (file.endsWith('.json')) {
|
2023-02-07 01:27:37 -08:00
|
|
|
workflows.push(path.join(testFolder, file));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return workflows;
|
|
|
|
};
|
2023-04-12 07:24:17 -07:00
|
|
|
|
|
|
|
export const createMockExecuteFunction = (
|
|
|
|
nodeParameters: IDataObject,
|
|
|
|
nodeMock: INode,
|
|
|
|
continueBool = false,
|
|
|
|
) => {
|
|
|
|
const fakeExecuteFunction = {
|
|
|
|
getNodeParameter(
|
|
|
|
parameterName: string,
|
|
|
|
_itemIndex: number,
|
|
|
|
fallbackValue?: IDataObject | undefined,
|
|
|
|
options?: IGetNodeParameterOptions | undefined,
|
|
|
|
) {
|
|
|
|
const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
|
|
|
|
return get(nodeParameters, parameter, fallbackValue);
|
|
|
|
},
|
|
|
|
getNode() {
|
|
|
|
return nodeMock;
|
|
|
|
},
|
|
|
|
continueOnFail() {
|
|
|
|
return continueBool;
|
|
|
|
},
|
|
|
|
helpers: {
|
|
|
|
constructExecutionMetaData,
|
|
|
|
},
|
|
|
|
} as unknown as IExecuteFunctions;
|
|
|
|
return fakeExecuteFunction;
|
|
|
|
};
|