mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-06 05:23:08 +00:00
This fix addresses issue #351 where Execute Workflow Trigger and other trigger nodes were incorrectly treated as regular nodes, causing "disconnected node" errors during partial workflow updates. ## Changes **1. Created Shared Trigger Detection Utilities** - src/utils/node-type-utils.ts: - isTriggerNode(): Recognizes ALL trigger types using flexible pattern matching - isActivatableTrigger(): Returns false for executeWorkflowTrigger (not activatable) - getTriggerTypeDescription(): Human-readable trigger descriptions **2. Updated Workflow Validation** - src/services/n8n-validation.ts: - Replaced hardcoded webhookTypes Set with isTriggerNode() function - Added validation preventing activation of workflows with only executeWorkflowTrigger - Now recognizes 200+ trigger types across n8n packages **3. Updated Workflow Validator** - src/services/workflow-validator.ts: - Replaced inline trigger detection with shared isTriggerNode() function - Ensures consistency across all validation code paths **4. Comprehensive Tests** - tests/unit/utils/node-type-utils.test.ts: - Added 30+ tests for trigger detection functions - Validates all trigger types are recognized correctly - Confirms executeWorkflowTrigger is trigger but not activatable ## Impact Before: - Execute Workflow Trigger flagged as disconnected node - Schedule/email/polling triggers also rejected - Users forced to keep unnecessary webhook triggers After: - ALL trigger types recognized (executeWorkflowTrigger, scheduleTrigger, etc.) - No disconnected node errors for triggers - Clear error when activating workflow with only executeWorkflowTrigger - Future-proof (new triggers automatically supported) ## Testing - Build: ✅ Passes - Typecheck: ✅ Passes - Unit tests: ✅ All pass - Validation test: ✅ Trigger detection working correctly Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
This commit is contained in:
committed by
GitHub
parent
c76ffd9fb1
commit
eac4e67101
@@ -7,7 +7,10 @@ import {
|
||||
isBaseNode,
|
||||
isLangChainNode,
|
||||
isValidNodeTypeFormat,
|
||||
getNodeTypeVariations
|
||||
getNodeTypeVariations,
|
||||
isTriggerNode,
|
||||
isActivatableTrigger,
|
||||
getTriggerTypeDescription
|
||||
} from '@/utils/node-type-utils';
|
||||
|
||||
describe('node-type-utils', () => {
|
||||
@@ -196,4 +199,165 @@ describe('node-type-utils', () => {
|
||||
expect(variations.length).toBe(uniqueVariations.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isTriggerNode', () => {
|
||||
it('recognizes executeWorkflowTrigger as a trigger', () => {
|
||||
expect(isTriggerNode('n8n-nodes-base.executeWorkflowTrigger')).toBe(true);
|
||||
expect(isTriggerNode('nodes-base.executeWorkflowTrigger')).toBe(true);
|
||||
});
|
||||
|
||||
it('recognizes schedule triggers', () => {
|
||||
expect(isTriggerNode('n8n-nodes-base.scheduleTrigger')).toBe(true);
|
||||
expect(isTriggerNode('n8n-nodes-base.cronTrigger')).toBe(true);
|
||||
});
|
||||
|
||||
it('recognizes webhook triggers', () => {
|
||||
expect(isTriggerNode('n8n-nodes-base.webhook')).toBe(true);
|
||||
expect(isTriggerNode('n8n-nodes-base.webhookTrigger')).toBe(true);
|
||||
});
|
||||
|
||||
it('recognizes manual triggers', () => {
|
||||
expect(isTriggerNode('n8n-nodes-base.manualTrigger')).toBe(true);
|
||||
expect(isTriggerNode('n8n-nodes-base.start')).toBe(true);
|
||||
expect(isTriggerNode('n8n-nodes-base.formTrigger')).toBe(true);
|
||||
});
|
||||
|
||||
it('recognizes email and polling triggers', () => {
|
||||
expect(isTriggerNode('n8n-nodes-base.emailTrigger')).toBe(true);
|
||||
expect(isTriggerNode('n8n-nodes-base.imapTrigger')).toBe(true);
|
||||
expect(isTriggerNode('n8n-nodes-base.gmailTrigger')).toBe(true);
|
||||
});
|
||||
|
||||
it('recognizes various trigger types', () => {
|
||||
expect(isTriggerNode('n8n-nodes-base.slackTrigger')).toBe(true);
|
||||
expect(isTriggerNode('n8n-nodes-base.githubTrigger')).toBe(true);
|
||||
expect(isTriggerNode('n8n-nodes-base.twilioTrigger')).toBe(true);
|
||||
});
|
||||
|
||||
it('does NOT recognize respondToWebhook as a trigger', () => {
|
||||
expect(isTriggerNode('n8n-nodes-base.respondToWebhook')).toBe(false);
|
||||
});
|
||||
|
||||
it('does NOT recognize regular nodes as triggers', () => {
|
||||
expect(isTriggerNode('n8n-nodes-base.set')).toBe(false);
|
||||
expect(isTriggerNode('n8n-nodes-base.httpRequest')).toBe(false);
|
||||
expect(isTriggerNode('n8n-nodes-base.code')).toBe(false);
|
||||
expect(isTriggerNode('n8n-nodes-base.slack')).toBe(false);
|
||||
});
|
||||
|
||||
it('handles normalized and non-normalized node types', () => {
|
||||
expect(isTriggerNode('n8n-nodes-base.webhook')).toBe(true);
|
||||
expect(isTriggerNode('nodes-base.webhook')).toBe(true);
|
||||
});
|
||||
|
||||
it('is case-insensitive', () => {
|
||||
expect(isTriggerNode('n8n-nodes-base.WebhookTrigger')).toBe(true);
|
||||
expect(isTriggerNode('n8n-nodes-base.EMAILTRIGGER')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isActivatableTrigger', () => {
|
||||
it('executeWorkflowTrigger is NOT activatable', () => {
|
||||
expect(isActivatableTrigger('n8n-nodes-base.executeWorkflowTrigger')).toBe(false);
|
||||
expect(isActivatableTrigger('nodes-base.executeWorkflowTrigger')).toBe(false);
|
||||
});
|
||||
|
||||
it('webhook triggers ARE activatable', () => {
|
||||
expect(isActivatableTrigger('n8n-nodes-base.webhook')).toBe(true);
|
||||
expect(isActivatableTrigger('n8n-nodes-base.webhookTrigger')).toBe(true);
|
||||
});
|
||||
|
||||
it('schedule triggers ARE activatable', () => {
|
||||
expect(isActivatableTrigger('n8n-nodes-base.scheduleTrigger')).toBe(true);
|
||||
expect(isActivatableTrigger('n8n-nodes-base.cronTrigger')).toBe(true);
|
||||
});
|
||||
|
||||
it('manual triggers ARE activatable', () => {
|
||||
expect(isActivatableTrigger('n8n-nodes-base.manualTrigger')).toBe(true);
|
||||
expect(isActivatableTrigger('n8n-nodes-base.start')).toBe(true);
|
||||
expect(isActivatableTrigger('n8n-nodes-base.formTrigger')).toBe(true);
|
||||
});
|
||||
|
||||
it('polling triggers ARE activatable', () => {
|
||||
expect(isActivatableTrigger('n8n-nodes-base.emailTrigger')).toBe(true);
|
||||
expect(isActivatableTrigger('n8n-nodes-base.slackTrigger')).toBe(true);
|
||||
expect(isActivatableTrigger('n8n-nodes-base.gmailTrigger')).toBe(true);
|
||||
});
|
||||
|
||||
it('regular nodes are NOT activatable', () => {
|
||||
expect(isActivatableTrigger('n8n-nodes-base.set')).toBe(false);
|
||||
expect(isActivatableTrigger('n8n-nodes-base.httpRequest')).toBe(false);
|
||||
expect(isActivatableTrigger('n8n-nodes-base.respondToWebhook')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTriggerTypeDescription', () => {
|
||||
it('describes executeWorkflowTrigger correctly', () => {
|
||||
const desc = getTriggerTypeDescription('n8n-nodes-base.executeWorkflowTrigger');
|
||||
expect(desc).toContain('Execute Workflow');
|
||||
expect(desc).toContain('invoked by other workflows');
|
||||
});
|
||||
|
||||
it('describes webhook triggers correctly', () => {
|
||||
const desc = getTriggerTypeDescription('n8n-nodes-base.webhook');
|
||||
expect(desc).toContain('Webhook');
|
||||
expect(desc).toContain('HTTP');
|
||||
});
|
||||
|
||||
it('describes schedule triggers correctly', () => {
|
||||
const desc = getTriggerTypeDescription('n8n-nodes-base.scheduleTrigger');
|
||||
expect(desc).toContain('Schedule');
|
||||
expect(desc).toContain('time-based');
|
||||
});
|
||||
|
||||
it('describes manual triggers correctly', () => {
|
||||
const desc = getTriggerTypeDescription('n8n-nodes-base.manualTrigger');
|
||||
expect(desc).toContain('Manual');
|
||||
});
|
||||
|
||||
it('describes email triggers correctly', () => {
|
||||
const desc = getTriggerTypeDescription('n8n-nodes-base.emailTrigger');
|
||||
expect(desc).toContain('Email');
|
||||
expect(desc).toContain('polling');
|
||||
});
|
||||
|
||||
it('provides generic description for unknown triggers', () => {
|
||||
const desc = getTriggerTypeDescription('n8n-nodes-base.customTrigger');
|
||||
expect(desc).toContain('Trigger');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Integration: Trigger Classification', () => {
|
||||
it('all triggers detected by isTriggerNode should be classified correctly', () => {
|
||||
const triggers = [
|
||||
'n8n-nodes-base.webhook',
|
||||
'n8n-nodes-base.webhookTrigger',
|
||||
'n8n-nodes-base.scheduleTrigger',
|
||||
'n8n-nodes-base.manualTrigger',
|
||||
'n8n-nodes-base.executeWorkflowTrigger',
|
||||
'n8n-nodes-base.emailTrigger'
|
||||
];
|
||||
|
||||
for (const trigger of triggers) {
|
||||
expect(isTriggerNode(trigger)).toBe(true);
|
||||
const desc = getTriggerTypeDescription(trigger);
|
||||
expect(desc).toBeTruthy();
|
||||
expect(desc).not.toBe('Unknown trigger type');
|
||||
}
|
||||
});
|
||||
|
||||
it('only executeWorkflowTrigger is non-activatable', () => {
|
||||
const triggers = [
|
||||
{ type: 'n8n-nodes-base.webhook', activatable: true },
|
||||
{ type: 'n8n-nodes-base.scheduleTrigger', activatable: true },
|
||||
{ type: 'n8n-nodes-base.executeWorkflowTrigger', activatable: false },
|
||||
{ type: 'n8n-nodes-base.emailTrigger', activatable: true }
|
||||
];
|
||||
|
||||
for (const { type, activatable } of triggers) {
|
||||
expect(isTriggerNode(type)).toBe(true); // All are triggers
|
||||
expect(isActivatableTrigger(type)).toBe(activatable); // But only some are activatable
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user