refactor(core): Add tests to workflowPostExecute in TelemetryEventRelay (#12437)

This commit is contained in:
Ricardo Espinoza 2025-01-03 06:21:25 -05:00 committed by GitHub
parent 1d3cb9f5ac
commit b78210c58a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,7 +1,8 @@
import type { GlobalConfig } from '@n8n/config';
import { mock } from 'jest-mock-extended';
import { InstanceSettings } from 'n8n-core';
import type { IWorkflowBase } from 'n8n-workflow';
import type { INode, INodesGraphResult } from 'n8n-workflow';
import { NodeApiError, TelemetryHelpers, type IRun, type IWorkflowBase } from 'n8n-workflow';
import { N8N_VERSION } from '@/constants';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
@ -28,6 +29,9 @@ describe('TelemetryEventRelay', () => {
mode: 'smtp',
},
},
diagnostics: {
enabled: true,
},
endpoints: {
metrics: {
enable: true,
@ -1106,4 +1110,393 @@ describe('TelemetryEventRelay', () => {
});
});
});
describe('workflow post execute events', () => {
beforeEach(() => {
jest.clearAllMocks();
});
const mockWorkflowBase = mock<IWorkflowBase>({
id: 'workflow123',
name: 'Test Workflow',
active: true,
nodes: [
{
id: 'node1',
name: 'Start',
type: 'n8n-nodes-base.start',
parameters: {},
typeVersion: 1,
position: [100, 200],
},
],
connections: {},
createdAt: new Date(),
updatedAt: new Date(),
staticData: {},
settings: {},
});
it('should not track when workflow has no id', async () => {
const event: RelayEventMap['workflow-post-execute'] = {
workflow: { ...mockWorkflowBase, id: '' },
executionId: 'execution123',
userId: 'user123',
};
eventService.emit('workflow-post-execute', event);
expect(telemetry.trackWorkflowExecution).not.toHaveBeenCalled();
});
it('should not track when execution status is "waiting"', async () => {
const event: RelayEventMap['workflow-post-execute'] = {
workflow: mockWorkflowBase,
executionId: 'execution123',
userId: 'user123',
runData: {
status: 'waiting',
data: { resultData: {} },
} as IRun,
};
eventService.emit('workflow-post-execute', event);
expect(telemetry.trackWorkflowExecution).not.toHaveBeenCalled();
});
it('should track successful workflow execution', async () => {
const runData = mock<IRun>({
finished: true,
status: 'success',
mode: 'manual',
data: { resultData: {} },
});
const event: RelayEventMap['workflow-post-execute'] = {
workflow: mockWorkflowBase,
executionId: 'execution123',
userId: 'user123',
runData: runData as unknown as IRun,
};
eventService.emit('workflow-post-execute', event);
await flushPromises();
expect(telemetry.trackWorkflowExecution).toHaveBeenCalledWith(
expect.objectContaining({
workflow_id: 'workflow123',
user_id: 'user123',
success: true,
is_manual: true,
execution_mode: 'manual',
}),
);
});
it('should call telemetry.track when manual node execution finished', async () => {
sharedWorkflowRepository.findSharingRole.mockResolvedValue('workflow:editor');
const runData = {
status: 'error',
mode: 'manual',
data: {
startData: {
destinationNode: 'OpenAI',
runNodeFilter: ['OpenAI'],
},
resultData: {
runData: {},
lastNodeExecuted: 'OpenAI',
error: new NodeApiError(
{
id: '1',
typeVersion: 1,
name: 'Jira',
type: 'n8n-nodes-base.jira',
parameters: {},
position: [100, 200],
},
{
message: 'Error message',
description: 'Incorrect API key provided',
httpCode: '401',
stack: '',
},
{
message: 'Error message',
description: 'Error description',
level: 'warning',
functionality: 'regular',
},
),
},
},
} as IRun;
const nodeGraph: INodesGraphResult = {
nodeGraph: { node_types: [], node_connections: [], webhookNodeNames: [] },
nameIndices: {
Jira: '1',
OpenAI: '1',
},
} as unknown as INodesGraphResult;
jest.spyOn(TelemetryHelpers, 'generateNodesGraph').mockImplementation(() => nodeGraph);
jest
.spyOn(TelemetryHelpers, 'getNodeTypeForName')
.mockImplementation(
() => ({ type: 'n8n-nodes-base.jira', version: 1, name: 'Jira' }) as unknown as INode,
);
const event: RelayEventMap['workflow-post-execute'] = {
workflow: mockWorkflowBase,
executionId: 'execution123',
userId: 'user123',
runData,
};
eventService.emit('workflow-post-execute', event);
await flushPromises();
expect(telemetry.track).toHaveBeenCalledWith(
'Manual node exec finished',
expect.objectContaining({
webhook_domain: null,
user_id: 'user123',
workflow_id: 'workflow123',
status: 'error',
executionStatus: 'error',
sharing_role: 'sharee',
error_message: 'Error message',
error_node_type: 'n8n-nodes-base.jira',
error_node_id: '1',
node_id: '1',
node_type: 'n8n-nodes-base.jira',
node_graph_string: JSON.stringify(nodeGraph.nodeGraph),
}),
);
expect(telemetry.trackWorkflowExecution).toHaveBeenCalledWith(
expect.objectContaining({
workflow_id: 'workflow123',
success: false,
is_manual: true,
execution_mode: 'manual',
version_cli: N8N_VERSION,
error_message: 'Error message',
error_node_type: 'n8n-nodes-base.jira',
node_graph_string: JSON.stringify(nodeGraph.nodeGraph),
error_node_id: '1',
}),
);
});
it('should call telemetry.track when manual node execution finished with canceled error message', async () => {
sharedWorkflowRepository.findSharingRole.mockResolvedValue('workflow:owner');
const runData = {
status: 'error',
mode: 'manual',
data: {
startData: {
destinationNode: 'OpenAI',
runNodeFilter: ['OpenAI'],
},
resultData: {
runData: {},
lastNodeExecuted: 'OpenAI',
error: new NodeApiError(
{
id: '1',
typeVersion: 1,
name: 'Jira',
type: 'n8n-nodes-base.jira',
parameters: {},
position: [100, 200],
},
{
message: 'Error message',
description: 'Incorrect API key provided',
httpCode: '401',
stack: '',
},
{
message: 'Error message canceled',
description: 'Error description',
level: 'warning',
functionality: 'regular',
},
),
},
},
} as IRun;
const nodeGraph: INodesGraphResult = {
nodeGraph: { node_types: [], node_connections: [] },
nameIndices: {
Jira: '1',
OpenAI: '1',
},
} as unknown as INodesGraphResult;
jest.spyOn(TelemetryHelpers, 'generateNodesGraph').mockImplementation(() => nodeGraph);
jest
.spyOn(TelemetryHelpers, 'getNodeTypeForName')
.mockImplementation(
() => ({ type: 'n8n-nodes-base.jira', version: 1, name: 'Jira' }) as unknown as INode,
);
const event: RelayEventMap['workflow-post-execute'] = {
workflow: mockWorkflowBase,
executionId: 'execution123',
userId: 'user123',
runData,
};
eventService.emit('workflow-post-execute', event);
await flushPromises();
expect(telemetry.track).toHaveBeenCalledWith(
'Manual node exec finished',
expect.objectContaining({
webhook_domain: null,
user_id: 'user123',
workflow_id: 'workflow123',
status: 'canceled',
executionStatus: 'canceled',
sharing_role: 'owner',
error_message: 'Error message canceled',
error_node_type: 'n8n-nodes-base.jira',
error_node_id: '1',
node_id: '1',
node_type: 'n8n-nodes-base.jira',
node_graph_string: JSON.stringify(nodeGraph.nodeGraph),
}),
);
expect(telemetry.trackWorkflowExecution).toHaveBeenCalledWith(
expect.objectContaining({
workflow_id: 'workflow123',
success: false,
is_manual: true,
execution_mode: 'manual',
version_cli: N8N_VERSION,
error_message: 'Error message canceled',
error_node_type: 'n8n-nodes-base.jira',
node_graph_string: JSON.stringify(nodeGraph.nodeGraph),
error_node_id: '1',
}),
);
});
it('should call telemetry.track when manual workflow execution finished', async () => {
sharedWorkflowRepository.findSharingRole.mockResolvedValue('workflow:owner');
const runData = {
status: 'error',
mode: 'manual',
data: {
startData: {
runNodeFilter: ['OpenAI'],
},
resultData: {
runData: {
Jira: [
{
data: { main: [[{ json: { headers: { origin: 'https://www.test.com' } } }]] },
},
],
},
lastNodeExecuted: 'OpenAI',
error: new NodeApiError(
{
id: '1',
typeVersion: 1,
name: 'Jira',
type: 'n8n-nodes-base.jira',
parameters: {},
position: [100, 200],
},
{
message: 'Error message',
description: 'Incorrect API key provided',
httpCode: '401',
stack: '',
},
{
message: 'Error message',
description: 'Error description',
level: 'warning',
functionality: 'regular',
},
),
},
},
} as unknown as IRun;
const nodeGraph: INodesGraphResult = {
webhookNodeNames: ['Jira'],
nodeGraph: { node_types: [], node_connections: [] },
nameIndices: {
Jira: '1',
OpenAI: '1',
},
} as unknown as INodesGraphResult;
jest.spyOn(TelemetryHelpers, 'generateNodesGraph').mockImplementation(() => nodeGraph);
jest
.spyOn(TelemetryHelpers, 'getNodeTypeForName')
.mockImplementation(
() => ({ type: 'n8n-nodes-base.jira', version: 1, name: 'Jira' }) as unknown as INode,
);
const event: RelayEventMap['workflow-post-execute'] = {
workflow: mockWorkflowBase,
executionId: 'execution123',
userId: 'user123',
runData,
};
eventService.emit('workflow-post-execute', event);
await flushPromises();
expect(telemetry.track).toHaveBeenCalledWith(
'Manual workflow exec finished',
expect.objectContaining({
webhook_domain: 'test.com',
user_id: 'user123',
workflow_id: 'workflow123',
status: 'error',
executionStatus: 'error',
sharing_role: 'owner',
error_message: 'Error message',
error_node_type: 'n8n-nodes-base.jira',
error_node_id: '1',
node_graph_string: JSON.stringify(nodeGraph.nodeGraph),
}),
);
expect(telemetry.trackWorkflowExecution).toHaveBeenCalledWith(
expect.objectContaining({
workflow_id: 'workflow123',
success: false,
is_manual: true,
execution_mode: 'manual',
version_cli: N8N_VERSION,
error_message: 'Error message',
error_node_type: 'n8n-nodes-base.jira',
node_graph_string: JSON.stringify(nodeGraph.nodeGraph),
error_node_id: '1',
}),
);
});
});
});