mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
This commit is contained in:
committed by
GitHub
parent
e1d8eaa170
commit
62f4361f46
@@ -11,7 +11,6 @@ import type {
|
||||
INode,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
IPinData,
|
||||
IRunExecutionData,
|
||||
NodeParameterValueType,
|
||||
} from '../src/interfaces';
|
||||
@@ -2363,479 +2362,6 @@ describe('Workflow', () => {
|
||||
const result = WORKFLOW_WITH_LOOPS.getParentMainInputNode(set1Node);
|
||||
expect(result).toBe(set1Node);
|
||||
});
|
||||
|
||||
describe('nodes with only main outputs', () => {
|
||||
test('should return the same node when it only has main outputs', () => {
|
||||
const nodes: INode[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'SimpleNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 100],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'TargetNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [200, 100],
|
||||
parameters: {},
|
||||
},
|
||||
];
|
||||
|
||||
const connections = {
|
||||
SimpleNode: {
|
||||
[NodeConnectionTypes.Main]: [
|
||||
[{ node: 'TargetNode', type: NodeConnectionTypes.Main, index: 0 }],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes,
|
||||
connections,
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
const simpleNode = workflow.getNode('SimpleNode')!;
|
||||
const result = workflow.getParentMainInputNode(simpleNode);
|
||||
|
||||
expect(result).toBe(simpleNode);
|
||||
expect(result!.name).toBe('SimpleNode');
|
||||
});
|
||||
|
||||
test('should return the same node when it has no connections', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'IsolatedNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 100],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
const isolatedNode = workflow.getNode('IsolatedNode')!;
|
||||
const result = workflow.getParentMainInputNode(isolatedNode);
|
||||
|
||||
expect(result).toBe(isolatedNode);
|
||||
expect(result!.name).toBe('IsolatedNode');
|
||||
});
|
||||
});
|
||||
|
||||
describe('nodes with non-main outputs (AI/Tool connections)', () => {
|
||||
test('should follow AI tool connection to find main input node', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'ToolNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 100],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'AgentNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [200, 100],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
ToolNode: {
|
||||
[NodeConnectionTypes.AiTool]: [
|
||||
[{ node: 'AgentNode', type: NodeConnectionTypes.AiTool, index: 0 }],
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
const toolNode = workflow.getNode('ToolNode')!;
|
||||
const result = workflow.getParentMainInputNode(toolNode);
|
||||
|
||||
expect(result!.name).toBe('AgentNode');
|
||||
});
|
||||
|
||||
test('should follow AI memory connection to find main input node', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'MemoryNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 100],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'ChatNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [200, 100],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
MemoryNode: {
|
||||
[NodeConnectionTypes.AiMemory]: [
|
||||
[{ node: 'ChatNode', type: NodeConnectionTypes.AiMemory, index: 0 }],
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
const memoryNode = workflow.getNode('MemoryNode')!;
|
||||
const result = workflow.getParentMainInputNode(memoryNode);
|
||||
|
||||
expect(result!.name).toBe('ChatNode');
|
||||
});
|
||||
|
||||
test('should handle mixed main and non-main outputs', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'MixedNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 100],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'MainTarget',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [200, 100],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'ToolTarget',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [200, 200],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
MixedNode: {
|
||||
[NodeConnectionTypes.Main]: [
|
||||
[{ node: 'MainTarget', type: NodeConnectionTypes.Main, index: 0 }],
|
||||
],
|
||||
[NodeConnectionTypes.AiTool]: [
|
||||
[{ node: 'ToolTarget', type: NodeConnectionTypes.AiTool, index: 0 }],
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
const mixedNode = workflow.getNode('MixedNode')!;
|
||||
const result = workflow.getParentMainInputNode(mixedNode);
|
||||
|
||||
// Should follow the first non-main connection (AiTool)
|
||||
expect(result!.name).toBe('ToolTarget');
|
||||
});
|
||||
});
|
||||
|
||||
describe('chain traversal scenarios', () => {
|
||||
test('should follow a chain of AI connections until reaching main input node', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'StartTool',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 100],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'MiddleTool',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [200, 100],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'FinalAgent',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [300, 100],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
StartTool: {
|
||||
[NodeConnectionTypes.AiTool]: [
|
||||
[{ node: 'MiddleTool', type: NodeConnectionTypes.AiTool, index: 0 }],
|
||||
],
|
||||
},
|
||||
MiddleTool: {
|
||||
[NodeConnectionTypes.AiTool]: [
|
||||
[{ node: 'FinalAgent', type: NodeConnectionTypes.AiTool, index: 0 }],
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
const startTool = workflow.getNode('StartTool')!;
|
||||
const result = workflow.getParentMainInputNode(startTool);
|
||||
|
||||
expect(result!.name).toBe('FinalAgent');
|
||||
});
|
||||
|
||||
test('should handle chain that ends with a node having only main outputs', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'ToolNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 100],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'IntermediateNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [200, 100],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'EndNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [300, 100],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
ToolNode: {
|
||||
[NodeConnectionTypes.AiTool]: [
|
||||
[{ node: 'IntermediateNode', type: NodeConnectionTypes.AiTool, index: 0 }],
|
||||
],
|
||||
},
|
||||
IntermediateNode: {
|
||||
[NodeConnectionTypes.Main]: [
|
||||
[{ node: 'EndNode', type: NodeConnectionTypes.Main, index: 0 }],
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
const toolNode = workflow.getNode('ToolNode')!;
|
||||
const result = workflow.getParentMainInputNode(toolNode);
|
||||
|
||||
expect(result!.name).toBe('IntermediateNode');
|
||||
});
|
||||
|
||||
test('should handle complex multi-branch AI connections', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'MultiTool',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 100],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Agent1',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [200, 50],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Agent2',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [200, 150],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
MultiTool: {
|
||||
[NodeConnectionTypes.AiTool]: [
|
||||
[
|
||||
{ node: 'Agent1', type: NodeConnectionTypes.AiTool, index: 0 },
|
||||
{ node: 'Agent2', type: NodeConnectionTypes.AiTool, index: 0 },
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
const multiTool = workflow.getNode('MultiTool')!;
|
||||
const result = workflow.getParentMainInputNode(multiTool);
|
||||
|
||||
// Should follow the first connection in the array
|
||||
expect(result!.name).toBe('Agent1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
test('should handle null node input', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
const result = workflow.getParentMainInputNode(null as any);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test('should handle undefined node input', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
const result = workflow.getParentMainInputNode(undefined as any);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should throw error when connected node does not exist in workflow', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'ToolNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 100],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
ToolNode: {
|
||||
[NodeConnectionTypes.AiTool]: [
|
||||
[{ node: 'NonExistentNode', type: NodeConnectionTypes.AiTool, index: 0 }],
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
const toolNode = workflow.getNode('ToolNode')!;
|
||||
|
||||
expect(() => {
|
||||
workflow.getParentMainInputNode(toolNode);
|
||||
}).toThrow('Node "NonExistentNode" not found');
|
||||
});
|
||||
|
||||
test('should handle empty connection arrays', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'EmptyConnectionNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 100],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
EmptyConnectionNode: {
|
||||
[NodeConnectionTypes.AiTool]: [
|
||||
[], // Empty connection array
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
const emptyConnectionNode = workflow.getNode('EmptyConnectionNode')!;
|
||||
const result = workflow.getParentMainInputNode(emptyConnectionNode);
|
||||
|
||||
expect(result).toBe(emptyConnectionNode);
|
||||
});
|
||||
|
||||
test('should handle null connections in connection array', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'NullConnectionNode',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 100],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
NullConnectionNode: {
|
||||
[NodeConnectionTypes.AiTool]: [
|
||||
[{ node: '', type: NodeConnectionTypes.AiTool, index: 0 }], // Connection with empty node name
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
const nullConnectionNode = workflow.getNode('NullConnectionNode')!;
|
||||
const result = workflow.getParentMainInputNode(nullConnectionNode);
|
||||
|
||||
expect(result).toBe(nullConnectionNode);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNodeConnectionIndexes', () => {
|
||||
@@ -3243,397 +2769,4 @@ describe('Workflow', () => {
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasPath method', () => {
|
||||
test('should return true for self-reference', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: 'Node1',
|
||||
name: 'Node1',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
expect(workflow.hasPath('Node1', 'Node1')).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false when nodes are not connected', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: 'Node1',
|
||||
name: 'Node1',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: 'Node2',
|
||||
name: 'Node2',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 0],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
expect(workflow.hasPath('Node1', 'Node2')).toBe(false);
|
||||
});
|
||||
|
||||
test('should return true for directly connected nodes', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: 'Node1',
|
||||
name: 'Node1',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: 'Node2',
|
||||
name: 'Node2',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 0],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
Node1: {
|
||||
[NodeConnectionTypes.Main]: [
|
||||
[{ node: 'Node2', type: NodeConnectionTypes.Main, index: 0 }],
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
expect(workflow.hasPath('Node1', 'Node2')).toBe(true);
|
||||
expect(workflow.hasPath('Node2', 'Node1')).toBe(true);
|
||||
});
|
||||
|
||||
test('should respect maximum depth limit', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: 'Node1',
|
||||
name: 'Node1',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: 'Node2',
|
||||
name: 'Node2',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 0],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
Node1: {
|
||||
[NodeConnectionTypes.Main]: [
|
||||
[{ node: 'Node2', type: NodeConnectionTypes.Main, index: 0 }],
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
// Should find path with sufficient depth
|
||||
expect(workflow.hasPath('Node1', 'Node2', 5)).toBe(true);
|
||||
expect(workflow.hasPath('Node1', 'Node2', 1)).toBe(true);
|
||||
|
||||
// Should not find path with insufficient depth
|
||||
expect(workflow.hasPath('Node1', 'Node2', 0)).toBe(false);
|
||||
});
|
||||
|
||||
test('should handle AI connection types', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: 'Agent',
|
||||
name: 'Agent',
|
||||
type: 'test.ai.agent',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: 'Tool1',
|
||||
name: 'Tool1',
|
||||
type: 'test.ai.tool',
|
||||
typeVersion: 1,
|
||||
position: [100, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: 'Memory',
|
||||
name: 'Memory',
|
||||
type: 'test.ai.memory',
|
||||
typeVersion: 1,
|
||||
position: [200, 0],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
Tool1: {
|
||||
[NodeConnectionTypes.AiTool]: [
|
||||
[{ node: 'Agent', type: NodeConnectionTypes.AiTool, index: 0 }],
|
||||
],
|
||||
},
|
||||
Memory: {
|
||||
[NodeConnectionTypes.AiMemory]: [
|
||||
[{ node: 'Agent', type: NodeConnectionTypes.AiMemory, index: 0 }],
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
expect(workflow.hasPath('Tool1', 'Agent')).toBe(true);
|
||||
expect(workflow.hasPath('Memory', 'Agent')).toBe(true);
|
||||
expect(workflow.hasPath('Tool1', 'Memory')).toBe(true);
|
||||
});
|
||||
|
||||
test('should handle complex paths with multiple connection types', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: 'Start',
|
||||
name: 'Start',
|
||||
type: 'test.start',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: 'VectorStore',
|
||||
name: 'VectorStore',
|
||||
type: 'test.vectorstore',
|
||||
typeVersion: 1,
|
||||
position: [100, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: 'Document',
|
||||
name: 'Document',
|
||||
type: 'test.document',
|
||||
typeVersion: 1,
|
||||
position: [200, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: 'End',
|
||||
name: 'End',
|
||||
type: 'test.end',
|
||||
typeVersion: 1,
|
||||
position: [300, 0],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
Start: {
|
||||
[NodeConnectionTypes.Main]: [
|
||||
[{ node: 'VectorStore', type: NodeConnectionTypes.AiVectorStore, index: 0 }],
|
||||
],
|
||||
},
|
||||
Document: {
|
||||
[NodeConnectionTypes.Main]: [
|
||||
[{ node: 'VectorStore', type: NodeConnectionTypes.AiDocument, index: 0 }],
|
||||
],
|
||||
},
|
||||
VectorStore: {
|
||||
[NodeConnectionTypes.Main]: [
|
||||
[{ node: 'End', type: NodeConnectionTypes.Main, index: 0 }],
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
expect(workflow.hasPath('Start', 'End')).toBe(true);
|
||||
expect(workflow.hasPath('Document', 'End')).toBe(true);
|
||||
expect(workflow.hasPath('Start', 'Document')).toBe(true);
|
||||
});
|
||||
|
||||
test('should handle cyclic graphs without infinite loops', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: 'Node1',
|
||||
name: 'Node1',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: 'Node2',
|
||||
name: 'Node2',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: 'Node3',
|
||||
name: 'Node3',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [200, 0],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
Node1: {
|
||||
[NodeConnectionTypes.Main]: [
|
||||
[{ node: 'Node2', type: NodeConnectionTypes.Main, index: 0 }],
|
||||
],
|
||||
},
|
||||
Node2: {
|
||||
[NodeConnectionTypes.Main]: [
|
||||
[{ node: 'Node3', type: NodeConnectionTypes.Main, index: 0 }],
|
||||
],
|
||||
},
|
||||
Node3: {
|
||||
[NodeConnectionTypes.Main]: [
|
||||
[{ node: 'Node1', type: NodeConnectionTypes.Main, index: 0 }],
|
||||
],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
expect(workflow.hasPath('Node1', 'Node3')).toBe(true);
|
||||
expect(workflow.hasPath('Node2', 'Node1')).toBe(true);
|
||||
expect(workflow.hasPath('Node3', 'Node2')).toBe(true);
|
||||
});
|
||||
|
||||
test('should handle empty workflow', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [],
|
||||
connections: {},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
expect(workflow.hasPath('NonExistent1', 'NonExistent2')).toBe(false);
|
||||
});
|
||||
|
||||
test('should handle nodes with no outgoing connections', () => {
|
||||
const workflow = new Workflow({
|
||||
id: 'test',
|
||||
nodes: [
|
||||
{
|
||||
id: 'Node1',
|
||||
name: 'Node1',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: 'Node2',
|
||||
name: 'Node2',
|
||||
type: 'test.set',
|
||||
typeVersion: 1,
|
||||
position: [100, 0],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
Node1: {
|
||||
[NodeConnectionTypes.Main]: [[]],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
nodeTypes,
|
||||
});
|
||||
|
||||
expect(workflow.hasPath('Node1', 'Node2')).toBe(false);
|
||||
expect(workflow.hasPath('Node2', 'Node1')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when source node has pinned data (virtual path)', () => {
|
||||
const nodes: INode[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Trigger',
|
||||
type: 'n8n-nodes-base.executeWorkflowTrigger',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'EditFields',
|
||||
type: 'n8n-nodes-base.set',
|
||||
typeVersion: 1,
|
||||
position: [200, 0],
|
||||
parameters: {},
|
||||
},
|
||||
];
|
||||
|
||||
const connections: IConnections = {
|
||||
Trigger: {
|
||||
main: [[{ node: 'EditFields', type: 'main', index: 0 }]],
|
||||
},
|
||||
};
|
||||
|
||||
const pinData: IPinData = {
|
||||
Trigger: [
|
||||
{
|
||||
json: {
|
||||
name: 'Test item',
|
||||
value: 123,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const workflow = new Workflow({
|
||||
nodes,
|
||||
connections,
|
||||
active: false,
|
||||
nodeTypes,
|
||||
pinData,
|
||||
});
|
||||
|
||||
// Should return true because Trigger has pinned data, creating a virtual path
|
||||
expect(workflow.hasPath('Trigger', 'EditFields')).toBe(true);
|
||||
// Should also work for self-reference with pinned data
|
||||
expect(workflow.hasPath('Trigger', 'Trigger')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user