From 83b72b9283167dfd56ad5325fda5dbff12274e37 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 24 Apr 2025 15:45:32 +0000 Subject: [PATCH] feat(api): add get execution endpoint --- .../api/v1/executions/get-execution.ee.js | 16 +++++ .../v1/executions/get-execution.ee.test.js | 66 +++++++++++++++++++ .../src/routes/api/v1/executions.ee.js | 2 + .../rest/api/v1/executions/get-execution.js | 42 ++++++++++++ packages/backend/vitest.config.js | 2 +- 5 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 packages/backend/src/controllers/api/v1/executions/get-execution.ee.js create mode 100644 packages/backend/src/controllers/api/v1/executions/get-execution.ee.test.js create mode 100644 packages/backend/test/mocks/rest/api/v1/executions/get-execution.js diff --git a/packages/backend/src/controllers/api/v1/executions/get-execution.ee.js b/packages/backend/src/controllers/api/v1/executions/get-execution.ee.js new file mode 100644 index 00000000..1d0ed881 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/executions/get-execution.ee.js @@ -0,0 +1,16 @@ +import Execution from '../../../../models/execution.js'; +import { renderObject } from '../../../../helpers/renderer.js'; + +export default async (request, response) => { + const execution = await Execution.query() + .withGraphFetched({ + flow: { + steps: true, + }, + }) + .withSoftDeleted() + .findById(request.params.executionId) + .throwIfNotFound(); + + renderObject(response, execution); +}; diff --git a/packages/backend/src/controllers/api/v1/executions/get-execution.ee.test.js b/packages/backend/src/controllers/api/v1/executions/get-execution.ee.test.js new file mode 100644 index 00000000..468f9c70 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/executions/get-execution.ee.test.js @@ -0,0 +1,66 @@ +import Crypto from 'node:crypto'; +import request from 'supertest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { createApiToken } from '../../../../../test/factories/api-token.js'; +import { createExecution } from '../../../../../test/factories/execution.js'; +import { createFlow } from '../../../../../test/factories/flow.js'; +import { createStep } from '../../../../../test/factories/step.js'; +import getExecutionMock from '../../../../../test/mocks/rest/api/v1/executions/get-execution.js'; +import app from '../../../../app.js'; +import * as license from '../../../../helpers/license.ee.js'; + +describe('GET /api/v1/executions/:executionId', () => { + let token; + + beforeEach(async () => { + vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); + + token = (await createApiToken()).token; + }); + + it('should return the execution data', async () => { + const flow = await createFlow(); + + const stepOne = await createStep({ + flowId: flow.id, + type: 'trigger', + }); + + const stepTwo = await createStep({ + flowId: flow.id, + type: 'action', + }); + + const execution = await createExecution({ + flowId: flow.id, + }); + + const response = await request(app) + .get(`/api/v1/executions/${execution.id}`) + .set('x-api-token', token) + .expect(200); + + const expectedPayload = await getExecutionMock(execution, flow, [ + stepOne, + stepTwo, + ]); + + expect(response.body).toStrictEqual(expectedPayload); + }); + + it('should return not found response for not existing execution UUID', async () => { + const notExistingExcecutionUUID = Crypto.randomUUID(); + + await request(app) + .get(`/api/v1/executions/${notExistingExcecutionUUID}`) + .set('x-api-token', token) + .expect(404); + }); + + it('should return bad request response for invalid UUID', async () => { + await request(app) + .get('/api/v1/executions/invalidExecutionUUID') + .set('x-api-token', token) + .expect(400); + }); +}); diff --git a/packages/backend/src/routes/api/v1/executions.ee.js b/packages/backend/src/routes/api/v1/executions.ee.js index 2b443f11..e9e8a5a4 100644 --- a/packages/backend/src/routes/api/v1/executions.ee.js +++ b/packages/backend/src/routes/api/v1/executions.ee.js @@ -1,8 +1,10 @@ import { Router } from 'express'; import getExecutionsAction from '../../../controllers/api/v1/executions/get-executions.ee.js'; +import getExecutionAction from '../../../controllers/api/v1/executions/get-execution.ee.js'; const router = Router(); router.get('/', getExecutionsAction); +router.get('/:executionId', getExecutionAction); export default router; diff --git a/packages/backend/test/mocks/rest/api/v1/executions/get-execution.js b/packages/backend/test/mocks/rest/api/v1/executions/get-execution.js new file mode 100644 index 00000000..667b8940 --- /dev/null +++ b/packages/backend/test/mocks/rest/api/v1/executions/get-execution.js @@ -0,0 +1,42 @@ +const getExecutionMock = async (execution, flow, steps) => { + const data = { + id: execution.id, + testRun: execution.testRun, + status: execution.status, + createdAt: execution.createdAt.getTime(), + updatedAt: execution.updatedAt.getTime(), + flow: { + id: flow.id, + name: flow.name, + active: flow.active, + status: flow.active ? 'published' : 'draft', + createdAt: flow.createdAt.getTime(), + updatedAt: flow.updatedAt.getTime(), + steps: steps.map((step) => ({ + id: step.id, + type: step.type, + key: step.key, + name: step.name, + appKey: step.appKey, + iconUrl: step.iconUrl, + webhookUrl: step.webhookUrl, + status: step.status, + position: step.position, + parameters: step.parameters, + })), + }, + }; + + return { + data: data, + meta: { + count: 1, + currentPage: null, + isArray: false, + totalPages: null, + type: 'Execution', + }, + }; +}; + +export default getExecutionMock; diff --git a/packages/backend/vitest.config.js b/packages/backend/vitest.config.js index ccde8be0..a7b0d9a9 100644 --- a/packages/backend/vitest.config.js +++ b/packages/backend/vitest.config.js @@ -29,7 +29,7 @@ export default defineConfig({ thresholds: { autoUpdate: true, statements: 99.4, - branches: 98.31, + branches: 98.32, functions: 99.05, lines: 99.4, },