feat: Implement get execution steps API endpoint

This commit is contained in:
Faruk AYDIN
2024-03-06 17:22:09 +01:00
parent e40d6c5ef0
commit c4fd03542b
5 changed files with 227 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
import { renderObject } from '../../../../helpers/renderer.js';
import paginateRest from '../../../../helpers/pagination-rest.js';
export default async (request, response) => {
const execution = await request.currentUser.authorizedExecutions
.clone()
.withSoftDeleted()
.findById(request.params.executionId)
.throwIfNotFound();
const executionStepsQuery = execution
.$relatedQuery('executionSteps')
.withSoftDeleted()
.withGraphFetched('step')
.orderBy('created_at', 'asc');
const executionSteps = await paginateRest(
executionStepsQuery,
request.query.page
);
renderObject(response, executionSteps);
};

View File

@@ -0,0 +1,153 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createExecution } from '../../../../../test/factories/execution.js';
import { createExecutionStep } from '../../../../../test/factories/execution-step.js';
import { createPermission } from '../../../../../test/factories/permission';
import getExecutionStepsMock from '../../../../../test/mocks/rest/api/v1/executions/get-execution-steps';
describe('GET /api/v1/executions/:executionId/execution-steps', () => {
let currentUser, currentUserRole, anotherUser, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
anotherUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the execution steps of current user execution', async () => {
const currentUserFlow = await createFlow({
userId: currentUser.id,
});
const stepOne = await createStep({
flowId: currentUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: currentUserFlow.id,
type: 'action',
});
const currentUserExecution = await createExecution({
flowId: currentUserFlow.id,
});
const currentUserExecutionStepOne = await createExecutionStep({
executionId: currentUserExecution.id,
stepId: stepOne.id,
});
const currentUserExecutionStepTwo = await createExecutionStep({
executionId: currentUserExecution.id,
stepId: stepTwo.id,
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get(`/api/v1/executions/${currentUserExecution.id}/execution-steps`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionStepsMock(
[currentUserExecutionStepOne, currentUserExecutionStepTwo],
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return the execution steps of another user execution', async () => {
const anotherUserFlow = await createFlow({
userId: anotherUser.id,
});
const stepOne = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: anotherUserFlow.id,
type: 'action',
});
const anotherUserExecution = await createExecution({
flowId: anotherUserFlow.id,
});
const anotherUserExecutionStepOne = await createExecutionStep({
executionId: anotherUserExecution.id,
stepId: stepOne.id,
});
const anotherUserExecutionStepTwo = await createExecutionStep({
executionId: anotherUserExecution.id,
stepId: stepTwo.id,
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get(`/api/v1/executions/${anotherUserExecution.id}/execution-steps`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionStepsMock(
[anotherUserExecutionStepOne, anotherUserExecutionStepTwo],
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing execution step UUID', async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingExcecutionUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/executions/${notExistingExcecutionUUID}/execution-steps`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.get('/api/v1/executions/invalidExecutionUUID/execution-steps')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -19,6 +19,10 @@ const authorizationList = {
action: 'read',
subject: 'Execution',
},
'GET /api/v1/executions/:executionId/execution-steps': {
action: 'read',
subject: 'Execution',
},
};
export const authorizeUser = async (request, response, next) => {

View File

@@ -4,6 +4,7 @@ import { authenticateUser } from '../../../helpers/authentication.js';
import { authorizeUser } from '../../../helpers/authorization.js';
import getExecutionsAction from '../../../controllers/api/v1/executions/get-executions.js';
import getExecutionAction from '../../../controllers/api/v1/executions/get-execution.js';
import getExecutionStepsAction from '../../../controllers/api/v1/executions/get-execution-steps.js';
const router = Router();
@@ -21,4 +22,11 @@ router.get(
asyncHandler(getExecutionAction)
);
router.get(
'/:executionId/execution-steps',
authenticateUser,
authorizeUser,
asyncHandler(getExecutionStepsAction)
);
export default router;

View File

@@ -0,0 +1,39 @@
const getExecutionStepsMock = async (executionSteps, steps) => {
const data = executionSteps.map((executionStep) => {
const step = steps.find((step) => step.id === executionStep.stepId);
return {
id: executionStep.id,
dataIn: executionStep.dataIn,
dataOut: executionStep.dataOut,
errorDetails: executionStep.errorDetails,
status: executionStep.status,
createdAt: executionStep.createdAt.getTime(),
updatedAt: executionStep.updatedAt.getTime(),
step: {
id: step.id,
type: step.type,
key: step.key,
appKey: step.appKey,
iconUrl: step.iconUrl,
webhookUrl: step.webhookUrl,
status: step.status,
position: step.position,
parameters: step.parameters,
},
};
});
return {
data: data,
meta: {
count: executionSteps.length,
currentPage: 1,
isArray: true,
totalPages: 1,
type: 'ExecutionStep',
},
};
};
export default getExecutionStepsMock;