mirror of
https://github.com/n8n-io/n8n.git
synced 2026-07-28 03:24:59 +02:00
fix(Email Trigger (IMAP) Node): Surface connection error details in activation errors (#32913)
This commit is contained in:
parent
be06f86d3d
commit
8fa4e8cc0f
|
|
@ -547,6 +547,36 @@ describe('ActiveWorkflowManager', () => {
|
|||
expect(executeErrorWorkflowSpy).toHaveBeenCalled();
|
||||
expect(addQueuedWorkflowActivationSpy).toHaveBeenCalledWith(activation, workflowData);
|
||||
});
|
||||
|
||||
test('wraps the cause in a WorkflowActivationError that surfaces the cause message in `description`', () => {
|
||||
const workflowData = mock<WorkflowEntity>({ id: 'wf-1', name: 'Test Workflow' });
|
||||
const additionalData = mock<IWorkflowExecuteAdditionalData>();
|
||||
const mode: WorkflowExecuteMode = 'trigger';
|
||||
const activation: WorkflowActivateMode = 'activate';
|
||||
const workflow = mock<Workflow>({ name: 'Test Workflow' });
|
||||
const node = mock<INode>({ name: 'Trigger Node' });
|
||||
const triggerError = new Error('IMAP connection closed unexpectedly');
|
||||
|
||||
const executeErrorWorkflowSpy = vi
|
||||
.spyOn(activeWorkflowManager, 'executeErrorWorkflow')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
const getTriggerFunctions = activeWorkflowManager.getExecuteTriggerFunctions(
|
||||
workflowData,
|
||||
additionalData,
|
||||
mode,
|
||||
activation,
|
||||
async () => workflowData,
|
||||
);
|
||||
const context = getTriggerFunctions(workflow, node, additionalData, mode, activation);
|
||||
|
||||
context.emitError(triggerError);
|
||||
|
||||
const wrappedError = executeErrorWorkflowSpy.mock.calls[0][0] as WorkflowActivationError;
|
||||
expect(wrappedError).toBeInstanceOf(WorkflowActivationError);
|
||||
expect(wrappedError.message).not.toBe(triggerError.message);
|
||||
expect(wrappedError.description).toBe('IMAP connection closed unexpectedly');
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveFailedExecution', () => {
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@ export class ActiveWorkflowManager {
|
|||
void this.activeWorkflowTriggers.remove(failedWorkflowData.id);
|
||||
void this.activationErrorsService.register(failedWorkflowData.id, error.message);
|
||||
const activationError = new WorkflowActivationError(
|
||||
`There was a problem with the trigger node "${node.name}", for that reason did the workflow had to be deactivated`,
|
||||
`The workflow was deactivated because its trigger node "${node.name}" failed`,
|
||||
{ cause: error, node },
|
||||
);
|
||||
this.executeErrorWorkflow(activationError, failedWorkflowData, failureMode);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { EventEmitter } from 'events';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import type { INode, INodeTypeBaseDescription, ITriggerFunctions, IDataObject } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { type ICredentialsDataImap } from '@credentials/Imap.credentials';
|
||||
|
||||
|
|
@ -99,10 +100,22 @@ describe('ECONNRESET error handling', () => {
|
|||
mockConnection.emit('close', false);
|
||||
|
||||
expect(triggerFunctions.emitError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ message: 'Imap connection closed unexpectedly' }),
|
||||
expect.objectContaining({ message: 'IMAP connection closed unexpectedly' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should emit a NodeOperationError with an actionable description on unexpected close', async () => {
|
||||
const node = new EmailReadImapV2(baseDescription);
|
||||
await node.trigger.call(triggerFunctions);
|
||||
|
||||
mockConnection.emit('close', false);
|
||||
|
||||
const emittedError = (triggerFunctions.emitError as Mock).mock
|
||||
.calls[0][0] as NodeOperationError;
|
||||
expect(emittedError).toBeInstanceOf(NodeOperationError);
|
||||
expect(emittedError.description).toContain('Force Reconnect');
|
||||
});
|
||||
|
||||
it('should call emitError only once when ECONNRESET is followed by close', async () => {
|
||||
const node = new EmailReadImapV2(baseDescription);
|
||||
await node.trigger.call(triggerFunctions);
|
||||
|
|
|
|||
|
|
@ -476,7 +476,12 @@ export class EmailReadImapV2 implements INodeType {
|
|||
this.logger.debug('Email Read Imap: Shutting down workflow - connected closed');
|
||||
} else if (!errorReported) {
|
||||
this.logger.error('Email Read Imap: Connected closed unexpectedly');
|
||||
this.emitError(new Error('Imap connection closed unexpectedly'));
|
||||
this.emitError(
|
||||
new NodeOperationError(this.getNode(), 'IMAP connection closed unexpectedly', {
|
||||
description:
|
||||
'The IMAP server closed the connection without reporting an error, usually because the server (or a proxy/firewall) periodically closes long-lived connections, or was temporarily unavailable. n8n will automatically retry reactivating the workflow. If this happens on a regular cycle, enable the "Force Reconnect" option with an interval shorter than that cycle, so n8n reconnects before the server does.',
|
||||
}),
|
||||
);
|
||||
}
|
||||
conn.removeAllListeners();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -32,6 +32,12 @@ export class WorkflowActivationError extends ExecutionBaseError {
|
|||
this.node = node;
|
||||
this.workflowId = workflowId;
|
||||
this.message = message;
|
||||
if (cause && !this.description) {
|
||||
this.description =
|
||||
'description' in cause && typeof cause.description === 'string'
|
||||
? cause.description
|
||||
: cause.message;
|
||||
}
|
||||
this.setLevel(level);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,4 +29,19 @@ describe('WorkflowActivationError', () => {
|
|||
|
||||
expect(error.level).toBe('warning');
|
||||
});
|
||||
|
||||
it('should surface the cause message in `description`', () => {
|
||||
const error = new WorkflowActivationError('Generic wrapper message', { cause });
|
||||
|
||||
expect(error.description).toBe('Some error message');
|
||||
});
|
||||
|
||||
it("should prefer the cause's own `description` over its message", () => {
|
||||
const richCause = new WorkflowActivationError('Cause message');
|
||||
richCause.description = 'Actionable detail';
|
||||
|
||||
const error = new WorkflowActivationError('Generic wrapper message', { cause: richCause });
|
||||
|
||||
expect(error.description).toBe('Actionable detail');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user