diff --git a/packages/backend/src/graphql/queries/get-executions.test.ts b/packages/backend/src/graphql/queries/get-executions.test.ts new file mode 100644 index 00000000..8542eb8b --- /dev/null +++ b/packages/backend/src/graphql/queries/get-executions.test.ts @@ -0,0 +1,277 @@ +import request, { Test } from 'supertest'; +import app from '../../app'; +import appConfig from '../../config/app'; +import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id'; +import { createRole } from '../../../test/factories/role'; +import { createPermission } from '../../../test/factories/permission'; +import { createUser } from '../../../test/factories/user'; +import { createFlow } from '../../../test/factories/flow'; +import { createStep } from '../../../test/factories/step'; +import { createExecution } from '../../../test/factories/execution'; +import { createExecutionStep } from '../../../test/factories/execution-step'; +import { + IRole, + IUser, + IExecution, + IFlow, + IExecutionStep, + IStep, +} from '@automatisch/types'; + +describe('graphQL getExecutions query', () => { + const query = ` + query { + getExecutions(limit: 10, offset: 0) { + pageInfo { + currentPage + totalPages + } + edges { + node { + id + testRun + createdAt + updatedAt + status + flow { + id + name + active + steps { + iconUrl + } + } + } + } + } + } + `; + + const invalidToken = 'invalid-token'; + + describe('with unauthenticated user', () => { + it('should throw not authorized error', async () => { + const response = await request(app) + .post('/graphql') + .set('Authorization', invalidToken) + .send({ query }) + .expect(200); + + expect(response.body.errors).toBeDefined(); + expect(response.body.errors[0].message).toEqual('Not Authorised!'); + }); + }); + + describe('with authenticated user', () => { + describe('and without permissions', () => { + it('should throw not authorized error', async () => { + const userWithoutPermissions = await createUser(); + const token = createAuthTokenByUserId(userWithoutPermissions.id); + + const response = await request(app) + .post('/graphql') + .set('Authorization', token) + .send({ query }) + .expect(200); + + expect(response.body.errors).toBeDefined(); + expect(response.body.errors[0].message).toEqual('Not authorized!'); + }); + }); + + describe('and with correct permission and isCreator condition', () => { + let role: IRole, + currentUser: IUser, + anotherUser: IUser, + token: string, + requestObject: Test, + flowOne: IFlow, + stepOneForFlowOne: IStep, + stepTwoForFlowOne: IStep, + executionOne: IExecution, + executionStepOneForExecutionOne: IExecutionStep, + executionStepTwoForExecutionOne: IExecutionStep, + flowTwo: IFlow, + stepOneForFlowTwo: IStep, + stepTwoForFlowTwo: IStep, + executionTwo: IExecution, + executionStepOneForExecutionTwo: IExecutionStep, + executionStepTwoForExecutionTwo: IExecutionStep; + + beforeEach(async () => { + role = await createRole({ + key: 'sample', + name: 'sample', + }); + + await createPermission({ + action: 'read', + subject: 'Execution', + roleId: role.id, + conditions: ['isCreator'], + }); + + currentUser = await createUser({ + roleId: role.id, + fullName: 'Current User', + }); + + anotherUser = await createUser(); + + token = createAuthTokenByUserId(currentUser.id); + + flowOne = await createFlow({ + userId: currentUser.id, + }); + + stepOneForFlowOne = await createStep({ + flowId: flowOne.id, + }); + + stepTwoForFlowOne = await createStep({ + flowId: flowOne.id, + }); + + executionOne = await createExecution({ + flowId: flowOne.id, + }); + + executionStepOneForExecutionOne = await createExecutionStep({ + executionId: executionOne.id, + stepId: stepOneForFlowOne.id, + status: 'success', + }); + + executionStepTwoForExecutionOne = await createExecutionStep({ + executionId: executionOne.id, + stepId: stepTwoForFlowOne.id, + status: 'success', + }); + + flowTwo = await createFlow({ + userId: currentUser.id, + }); + + stepOneForFlowTwo = await createStep({ + flowId: flowTwo.id, + }); + + stepTwoForFlowTwo = await createStep({ + flowId: flowTwo.id, + }); + + executionTwo = await createExecution({ + flowId: flowTwo.id, + }); + + executionStepOneForExecutionTwo = await createExecutionStep({ + executionId: executionTwo.id, + stepId: stepOneForFlowTwo.id, + status: 'success', + }); + + executionStepTwoForExecutionTwo = await createExecutionStep({ + executionId: executionTwo.id, + stepId: stepTwoForFlowTwo.id, + status: 'failure', + }); + }); + + it('should return executions data of the current user', async () => { + const response = await request(app) + .post('/graphql') + .set('Authorization', token) + .send({ query }) + .expect(200); + + const expectedResponsePayload = { + data: { + getExecutions: { + edges: [ + { + node: { + createdAt: (flowTwo.createdAt as Date).getTime().toString(), + flow: { + active: flowTwo.active, + id: flowTwo.id, + name: flowTwo.name, + steps: [ + { + iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`, + }, + { + iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`, + }, + ], + }, + id: executionTwo.id, + status: 'failure', + testRun: executionTwo.testRun, + updatedAt: (executionTwo.updatedAt as Date) + .getTime() + .toString(), + }, + }, + { + node: { + createdAt: (flowOne.createdAt as Date).getTime().toString(), + flow: { + active: flowOne.active, + id: flowOne.id, + name: flowOne.name, + steps: [ + { + iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowOne.appKey}/assets/favicon.svg`, + }, + { + iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowOne.appKey}/assets/favicon.svg`, + }, + ], + }, + id: executionOne.id, + status: 'success', + testRun: executionOne.testRun, + updatedAt: (executionOne.updatedAt as Date) + .getTime() + .toString(), + }, + }, + ], + pageInfo: { currentPage: 1, totalPages: 1 }, + }, + }, + }; + + expect(response.body).toEqual(expectedResponsePayload); + }); + + // it('should not return users data with password', async () => { + // const query = ` + // query { + // getUsers(limit: 10, offset: 0) { + // pageInfo { + // currentPage + // totalPages + // } + // totalCount + // edges { + // node { + // id + // fullName + // password + // } + // } + // } + // } + // `; + + // const response = await requestObject.send({ query }).expect(400); + + // expect(response.body.errors).toBeDefined(); + // expect(response.body.errors[0].message).toEqual( + // 'Cannot query field "password" on type "User".' + // ); + // }); + }); + }); +}); diff --git a/packages/backend/src/graphql/queries/get-executions.ts b/packages/backend/src/graphql/queries/get-executions.ts index 9ea50144..80504722 100644 --- a/packages/backend/src/graphql/queries/get-executions.ts +++ b/packages/backend/src/graphql/queries/get-executions.ts @@ -11,7 +11,7 @@ type Filters = { from?: string; to?: string; }; -} +}; type Params = { limit: number; @@ -30,7 +30,9 @@ const getExecutions = async ( const userExecutions = context.currentUser.$relatedQuery('executions'); const allExecutions = Execution.query(); - const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions; + const executionBaseQuery = conditions.isCreator + ? userExecutions + : allExecutions; const selectStatusStatement = ` case @@ -48,8 +50,7 @@ const getExecutions = async ( .groupBy('executions.id') .orderBy('updated_at', 'desc'); - const computedExecutions = Execution - .query() + const computedExecutions = Execution.query() .with('executions', executions) .withSoftDeleted() .withGraphFetched({ @@ -69,20 +70,16 @@ const getExecutions = async ( if (filters?.updatedAt) { const updatedAtFilter = filters.updatedAt; if (updatedAtFilter.from) { - const isoFromDateTime = DateTime - .fromMillis( - parseInt(updatedAtFilter.from, 10) - ) - .toISO(); + const isoFromDateTime = DateTime.fromMillis( + parseInt(updatedAtFilter.from, 10) + ).toISO(); computedExecutions.where('executions.updated_at', '>=', isoFromDateTime); } if (updatedAtFilter.to) { - const isoToDateTime = DateTime - .fromMillis( - parseInt(updatedAtFilter.to, 10) - ) - .toISO(); + const isoToDateTime = DateTime.fromMillis( + parseInt(updatedAtFilter.to, 10) + ).toISO(); computedExecutions.where('executions.updated_at', '<=', isoToDateTime); } } diff --git a/packages/backend/test/factories/permission.ts b/packages/backend/test/factories/permission.ts index d82d96f6..aae670ea 100644 --- a/packages/backend/test/factories/permission.ts +++ b/packages/backend/test/factories/permission.ts @@ -1,24 +1,15 @@ -import { IPermission } from '@automatisch/types'; +import Permission from '../../src/models/permission'; import { createRole } from './role'; -type PermissionParams = { - roleId?: string; - action?: string; - subject?: string; -}; - -export const createPermission = async ( - params: PermissionParams = {} -): Promise => { - const permissionData = { - roleId: params?.roleId || (await createRole()).id, - action: params?.action || 'read', - subject: params?.subject || 'User', - }; +export const createPermission = async (params: Partial = {}) => { + params.roleId = params?.roleId || (await createRole()).id; + params.action = params?.action || 'read'; + params.subject = params?.subject || 'User'; + params.conditions = params?.conditions || ['isCreator']; const [permission] = await global.knex .table('permissions') - .insert(permissionData) + .insert(params) .returning('*'); return permission; diff --git a/packages/backend/test/factories/step.ts b/packages/backend/test/factories/step.ts index 409adda2..baaaf090 100644 --- a/packages/backend/test/factories/step.ts +++ b/packages/backend/test/factories/step.ts @@ -13,7 +13,9 @@ export const createStep = async (params: Partial = {}) => { .first(); params.position = params?.position || (lastStep?.position || 0) + 1; - params.status = params?.status || 'incomplete'; + params.status = params?.status || 'completed'; + params.appKey = + params?.appKey || (params.type === 'action' ? 'webhook' : 'deepl'); const [step] = await global.knex.table('steps').insert(params).returning('*'); diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts index e8a70979..2e4e97d7 100644 --- a/packages/types/index.d.ts +++ b/packages/types/index.d.ts @@ -51,8 +51,8 @@ export interface IExecution { testRun: boolean; status: 'success' | 'failure'; executionSteps: IExecutionStep[]; - updatedAt: string; - createdAt: string; + updatedAt: string | Date; + createdAt: string | Date; } export interface IStep { @@ -83,8 +83,8 @@ export interface IFlow { active: boolean; status: 'paused' | 'published' | 'draft'; steps: IStep[]; - createdAt: string; - updatedAt: string; + createdAt: string | Date; + updatedAt: string | Date; remoteWebhookId: string; lastInternalId: () => Promise; }