trigger-detector.test.tsβ’10.1 kB
/**
* Unit tests for trigger detection
*/
import { describe, it, expect } from 'vitest';
import { detectTriggerFromWorkflow, buildTriggerUrl, describeTrigger } from '../../../src/triggers/trigger-detector';
import type { Workflow } from '../../../src/types/n8n-api';
// Helper to create a workflow with a specific trigger node
function createWorkflowWithTrigger(triggerType: string, params: Record<string, unknown> = {}): Workflow {
return {
id: 'test-workflow',
name: 'Test Workflow',
active: true,
nodes: [
{
id: 'trigger-node',
name: 'Trigger',
type: triggerType,
typeVersion: 1,
position: [0, 0],
parameters: params,
},
{
id: 'action-node',
name: 'Action',
type: 'n8n-nodes-base.noOp',
typeVersion: 1,
position: [200, 0],
parameters: {},
},
],
connections: {},
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
settings: {},
staticData: undefined,
} as Workflow;
}
describe('Trigger Detector', () => {
describe('detectTriggerFromWorkflow', () => {
describe('webhook detection', () => {
it('should detect n8n-nodes-base.webhook as webhook trigger', () => {
const workflow = createWorkflowWithTrigger('n8n-nodes-base.webhook', {
path: 'my-webhook',
httpMethod: 'POST',
});
const result = detectTriggerFromWorkflow(workflow);
expect(result.detected).toBe(true);
expect(result.trigger?.type).toBe('webhook');
expect(result.trigger?.webhookPath).toBe('my-webhook');
expect(result.trigger?.httpMethod).toBe('POST');
});
it('should detect webhook node with httpMethod from parameters', () => {
const workflow = createWorkflowWithTrigger('n8n-nodes-base.webhook', {
path: 'get-data',
httpMethod: 'GET',
});
const result = detectTriggerFromWorkflow(workflow);
expect(result.detected).toBe(true);
expect(result.trigger?.type).toBe('webhook');
expect(result.trigger?.httpMethod).toBe('GET');
});
it('should default httpMethod to POST when not specified', () => {
const workflow = createWorkflowWithTrigger('n8n-nodes-base.webhook', {
path: 'test-path',
});
const result = detectTriggerFromWorkflow(workflow);
expect(result.detected).toBe(true);
expect(result.trigger?.type).toBe('webhook');
// Default is POST when not specified
expect(result.trigger?.httpMethod).toBe('POST');
});
});
describe('form detection', () => {
it('should detect n8n-nodes-base.formTrigger as form trigger', () => {
const workflow = createWorkflowWithTrigger('n8n-nodes-base.formTrigger', {
path: 'my-form',
});
const result = detectTriggerFromWorkflow(workflow);
expect(result.detected).toBe(true);
expect(result.trigger?.type).toBe('form');
expect(result.trigger?.node?.parameters?.path).toBe('my-form');
});
});
describe('chat detection', () => {
it('should detect @n8n/n8n-nodes-langchain.chatTrigger as chat trigger', () => {
const workflow = createWorkflowWithTrigger('@n8n/n8n-nodes-langchain.chatTrigger', {
path: 'chat-endpoint',
});
const result = detectTriggerFromWorkflow(workflow);
expect(result.detected).toBe(true);
expect(result.trigger?.type).toBe('chat');
});
it('should detect n8n-nodes-langchain.chatTrigger as chat trigger', () => {
const workflow = createWorkflowWithTrigger('n8n-nodes-langchain.chatTrigger', {
webhookPath: 'ai-chat',
});
const result = detectTriggerFromWorkflow(workflow);
expect(result.detected).toBe(true);
expect(result.trigger?.type).toBe('chat');
});
});
describe('non-triggerable workflows', () => {
it('should return not detected for schedule trigger', () => {
const workflow = createWorkflowWithTrigger('n8n-nodes-base.scheduleTrigger', {
rule: { interval: [{ field: 'hours', value: 1 }] },
});
const result = detectTriggerFromWorkflow(workflow);
expect(result.detected).toBe(false);
// Fallback reason may be undefined for non-input triggers
});
it('should return not detected for manual trigger', () => {
const workflow = createWorkflowWithTrigger('n8n-nodes-base.manualTrigger', {});
const result = detectTriggerFromWorkflow(workflow);
expect(result.detected).toBe(false);
});
it('should return not detected for email trigger', () => {
const workflow = createWorkflowWithTrigger('n8n-nodes-base.emailReadImap', {
mailbox: 'INBOX',
});
const result = detectTriggerFromWorkflow(workflow);
expect(result.detected).toBe(false);
});
});
describe('workflows without triggers', () => {
it('should return not detected for workflow with no trigger node', () => {
const workflow: Workflow = {
id: 'test-workflow',
name: 'Test Workflow',
active: true,
nodes: [
{
id: 'action-node',
name: 'Action',
type: 'n8n-nodes-base.noOp',
typeVersion: 1,
position: [0, 0],
parameters: {},
},
],
connections: {},
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
settings: {},
staticData: undefined,
} as Workflow;
const result = detectTriggerFromWorkflow(workflow);
expect(result.detected).toBe(false);
});
});
});
describe('buildTriggerUrl', () => {
it('should build webhook URL correctly', () => {
const baseUrl = 'https://n8n.example.com';
const trigger = {
type: 'webhook' as const,
node: {
id: 'trigger',
name: 'Webhook',
type: 'n8n-nodes-base.webhook',
typeVersion: 1,
position: [0, 0] as [number, number],
parameters: { path: 'my-webhook' },
},
webhookPath: 'my-webhook',
};
const url = buildTriggerUrl(baseUrl, trigger, 'production');
expect(url).toBe('https://n8n.example.com/webhook/my-webhook');
});
it('should build test webhook URL correctly', () => {
const baseUrl = 'https://n8n.example.com/';
const trigger = {
type: 'webhook' as const,
node: {
id: 'trigger',
name: 'Webhook',
type: 'n8n-nodes-base.webhook',
typeVersion: 1,
position: [0, 0] as [number, number],
parameters: { path: 'test-path' },
},
webhookPath: 'test-path',
};
const url = buildTriggerUrl(baseUrl, trigger, 'test');
expect(url).toBe('https://n8n.example.com/webhook-test/test-path');
});
it('should build form URL with node ID when webhookPath not set', () => {
const baseUrl = 'https://n8n.example.com';
const trigger = {
type: 'form' as const,
node: {
id: 'trigger',
name: 'Form',
type: 'n8n-nodes-base.formTrigger',
typeVersion: 1,
position: [0, 0] as [number, number],
parameters: { path: 'my-form' },
},
// webhookPath is undefined - should use node.id
};
const url = buildTriggerUrl(baseUrl, trigger, 'production');
// When webhookPath is not set, uses node.id as fallback
expect(url).toContain('/form/');
});
it('should build chat URL correctly', () => {
const baseUrl = 'https://n8n.example.com';
const trigger = {
type: 'chat' as const,
node: {
id: 'trigger',
name: 'Chat',
type: '@n8n/n8n-nodes-langchain.chatTrigger',
typeVersion: 1,
position: [0, 0] as [number, number],
parameters: { path: 'ai-chat' },
},
webhookPath: 'ai-chat',
};
const url = buildTriggerUrl(baseUrl, trigger, 'production');
expect(url).toBe('https://n8n.example.com/webhook/ai-chat');
});
});
describe('describeTrigger', () => {
it('should describe webhook trigger', () => {
const trigger = {
type: 'webhook' as const,
node: {
id: 'trigger',
name: 'My Webhook',
type: 'n8n-nodes-base.webhook',
typeVersion: 1,
position: [0, 0] as [number, number],
parameters: { path: 'my-webhook' },
},
webhookPath: 'my-webhook',
httpMethod: 'POST' as const,
};
const description = describeTrigger(trigger);
// Case-insensitive check
expect(description.toLowerCase()).toContain('webhook');
expect(description).toContain('POST');
expect(description).toContain('my-webhook');
});
it('should describe form trigger', () => {
const trigger = {
type: 'form' as const,
node: {
id: 'trigger',
name: 'Contact Form',
type: 'n8n-nodes-base.formTrigger',
typeVersion: 1,
position: [0, 0] as [number, number],
parameters: { path: 'contact' },
},
webhookPath: 'contact',
};
const description = describeTrigger(trigger);
// Case-insensitive check
expect(description.toLowerCase()).toContain('form');
});
it('should describe chat trigger', () => {
const trigger = {
type: 'chat' as const,
node: {
id: 'trigger',
name: 'AI Chat',
type: '@n8n/n8n-nodes-langchain.chatTrigger',
typeVersion: 1,
position: [0, 0] as [number, number],
parameters: {},
},
};
const description = describeTrigger(trigger);
// Case-insensitive check
expect(description.toLowerCase()).toContain('chat');
});
});
});