From a39b8bd32be50c8323e415f820b25b4bcb81d960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Wed, 22 Jan 2025 09:00:17 +0100 Subject: [PATCH] fix(core): Recover successful data-less executions (#12720) --- .../execution-recovery.service.test.ts | 41 +++++++++++++++++-- .../executions/execution-recovery.service.ts | 2 +- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/executions/__tests__/execution-recovery.service.test.ts b/packages/cli/src/executions/__tests__/execution-recovery.service.test.ts index 9cb681a7ef21f..20abce70cd7bf 100644 --- a/packages/cli/src/executions/__tests__/execution-recovery.service.test.ts +++ b/packages/cli/src/executions/__tests__/execution-recovery.service.test.ts @@ -3,6 +3,7 @@ import { stringify } from 'flatted'; import { mock } from 'jest-mock-extended'; import { InstanceSettings } from 'n8n-core'; import { randomInt } from 'n8n-workflow'; +import assert from 'node:assert'; import { ARTIFICIAL_TASK_DATA } from '@/constants'; import { ExecutionRepository } from '@/databases/repositories/execution.repository'; @@ -127,12 +128,15 @@ describe('ExecutionRecoveryService', () => { }); describe('if leader, with 1+ messages', () => { - test('should return `null` if execution succeeded', async () => { + test('for successful dataful execution, should return `null`', async () => { /** * Arrange */ const workflow = await createWorkflow(); - const execution = await createExecution({ status: 'success' }, workflow); + const execution = await createExecution( + { status: 'success', data: stringify({ runData: { foo: 'bar' } }) }, + workflow, + ); const messages = setupMessages(execution.id, 'Some workflow'); /** @@ -170,7 +174,38 @@ describe('ExecutionRecoveryService', () => { expect(amendedExecution).toBeNull(); }); - test('should update `status`, `stoppedAt` and `data` if last node did not finish', async () => { + test('for successful dataless execution, should update `status`, `stoppedAt` and `data`', async () => { + /** + * Arrange + */ + const workflow = await createWorkflow(); + const execution = await createExecution( + { + status: 'success', + data: stringify(undefined), // saved execution but likely crashed while saving high-volume data + }, + workflow, + ); + const messages = setupMessages(execution.id, 'Some workflow'); + + /** + * Act + */ + const amendedExecution = await executionRecoveryService.recoverFromLogs( + execution.id, + messages, + ); + + /** + * Assert + */ + assert(amendedExecution); + expect(amendedExecution.stoppedAt).not.toBe(execution.stoppedAt); + expect(amendedExecution.data).toEqual({ resultData: { runData: {} } }); + expect(amendedExecution.status).toBe('crashed'); + }); + + test('for running execution, should update `status`, `stoppedAt` and `data` if last node did not finish', async () => { /** * Arrange */ diff --git a/packages/cli/src/executions/execution-recovery.service.ts b/packages/cli/src/executions/execution-recovery.service.ts index bb759eae2c588..f3cae9196794e 100644 --- a/packages/cli/src/executions/execution-recovery.service.ts +++ b/packages/cli/src/executions/execution-recovery.service.ts @@ -73,7 +73,7 @@ export class ExecutionRecoveryService { unflattenData: true, }); - if (!execution || execution.status === 'success') return null; + if (!execution || (execution.status === 'success' && execution.data)) return null; const runExecutionData = execution.data ?? { resultData: { runData: {} } };