mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-01-30 06:22:04 +00:00
Merge pull request #244 from czlonkowski/feature/webhook-error-execution-guidance
feat: enhance webhook error messages with execution guidance
This commit is contained in:
37
CHANGELOG.md
37
CHANGELOG.md
@@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.14.6] - 2025-10-01
|
||||
|
||||
### Enhanced
|
||||
- **Webhook Error Messages**: Replaced generic "Please try again later or contact support" messages with actionable guidance
|
||||
- Error messages now extract execution ID and workflow ID from failed webhook triggers
|
||||
- Guide users to use `n8n_get_execution({id: executionId, mode: 'preview'})` for efficient debugging
|
||||
- Format: "Workflow {workflowId} execution {executionId} failed. Use n8n_get_execution({id: '{executionId}', mode: 'preview'}) to investigate the error."
|
||||
- When no execution ID available: "Workflow failed to execute. Use n8n_list_executions to find recent executions, then n8n_get_execution with mode='preview' to investigate."
|
||||
|
||||
### Added
|
||||
- New error formatting functions in `n8n-errors.ts`:
|
||||
- `formatExecutionError()` - Creates execution-specific error messages with debugging guidance
|
||||
- `formatNoExecutionError()` - Provides guidance when execution context unavailable
|
||||
- Enhanced `McpToolResponse` type with optional `executionId` and `workflowId` fields
|
||||
- Error handling documentation in `n8n-trigger-webhook-workflow` tool docs
|
||||
- 30 new comprehensive tests for error message formatting and webhook error handling
|
||||
|
||||
### Changed
|
||||
- `handleTriggerWebhookWorkflow` now extracts execution context from error responses
|
||||
- `getUserFriendlyErrorMessage` returns actual server error messages instead of generic text
|
||||
- Tool documentation type enhanced with optional `errorHandling` field
|
||||
|
||||
### Fixed
|
||||
- Test expectations updated to match new error message format (handlers-workflow-diff.test.ts)
|
||||
|
||||
### Benefits
|
||||
- **Fast debugging**: Preview mode executes in <50ms (vs seconds for full data)
|
||||
- **Efficient**: Uses ~500 tokens (vs 50K+ tokens for full execution data)
|
||||
- **Safe**: No timeout or token limit risks
|
||||
- **Actionable**: Clear next steps for users to investigate failures
|
||||
|
||||
### Impact
|
||||
- Eliminates unhelpful "contact support" messages
|
||||
- Provides specific, actionable debugging guidance
|
||||
- Reduces debugging time by directing users to efficient tools
|
||||
- 100% backward compatible - only improves error messages
|
||||
|
||||
## [2.14.5] - 2025-09-30
|
||||
|
||||
### Added
|
||||
|
||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.14.5",
|
||||
"version": "2.14.6",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp-runtime",
|
||||
"version": "2.14.4",
|
||||
"version": "2.14.5",
|
||||
"description": "n8n MCP Server Runtime Dependencies Only",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -18,7 +18,9 @@ import {
|
||||
import {
|
||||
N8nApiError,
|
||||
N8nNotFoundError,
|
||||
getUserFriendlyErrorMessage
|
||||
getUserFriendlyErrorMessage,
|
||||
formatExecutionError,
|
||||
formatNoExecutionError
|
||||
} from '../utils/n8n-errors';
|
||||
import { logger } from '../utils/logger';
|
||||
import { z } from 'zod';
|
||||
@@ -968,6 +970,33 @@ export async function handleTriggerWebhookWorkflow(args: unknown, context?: Inst
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
// Try to extract execution context from error response
|
||||
const errorData = error.details as any;
|
||||
const executionId = errorData?.executionId || errorData?.id || errorData?.execution?.id;
|
||||
const workflowId = errorData?.workflowId || errorData?.workflow?.id;
|
||||
|
||||
// If we have execution ID, provide specific guidance with n8n_get_execution
|
||||
if (executionId) {
|
||||
return {
|
||||
success: false,
|
||||
error: formatExecutionError(executionId, workflowId),
|
||||
code: error.code,
|
||||
executionId,
|
||||
workflowId: workflowId || undefined
|
||||
};
|
||||
}
|
||||
|
||||
// No execution ID available - workflow likely didn't start
|
||||
// Provide guidance to check recent executions
|
||||
if (error.code === 'SERVER_ERROR' || error.statusCode && error.statusCode >= 500) {
|
||||
return {
|
||||
success: false,
|
||||
error: formatNoExecutionError(),
|
||||
code: error.code
|
||||
};
|
||||
}
|
||||
|
||||
// For other errors (auth, validation, etc), use standard message
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface ToolDocumentation {
|
||||
examples: string[];
|
||||
useCases: string[];
|
||||
performance: string;
|
||||
errorHandling?: string; // Optional: Documentation on error handling and debugging
|
||||
bestPractices: string[];
|
||||
pitfalls: string[];
|
||||
modeComparison?: string; // Optional: Comparison of different modes for tools with multiple modes
|
||||
|
||||
@@ -59,19 +59,59 @@ export const n8nTriggerWebhookWorkflowDoc: ToolDocumentation = {
|
||||
'Implement event-driven architectures with n8n'
|
||||
],
|
||||
performance: `Performance varies based on workflow complexity and waitForResponse setting. Synchronous calls (waitForResponse: true) block until workflow completes. For long-running workflows, use async mode (waitForResponse: false) and monitor execution separately.`,
|
||||
errorHandling: `**Enhanced Error Messages with Execution Guidance**
|
||||
|
||||
When a webhook trigger fails, the error response now includes specific guidance to help debug the issue:
|
||||
|
||||
**Error with Execution ID** (workflow started but failed):
|
||||
- Format: "Workflow {workflowId} execution {executionId} failed. Use n8n_get_execution({id: '{executionId}', mode: 'preview'}) to investigate the error."
|
||||
- Response includes: executionId and workflowId fields for direct access
|
||||
- Recommended action: Use n8n_get_execution with mode='preview' for fast, efficient error inspection
|
||||
|
||||
**Error without Execution ID** (workflow didn't start):
|
||||
- Format: "Workflow failed to execute. Use n8n_list_executions to find recent executions, then n8n_get_execution with mode='preview' to investigate."
|
||||
- Recommended action: Check recent executions with n8n_list_executions
|
||||
|
||||
**Why mode='preview'?**
|
||||
- Fast: <50ms response time
|
||||
- Efficient: ~500 tokens (vs 50K+ for full mode)
|
||||
- Safe: No timeout or token limit risks
|
||||
- Informative: Shows structure, counts, and error details
|
||||
- Provides recommendations for fetching more data if needed
|
||||
|
||||
**Example Error Responses**:
|
||||
\`\`\`json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Workflow wf_123 execution exec_456 failed. Use n8n_get_execution({id: 'exec_456', mode: 'preview'}) to investigate the error.",
|
||||
"executionId": "exec_456",
|
||||
"workflowId": "wf_123",
|
||||
"code": "SERVER_ERROR"
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
**Investigation Workflow**:
|
||||
1. Trigger returns error with execution ID
|
||||
2. Call n8n_get_execution({id: executionId, mode: 'preview'}) to see structure and error
|
||||
3. Based on preview recommendation, fetch more data if needed
|
||||
4. Fix issues in workflow and retry`,
|
||||
bestPractices: [
|
||||
'Always verify workflow is active before attempting webhook triggers',
|
||||
'Match HTTP method exactly with webhook node configuration',
|
||||
'Use async mode (waitForResponse: false) for long-running workflows',
|
||||
'Include authentication headers when webhook requires them',
|
||||
'Test webhook URL manually first to ensure it works'
|
||||
'Test webhook URL manually first to ensure it works',
|
||||
'When errors occur, use n8n_get_execution with mode="preview" first for efficient debugging',
|
||||
'Store execution IDs from error responses for later investigation'
|
||||
],
|
||||
pitfalls: [
|
||||
'Workflow must be ACTIVE - inactive workflows cannot be triggered',
|
||||
'HTTP method mismatch returns 404 even if URL is correct',
|
||||
'Webhook node must be the trigger node in the workflow',
|
||||
'Timeout errors occur with long workflows in sync mode',
|
||||
'Data format must match webhook node expectations'
|
||||
'Data format must match webhook node expectations',
|
||||
'Error messages always include n8n_get_execution guidance - follow the suggested steps for efficient debugging',
|
||||
'Execution IDs in error responses are crucial for debugging - always check for and use them'
|
||||
],
|
||||
relatedTools: ['n8n_get_execution', 'n8n_list_executions', 'n8n_get_workflow', 'n8n_create_workflow']
|
||||
}
|
||||
|
||||
@@ -290,6 +290,8 @@ export interface McpToolResponse {
|
||||
message?: string;
|
||||
code?: string;
|
||||
details?: Record<string, unknown>;
|
||||
executionId?: string;
|
||||
workflowId?: string;
|
||||
}
|
||||
|
||||
// Execution Filtering Types
|
||||
|
||||
@@ -95,6 +95,25 @@ export function handleN8nApiError(error: unknown): N8nApiError {
|
||||
return new N8nApiError('Unknown error occurred', undefined, 'UNKNOWN_ERROR', error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format execution error message with guidance to use n8n_get_execution
|
||||
* @param executionId - The execution ID from the failed execution
|
||||
* @param workflowId - Optional workflow ID
|
||||
* @returns Formatted error message with n8n_get_execution guidance
|
||||
*/
|
||||
export function formatExecutionError(executionId: string, workflowId?: string): string {
|
||||
const workflowPrefix = workflowId ? `Workflow ${workflowId} execution ` : 'Execution ';
|
||||
return `${workflowPrefix}${executionId} failed. Use n8n_get_execution({id: '${executionId}', mode: 'preview'}) to investigate the error.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format error message when no execution ID is available
|
||||
* @returns Generic guidance to check executions
|
||||
*/
|
||||
export function formatNoExecutionError(): string {
|
||||
return "Workflow failed to execute. Use n8n_list_executions to find recent executions, then n8n_get_execution with mode='preview' to investigate.";
|
||||
}
|
||||
|
||||
// Utility to extract user-friendly error messages
|
||||
export function getUserFriendlyErrorMessage(error: N8nApiError): string {
|
||||
switch (error.code) {
|
||||
@@ -109,7 +128,9 @@ export function getUserFriendlyErrorMessage(error: N8nApiError): string {
|
||||
case 'NO_RESPONSE':
|
||||
return 'Unable to connect to n8n. Please check the server URL and ensure n8n is running.';
|
||||
case 'SERVER_ERROR':
|
||||
return 'n8n server error. Please try again later or contact support.';
|
||||
// For server errors, we should not show generic message
|
||||
// Callers should check for execution context and use formatExecutionError instead
|
||||
return error.message || 'n8n server error occurred';
|
||||
default:
|
||||
return error.message || 'An unexpected error occurred';
|
||||
}
|
||||
|
||||
@@ -542,7 +542,7 @@ describe('handlers-n8n-manager', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'n8n server error. Please try again later or contact support.',
|
||||
error: 'Service unavailable',
|
||||
code: 'SERVER_ERROR',
|
||||
details: {
|
||||
apiUrl: 'https://n8n.test.com',
|
||||
@@ -642,4 +642,179 @@ describe('handlers-n8n-manager', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleTriggerWebhookWorkflow', () => {
|
||||
it('should trigger webhook successfully', async () => {
|
||||
const webhookResponse = {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
data: { result: 'success' },
|
||||
headers: {}
|
||||
};
|
||||
|
||||
mockApiClient.triggerWebhook.mockResolvedValue(webhookResponse);
|
||||
|
||||
const result = await handlers.handleTriggerWebhookWorkflow({
|
||||
webhookUrl: 'https://n8n.test.com/webhook/test-123',
|
||||
httpMethod: 'POST',
|
||||
data: { test: 'data' }
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
data: webhookResponse,
|
||||
message: 'Webhook triggered successfully'
|
||||
});
|
||||
});
|
||||
|
||||
it('should extract execution ID from webhook error response', async () => {
|
||||
const apiError = new N8nServerError('Workflow execution failed');
|
||||
apiError.details = {
|
||||
executionId: 'exec_abc123',
|
||||
workflowId: 'wf_xyz789'
|
||||
};
|
||||
|
||||
mockApiClient.triggerWebhook.mockRejectedValue(apiError);
|
||||
|
||||
const result = await handlers.handleTriggerWebhookWorkflow({
|
||||
webhookUrl: 'https://n8n.test.com/webhook/test-123',
|
||||
httpMethod: 'POST'
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Workflow wf_xyz789 execution exec_abc123 failed');
|
||||
expect(result.error).toContain('n8n_get_execution');
|
||||
expect(result.error).toContain("mode: 'preview'");
|
||||
expect(result.executionId).toBe('exec_abc123');
|
||||
expect(result.workflowId).toBe('wf_xyz789');
|
||||
});
|
||||
|
||||
it('should extract execution ID without workflow ID', async () => {
|
||||
const apiError = new N8nServerError('Execution failed');
|
||||
apiError.details = {
|
||||
executionId: 'exec_only_123'
|
||||
};
|
||||
|
||||
mockApiClient.triggerWebhook.mockRejectedValue(apiError);
|
||||
|
||||
const result = await handlers.handleTriggerWebhookWorkflow({
|
||||
webhookUrl: 'https://n8n.test.com/webhook/test-123',
|
||||
httpMethod: 'GET'
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Execution exec_only_123 failed');
|
||||
expect(result.error).toContain('n8n_get_execution');
|
||||
expect(result.error).toContain("mode: 'preview'");
|
||||
expect(result.executionId).toBe('exec_only_123');
|
||||
expect(result.workflowId).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle execution ID as "id" field', async () => {
|
||||
const apiError = new N8nServerError('Error');
|
||||
apiError.details = {
|
||||
id: 'exec_from_id_field',
|
||||
workflowId: 'wf_test'
|
||||
};
|
||||
|
||||
mockApiClient.triggerWebhook.mockRejectedValue(apiError);
|
||||
|
||||
const result = await handlers.handleTriggerWebhookWorkflow({
|
||||
webhookUrl: 'https://n8n.test.com/webhook/test',
|
||||
httpMethod: 'POST'
|
||||
});
|
||||
|
||||
expect(result.error).toContain('exec_from_id_field');
|
||||
expect(result.executionId).toBe('exec_from_id_field');
|
||||
});
|
||||
|
||||
it('should provide generic guidance when no execution ID is available', async () => {
|
||||
const apiError = new N8nServerError('Server error without execution context');
|
||||
apiError.details = {}; // No execution ID
|
||||
|
||||
mockApiClient.triggerWebhook.mockRejectedValue(apiError);
|
||||
|
||||
const result = await handlers.handleTriggerWebhookWorkflow({
|
||||
webhookUrl: 'https://n8n.test.com/webhook/test',
|
||||
httpMethod: 'POST'
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Workflow failed to execute');
|
||||
expect(result.error).toContain('n8n_list_executions');
|
||||
expect(result.error).toContain('n8n_get_execution');
|
||||
expect(result.error).toContain("mode='preview'");
|
||||
expect(result.executionId).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should use standard error message for authentication errors', async () => {
|
||||
const authError = new N8nAuthenticationError('Invalid API key');
|
||||
mockApiClient.triggerWebhook.mockRejectedValue(authError);
|
||||
|
||||
const result = await handlers.handleTriggerWebhookWorkflow({
|
||||
webhookUrl: 'https://n8n.test.com/webhook/test',
|
||||
httpMethod: 'POST'
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'Failed to authenticate with n8n. Please check your API key.',
|
||||
code: 'AUTHENTICATION_ERROR',
|
||||
details: undefined
|
||||
});
|
||||
});
|
||||
|
||||
it('should use standard error message for validation errors', async () => {
|
||||
const validationError = new N8nValidationError('Invalid webhook URL');
|
||||
mockApiClient.triggerWebhook.mockRejectedValue(validationError);
|
||||
|
||||
const result = await handlers.handleTriggerWebhookWorkflow({
|
||||
webhookUrl: 'https://n8n.test.com/webhook/test',
|
||||
httpMethod: 'POST'
|
||||
});
|
||||
|
||||
expect(result.error).toBe('Invalid request: Invalid webhook URL');
|
||||
expect(result.code).toBe('VALIDATION_ERROR');
|
||||
});
|
||||
|
||||
it('should handle invalid input with Zod validation error', async () => {
|
||||
const result = await handlers.handleTriggerWebhookWorkflow({
|
||||
webhookUrl: 'not-a-url',
|
||||
httpMethod: 'INVALID_METHOD'
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Invalid input');
|
||||
expect(result.details).toHaveProperty('errors');
|
||||
});
|
||||
|
||||
it('should not include "contact support" in error messages', async () => {
|
||||
const apiError = new N8nServerError('Test error');
|
||||
apiError.details = { executionId: 'test_exec' };
|
||||
|
||||
mockApiClient.triggerWebhook.mockRejectedValue(apiError);
|
||||
|
||||
const result = await handlers.handleTriggerWebhookWorkflow({
|
||||
webhookUrl: 'https://n8n.test.com/webhook/test',
|
||||
httpMethod: 'POST'
|
||||
});
|
||||
|
||||
expect(result.error?.toLowerCase()).not.toContain('contact support');
|
||||
expect(result.error?.toLowerCase()).not.toContain('try again later');
|
||||
});
|
||||
|
||||
it('should always recommend preview mode in error messages', async () => {
|
||||
const apiError = new N8nServerError('Error');
|
||||
apiError.details = { executionId: 'test_123' };
|
||||
|
||||
mockApiClient.triggerWebhook.mockRejectedValue(apiError);
|
||||
|
||||
const result = await handlers.handleTriggerWebhookWorkflow({
|
||||
webhookUrl: 'https://n8n.test.com/webhook/test',
|
||||
httpMethod: 'POST'
|
||||
});
|
||||
|
||||
expect(result.error).toMatch(/mode:\s*'preview'/);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -499,7 +499,7 @@ describe('handlers-workflow-diff', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'n8n server error. Please try again later or contact support.',
|
||||
error: 'Internal server error',
|
||||
code: 'SERVER_ERROR',
|
||||
});
|
||||
});
|
||||
|
||||
171
tests/unit/utils/n8n-errors.test.ts
Normal file
171
tests/unit/utils/n8n-errors.test.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
formatExecutionError,
|
||||
formatNoExecutionError,
|
||||
getUserFriendlyErrorMessage,
|
||||
N8nApiError,
|
||||
N8nAuthenticationError,
|
||||
N8nNotFoundError,
|
||||
N8nValidationError,
|
||||
N8nRateLimitError,
|
||||
N8nServerError
|
||||
} from '../../../src/utils/n8n-errors';
|
||||
|
||||
describe('formatExecutionError', () => {
|
||||
it('should format error with both execution ID and workflow ID', () => {
|
||||
const result = formatExecutionError('exec_12345', 'wf_abc');
|
||||
|
||||
expect(result).toBe("Workflow wf_abc execution exec_12345 failed. Use n8n_get_execution({id: 'exec_12345', mode: 'preview'}) to investigate the error.");
|
||||
expect(result).toContain('mode: \'preview\'');
|
||||
expect(result).toContain('exec_12345');
|
||||
expect(result).toContain('wf_abc');
|
||||
});
|
||||
|
||||
it('should format error with only execution ID', () => {
|
||||
const result = formatExecutionError('exec_67890');
|
||||
|
||||
expect(result).toBe("Execution exec_67890 failed. Use n8n_get_execution({id: 'exec_67890', mode: 'preview'}) to investigate the error.");
|
||||
expect(result).toContain('mode: \'preview\'');
|
||||
expect(result).toContain('exec_67890');
|
||||
expect(result).not.toContain('Workflow');
|
||||
});
|
||||
|
||||
it('should include preview mode guidance', () => {
|
||||
const result = formatExecutionError('test_id');
|
||||
|
||||
expect(result).toMatch(/mode:\s*'preview'/);
|
||||
});
|
||||
|
||||
it('should format with undefined workflow ID (treated as missing)', () => {
|
||||
const result = formatExecutionError('exec_123', undefined);
|
||||
|
||||
expect(result).toBe("Execution exec_123 failed. Use n8n_get_execution({id: 'exec_123', mode: 'preview'}) to investigate the error.");
|
||||
});
|
||||
|
||||
it('should properly escape execution ID in suggestion', () => {
|
||||
const result = formatExecutionError('exec-with-special_chars.123');
|
||||
|
||||
expect(result).toContain("id: 'exec-with-special_chars.123'");
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatNoExecutionError', () => {
|
||||
it('should provide guidance to check recent executions', () => {
|
||||
const result = formatNoExecutionError();
|
||||
|
||||
expect(result).toBe("Workflow failed to execute. Use n8n_list_executions to find recent executions, then n8n_get_execution with mode='preview' to investigate.");
|
||||
expect(result).toContain('n8n_list_executions');
|
||||
expect(result).toContain('n8n_get_execution');
|
||||
expect(result).toContain("mode='preview'");
|
||||
});
|
||||
|
||||
it('should include preview mode in guidance', () => {
|
||||
const result = formatNoExecutionError();
|
||||
|
||||
expect(result).toMatch(/mode\s*=\s*'preview'/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserFriendlyErrorMessage', () => {
|
||||
it('should handle authentication error', () => {
|
||||
const error = new N8nAuthenticationError('Invalid API key');
|
||||
const message = getUserFriendlyErrorMessage(error);
|
||||
|
||||
expect(message).toBe('Failed to authenticate with n8n. Please check your API key.');
|
||||
});
|
||||
|
||||
it('should handle not found error', () => {
|
||||
const error = new N8nNotFoundError('Workflow', '123');
|
||||
const message = getUserFriendlyErrorMessage(error);
|
||||
|
||||
expect(message).toContain('not found');
|
||||
});
|
||||
|
||||
it('should handle validation error', () => {
|
||||
const error = new N8nValidationError('Missing required field');
|
||||
const message = getUserFriendlyErrorMessage(error);
|
||||
|
||||
expect(message).toBe('Invalid request: Missing required field');
|
||||
});
|
||||
|
||||
it('should handle rate limit error', () => {
|
||||
const error = new N8nRateLimitError(60);
|
||||
const message = getUserFriendlyErrorMessage(error);
|
||||
|
||||
expect(message).toBe('Too many requests. Please wait a moment and try again.');
|
||||
});
|
||||
|
||||
it('should handle server error with custom message', () => {
|
||||
const error = new N8nServerError('Database connection failed', 503);
|
||||
const message = getUserFriendlyErrorMessage(error);
|
||||
|
||||
expect(message).toBe('Database connection failed');
|
||||
});
|
||||
|
||||
it('should handle server error without message', () => {
|
||||
const error = new N8nApiError('', 500, 'SERVER_ERROR');
|
||||
const message = getUserFriendlyErrorMessage(error);
|
||||
|
||||
expect(message).toBe('n8n server error occurred');
|
||||
});
|
||||
|
||||
it('should handle no response error', () => {
|
||||
const error = new N8nApiError('Network error', undefined, 'NO_RESPONSE');
|
||||
const message = getUserFriendlyErrorMessage(error);
|
||||
|
||||
expect(message).toBe('Unable to connect to n8n. Please check the server URL and ensure n8n is running.');
|
||||
});
|
||||
|
||||
it('should handle unknown error with message', () => {
|
||||
const error = new N8nApiError('Custom error message');
|
||||
const message = getUserFriendlyErrorMessage(error);
|
||||
|
||||
expect(message).toBe('Custom error message');
|
||||
});
|
||||
|
||||
it('should handle unknown error without message', () => {
|
||||
const error = new N8nApiError('');
|
||||
const message = getUserFriendlyErrorMessage(error);
|
||||
|
||||
expect(message).toBe('An unexpected error occurred');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error message integration', () => {
|
||||
it('should use formatExecutionError for webhook failures with execution ID', () => {
|
||||
const executionId = 'exec_webhook_123';
|
||||
const workflowId = 'wf_webhook_abc';
|
||||
const message = formatExecutionError(executionId, workflowId);
|
||||
|
||||
expect(message).toContain('Workflow wf_webhook_abc execution exec_webhook_123 failed');
|
||||
expect(message).toContain('n8n_get_execution');
|
||||
expect(message).toContain("mode: 'preview'");
|
||||
});
|
||||
|
||||
it('should use formatNoExecutionError for server errors without execution context', () => {
|
||||
const message = formatNoExecutionError();
|
||||
|
||||
expect(message).toContain('Workflow failed to execute');
|
||||
expect(message).toContain('n8n_list_executions');
|
||||
expect(message).toContain('n8n_get_execution');
|
||||
});
|
||||
|
||||
it('should not include "contact support" in any error message', () => {
|
||||
const executionMessage = formatExecutionError('test');
|
||||
const noExecutionMessage = formatNoExecutionError();
|
||||
const serverError = new N8nServerError();
|
||||
const serverErrorMessage = getUserFriendlyErrorMessage(serverError);
|
||||
|
||||
expect(executionMessage.toLowerCase()).not.toContain('contact support');
|
||||
expect(noExecutionMessage.toLowerCase()).not.toContain('contact support');
|
||||
expect(serverErrorMessage.toLowerCase()).not.toContain('contact support');
|
||||
});
|
||||
|
||||
it('should always guide users to use preview mode first', () => {
|
||||
const executionMessage = formatExecutionError('test');
|
||||
const noExecutionMessage = formatNoExecutionError();
|
||||
|
||||
expect(executionMessage).toContain("mode: 'preview'");
|
||||
expect(noExecutionMessage).toContain("mode='preview'");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user