n8n/packages/workflow/test/TelemetryHelpers.test.ts

1282 lines
31 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { mock } from 'jest-mock-extended';
import { v5 as uuidv5, v3 as uuidv3, v4 as uuidv4, v1 as uuidv1 } from 'uuid';
import { STICKY_NODE_TYPE } from '@/Constants';
import { ApplicationError } from '@/errors';
import type { IRunData } from '@/Interfaces';
import { NodeConnectionType, type IWorkflowBase } from '@/Interfaces';
import * as nodeHelpers from '@/NodeHelpers';
import {
ANONYMIZATION_CHARACTER as CHAR,
generateNodesGraph,
getDomainBase,
getDomainPath,
} from '@/TelemetryHelpers';
import { randomInt } from '@/utils';
import { nodeTypes } from './ExpressionExtensions/Helpers';
describe('getDomainBase should return protocol plus domain', () => {
test('in valid URLs', () => {
for (const url of validUrls(numericId)) {
const { full, protocolPlusDomain } = url;
expect(getDomainBase(full)).toBe(protocolPlusDomain);
}
});
test('in malformed URLs', () => {
for (const url of malformedUrls(numericId)) {
const { full, protocolPlusDomain } = url;
expect(getDomainBase(full)).toBe(protocolPlusDomain);
}
});
});
describe('getDomainPath should return pathname, excluding query string', () => {
describe('anonymizing strings containing at least one number', () => {
test('in valid URLs', () => {
for (const url of validUrls(alphanumericId)) {
const { full, pathname } = url;
expect(getDomainPath(full)).toBe(pathname);
}
});
test('in malformed URLs', () => {
for (const url of malformedUrls(alphanumericId)) {
const { full, pathname } = url;
expect(getDomainPath(full)).toBe(pathname);
}
});
});
describe('anonymizing UUIDs', () => {
test('in valid URLs', () => {
for (const url of uuidUrls(validUrls)) {
const { full, pathname } = url;
expect(getDomainPath(full)).toBe(pathname);
}
});
test('in malformed URLs', () => {
for (const url of uuidUrls(malformedUrls)) {
const { full, pathname } = url;
expect(getDomainPath(full)).toBe(pathname);
}
});
});
describe('anonymizing emails', () => {
test('in valid URLs', () => {
for (const url of validUrls(email)) {
const { full, pathname } = url;
expect(getDomainPath(full)).toBe(pathname);
}
});
test('in malformed URLs', () => {
for (const url of malformedUrls(email)) {
const { full, pathname } = url;
expect(getDomainPath(full)).toBe(pathname);
}
});
});
});
describe('generateNodesGraph', () => {
test('should return node graph when node type is unknown', () => {
const workflow: IWorkflowBase = {
createdAt: new Date('2024-01-05T13:49:14.244Z'),
updatedAt: new Date('2024-01-05T15:44:31.000Z'),
id: 'NfV4GV9aQTifSLc2',
name: 'My workflow 26',
active: false,
nodes: [
{
parameters: {},
id: 'fa7d5628-5a47-4c8f-98ef-fb3532e5a9f5',
name: 'When clicking "Execute Workflow"',
type: 'n8n-nodes-base.manualTrigger',
typeVersion: 1,
position: [420, 420],
},
{
parameters: {
documentId: { __rl: true, mode: 'list', value: '' },
sheetName: { __rl: true, mode: 'list', value: '' },
},
id: '266128b9-e5db-4c26-9555-185d48946afb',
name: 'Google Sheets',
type: 'test.unknown',
typeVersion: 4.2,
position: [640, 420],
},
],
connections: {
'When clicking "Execute Workflow"': {
main: [[{ node: 'Google Sheets', type: NodeConnectionType.Main, index: 0 }]],
},
},
settings: { executionOrder: 'v1' },
pinData: {},
versionId: '70b92d94-0e9a-4b41-9976-a654df420af5',
};
expect(generateNodesGraph(workflow, nodeTypes)).toEqual({
nodeGraph: {
node_types: ['n8n-nodes-base.manualTrigger', 'test.unknown'],
node_connections: [{ start: '0', end: '1' }],
nodes: {
'0': {
id: 'fa7d5628-5a47-4c8f-98ef-fb3532e5a9f5',
type: 'n8n-nodes-base.manualTrigger',
version: 1,
position: [420, 420],
},
'1': {
id: '266128b9-e5db-4c26-9555-185d48946afb',
type: 'test.unknown',
version: 4.2,
position: [640, 420],
},
},
notes: {},
is_pinned: false,
},
nameIndices: { 'When clicking "Execute Workflow"': '0', 'Google Sheets': '1' },
webhookNodeNames: [],
});
});
test('should return node graph when workflow is empty', () => {
const workflow: IWorkflowBase = {
createdAt: new Date('2024-01-05T13:49:14.244Z'),
updatedAt: new Date('2024-01-05T15:44:31.000Z'),
id: 'NfV4GV9aQTifSLc2',
name: 'My workflow 26',
active: false,
nodes: [],
connections: {},
settings: { executionOrder: 'v1' },
pinData: {},
versionId: '70b92d94-0e9a-4b41-9976-a654df420af5',
};
expect(generateNodesGraph(workflow, nodeTypes)).toEqual({
nodeGraph: {
node_types: [],
node_connections: [],
nodes: {},
notes: {},
is_pinned: false,
},
nameIndices: {},
webhookNodeNames: [],
});
});
test('should return node graph when workflow keys are not set', () => {
const workflow: Partial<IWorkflowBase> = {};
expect(generateNodesGraph(workflow, nodeTypes)).toEqual({
nodeGraph: {
node_types: [],
node_connections: [],
nodes: {},
notes: {},
is_pinned: false,
},
nameIndices: {},
webhookNodeNames: [],
});
});
test('should return node graph when node has multiple operation fields with different display options', () => {
const workflow: IWorkflowBase = {
createdAt: new Date('2024-01-05T13:49:14.244Z'),
updatedAt: new Date('2024-01-05T15:44:31.000Z'),
id: 'NfV4GV9aQTifSLc2',
name: 'My workflow 26',
active: false,
nodes: [
{
parameters: {},
id: 'fa7d5628-5a47-4c8f-98ef-fb3532e5a9f5',
name: 'When clicking "Execute Workflow"',
type: 'n8n-nodes-base.manualTrigger',
typeVersion: 1,
position: [420, 420],
},
{
parameters: {
documentId: { __rl: true, mode: 'list', value: '' },
sheetName: { __rl: true, mode: 'list', value: '' },
},
id: '266128b9-e5db-4c26-9555-185d48946afb',
name: 'Google Sheets',
type: 'test.googleSheets',
typeVersion: 4.2,
position: [640, 420],
},
],
connections: {
'When clicking "Execute Workflow"': {
main: [[{ node: 'Google Sheets', type: NodeConnectionType.Main, index: 0 }]],
},
},
settings: { executionOrder: 'v1' },
pinData: {},
versionId: '70b92d94-0e9a-4b41-9976-a654df420af5',
};
expect(generateNodesGraph(workflow, nodeTypes)).toEqual({
nodeGraph: {
node_types: ['n8n-nodes-base.manualTrigger', 'test.googleSheets'],
node_connections: [{ start: '0', end: '1' }],
nodes: {
'0': {
id: 'fa7d5628-5a47-4c8f-98ef-fb3532e5a9f5',
type: 'n8n-nodes-base.manualTrigger',
version: 1,
position: [420, 420],
},
'1': {
id: '266128b9-e5db-4c26-9555-185d48946afb',
type: 'test.googleSheets',
version: 4.2,
position: [640, 420],
operation: 'read',
resource: 'sheet',
},
},
notes: {},
is_pinned: false,
},
nameIndices: { 'When clicking "Execute Workflow"': '0', 'Google Sheets': '1' },
webhookNodeNames: [],
});
});
test('should return node graph with stickies of default size', () => {
const workflow: IWorkflowBase = {
createdAt: new Date('2024-01-05T13:49:14.244Z'),
updatedAt: new Date('2024-01-05T15:44:31.000Z'),
id: 'NfV4GV9aQTifSLc2',
name: 'My workflow 26',
active: false,
nodes: [
{
parameters: {},
id: 'fa7d5628-5a47-4c8f-98ef-fb3532e5a9f5',
name: 'When clicking "Execute Workflow"',
type: 'n8n-nodes-base.manualTrigger',
typeVersion: 1,
position: [420, 420],
},
{
parameters: {
documentId: { __rl: true, mode: 'list', value: '' },
sheetName: { __rl: true, mode: 'list', value: '' },
},
id: '266128b9-e5db-4c26-9555-185d48946afb',
name: 'Google Sheets',
type: 'test.googleSheets',
typeVersion: 4.2,
position: [640, 420],
},
{
parameters: {
content:
"test\n\n## I'm a note \n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/sticky-notes/)",
},
id: '03e85c3e-4303-4f93-8d62-e05d457e8f70',
name: 'Sticky Note',
type: 'n8n-nodes-base.stickyNote',
typeVersion: 1,
position: [240, 140],
},
],
connections: {
'When clicking "Execute Workflow"': {
main: [[{ node: 'Google Sheets', type: NodeConnectionType.Main, index: 0 }]],
},
},
settings: { executionOrder: 'v1' },
pinData: {},
versionId: '70b92d94-0e9a-4b41-9976-a654df420af5',
};
expect(generateNodesGraph(workflow, nodeTypes)).toEqual({
nodeGraph: {
node_types: ['n8n-nodes-base.manualTrigger', 'test.googleSheets'],
node_connections: [{ start: '0', end: '1' }],
nodes: {
'0': {
id: 'fa7d5628-5a47-4c8f-98ef-fb3532e5a9f5',
type: 'n8n-nodes-base.manualTrigger',
version: 1,
position: [420, 420],
},
'1': {
id: '266128b9-e5db-4c26-9555-185d48946afb',
type: 'test.googleSheets',
version: 4.2,
position: [640, 420],
operation: 'read',
resource: 'sheet',
},
},
notes: { '0': { overlapping: false, position: [240, 140], height: 160, width: 240 } },
is_pinned: false,
},
nameIndices: { 'When clicking "Execute Workflow"': '0', 'Google Sheets': '1' },
webhookNodeNames: [],
});
});
test('should return node graph with stickies indicating overlap', () => {
const workflow: IWorkflowBase = {
createdAt: new Date('2024-01-05T13:49:14.244Z'),
updatedAt: new Date('2024-01-05T15:44:31.000Z'),
id: 'NfV4GV9aQTifSLc2',
name: 'My workflow 26',
active: false,
nodes: [
{
parameters: {},
id: 'fa7d5628-5a47-4c8f-98ef-fb3532e5a9f5',
name: 'When clicking "Execute Workflow"',
type: 'n8n-nodes-base.manualTrigger',
typeVersion: 1,
position: [420, 420],
},
{
parameters: {
documentId: { __rl: true, mode: 'list', value: '' },
sheetName: { __rl: true, mode: 'list', value: '' },
},
id: '266128b9-e5db-4c26-9555-185d48946afb',
name: 'Google Sheets',
type: 'test.googleSheets',
typeVersion: 4.2,
position: [640, 420],
},
{
parameters: {
content:
"test\n\n## I'm a note \n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/sticky-notes/)",
height: 488,
width: 645,
},
id: '03e85c3e-4303-4f93-8d62-e05d457e8f70',
name: 'Sticky Note',
type: 'n8n-nodes-base.stickyNote',
typeVersion: 1,
position: [240, 140],
},
],
connections: {
'When clicking "Execute Workflow"': {
main: [[{ node: 'Google Sheets', type: NodeConnectionType.Main, index: 0 }]],
},
},
settings: { executionOrder: 'v1' },
pinData: {},
versionId: '70b92d94-0e9a-4b41-9976-a654df420af5',
};
expect(generateNodesGraph(workflow, nodeTypes)).toEqual({
nodeGraph: {
node_types: ['n8n-nodes-base.manualTrigger', 'test.googleSheets'],
node_connections: [{ start: '0', end: '1' }],
nodes: {
'0': {
id: 'fa7d5628-5a47-4c8f-98ef-fb3532e5a9f5',
type: 'n8n-nodes-base.manualTrigger',
version: 1,
position: [420, 420],
},
'1': {
id: '266128b9-e5db-4c26-9555-185d48946afb',
type: 'test.googleSheets',
version: 4.2,
position: [640, 420],
operation: 'read',
resource: 'sheet',
},
},
notes: { '0': { overlapping: true, position: [240, 140], height: 488, width: 645 } },
is_pinned: false,
},
nameIndices: { 'When clicking "Execute Workflow"': '0', 'Google Sheets': '1' },
webhookNodeNames: [],
});
});
test('should return node graph indicating pinned data', () => {
const workflow: Partial<IWorkflowBase> = {
nodes: [
{
parameters: {},
id: 'e59d3ad9-3448-4899-9f47-d2922c8727ce',
name: 'When clicking "Execute Workflow"',
type: 'n8n-nodes-base.manualTrigger',
typeVersion: 1,
position: [460, 460],
},
],
connections: {},
pinData: {
'When clicking "Execute Workflow"': [],
},
};
expect(generateNodesGraph(workflow, nodeTypes)).toEqual({
nameIndices: {
'When clicking "Execute Workflow"': '0',
},
nodeGraph: {
is_pinned: true,
node_connections: [],
node_types: ['n8n-nodes-base.manualTrigger'],
nodes: {
'0': {
id: 'e59d3ad9-3448-4899-9f47-d2922c8727ce',
position: [460, 460],
type: 'n8n-nodes-base.manualTrigger',
version: 1,
},
},
notes: {},
},
webhookNodeNames: [],
});
});
test('should return graph with webhook node', () => {
const workflow: Partial<IWorkflowBase> = {
nodes: [
{
parameters: {
path: 'bf4c0699-cff8-4440-8964-8e97fda8b4f8',
options: {},
},
id: '5e49e129-2c59-4650-95ea-14d4b94db1f3',
name: 'Webhook',
type: 'n8n-nodes-base.webhook',
typeVersion: 1.1,
position: [520, 380],
webhookId: 'bf4c0699-cff8-4440-8964-8e97fda8b4f8',
},
],
connections: {},
pinData: {},
};
expect(generateNodesGraph(workflow, nodeTypes)).toEqual({
nodeGraph: {
node_types: ['n8n-nodes-base.webhook'],
node_connections: [],
nodes: {
'0': {
id: '5e49e129-2c59-4650-95ea-14d4b94db1f3',
type: 'n8n-nodes-base.webhook',
version: 1.1,
position: [520, 380],
},
},
notes: {},
is_pinned: false,
},
nameIndices: { Webhook: '0' },
webhookNodeNames: ['Webhook'],
});
});
test('should return graph with http v4 node with generic auth', () => {
const workflow: Partial<IWorkflowBase> = {
nodes: [
{
parameters: {
url: 'google.com/path/test',
authentication: 'genericCredentialType',
genericAuthType: 'httpBasicAuth',
options: {},
},
id: '04d6e44f-09c1-454d-9225-60aeed7f022c',
name: 'HTTP Request V4 with generic auth',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4.1,
position: [780, 120],
credentials: {
httpBasicAuth: {
id: 'yuuJAO2Ang5B64wd',
name: 'Unnamed credential',
},
},
},
],
connections: {},
pinData: {},
};
expect(generateNodesGraph(workflow, nodeTypes)).toEqual({
nodeGraph: {
node_types: ['n8n-nodes-base.httpRequest'],
node_connections: [],
nodes: {
'0': {
id: '04d6e44f-09c1-454d-9225-60aeed7f022c',
type: 'n8n-nodes-base.httpRequest',
version: 4.1,
position: [780, 120],
credential_type: 'httpBasicAuth',
credential_set: true,
domain_base: 'google.com',
domain_path: '/path/test',
},
},
notes: {},
is_pinned: false,
},
nameIndices: { 'HTTP Request V4 with generic auth': '0' },
webhookNodeNames: [],
});
});
test('should return graph with HTTP V4 with predefined cred', () => {
const workflow: Partial<IWorkflowBase> = {
nodes: [
{
parameters: {
url: 'google.com/path/test',
authentication: 'predefinedCredentialType',
nodeCredentialType: 'activeCampaignApi',
options: {},
},
id: 'dcc4a9e1-c2c5-4d7e-aec0-2a23adabbb77',
name: 'HTTP Request V4 with predefined cred',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4.1,
position: [320, 220],
credentials: {
httpBasicAuth: {
id: 'yuuJAO2Ang5B64wd',
name: 'Unnamed credential',
},
activeCampaignApi: {
id: 'SFCbnfgRBuSzRu6N',
name: 'ActiveCampaign account',
},
},
},
],
connections: {},
pinData: {},
};
expect(generateNodesGraph(workflow, nodeTypes)).toEqual({
nodeGraph: {
node_types: ['n8n-nodes-base.httpRequest'],
node_connections: [],
nodes: {
'0': {
id: 'dcc4a9e1-c2c5-4d7e-aec0-2a23adabbb77',
type: 'n8n-nodes-base.httpRequest',
version: 4.1,
position: [320, 220],
credential_type: 'activeCampaignApi',
credential_set: true,
domain_base: 'google.com',
domain_path: '/path/test',
},
},
notes: {},
is_pinned: false,
},
nameIndices: { 'HTTP Request V4 with predefined cred': '0' },
webhookNodeNames: [],
});
});
test('should return graph with http v1 node', () => {
const workflow: Partial<IWorkflowBase> = {
nodes: [
{
parameters: {
url: 'https://google.com',
options: {},
},
id: 'b468b603-3e59-4515-b555-90cfebd64d47',
name: 'HTTP Request V1',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 1,
position: [320, 460],
},
],
connections: {},
pinData: {},
};
expect(generateNodesGraph(workflow, nodeTypes)).toEqual({
nodeGraph: {
node_types: ['n8n-nodes-base.httpRequest'],
node_connections: [],
nodes: {
'0': {
id: 'b468b603-3e59-4515-b555-90cfebd64d47',
type: 'n8n-nodes-base.httpRequest',
version: 1,
position: [320, 460],
domain: 'google.com',
},
},
notes: {},
is_pinned: false,
},
nameIndices: { 'HTTP Request V1': '0' },
webhookNodeNames: [],
});
});
test('should return graph with http v4 node with no parameters and no credentials', () => {
const workflow: Partial<IWorkflowBase> = {
nodes: [
{
parameters: {
options: {},
},
id: 'd002e66f-deba-455c-9f8b-65239db453c3',
name: 'HTTP Request v4 with defaults',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4.1,
position: [600, 240],
},
],
connections: {},
pinData: {},
};
expect(generateNodesGraph(workflow, nodeTypes)).toEqual({
nodeGraph: {
node_types: ['n8n-nodes-base.httpRequest'],
node_connections: [],
nodes: {
'0': {
id: 'd002e66f-deba-455c-9f8b-65239db453c3',
type: 'n8n-nodes-base.httpRequest',
version: 4.1,
position: [600, 240],
credential_set: false,
domain_base: '',
domain_path: '',
},
},
notes: {},
is_pinned: false,
},
nameIndices: { 'HTTP Request v4 with defaults': '0' },
webhookNodeNames: [],
});
});
test('should support custom connections like in AI nodes', () => {
const workflow: Partial<IWorkflowBase> = {
nodes: [
{
parameters: {},
id: 'fe69383c-e418-4f98-9c0e-924deafa7f93',
name: 'When clicking Test workflow',
type: 'n8n-nodes-base.manualTrigger',
typeVersion: 1,
position: [540, 220],
},
{
parameters: {},
id: 'c5c374f1-6fad-46bb-8eea-ceec126b300a',
name: 'Chain',
type: '@n8n/n8n-nodes-langchain.chainLlm',
typeVersion: 1,
position: [760, 320],
},
{
parameters: {
options: {},
},
id: '198133b6-95dd-4f7e-90e5-e16c4cdbad12',
name: 'Model',
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
typeVersion: 1,
position: [780, 500],
},
],
connections: {
'When clicking Test workflow': {
main: [
[
{
node: 'Chain',
type: NodeConnectionType.Main,
index: 0,
},
],
],
},
Model: {
ai_languageModel: [
[
{
node: 'Chain',
type: NodeConnectionType.AiLanguageModel,
index: 0,
},
],
],
},
},
};
expect(generateNodesGraph(workflow, nodeTypes)).toEqual({
nodeGraph: {
node_types: [
'n8n-nodes-base.manualTrigger',
'@n8n/n8n-nodes-langchain.chainLlm',
'@n8n/n8n-nodes-langchain.lmChatOpenAi',
],
node_connections: [
{
start: '0',
end: '1',
},
{
start: '2',
end: '1',
},
],
nodes: {
'0': {
id: 'fe69383c-e418-4f98-9c0e-924deafa7f93',
type: 'n8n-nodes-base.manualTrigger',
version: 1,
position: [540, 220],
},
'1': {
id: 'c5c374f1-6fad-46bb-8eea-ceec126b300a',
type: '@n8n/n8n-nodes-langchain.chainLlm',
version: 1,
position: [760, 320],
},
'2': {
id: '198133b6-95dd-4f7e-90e5-e16c4cdbad12',
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
version: 1,
position: [780, 500],
},
},
notes: {},
is_pinned: false,
},
nameIndices: {
'When clicking Test workflow': '0',
Chain: '1',
Model: '2',
},
webhookNodeNames: [],
});
});
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(() => {
throw new ApplicationError('Could not find property option');
});
expect(() => generateNodesGraph(workflow, nodeTypes)).not.toThrow();
});
test('should add run and items count', () => {
const { workflow, runData } = generateTestWorkflowAndRunData();
expect(generateNodesGraph(workflow, nodeTypes, { runData })).toEqual({
nameIndices: {
DebugHelper: '4',
'Edit Fields': '1',
'Edit Fields1': '2',
'Edit Fields2': '3',
'Execute Workflow Trigger': '0',
Switch: '5',
},
nodeGraph: {
is_pinned: false,
node_connections: [
{
end: '1',
start: '0',
},
{
end: '4',
start: '0',
},
{
end: '5',
start: '1',
},
{
end: '1',
start: '4',
},
{
end: '2',
start: '5',
},
{
end: '3',
start: '5',
},
],
node_types: [
'n8n-nodes-base.executeWorkflowTrigger',
'n8n-nodes-base.set',
'n8n-nodes-base.set',
'n8n-nodes-base.set',
'n8n-nodes-base.debugHelper',
'n8n-nodes-base.switch',
],
nodes: {
'0': {
id: 'a2372c14-87de-42de-9f9e-1c499aa2c279',
items_total: 1,
position: [1000, 240],
runs: 1,
type: 'n8n-nodes-base.executeWorkflowTrigger',
version: 1,
},
'1': {
id: '0f7aa00e-248c-452c-8cd0-62cb55941633',
items_total: 4,
position: [1460, 640],
runs: 2,
type: 'n8n-nodes-base.set',
version: 3.1,
},
'2': {
id: '9165c185-9f1c-4ec1-87bf-76ca66dfae38',
items_total: 4,
position: [1860, 260],
runs: 2,
type: 'n8n-nodes-base.set',
version: 3.4,
},
'3': {
id: '7a915fd5-5987-4ff1-9509-06b24a0a4613',
position: [1940, 680],
type: 'n8n-nodes-base.set',
version: 3.4,
},
'4': {
id: '63050e7c-8ad5-4f44-8fdd-da555e40471b',
items_total: 3,
position: [1220, 240],
runs: 1,
type: 'n8n-nodes-base.debugHelper',
version: 1,
},
'5': {
id: 'fbf7525d-2d1d-4dcf-97a0-43b53d087ef3',
items_total: 4,
position: [1680, 640],
runs: 2,
type: 'n8n-nodes-base.switch',
version: 3.2,
},
},
notes: {},
},
webhookNodeNames: [],
});
});
});
function validUrls(idMaker: typeof alphanumericId | typeof email, char = CHAR) {
const firstId = idMaker();
const secondId = idMaker();
const firstIdObscured = char.repeat(firstId.length);
const secondIdObscured = char.repeat(secondId.length);
return [
{
full: `https://test.com/api/v1/users/${firstId}`,
protocolPlusDomain: 'https://test.com',
pathname: `/api/v1/users/${firstIdObscured}`,
},
{
full: `https://test.com/api/v1/users/${firstId}/`,
protocolPlusDomain: 'https://test.com',
pathname: `/api/v1/users/${firstIdObscured}/`,
},
{
full: `https://test.com/api/v1/users/${firstId}/posts/${secondId}`,
protocolPlusDomain: 'https://test.com',
pathname: `/api/v1/users/${firstIdObscured}/posts/${secondIdObscured}`,
},
{
full: `https://test.com/api/v1/users/${firstId}/posts/${secondId}/`,
protocolPlusDomain: 'https://test.com',
pathname: `/api/v1/users/${firstIdObscured}/posts/${secondIdObscured}/`,
},
{
full: `https://test.com/api/v1/users/${firstId}/posts/${secondId}/`,
protocolPlusDomain: 'https://test.com',
pathname: `/api/v1/users/${firstIdObscured}/posts/${secondIdObscured}/`,
},
{
full: `https://test.com/api/v1/users?id=${firstId}`,
protocolPlusDomain: 'https://test.com',
pathname: '/api/v1/users',
},
{
full: `https://test.com/api/v1/users?id=${firstId}&post=${secondId}`,
protocolPlusDomain: 'https://test.com',
pathname: '/api/v1/users',
},
{
full: `https://test.com/api/v1/users/${firstId}/posts/${secondId}`,
protocolPlusDomain: 'https://test.com',
pathname: `/api/v1/users/${firstIdObscured}/posts/${secondIdObscured}`,
},
];
}
function malformedUrls(idMaker: typeof numericId | typeof email, char = CHAR) {
const firstId = idMaker();
const secondId = idMaker();
const firstIdObscured = char.repeat(firstId.length);
const secondIdObscured = char.repeat(secondId.length);
return [
{
full: `test.com/api/v1/users/${firstId}/posts/${secondId}/`,
protocolPlusDomain: 'test.com',
pathname: `/api/v1/users/${firstIdObscured}/posts/${secondIdObscured}/`,
},
{
full: `htp://test.com/api/v1/users/${firstId}/posts/${secondId}/`,
protocolPlusDomain: 'htp://test.com',
pathname: `/api/v1/users/${firstIdObscured}/posts/${secondIdObscured}/`,
},
{
full: `test.com/api/v1/users?id=${firstId}`,
protocolPlusDomain: 'test.com',
pathname: '/api/v1/users',
},
{
full: `test.com/api/v1/users?id=${firstId}&post=${secondId}`,
protocolPlusDomain: 'test.com',
pathname: '/api/v1/users',
},
];
}
const email = () => encodeURIComponent('test@test.com');
function uuidUrls(
urlsMaker: typeof validUrls | typeof malformedUrls,
baseName = 'test',
namespaceUuid = uuidv4(),
) {
return [
...urlsMaker(() => uuidv5(baseName, namespaceUuid)),
...urlsMaker(uuidv4),
...urlsMaker(() => uuidv3(baseName, namespaceUuid)),
...urlsMaker(uuidv1),
];
}
function numericId(length = randomInt(1, 10)) {
return Array.from({ length }, () => randomInt(10)).join('');
}
function alphanumericId() {
return chooseRandomly([`john${numericId()}`, `title${numericId(1)}`, numericId()]);
}
const chooseRandomly = <T>(array: T[]) => array[randomInt(array.length)];
function generateTestWorkflowAndRunData(): { workflow: IWorkflowBase; runData: IRunData } {
const workflow: IWorkflowBase = {
meta: {
instanceId: 'a786b722078489c1fa382391a9f3476c2784761624deb2dfb4634827256d51a0',
},
nodes: [
{
parameters: {},
id: 'a2372c14-87de-42de-9f9e-1c499aa2c279',
name: 'Execute Workflow Trigger',
type: 'n8n-nodes-base.executeWorkflowTrigger',
typeVersion: 1,
position: [1000, 240],
},
{
parameters: {
options: {},
},
id: '0f7aa00e-248c-452c-8cd0-62cb55941633',
name: 'Edit Fields',
type: 'n8n-nodes-base.set',
typeVersion: 3.1,
position: [1460, 640],
},
{
parameters: {
options: {},
},
id: '9165c185-9f1c-4ec1-87bf-76ca66dfae38',
name: 'Edit Fields1',
type: 'n8n-nodes-base.set',
typeVersion: 3.4,
position: [1860, 260],
},
{
parameters: {
options: {},
},
id: '7a915fd5-5987-4ff1-9509-06b24a0a4613',
name: 'Edit Fields2',
type: 'n8n-nodes-base.set',
typeVersion: 3.4,
position: [1940, 680],
},
{
parameters: {
category: 'randomData',
randomDataSeed: '0',
randomDataCount: 3,
},
id: '63050e7c-8ad5-4f44-8fdd-da555e40471b',
name: 'DebugHelper',
type: 'n8n-nodes-base.debugHelper',
typeVersion: 1,
position: [1220, 240],
},
{
id: 'fbf7525d-2d1d-4dcf-97a0-43b53d087ef3',
name: 'Switch',
type: 'n8n-nodes-base.switch',
typeVersion: 3.2,
position: [1680, 640],
parameters: {},
},
],
connections: {
'Execute Workflow Trigger': {
main: [
[
{
node: 'Edit Fields',
type: 'main' as NodeConnectionType,
index: 0,
},
{
node: 'DebugHelper',
type: 'main' as NodeConnectionType,
index: 0,
},
],
],
},
'Edit Fields': {
main: [
[
{
node: 'Switch',
type: 'main' as NodeConnectionType,
index: 0,
},
],
],
},
DebugHelper: {
main: [
[
{
node: 'Edit Fields',
type: 'main' as NodeConnectionType,
index: 0,
},
],
],
},
Switch: {
main: [
// @ts-ignore
null,
// @ts-ignore
null,
[
{
node: 'Edit Fields1',
type: 'main' as NodeConnectionType,
index: 0,
},
],
[
{
node: 'Edit Fields2',
type: 'main' as NodeConnectionType,
index: 0,
},
],
],
},
},
pinData: {},
};
const runData: IRunData = {
'Execute Workflow Trigger': [
{
hints: [],
startTime: 1727793340927,
executionTime: 0,
source: [],
executionStatus: 'success',
data: { main: [[{ json: {}, pairedItem: { item: 0 } }]] },
},
],
DebugHelper: [
{
hints: [],
startTime: 1727793340928,
executionTime: 0,
source: [{ previousNode: 'Execute Workflow Trigger' }],
executionStatus: 'success',
data: {
main: [
[
{
json: {
test: 'abc',
},
pairedItem: { item: 0 },
},
{
json: {
test: 'abc',
},
pairedItem: { item: 0 },
},
{
json: {
test: 'abc',
},
pairedItem: { item: 0 },
},
],
],
},
},
],
'Edit Fields': [
{
hints: [],
startTime: 1727793340928,
executionTime: 1,
source: [{ previousNode: 'DebugHelper' }],
executionStatus: 'success',
data: {
main: [
[
{
json: {
test: 'abc',
},
pairedItem: { item: 0 },
},
{
json: {
test: 'abc',
},
pairedItem: { item: 1 },
},
{
json: {
test: 'abc',
},
pairedItem: { item: 2 },
},
],
],
},
},
{
hints: [],
startTime: 1727793340931,
executionTime: 0,
source: [{ previousNode: 'Execute Workflow Trigger' }],
executionStatus: 'success',
data: { main: [[{ json: {}, pairedItem: { item: 0 } }]] },
},
],
Switch: [
{
hints: [],
startTime: 1727793340929,
executionTime: 1,
source: [{ previousNode: 'Edit Fields' }],
executionStatus: 'success',
data: {
main: [
[],
[],
[
{
json: {
test: 'abc',
},
pairedItem: { item: 0 },
},
{
json: {
test: 'abc',
},
pairedItem: { item: 1 },
},
{
json: {
test: 'abc',
},
pairedItem: { item: 2 },
},
],
[],
],
},
},
{
hints: [],
startTime: 1727793340931,
executionTime: 0,
source: [{ previousNode: 'Edit Fields', previousNodeRun: 1 }],
executionStatus: 'success',
data: { main: [[], [], [{ json: {}, pairedItem: { item: 0 } }], []] },
},
],
'Edit Fields1': [
{
hints: [],
startTime: 1727793340930,
executionTime: 0,
source: [{ previousNode: 'Switch', previousNodeOutput: 2 }],
executionStatus: 'success',
data: {
main: [
[
{ json: {}, pairedItem: { item: 0 } },
{ json: {}, pairedItem: { item: 1 } },
{ json: {}, pairedItem: { item: 2 } },
],
],
},
},
{
hints: [],
startTime: 1727793340932,
executionTime: 1,
source: [{ previousNode: 'Switch', previousNodeOutput: 2, previousNodeRun: 1 }],
executionStatus: 'success',
data: { main: [[{ json: {}, pairedItem: { item: 0 } }]] },
},
],
};
return { workflow, runData };
}