mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-04-04 16:43:11 +00:00
feat: rename n8n_trigger_webhook_workflow to n8n_test_workflow with multi-trigger support (#460)
* feat: rename n8n_trigger_webhook_workflow to n8n_test_workflow with multi-trigger support - Rename tool from n8n_trigger_webhook_workflow to n8n_test_workflow - Add support for webhook, form, and chat triggers (auto-detection) - Implement modular trigger system with registry pattern - Add trigger detector for automatic trigger type inference - Remove execute trigger type (n8n public API limitation) - Add comprehensive tests for trigger detection and handlers The tool now auto-detects trigger type from workflow structure and supports all externally-triggerable workflows via n8n's public API. Note: Direct workflow execution (Schedule/Manual triggers) requires n8n's instance-level MCP access, not available via REST API. Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: add SSRF protection to webhook handler and update tests - Add SSRF URL validation to webhook-handler.ts (critical security fix) Aligns with existing SSRF protection in form-handler.ts and chat-handler.ts - Update parameter-validation.test.ts to use new n8n_test_workflow tool name Conceived by Romuald Członkowski - www.aiadvisors.pl/en 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: n8n_test_workflow unified trigger tool (v2.28.0) Added new `n8n_test_workflow` tool replacing `n8n_trigger_webhook_workflow`: Features: - Auto-detects trigger type (webhook/form/chat) from workflow - Supports multiple trigger types with type-specific parameters - SSRF protection for all trigger handlers - Extensible handler architecture with registry pattern Changes: - Fixed Zod schema to remove invalid 'execute' trigger type - Updated README.md tool documentation - Added CHANGELOG entry for v2.28.0 - Bumped version to 2.28.0 Conceived by Romuald Członkowski - www.aiadvisors.pl/en 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * test: add comprehensive unit tests for trigger handlers Added 87 unit tests across 4 test files to improve code coverage: - base-handler.test.ts (19 tests) - 100% coverage - webhook-handler.test.ts (22 tests) - 100% coverage - chat-handler.test.ts (23 tests) - 100% coverage - form-handler.test.ts (23 tests) - 100% coverage Tests cover: - Input validation and parameter handling - SSRF protection integration - HTTP method handling and URL building - Error response formatting - Execution paths for all trigger types Conceived by Romuald Członkowski - www.aiadvisors.pl/en 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
ddf9556759
commit
33690c5650
156
tests/unit/triggers/trigger-registry.test.ts
Normal file
156
tests/unit/triggers/trigger-registry.test.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Unit tests for trigger registry
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { TriggerRegistry, initializeTriggerRegistry, ensureRegistryInitialized } from '../../../src/triggers/trigger-registry';
|
||||
import type { N8nApiClient } from '../../../src/services/n8n-api-client';
|
||||
|
||||
// Mock N8nApiClient
|
||||
const createMockClient = (): N8nApiClient => ({
|
||||
getWorkflow: vi.fn(),
|
||||
listWorkflows: vi.fn(),
|
||||
createWorkflow: vi.fn(),
|
||||
updateWorkflow: vi.fn(),
|
||||
deleteWorkflow: vi.fn(),
|
||||
triggerWebhook: vi.fn(),
|
||||
getExecution: vi.fn(),
|
||||
listExecutions: vi.fn(),
|
||||
deleteExecution: vi.fn(),
|
||||
} as unknown as N8nApiClient);
|
||||
|
||||
describe('TriggerRegistry', () => {
|
||||
describe('initialization', () => {
|
||||
it('should initialize with all handlers registered', async () => {
|
||||
await initializeTriggerRegistry();
|
||||
|
||||
const registeredTypes = TriggerRegistry.getRegisteredTypes();
|
||||
|
||||
expect(registeredTypes).toContain('webhook');
|
||||
expect(registeredTypes).toContain('form');
|
||||
expect(registeredTypes).toContain('chat');
|
||||
expect(registeredTypes.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should not register duplicate handlers on multiple init calls', async () => {
|
||||
await initializeTriggerRegistry();
|
||||
const firstTypes = TriggerRegistry.getRegisteredTypes();
|
||||
|
||||
await initializeTriggerRegistry();
|
||||
const secondTypes = TriggerRegistry.getRegisteredTypes();
|
||||
|
||||
expect(firstTypes.length).toBe(secondTypes.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasHandler', () => {
|
||||
beforeEach(async () => {
|
||||
await ensureRegistryInitialized();
|
||||
});
|
||||
|
||||
it('should return true for webhook handler', () => {
|
||||
expect(TriggerRegistry.hasHandler('webhook')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for form handler', () => {
|
||||
expect(TriggerRegistry.hasHandler('form')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for chat handler', () => {
|
||||
expect(TriggerRegistry.hasHandler('chat')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for unknown trigger type', () => {
|
||||
expect(TriggerRegistry.hasHandler('unknown' as any)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHandler', () => {
|
||||
let mockClient: N8nApiClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
await ensureRegistryInitialized();
|
||||
mockClient = createMockClient();
|
||||
});
|
||||
|
||||
it('should return a webhook handler', () => {
|
||||
const handler = TriggerRegistry.getHandler('webhook', mockClient);
|
||||
|
||||
expect(handler).toBeDefined();
|
||||
expect(handler?.triggerType).toBe('webhook');
|
||||
});
|
||||
|
||||
it('should return a form handler', () => {
|
||||
const handler = TriggerRegistry.getHandler('form', mockClient);
|
||||
|
||||
expect(handler).toBeDefined();
|
||||
expect(handler?.triggerType).toBe('form');
|
||||
});
|
||||
|
||||
it('should return a chat handler', () => {
|
||||
const handler = TriggerRegistry.getHandler('chat', mockClient);
|
||||
|
||||
expect(handler).toBeDefined();
|
||||
expect(handler?.triggerType).toBe('chat');
|
||||
});
|
||||
|
||||
it('should return undefined for unknown trigger type', () => {
|
||||
const handler = TriggerRegistry.getHandler('unknown' as any, mockClient);
|
||||
|
||||
expect(handler).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handler capabilities', () => {
|
||||
let mockClient: N8nApiClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
await ensureRegistryInitialized();
|
||||
mockClient = createMockClient();
|
||||
});
|
||||
|
||||
it('webhook handler should require active workflow', () => {
|
||||
const handler = TriggerRegistry.getHandler('webhook', mockClient);
|
||||
|
||||
expect(handler?.capabilities.requiresActiveWorkflow).toBe(true);
|
||||
expect(handler?.capabilities.canPassInputData).toBe(true);
|
||||
});
|
||||
|
||||
it('form handler should require active workflow', () => {
|
||||
const handler = TriggerRegistry.getHandler('form', mockClient);
|
||||
|
||||
expect(handler?.capabilities.requiresActiveWorkflow).toBe(true);
|
||||
expect(handler?.capabilities.canPassInputData).toBe(true);
|
||||
});
|
||||
|
||||
it('chat handler should require active workflow', () => {
|
||||
const handler = TriggerRegistry.getHandler('chat', mockClient);
|
||||
|
||||
expect(handler?.capabilities.requiresActiveWorkflow).toBe(true);
|
||||
expect(handler?.capabilities.canPassInputData).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensureRegistryInitialized', () => {
|
||||
it('should be safe to call multiple times', async () => {
|
||||
await ensureRegistryInitialized();
|
||||
await ensureRegistryInitialized();
|
||||
await ensureRegistryInitialized();
|
||||
|
||||
const types = TriggerRegistry.getRegisteredTypes();
|
||||
expect(types.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should handle concurrent initialization calls', async () => {
|
||||
const promises = [
|
||||
ensureRegistryInitialized(),
|
||||
ensureRegistryInitialized(),
|
||||
ensureRegistryInitialized(),
|
||||
];
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
const types = TriggerRegistry.getRegisteredTypes();
|
||||
expect(types.length).toBe(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user