feat(core): Add retry execution endpoint to public api (#19132)

Co-authored-by: Csaba Tuncsik <csaba.tuncsik@gmail.com>
Co-authored-by: Marc Littlemore <MarcL@users.noreply.github.com>
This commit is contained in:
Konstantin Tieber
2025-09-11 10:12:53 +02:00
committed by GitHub
parent b147709189
commit c4f41bb534
16 changed files with 229 additions and 20 deletions

View File

@@ -7,10 +7,8 @@ import {
testDb,
} from '@n8n/backend-test-utils';
import type { ExecutionEntity, User } from '@n8n/db';
import type { ExecutionStatus } from 'n8n-workflow';
import type { ActiveWorkflowManager } from '@/active-workflow-manager';
import { Telemetry } from '@/telemetry';
import { Container } from '@n8n/di';
import { UnexpectedError, type ExecutionStatus } from 'n8n-workflow';
import {
createdExecutionWithStatus,
@@ -23,6 +21,12 @@ import { createMemberWithApiKey, createOwnerWithApiKey } from '../shared/db/user
import type { SuperAgentTest } from '../shared/types';
import * as utils from '../shared/utils/';
import type { ActiveWorkflowManager } from '@/active-workflow-manager';
import { ExecutionService } from '@/executions/execution.service';
import { Telemetry } from '@/telemetry';
import { QueuedExecutionRetryError } from '@/errors/queued-execution-retry.error';
import { AbortedExecutionRetryError } from '@/errors/aborted-execution-retry.error';
let owner: User;
let user1: User;
let user2: User;
@@ -234,6 +238,107 @@ describe('DELETE /executions/:id', () => {
});
});
describe('POST /executions/:id/retry', () => {
test('should fail due to missing API Key', testWithAPIKey('post', '/executions/1/retry', null));
test(
'should fail due to invalid API Key',
testWithAPIKey('post', '/executions/1/retry', 'abcXYZ'),
);
test('should retry an execution', async () => {
const mockedExecutionResponse = { status: 'waiting' } as any;
const executionServiceSpy = jest
.spyOn(Container.get(ExecutionService), 'retry')
.mockResolvedValue(mockedExecutionResponse);
const workflow = await createWorkflow({}, user1);
const execution = await createSuccessfulExecution(workflow);
const response = await authUser1Agent.post(`/executions/${execution.id}/retry`);
expect(response.statusCode).toBe(200);
expect(response.body).toEqual(mockedExecutionResponse);
executionServiceSpy.mockRestore();
});
test('should return 404 when execution is not found', async () => {
const nonExistentExecutionId = 99999999;
const response = await authUser1Agent.post(`/executions/${nonExistentExecutionId}/retry`);
expect(response.statusCode).toBe(404);
expect(response.body.message).toBe('Not Found');
});
test('should return 409 when trying to retry a queued execution', async () => {
const executionServiceSpy = jest
.spyOn(Container.get(ExecutionService), 'retry')
.mockRejectedValue(new QueuedExecutionRetryError());
const workflow = await createWorkflow({}, user1);
const execution = await createExecution({ status: 'new', finished: false }, workflow);
const response = await authUser1Agent.post(`/executions/${execution.id}/retry`);
expect(response.statusCode).toBe(409);
expect(response.body.message).toBe(
'Execution is queued to run (not yet started) so it cannot be retried',
);
executionServiceSpy.mockRestore();
});
test('should return 409 when trying to retry an aborted execution without execution data', async () => {
const executionServiceSpy = jest
.spyOn(Container.get(ExecutionService), 'retry')
.mockRejectedValue(new AbortedExecutionRetryError());
const workflow = await createWorkflow({}, user1);
const execution = await createExecution(
{
status: 'error',
finished: false,
data: JSON.stringify({ executionData: null }),
},
workflow,
);
const response = await authUser1Agent.post(`/executions/${execution.id}/retry`);
expect(response.statusCode).toBe(409);
expect(response.body.message).toBe(
'The execution was aborted before starting, so it cannot be retried',
);
executionServiceSpy.mockRestore();
});
test('should return 400 when trying to retry a finished execution', async () => {
const executionServiceSpy = jest
.spyOn(Container.get(ExecutionService), 'retry')
.mockRejectedValue(new UnexpectedError('The execution succeeded, so it cannot be retried.'));
const workflow = await createWorkflow({}, user1);
const execution = await createExecution(
{
status: 'success',
finished: true,
data: {} as any,
},
workflow,
);
const response = await authUser1Agent.post(`/executions/${execution.id}/retry`);
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('The execution succeeded, so it cannot be retried.');
executionServiceSpy.mockRestore();
});
});
describe('GET /executions', () => {
test('should fail due to missing API Key', testWithAPIKey('get', '/executions', null));