From 264861e020a393708db2e229aed5559ca4d1c307 Mon Sep 17 00:00:00 2001 From: Marie <51697796+ijreilly@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:38:38 +0200 Subject: [PATCH] [permissions V2] Add integration tests on relations and objectRecord permissions (#12450) In this PR 1. adding tests on relations and nested relations to make sure that if any permission is missing, the query fails 2. adding tests on objectRecord permissions to make sure that permissions granted or restricted by objectPermissions take precedence on the role's allObjectRecords permissions --- .../interfaces/base-resolver-service.ts | 4 + .../permissions/permissions.service.ts | 9 +- ...ct-records-permissions.integration-spec.ts | 196 ++++++++++++++ ...rmissions-on-relations.integration-spec.ts | 252 ++++++++++++++++++ .../granular-settings-permissions.ts | 27 +- ...ustom-role-with-object-permissions.util.ts | 131 +++++++++ .../graphql/utils/delete-one-role.util.ts | 10 + .../update-workspace-member-role.util.ts | 31 +++ 8 files changed, 635 insertions(+), 25 deletions(-) create mode 100644 packages/twenty-server/test/integration/graphql/suites/object-records-permissions/granular-object-records-permissions.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/graphql/suites/object-records-permissions/permissions-on-relations.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/graphql/utils/create-custom-role-with-object-permissions.util.ts create mode 100644 packages/twenty-server/test/integration/graphql/utils/delete-one-role.util.ts create mode 100644 packages/twenty-server/test/integration/graphql/utils/update-workspace-member-role.util.ts diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts index 5236f4f2a..e1dfd4e90 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts @@ -101,6 +101,7 @@ export abstract class GraphqlQueryBaseResolverService< } else { if (!isPermissionsV2Enabled) await this.validateObjectRecordPermissionsOrThrow({ + objectMetadataId: objectMetadataItemWithFieldMaps.id, operationName, options, }); @@ -219,9 +220,11 @@ export abstract class GraphqlQueryBaseResolverService< } private async validateObjectRecordPermissionsOrThrow({ + objectMetadataId, operationName, options, }: { + objectMetadataId: string; operationName: WorkspaceResolverBuilderMethodNames; options: WorkspaceQueryRunnerOptions; }) { @@ -234,6 +237,7 @@ export abstract class GraphqlQueryBaseResolverService< requiredPermission, workspaceId: options.authContext.workspace.id, isExecutedByApiKey: isDefined(options.authContext.apiKey), + objectMetadataId, }); if (!userHasPermission) { diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts index ee828db79..c17583473 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts @@ -226,11 +226,13 @@ export class PermissionsService { workspaceId, requiredPermission, isExecutedByApiKey, + objectMetadataId, }: { userWorkspaceId?: string; workspaceId: string; requiredPermission: PermissionsOnAllObjectRecords; isExecutedByApiKey: boolean; + objectMetadataId: string; }): Promise { const isPermissionsV2Enabled = await this.featureFlagService.isFeatureEnabled( @@ -279,11 +281,10 @@ export class PermissionsService { const objectPermissionKey = this.getObjectPermissionKeyForRequiredPermission(requiredPermission); - // until permissions V2 is enabled all objects have the same permission values deriving from role, ex role.canReadAllObjectRecords const objectPermissionValue = - rolePermissionsForUserWorkspaceRole[ - Object.keys(rolePermissionsForUserWorkspaceRole)[0] - ]?.[objectPermissionKey]; + rolePermissionsForUserWorkspaceRole[objectMetadataId]?.[ + objectPermissionKey + ]; return objectPermissionValue === true; } diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/granular-object-records-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/granular-object-records-permissions.integration-spec.ts new file mode 100644 index 000000000..0551ac679 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/granular-object-records-permissions.integration-spec.ts @@ -0,0 +1,196 @@ +import { default as request } from 'supertest'; +import { createCustomRoleWithObjectPermissions } from 'test/integration/graphql/utils/create-custom-role-with-object-permissions.util'; +import { deleteRole } from 'test/integration/graphql/utils/delete-one-role.util'; +import { findOneOperationFactory } from 'test/integration/graphql/utils/find-one-operation-factory.util'; +import { makeGraphqlAPIRequestWithMemberRole as makeGraphqlAPIRequestWithJony } from 'test/integration/graphql/utils/make-graphql-api-request-with-member-role.util'; +import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; +import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util'; +import { updateWorkspaceMemberRole } from 'test/integration/graphql/utils/update-workspace-member-role.util'; + +import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces'; +import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members'; +import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('granularObjectRecordsPermissions', () => { + describe('permissions V2 enabled', () => { + let originalMemberRoleId: string; + let customRoleId: string; + + beforeAll(async () => { + // Enable Permissions V2 + const enablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IS_PERMISSIONS_V2_ENABLED', + true, + ); + + await makeGraphqlAPIRequest(enablePermissionsQuery); + + // Get the original Member role ID for restoration later + const getRolesQuery = { + query: ` + query GetRoles { + getRoles { + id + label + } + } + `, + }; + + const rolesResponse = await client + .post('/graphql') + .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`) + .send(getRolesQuery); + + originalMemberRoleId = rolesResponse.body.data.getRoles.find( + (role: any) => role.label === 'Member', + ).id; + }); + + afterAll(async () => { + const restoreMemberRoleQuery = { + query: ` + mutation UpdateWorkspaceMemberRole { + updateWorkspaceMemberRole( + workspaceMemberId: "${DEV_SEED_WORKSPACE_MEMBER_IDS.JONY}" + roleId: "${originalMemberRoleId}" + ) { + id + } + } + `, + }; + + await client + .post('/graphql') + .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`) + .send(restoreMemberRoleQuery); + + // Disable Permissions V2 + const disablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IS_PERMISSIONS_V2_ENABLED', + false, + ); + + await makeGraphqlAPIRequest(disablePermissionsQuery); + }); + + afterEach(async () => { + await deleteRole(client, customRoleId); + }); + + it('should throw permission error when querying person while person reading rights are overriden to false', async () => { + // Arrange + const { roleId } = await createCustomRoleWithObjectPermissions({ + label: 'PersonReadRightsExcludedRole', + canReadPerson: false, + }); + + customRoleId = roleId; + + await updateWorkspaceMemberRole({ + client, + roleId: customRoleId, + workspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.JONY, + }); + + // Act + const graphqlOperation = findOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: ` + id + city + jobTitle + `, + filter: { city: { eq: 'Seattle' } }, + }); + + const companyGraphqlOperation = findOneOperationFactory({ + objectMetadataSingularName: 'company', + gqlFields: ` + id + name + `, + filter: { name: { eq: 'Apple' } }, + }); + + const personResponse = + await makeGraphqlAPIRequestWithJony(graphqlOperation); + + const companyResponse = await makeGraphqlAPIRequestWithJony( + companyGraphqlOperation, + ); + + // Assert + expect(personResponse.body.errors).toBeDefined(); + expect(personResponse.body.errors[0].message).toBe( + PermissionsExceptionMessage.PERMISSION_DENIED, + ); + expect(personResponse.body.errors[0].extensions.code).toBe( + ErrorCode.FORBIDDEN, + ); + expect(companyResponse.body.data).toBeDefined(); + expect(companyResponse.body.data.company).toBeDefined(); + }); + + it('should successfully query person when person reading rights are overriden to true', async () => { + // Arrange + const { roleId } = await createCustomRoleWithObjectPermissions({ + label: 'PersonRole', + canReadPerson: true, + hasAllObjectRecordsReadPermission: false, + }); + + customRoleId = roleId; + + await updateWorkspaceMemberRole({ + client, + roleId: customRoleId, + workspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.JONY, + }); + + // Act + const graphqlOperation = findOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: ` + id + city + jobTitle + `, + filter: { city: { eq: 'Seattle' } }, + }); + + const companyGraphqlOperation = findOneOperationFactory({ + objectMetadataSingularName: 'company', + gqlFields: ` + id + name + `, + filter: { name: { eq: 'Apple' } }, + }); + + const personResponse = + await makeGraphqlAPIRequestWithJony(graphqlOperation); + + const companyResponse = await makeGraphqlAPIRequestWithJony( + companyGraphqlOperation, + ); + + // Assert + expect(personResponse.body.data).toBeDefined(); + expect(personResponse.body.data.person).toBeDefined(); + expect(companyResponse.body.errors).toBeDefined(); + expect(companyResponse.body.errors[0].message).toBe( + PermissionsExceptionMessage.PERMISSION_DENIED, + ); + expect(companyResponse.body.errors[0].extensions.code).toBe( + ErrorCode.FORBIDDEN, + ); + }); + }); +}); diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/permissions-on-relations.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/permissions-on-relations.integration-spec.ts new file mode 100644 index 000000000..f7b4dc46d --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/permissions-on-relations.integration-spec.ts @@ -0,0 +1,252 @@ +import { randomUUID } from 'crypto'; + +import { default as request } from 'supertest'; +import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants'; +import { createCustomRoleWithObjectPermissions } from 'test/integration/graphql/utils/create-custom-role-with-object-permissions.util'; +import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util'; +import { deleteRole } from 'test/integration/graphql/utils/delete-one-role.util'; +import { findManyOperationFactory } from 'test/integration/graphql/utils/find-many-operation-factory.util'; +import { makeGraphqlAPIRequestWithMemberRole as makeGraphqlAPIRequestWithJony } from 'test/integration/graphql/utils/make-graphql-api-request-with-member-role.util'; +import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; +import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util'; +import { updateWorkspaceMemberRole } from 'test/integration/graphql/utils/update-workspace-member-role.util'; + +import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces'; +import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members'; +import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('permissionsOnRelations', () => { + describe('permissions V2 enabled', () => { + let originalMemberRoleId: string; + let customRoleId: string; + + beforeAll(async () => { + // Enable Permissions V2 + const enablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IS_PERMISSIONS_V2_ENABLED', + true, + ); + + await makeGraphqlAPIRequest(enablePermissionsQuery); + + // Get the original Member role ID for restoration later + const getRolesQuery = { + query: ` + query GetRoles { + getRoles { + id + label + } + } + `, + }; + + const rolesResponse = await client + .post('/graphql') + .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`) + .send(getRolesQuery); + + originalMemberRoleId = rolesResponse.body.data.getRoles.find( + (role: any) => role.label === 'Member', + ).id; + + // Create a person record + const companyId = randomUUID(); + const graphqlOperationForCompanyCreation = createOneOperationFactory({ + objectMetadataSingularName: 'company', + gqlFields: ` + name + `, + data: { + id: companyId, + name: 'Twenty', + }, + }); + + await makeGraphqlAPIRequest(graphqlOperationForCompanyCreation); + + const graphqlOperationForPersonCreation = createOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + data: { + id: randomUUID(), + name: { + firstName: 'Marie', + }, + city: 'Paris', + companyId, + }, + }); + + await makeGraphqlAPIRequest(graphqlOperationForPersonCreation); + }); + + afterAll(async () => { + const restoreMemberRoleQuery = { + query: ` + mutation UpdateWorkspaceMemberRole { + updateWorkspaceMemberRole( + workspaceMemberId: "${DEV_SEED_WORKSPACE_MEMBER_IDS.JONY}" + roleId: "${originalMemberRoleId}" + ) { + id + } + } + `, + }; + + await client + .post('/graphql') + .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`) + .send(restoreMemberRoleQuery); + + // Disable Permissions V2 + const disablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IS_PERMISSIONS_V2_ENABLED', + false, + ); + + await makeGraphqlAPIRequest(disablePermissionsQuery); + }); + + afterEach(async () => { + await deleteRole(client, customRoleId); + }); + + it('should throw permission error when querying person with company relation without company read permission', async () => { + // Create a role with person read permission but no company read permission + const { roleId } = await createCustomRoleWithObjectPermissions({ + label: 'PersonOnlyRole', + canReadPerson: true, + canReadCompany: false, + }); + + customRoleId = roleId; + + await updateWorkspaceMemberRole({ + client, + roleId: customRoleId, + workspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.JONY, + }); + + // Create GraphQL query that includes company relation + const graphqlOperation = findManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: ` + id + city + jobTitle + company { + id + name + } + `, + }); + + const response = await makeGraphqlAPIRequestWithJony(graphqlOperation); + + // The query should fail when trying to access company relation without permission + expect(response.body.errors[0].message).toBe( + PermissionsExceptionMessage.PERMISSION_DENIED, + ); + expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN); + }); + + it('should successfully query person with company relation when having both permissions', async () => { + // Create a role with both person and company read permissions + const { roleId } = await createCustomRoleWithObjectPermissions({ + label: 'PersonAndCompanyRole', + canReadPerson: true, + canReadCompany: true, + }); + + customRoleId = roleId; + + await updateWorkspaceMemberRole({ + client, + roleId: customRoleId, + workspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.JONY, + }); + + // Create GraphQL query that includes company relation + const graphqlOperation = findManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: ` + id + city + jobTitle + company { + id + name + } + `, + }); + + const response = await makeGraphqlAPIRequestWithJony(graphqlOperation); + + // The query should succeed + expect(response.body.data).toBeDefined(); + expect(response.body.data.people).toBeDefined(); + const person = response.body.data.people.edges[0].node; + + expect(person.company).toBeDefined(); + expect(response.body.error).toBeUndefined(); + }); + + it('nested relations - should throw permission error when querying nested opportunity relation without opportunity read permission', async () => { + // Where user has person and company read permissions but not opportunity read permission + + const { roleId } = await createCustomRoleWithObjectPermissions({ + label: 'PersonCompanyOnlyRole', + canReadPerson: true, + canReadCompany: true, + canReadOpportunities: false, + }); + + customRoleId = roleId; + + await updateWorkspaceMemberRole({ + client, + roleId: customRoleId, + workspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.JONY, + }); + + // Create a query with nested relations + const graphqlOperation = findManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: ` + id + city + jobTitle + company { + id + name + opportunities { + edges { + node { + name + } + } + } + } + `, + }); + + const response = await makeGraphqlAPIRequestWithJony(graphqlOperation); + + expect(response.body.errors).toBeDefined(); + expect(response.body.errors[0].message).toBe( + PermissionsExceptionMessage.PERMISSION_DENIED, + ); + expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN); + }); + }); +}); diff --git a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.ts b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.ts index 33ae79e69..a1e3646e2 100644 --- a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.ts +++ b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.ts @@ -3,6 +3,7 @@ import request from 'supertest'; import { deleteOneRoleOperationFactory } from 'test/integration/graphql/utils/delete-one-role-operation-factory.util'; import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util'; +import { updateWorkspaceMemberRole } from 'test/integration/graphql/utils/update-workspace-member-role.util'; import { createOneObjectMetadataQueryFactory } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-query-factory.util'; import { deleteOneObjectMetadataQueryFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-query-factory.util'; @@ -99,27 +100,11 @@ describe('Granular settings permissions', () => { .send(upsertSettingPermissionsQuery); // Assign the custom role to JONY (who uses MEMBER_ACCESS_TOKEN) - const updateMemberRoleQuery = { - query: ` - mutation UpdateWorkspaceMemberRole { - updateWorkspaceMemberRole( - workspaceMemberId: "${DEV_SEED_WORKSPACE_MEMBER_IDS.JONY}" - roleId: "${customRoleId}" - ) { - id - roles { - id - label - } - } - } - `, - }; - - await client - .post('/graphql') - .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`) - .send(updateMemberRoleQuery); + await updateWorkspaceMemberRole({ + client, + roleId: customRoleId, + workspaceMemberId: DEV_SEED_WORKSPACE_MEMBER_IDS.JONY, + }); }); afterAll(async () => { diff --git a/packages/twenty-server/test/integration/graphql/utils/create-custom-role-with-object-permissions.util.ts b/packages/twenty-server/test/integration/graphql/utils/create-custom-role-with-object-permissions.util.ts new file mode 100644 index 000000000..55abd71f3 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/utils/create-custom-role-with-object-permissions.util.ts @@ -0,0 +1,131 @@ +import gql from 'graphql-tag'; +import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; +import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; + +export const createCustomRoleWithObjectPermissions = async (options: { + label: string; + canReadPerson?: boolean; + canReadCompany?: boolean; + canReadOpportunities?: boolean; + hasAllObjectRecordsReadPermission?: boolean; +}) => { + const createRoleOperation = { + query: gql` + mutation CreateOneRole { + createOneRole(createRoleInput: { + label: "${options.label}" + description: "Test role for permission testing" + canUpdateAllSettings: ${options.hasAllObjectRecordsReadPermission ?? true} + canReadAllObjectRecords: ${options.hasAllObjectRecordsReadPermission ?? true} + canUpdateAllObjectRecords: ${options.hasAllObjectRecordsReadPermission ?? true} + canSoftDeleteAllObjectRecords: ${options.hasAllObjectRecordsReadPermission ?? true} + canDestroyAllObjectRecords: ${options.hasAllObjectRecordsReadPermission ?? true} + }) { + id + label + } + } + `, + }; + + const response = await makeGraphqlAPIRequest(createRoleOperation); + const roleId = response.body.data.createOneRole.id; + + // Get object metadata IDs for Person and Company + const getObjectMetadataOperation = { + query: gql` + query { + objects(paging: { first: 1000 }) { + edges { + node { + id + nameSingular + } + } + } + } + `, + }; + + const objectMetadataResponse = await makeMetadataAPIRequest( + getObjectMetadataOperation, + ); + const objects = objectMetadataResponse.body.data.objects.edges; + + const personObjectId = objects.find( + (obj: any) => obj.node.nameSingular === 'person', + )?.node.id; + const companyObjectId = objects.find( + (obj: any) => obj.node.nameSingular === 'company', + )?.node.id; + const opportunityObjectId = objects.find( + (obj: any) => obj.node.nameSingular === 'opportunity', + )?.node.id; + + // Create object permissions based on the options + const objectPermissions = []; + + if (options.canReadPerson !== undefined) { + objectPermissions.push({ + objectMetadataId: personObjectId, + canReadObjectRecords: options.canReadPerson, + canUpdateObjectRecords: false, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + }); + } + + if (options.canReadCompany !== undefined) { + objectPermissions.push({ + objectMetadataId: companyObjectId, + canReadObjectRecords: options.canReadCompany, + canUpdateObjectRecords: false, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + }); + } + + if (options.canReadOpportunities !== undefined) { + objectPermissions.push({ + objectMetadataId: opportunityObjectId, + canReadObjectRecords: options.canReadOpportunities, + canUpdateObjectRecords: false, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + }); + } + + if (objectPermissions.length > 0) { + const upsertObjectPermissionsOperation = { + query: gql` + mutation UpsertObjectPermissions( + $roleId: String! + $objectPermissions: [ObjectPermissionInput!]! + ) { + upsertObjectPermissions( + upsertObjectPermissionsInput: { + roleId: $roleId + objectPermissions: $objectPermissions + } + ) { + objectMetadataId + canReadObjectRecords + } + } + `, + variables: { + roleId, + objectPermissions, + }, + }; + + await makeGraphqlAPIRequest(upsertObjectPermissionsOperation); + } + + return { + roleId, + personObjectId, + companyObjectId, + opportunityObjectId, + }; +}; diff --git a/packages/twenty-server/test/integration/graphql/utils/delete-one-role.util.ts b/packages/twenty-server/test/integration/graphql/utils/delete-one-role.util.ts new file mode 100644 index 000000000..2dbd4c844 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/utils/delete-one-role.util.ts @@ -0,0 +1,10 @@ +import { deleteOneRoleOperationFactory } from 'test/integration/graphql/utils/delete-one-role-operation-factory.util'; + +export const deleteRole = async (client: any, roleId: string) => { + const deleteRoleQuery = deleteOneRoleOperationFactory(roleId); + + await client + .post('/graphql') + .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`) + .send(deleteRoleQuery); +}; diff --git a/packages/twenty-server/test/integration/graphql/utils/update-workspace-member-role.util.ts b/packages/twenty-server/test/integration/graphql/utils/update-workspace-member-role.util.ts new file mode 100644 index 000000000..aa1652c1d --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/utils/update-workspace-member-role.util.ts @@ -0,0 +1,31 @@ +export const updateWorkspaceMemberRole = async ({ + client, + roleId, + workspaceMemberId, +}: { + client: any; + roleId: string; + workspaceMemberId: string; +}) => { + const updateMemberRoleQuery = { + query: ` + mutation UpdateWorkspaceMemberRole { + updateWorkspaceMemberRole( + workspaceMemberId: "${workspaceMemberId}" + roleId: "${roleId}" + ) { + id + roles { + id + label + } + } + } + `, + }; + + await client + .post('/graphql') + .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`) + .send(updateMemberRoleQuery); +};