diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/permissions.util.ts b/packages/twenty-server/src/engine/twenty-orm/repository/permissions.util.ts index 65bd23c66..b2969bfb9 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/permissions.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/permissions.util.ts @@ -69,6 +69,7 @@ export const validateQueryIsPermittedOrThrow = ( ); } break; + case 'restore': case 'soft-delete': if (!permissionsForEntity?.canSoftDelete) { throw new PermissionsException( diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts new file mode 100644 index 000000000..a0c83f193 --- /dev/null +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts @@ -0,0 +1,84 @@ +import { ObjectRecordsPermissions } from 'twenty-shared/types'; +import { + DeleteQueryBuilder, + DeleteResult, + InsertQueryBuilder, + ObjectLiteral, +} from 'typeorm'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; + +import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; + +import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util'; +import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; +import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; +import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder'; + +export class WorkspaceDeleteQueryBuilder< + T extends ObjectLiteral, +> extends DeleteQueryBuilder { + private objectRecordsPermissions: ObjectRecordsPermissions; + private shouldBypassPermissionChecks: boolean; + private internalContext: WorkspaceInternalContext; + constructor( + queryBuilder: DeleteQueryBuilder, + objectRecordsPermissions: ObjectRecordsPermissions, + internalContext: WorkspaceInternalContext, + shouldBypassPermissionChecks: boolean, + ) { + super(queryBuilder); + this.objectRecordsPermissions = objectRecordsPermissions; + this.internalContext = internalContext; + this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; + } + + override clone(): this { + const clonedQueryBuilder = super.clone(); + + return new WorkspaceDeleteQueryBuilder( + clonedQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + this.shouldBypassPermissionChecks, + ) as this; + } + + override execute(): Promise { + validateQueryIsPermittedOrThrow( + this.expressionMap, + this.objectRecordsPermissions, + this.internalContext.objectMetadataMaps, + this.shouldBypassPermissionChecks, + ); + + return super.execute(); + } + + override select(): WorkspaceSelectQueryBuilder { + throw new Error('This builder cannot morph into a select builder'); + } + + override update(): WorkspaceUpdateQueryBuilder; + + override update( + updateSet: QueryDeepPartialEntity, + ): WorkspaceUpdateQueryBuilder; + + override update( + _updateSet?: QueryDeepPartialEntity, + ): WorkspaceUpdateQueryBuilder { + throw new Error('This builder cannot morph into an update builder'); + } + + override insert(): InsertQueryBuilder { + throw new Error('This builder cannot morph into an insert builder'); + } + + override softDelete(): WorkspaceSoftDeleteQueryBuilder { + throw new Error('This builder cannot morph into a soft delete builder'); + } + + override restore(): WorkspaceSoftDeleteQueryBuilder { + throw new Error('This builder cannot morph into a soft delete builder'); + } +} diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts new file mode 100644 index 000000000..edaafbaf3 --- /dev/null +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts @@ -0,0 +1,72 @@ +import { ObjectRecordsPermissions } from 'twenty-shared/types'; +import { InsertQueryBuilder, ObjectLiteral } from 'typeorm'; + +import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; + +import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util'; +import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; +import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; +import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; +import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder'; + +export class WorkspaceInsertQueryBuilder< + T extends ObjectLiteral, +> extends InsertQueryBuilder { + private objectRecordsPermissions: ObjectRecordsPermissions; + private shouldBypassPermissionChecks: boolean; + private internalContext: WorkspaceInternalContext; + + constructor( + queryBuilder: InsertQueryBuilder, + objectRecordsPermissions: ObjectRecordsPermissions, + internalContext: WorkspaceInternalContext, + shouldBypassPermissionChecks: boolean, + ) { + super(queryBuilder); + this.objectRecordsPermissions = objectRecordsPermissions; + this.internalContext = internalContext; + this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; + } + + override clone(): this { + const clonedQueryBuilder = super.clone(); + + return new WorkspaceInsertQueryBuilder( + clonedQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + this.shouldBypassPermissionChecks, + ) as this; + } + + override execute(): Promise { + validateQueryIsPermittedOrThrow( + this.expressionMap, + this.objectRecordsPermissions, + this.internalContext.objectMetadataMaps, + this.shouldBypassPermissionChecks, + ); + + return super.execute(); + } + + override select(): WorkspaceSelectQueryBuilder { + throw new Error('This builder cannot morph into a select builder'); + } + + override update(): WorkspaceUpdateQueryBuilder { + throw new Error('This builder cannot morph into an update builder'); + } + + override delete(): WorkspaceDeleteQueryBuilder { + throw new Error('This builder cannot morph into a delete builder'); + } + + override softDelete(): WorkspaceSoftDeleteQueryBuilder { + throw new Error('This builder cannot morph into a soft delete builder'); + } + + override restore(): WorkspaceSoftDeleteQueryBuilder { + throw new Error('This builder cannot morph into a soft delete builder'); + } +} diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-query-builder.ts deleted file mode 100644 index 166d0f5d3..000000000 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-query-builder.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ObjectRecordsPermissions } from 'twenty-shared/types'; -import { ObjectLiteral, SelectQueryBuilder } from 'typeorm'; - -import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; - -import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; - -export class WorkspaceQueryBuilder< - T extends ObjectLiteral, -> extends WorkspaceSelectQueryBuilder { - constructor( - queryBuilder: SelectQueryBuilder, - objectRecordsPermissions: ObjectRecordsPermissions, - internalContext: WorkspaceInternalContext, - shouldBypassPermissionChecks: boolean, - ) { - super( - queryBuilder, - objectRecordsPermissions, - internalContext, - shouldBypassPermissionChecks, - ); - } - - override clone(): this { - const clonedQueryBuilder = super.clone(); - - return new WorkspaceQueryBuilder( - clonedQueryBuilder, - this.objectRecordsPermissions, - this.internalContext, - this.shouldBypassPermissionChecks, - ) as this; - } -} diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts index 65392d284..69362a96a 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts @@ -1,10 +1,12 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types'; -import { ObjectLiteral, SelectQueryBuilder, UpdateQueryBuilder } from 'typeorm'; +import { ObjectLiteral, SelectQueryBuilder } from 'typeorm'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util'; +import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; +import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder'; export class WorkspaceSelectQueryBuilder< @@ -25,6 +27,29 @@ export class WorkspaceSelectQueryBuilder< this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; } + override clone(): this { + const clonedQueryBuilder = super.clone(); + + return new WorkspaceSelectQueryBuilder( + clonedQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + this.shouldBypassPermissionChecks, + ) as this; + } + + override execute(): Promise { + this.validatePermissions(); + + return super.execute(); + } + + override getMany(): Promise { + this.validatePermissions(); + + return super.getMany(); + } + override update(): WorkspaceUpdateQueryBuilder; override update( @@ -33,7 +58,7 @@ export class WorkspaceSelectQueryBuilder< override update( updateSet?: QueryDeepPartialEntity, - ): UpdateQueryBuilder { + ): WorkspaceUpdateQueryBuilder { const updateQueryBuilder = updateSet ? super.update(updateSet) : super.update(); @@ -46,25 +71,45 @@ export class WorkspaceSelectQueryBuilder< ); } - override execute(): Promise { - validateQueryIsPermittedOrThrow( - this.expressionMap, + override delete(): WorkspaceDeleteQueryBuilder { + const deleteQueryBuilder = super.delete(); + + return new WorkspaceDeleteQueryBuilder( + deleteQueryBuilder, this.objectRecordsPermissions, - this.internalContext.objectMetadataMaps, + this.internalContext, this.shouldBypassPermissionChecks, ); - - return super.execute(); } - override getMany(): Promise { + override softDelete(): WorkspaceSoftDeleteQueryBuilder { + const softDeleteQueryBuilder = super.softDelete(); + + return new WorkspaceSoftDeleteQueryBuilder( + softDeleteQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + this.shouldBypassPermissionChecks, + ); + } + + override restore(): WorkspaceSoftDeleteQueryBuilder { + const restoreQueryBuilder = super.restore(); + + return new WorkspaceSoftDeleteQueryBuilder( + restoreQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + this.shouldBypassPermissionChecks, + ); + } + + private validatePermissions(): void { validateQueryIsPermittedOrThrow( this.expressionMap, this.objectRecordsPermissions, this.internalContext.objectMetadataMaps, this.shouldBypassPermissionChecks, ); - - return super.getMany(); } } diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-soft-delete-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-soft-delete-query-builder.ts new file mode 100644 index 000000000..ddaf38012 --- /dev/null +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-soft-delete-query-builder.ts @@ -0,0 +1,68 @@ +import { ObjectRecordsPermissions } from 'twenty-shared/types'; +import { InsertQueryBuilder, ObjectLiteral, UpdateResult } from 'typeorm'; +import { SoftDeleteQueryBuilder } from 'typeorm/query-builder/SoftDeleteQueryBuilder'; + +import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; + +import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util'; +import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; +import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; +import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder'; + +export class WorkspaceSoftDeleteQueryBuilder< + T extends ObjectLiteral, +> extends SoftDeleteQueryBuilder { + private objectRecordsPermissions: ObjectRecordsPermissions; + private shouldBypassPermissionChecks: boolean; + private internalContext: WorkspaceInternalContext; + + constructor( + queryBuilder: SoftDeleteQueryBuilder, + objectRecordsPermissions: ObjectRecordsPermissions, + internalContext: WorkspaceInternalContext, + shouldBypassPermissionChecks: boolean, + ) { + super(queryBuilder); + this.objectRecordsPermissions = objectRecordsPermissions; + this.internalContext = internalContext; + this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; + } + + override clone(): this { + const clonedQueryBuilder = super.clone(); + + return new WorkspaceSoftDeleteQueryBuilder( + clonedQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + this.shouldBypassPermissionChecks, + ) as this; + } + + override execute(): Promise { + validateQueryIsPermittedOrThrow( + this.expressionMap, + this.objectRecordsPermissions, + this.internalContext.objectMetadataMaps, + this.shouldBypassPermissionChecks, + ); + + return super.execute(); + } + + override select(): WorkspaceSelectQueryBuilder { + throw new Error('This builder cannot morph into a select builder'); + } + + override update(): WorkspaceUpdateQueryBuilder { + throw new Error('This builder cannot morph into an update builder'); + } + + override insert(): InsertQueryBuilder { + throw new Error('This builder cannot morph into an insert builder'); + } + + override delete(): WorkspaceDeleteQueryBuilder { + throw new Error('This builder cannot morph into a delete builder'); + } +} diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts index ac127e64f..3eb54c40c 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts @@ -4,15 +4,18 @@ import { ObjectLiteral, UpdateQueryBuilder, UpdateResult } from 'typeorm'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util'; +import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; +import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; +import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; export class WorkspaceUpdateQueryBuilder< - Entity extends ObjectLiteral, -> extends UpdateQueryBuilder { + T extends ObjectLiteral, +> extends UpdateQueryBuilder { private objectRecordsPermissions: ObjectRecordsPermissions; private shouldBypassPermissionChecks: boolean; private internalContext: WorkspaceInternalContext; constructor( - queryBuilder: UpdateQueryBuilder, + queryBuilder: UpdateQueryBuilder, objectRecordsPermissions: ObjectRecordsPermissions, internalContext: WorkspaceInternalContext, shouldBypassPermissionChecks: boolean, @@ -23,6 +26,17 @@ export class WorkspaceUpdateQueryBuilder< this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; } + override clone(): this { + const clonedQueryBuilder = super.clone(); + + return new WorkspaceUpdateQueryBuilder( + clonedQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + this.shouldBypassPermissionChecks, + ) as this; + } + override execute(): Promise { validateQueryIsPermittedOrThrow( this.expressionMap, @@ -33,4 +47,20 @@ export class WorkspaceUpdateQueryBuilder< return super.execute(); } + + override select(): WorkspaceSelectQueryBuilder { + throw new Error('This builder cannot morph into a select builder'); + } + + override delete(): WorkspaceDeleteQueryBuilder { + throw new Error('This builder cannot morph into a delete builder'); + } + + override softDelete(): WorkspaceSoftDeleteQueryBuilder { + throw new Error('This builder cannot morph into a soft delete builder'); + } + + override restore(): WorkspaceSoftDeleteQueryBuilder { + throw new Error('This builder cannot morph into a soft delete builder'); + } } diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts index 69abd972b..8140b6a00 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts @@ -27,7 +27,7 @@ import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/works import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util'; -import { WorkspaceQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-query-builder'; +import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspace-entities.storage'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @@ -58,11 +58,11 @@ export class WorkspaceRepository< override createQueryBuilder( alias?: string, queryRunner?: QueryRunner, - ): WorkspaceQueryBuilder { + ): WorkspaceSelectQueryBuilder { const queryBuilder = super.createQueryBuilder( alias, queryRunner, - ) as unknown as WorkspaceQueryBuilder; + ) as unknown as WorkspaceSelectQueryBuilder; const isPermissionsV2Enabled = this.featureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled]; @@ -73,7 +73,7 @@ export class WorkspaceRepository< throw new Error('Object records permissions are required'); } - return new WorkspaceQueryBuilder( + return new WorkspaceSelectQueryBuilder( queryBuilder, this.objectRecordsPermissions, this.internalContext, diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-many-object-records-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-many-object-records-permissions.integration-spec.ts index 26ebffc76..5dacfd1f2 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-many-object-records-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-many-object-records-permissions.integration-spec.ts @@ -9,55 +9,160 @@ import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors. import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; describe('createManyObjectRecordsPermissions', () => { - it('should throw a permission error when user does not have permission (guest role)', async () => { - const graphqlOperation = createManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - data: [ - { - id: randomUUID(), - }, - { - id: randomUUID(), - }, - ], + describe('permissions V2 disabled', () => { + it('should throw a permission error when user does not have permission (guest role)', async () => { + const graphqlOperation = createManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + data: [ + { + id: randomUUID(), + }, + { + id: randomUUID(), + }, + ], + }); + + const response = + await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + expect(response.body.data).toStrictEqual({ createPeople: null }); + 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); }); - const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + it('should create multiple object records when user has permission (admin role)', async () => { + const personId1 = randomUUID(); + const personId2 = randomUUID(); - expect(response.body.data).toStrictEqual({ createPeople: null }); - 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); - }); + const graphqlOperation = createManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + data: [ + { + id: personId1, + }, + { + id: personId2, + }, + ], + }); - it('should create multiple object records when user has permission (admin role)', async () => { - const personId1 = randomUUID(); - const personId2 = randomUUID(); + const response = await makeGraphqlAPIRequest(graphqlOperation); - const graphqlOperation = createManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - data: [ - { - id: personId1, - }, - { - id: personId2, - }, - ], + expect(response.body.data).toBeDefined(); + expect(response.body.data.createPeople).toBeDefined(); + expect(response.body.data.createPeople).toHaveLength(2); + expect(response.body.data.createPeople[0].id).toBe(personId1); + expect(response.body.data.createPeople[1].id).toBe(personId2); }); - - const response = await makeGraphqlAPIRequest(graphqlOperation); - - expect(response.body.data).toBeDefined(); - expect(response.body.data.createPeople).toBeDefined(); - expect(response.body.data.createPeople).toHaveLength(2); - expect(response.body.data.createPeople[0].id).toBe(personId1); - expect(response.body.data.createPeople[1].id).toBe(personId2); }); + + // describe('permissions V2 enabled', () => { + // beforeAll(async () => { + // const enablePermissionsQuery = updateFeatureFlagFactory( + // SEED_APPLE_WORKSPACE_ID, + // 'IsPermissionsV2Enabled', + // true, + // ); + + // await makeGraphqlAPIRequest(enablePermissionsQuery); + // }); + + // afterAll(async () => { + // const disablePermissionsQuery = updateFeatureFlagFactory( + // SEED_APPLE_WORKSPACE_ID, + // 'IsPermissionsV2Enabled', + // false, + // ); + + // await makeGraphqlAPIRequest(disablePermissionsQuery); + // }); + + // it('should throw a permission error when user does not have permission (guest role)', async () => { + // const graphqlOperation = createManyOperationFactory({ + // objectMetadataSingularName: 'person', + // objectMetadataPluralName: 'people', + // gqlFields: PERSON_GQL_FIELDS, + // data: [ + // { + // id: randomUUID(), + // }, + // { + // id: randomUUID(), + // }, + // ], + // }); + + // const response = + // await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + // expect(response.body.data).toStrictEqual({ createPeople: null }); + // 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); + // }); + + // it('should create multiple object records when user has permission (admin role)', async () => { + // const personId1 = randomUUID(); + // const personId2 = randomUUID(); + + // const graphqlOperation = createManyOperationFactory({ + // objectMetadataSingularName: 'person', + // objectMetadataPluralName: 'people', + // gqlFields: PERSON_GQL_FIELDS, + // data: [ + // { + // id: personId1, + // }, + // { + // id: personId2, + // }, + // ], + // }); + + // const response = await makeGraphqlAPIRequest(graphqlOperation); + + // expect(response.body.data).toBeDefined(); + // expect(response.body.data.createPeople).toBeDefined(); + // expect(response.body.data.createPeople).toHaveLength(2); + // expect(response.body.data.createPeople[0].id).toBe(personId1); + // expect(response.body.data.createPeople[1].id).toBe(personId2); + // }); + + // it('should create multiple object records when executed by api key', async () => { + // const personId1 = randomUUID(); + // const personId2 = randomUUID(); + + // const graphqlOperation = createManyOperationFactory({ + // objectMetadataSingularName: 'person', + // objectMetadataPluralName: 'people', + // gqlFields: PERSON_GQL_FIELDS, + // data: [ + // { + // id: personId1, + // }, + // { + // id: personId2, + // }, + // ], + // }); + + // const response = await makeGraphqlAPIRequestWithApiKey(graphqlOperation); + + // expect(response.body.data).toBeDefined(); + // expect(response.body.data.createPeople).toBeDefined(); + // expect(response.body.data.createPeople).toHaveLength(2); + // expect(response.body.data.createPeople[0].id).toBe(personId1); + // expect(response.body.data.createPeople[1].id).toBe(personId2); + // }); + // }); }); diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-one-object-records-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-one-object-records-permissions.integration-spec.ts index 40256f027..9565f3089 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-one-object-records-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/create-one-object-records-permissions.integration-spec.ts @@ -9,39 +9,118 @@ import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors. import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; describe('createOneObjectRecordsPermissions', () => { - it('should throw a permission error when user does not have permission (guest role)', async () => { - const graphqlOperation = createOneOperationFactory({ - objectMetadataSingularName: 'person', - gqlFields: PERSON_GQL_FIELDS, - data: { - id: randomUUID(), - }, + describe('permissions V2 disabled', () => { + it('should throw a permission error when user does not have permission (guest role)', async () => { + const graphqlOperation = createOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + data: { + id: randomUUID(), + }, + }); + + const response = + await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + expect(response.body.data).toStrictEqual({ createPerson: null }); + 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); }); - const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + it('should create an object record when user has permission (admin role)', async () => { + const personId = randomUUID(); + const graphqlOperation = createOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + data: { + id: personId, + }, + }); - expect(response.body.data).toStrictEqual({ createPerson: null }); - 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); - }); + const response = await makeGraphqlAPIRequest(graphqlOperation); - it('should create an object record when user has permission (admin role)', async () => { - const personId = randomUUID(); - const graphqlOperation = createOneOperationFactory({ - objectMetadataSingularName: 'person', - gqlFields: PERSON_GQL_FIELDS, - data: { - id: personId, - }, + expect(response.body.data).toBeDefined(); + expect(response.body.data.createPerson).toBeDefined(); + expect(response.body.data.createPerson.id).toBe(personId); }); - - const response = await makeGraphqlAPIRequest(graphqlOperation); - - expect(response.body.data).toBeDefined(); - expect(response.body.data.createPerson).toBeDefined(); - expect(response.body.data.createPerson.id).toBe(personId); }); + + // describe('permissions V2 enabled', () => { + // beforeAll(async () => { + // const enablePermissionsQuery = updateFeatureFlagFactory( + // SEED_APPLE_WORKSPACE_ID, + // 'IsPermissionsV2Enabled', + // true, + // ); + + // await makeGraphqlAPIRequest(enablePermissionsQuery); + // }); + + // afterAll(async () => { + // const disablePermissionsQuery = updateFeatureFlagFactory( + // SEED_APPLE_WORKSPACE_ID, + // 'IsPermissionsV2Enabled', + // false, + // ); + + // await makeGraphqlAPIRequest(disablePermissionsQuery); + // }); + + // it('should throw a permission error when user does not have permission (guest role)', async () => { + // const graphqlOperation = createOneOperationFactory({ + // objectMetadataSingularName: 'person', + // gqlFields: PERSON_GQL_FIELDS, + // data: { + // id: randomUUID(), + // }, + // }); + + // const response = + // await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + // expect(response.body.data).toStrictEqual({ createPerson: null }); + // 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); + // }); + + // it('should create an object record when user has permission (admin role)', async () => { + // const personId = randomUUID(); + // const graphqlOperation = createOneOperationFactory({ + // objectMetadataSingularName: 'person', + // gqlFields: PERSON_GQL_FIELDS, + // data: { + // id: personId, + // }, + // }); + + // const response = await makeGraphqlAPIRequest(graphqlOperation); + + // expect(response.body.data).toBeDefined(); + // expect(response.body.data.createPerson).toBeDefined(); + // expect(response.body.data.createPerson.id).toBe(personId); + // }); + + // it('should create an object record when executed by api key', async () => { + // const personId = randomUUID(); + // const graphqlOperation = createOneOperationFactory({ + // objectMetadataSingularName: 'person', + // gqlFields: PERSON_GQL_FIELDS, + // data: { + // id: personId, + // }, + // }); + + // const response = await makeGraphqlAPIRequestWithApiKey(graphqlOperation); + + // expect(response.body.data).toBeDefined(); + // expect(response.body.data.createPerson).toBeDefined(); + // expect(response.body.data.createPerson.id).toBe(personId); + // }); + // }); }); diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/delete-many-object-records-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/delete-many-object-records-permissions.integration-spec.ts index d37f2066b..c557a5c48 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/delete-many-object-records-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/delete-many-object-records-permissions.integration-spec.ts @@ -3,72 +3,205 @@ import { randomUUID } from 'node:crypto'; import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants'; import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util'; import { deleteManyOperationFactory } from 'test/integration/graphql/utils/delete-many-operation-factory.util'; +import { makeGraphqlAPIRequestWithApiKey } from 'test/integration/graphql/utils/make-graphql-api-request-with-api-key.util'; import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-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 { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; describe('deleteManyObjectRecordsPermissions', () => { - it('should throw a permission error when user does not have permission (guest role)', async () => { - const graphqlOperation = deleteManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - filter: { - id: { - in: [randomUUID(), randomUUID()], + describe('permissions V2 disabled', () => { + it('should throw a permission error when user does not have permission (guest role)', async () => { + const graphqlOperation = deleteManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [randomUUID(), randomUUID()], + }, }, - }, + }); + + const response = + await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + expect(response.body.data).toStrictEqual({ deletePeople: null }); + 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); }); - const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + it('should delete multiple object records when user has permission (admin role)', async () => { + const personId1 = randomUUID(); + const personId2 = randomUUID(); - expect(response.body.data).toStrictEqual({ deletePeople: null }); - 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); + const createGraphqlOperation = createManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + data: [ + { + id: personId1, + }, + { + id: personId2, + }, + ], + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); + + const deleteGraphqlOperation = deleteManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, + }, + }); + + const response = await makeGraphqlAPIRequest(deleteGraphqlOperation); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.deletePeople).toBeDefined(); + expect(response.body.data.deletePeople).toHaveLength(2); + expect(response.body.data.deletePeople[0].id).toBe(personId1); + expect(response.body.data.deletePeople[1].id).toBe(personId2); + }); }); - it('should delete multiple object records when user has permission (admin role)', async () => { - const personId1 = randomUUID(); - const personId2 = randomUUID(); + describe('permissions V2 enabled', () => { + beforeAll(async () => { + const enablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IsPermissionsV2Enabled', + true, + ); - const createGraphqlOperation = createManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - data: [ - { - id: personId1, - }, - { - id: personId2, - }, - ], + await makeGraphqlAPIRequest(enablePermissionsQuery); }); - await makeGraphqlAPIRequest(createGraphqlOperation); + afterAll(async () => { + const disablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IsPermissionsV2Enabled', + false, + ); - const deleteGraphqlOperation = deleteManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - filter: { - id: { - in: [personId1, personId2], - }, - }, + await makeGraphqlAPIRequest(disablePermissionsQuery); }); - const response = await makeGraphqlAPIRequest(deleteGraphqlOperation); + it('should throw a permission error when user does not have permission (guest role)', async () => { + const graphqlOperation = deleteManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [randomUUID(), randomUUID()], + }, + }, + }); - expect(response.body.data).toBeDefined(); - expect(response.body.data.deletePeople).toBeDefined(); - expect(response.body.data.deletePeople).toHaveLength(2); - expect(response.body.data.deletePeople[0].id).toBe(personId1); - expect(response.body.data.deletePeople[1].id).toBe(personId2); + const response = + await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + expect(response.body.data).toStrictEqual({ deletePeople: null }); + 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); + }); + + it('should delete multiple object records when user has permission (admin role)', async () => { + const personId1 = randomUUID(); + const personId2 = randomUUID(); + + const createGraphqlOperation = createManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + data: [ + { + id: personId1, + }, + { + id: personId2, + }, + ], + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); + + const deleteGraphqlOperation = deleteManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, + }, + }); + + const response = await makeGraphqlAPIRequest(deleteGraphqlOperation); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.deletePeople).toBeDefined(); + expect(response.body.data.deletePeople).toHaveLength(2); + expect(response.body.data.deletePeople[0].id).toBe(personId1); + expect(response.body.data.deletePeople[1].id).toBe(personId2); + }); + + it('should delete multiple object records when executed by api key', async () => { + const personId1 = randomUUID(); + const personId2 = randomUUID(); + + const createGraphqlOperation = createManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + data: [ + { + id: personId1, + }, + { + id: personId2, + }, + ], + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); + + const deleteGraphqlOperation = deleteManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, + }, + }); + + const response = await makeGraphqlAPIRequestWithApiKey( + deleteGraphqlOperation, + ); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.deletePeople).toBeDefined(); + expect(response.body.data.deletePeople).toHaveLength(2); + expect(response.body.data.deletePeople[0].id).toBe(personId1); + expect(response.body.data.deletePeople[1].id).toBe(personId2); + }); }); }); diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/delete-one-object-records-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/delete-one-object-records-permissions.integration-spec.ts index c2ffee0d5..6ac5ba4eb 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/delete-one-object-records-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/delete-one-object-records-permissions.integration-spec.ts @@ -3,56 +3,178 @@ import { randomUUID } from 'node:crypto'; import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants'; import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util'; import { deleteOneOperationFactory } from 'test/integration/graphql/utils/delete-one-operation-factory.util'; +import { makeGraphqlAPIRequestWithApiKey } from 'test/integration/graphql/utils/make-graphql-api-request-with-api-key.util'; import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-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 { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; describe('deleteOneObjectRecordsPermissions', () => { - const personId = randomUUID(); - - beforeAll(async () => { - const createOnePersonRecordOperation = createOneOperationFactory({ - objectMetadataSingularName: 'person', - gqlFields: PERSON_GQL_FIELDS, - data: { - id: personId, - }, - }); - - await makeGraphqlAPIRequest(createOnePersonRecordOperation); - }); - - it('should throw a permission error when user does not have permission (guest role)', async () => { + describe('permissions V2 disabled', () => { const personId = randomUUID(); - const graphqlOperation = deleteOneOperationFactory({ - objectMetadataSingularName: 'person', - gqlFields: PERSON_GQL_FIELDS, - recordId: personId, + + beforeAll(async () => { + const createOnePersonRecordOperation = createOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + data: { + id: personId, + }, + }); + + await makeGraphqlAPIRequest(createOnePersonRecordOperation); }); - const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + it('should throw a permission error when user does not have permission (guest role)', async () => { + const personId = randomUUID(); + const graphqlOperation = deleteOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + recordId: personId, + }); - expect(response.body.data).toStrictEqual({ deletePerson: null }); - 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); + const response = + await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + expect(response.body.data).toStrictEqual({ deletePerson: null }); + 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); + }); + + it('should delete an object record when user has permission (admin role)', async () => { + const deleteGraphqlOperation = deleteOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + recordId: personId, + }); + + const response = await makeGraphqlAPIRequest(deleteGraphqlOperation); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.deletePerson).toBeDefined(); + expect(response.body.data.deletePerson.id).toBe(personId); + }); }); - it('should delete an object record when user has permission (admin role)', async () => { - const deleteGraphqlOperation = deleteOneOperationFactory({ - objectMetadataSingularName: 'person', - gqlFields: PERSON_GQL_FIELDS, - recordId: personId, + describe('permissions V2 enabled', () => { + const personId = randomUUID(); + + beforeAll(async () => { + const createOnePersonRecordOperation = createOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + data: { + id: personId, + }, + }); + + await makeGraphqlAPIRequest(createOnePersonRecordOperation); + + const enablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IsPermissionsV2Enabled', + true, + ); + + await makeGraphqlAPIRequest(enablePermissionsQuery); }); - const response = await makeGraphqlAPIRequest(deleteGraphqlOperation); + afterAll(async () => { + const disablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IsPermissionsV2Enabled', + false, + ); - expect(response.body.data).toBeDefined(); - expect(response.body.data.deletePerson).toBeDefined(); - expect(response.body.data.deletePerson.id).toBe(personId); + await makeGraphqlAPIRequest(disablePermissionsQuery); + }); + + it('should throw a permission error when user does not have permission (guest role)', async () => { + const personId = randomUUID(); + const createGraphqlOperation = createOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + data: { + id: personId, + }, + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); + + const deleteGraphqlOperation = deleteOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + recordId: personId, + }); + + const response = await makeGraphqlAPIRequestWithGuestRole( + deleteGraphqlOperation, + ); + + expect(response.body.data).toStrictEqual({ deletePerson: null }); + 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); + }); + + it('should delete an object record when user has permission (admin role)', async () => { + const personId = randomUUID(); + const createGraphqlOperation = createOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + data: { + id: personId, + }, + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); + + const deleteGraphqlOperation = deleteOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + recordId: personId, + }); + + const response = await makeGraphqlAPIRequest(deleteGraphqlOperation); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.deletePerson).toBeDefined(); + expect(response.body.data.deletePerson.id).toBe(personId); + }); + + it('should delete an object record when executed by api key', async () => { + const personId = randomUUID(); + const createGraphqlOperation = createOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + data: { + id: personId, + }, + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); + + const deleteGraphqlOperation = deleteOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + recordId: personId, + }); + + const response = await makeGraphqlAPIRequestWithApiKey( + deleteGraphqlOperation, + ); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.deletePerson).toBeDefined(); + expect(response.body.data.deletePerson.id).toBe(personId); + }); }); }); diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/destroy-many-object-records-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/destroy-many-object-records-permissions.integration-spec.ts index 089b7403d..3d7f10418 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/destroy-many-object-records-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/destroy-many-object-records-permissions.integration-spec.ts @@ -5,70 +5,160 @@ import { createManyOperationFactory } from 'test/integration/graphql/utils/creat import { destroyManyOperationFactory } from 'test/integration/graphql/utils/destroy-many-operation-factory.util'; import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-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 { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; describe('destroyManyObjectRecordsPermissions', () => { - it('should throw a permission error when user does not have permission (guest role)', async () => { - const graphqlOperation = destroyManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - filter: { - id: { - in: [randomUUID(), randomUUID()], + describe('permissions V2 disabled', () => { + it('should throw a permission error when user does not have permission (guest role)', async () => { + const graphqlOperation = destroyManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [randomUUID(), randomUUID()], + }, }, - }, + }); + + const response = + await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + expect(response.body.data).toStrictEqual({ destroyPeople: null }); + 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); }); - const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + it('should destroy multiple object records when user has permission (admin role)', async () => { + const personId1 = randomUUID(); + const personId2 = randomUUID(); - expect(response.body.data).toStrictEqual({ destroyPeople: null }); - 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); + const createGraphqlOperation = createManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + data: [ + { + id: personId1, + }, + { + id: personId2, + }, + ], + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); + + const graphqlOperation = destroyManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, + }, + }); + + const response = await makeGraphqlAPIRequest(graphqlOperation); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.destroyPeople).toBeDefined(); + expect(response.body.data.destroyPeople).toHaveLength(2); + expect(response.body.data.destroyPeople[0].id).toBe(personId1); + expect(response.body.data.destroyPeople[1].id).toBe(personId2); + }); }); - it('should destroy multiple object records when user has permission (admin role)', async () => { - const personId1 = randomUUID(); - const personId2 = randomUUID(); + describe('permissions V2 enabled', () => { + beforeAll(async () => { + const enablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IsPermissionsV2Enabled', + true, + ); - const createGraphqlOperation = createManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - data: [ - { - id: personId1, - }, - { - id: personId2, - }, - ], + await makeGraphqlAPIRequest(enablePermissionsQuery); }); - await makeGraphqlAPIRequest(createGraphqlOperation); + afterAll(async () => { + const disablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IsPermissionsV2Enabled', + false, + ); - const graphqlOperation = destroyManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - filter: { - id: { - in: [personId1, personId2], - }, - }, + await makeGraphqlAPIRequest(disablePermissionsQuery); }); - const response = await makeGraphqlAPIRequest(graphqlOperation); + it('should throw a permission error when user does not have permission (guest role)', async () => { + const graphqlOperation = destroyManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [randomUUID(), randomUUID()], + }, + }, + }); - expect(response.body.data).toBeDefined(); - expect(response.body.data.destroyPeople).toBeDefined(); - expect(response.body.data.destroyPeople).toHaveLength(2); - expect(response.body.data.destroyPeople[0].id).toBe(personId1); - expect(response.body.data.destroyPeople[1].id).toBe(personId2); + const response = + await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + expect(response.body.data).toStrictEqual({ destroyPeople: null }); + 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); + }); + + it('should destroy multiple object records when user has permission (admin role)', async () => { + const personId1 = randomUUID(); + const personId2 = randomUUID(); + + const createGraphqlOperation = createManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + data: [ + { + id: personId1, + }, + { + id: personId2, + }, + ], + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); + + const graphqlOperation = destroyManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, + }, + }); + + const response = await makeGraphqlAPIRequest(graphqlOperation); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.destroyPeople).toBeDefined(); + expect(response.body.data.destroyPeople).toHaveLength(2); + expect(response.body.data.destroyPeople[0].id).toBe(personId1); + expect(response.body.data.destroyPeople[1].id).toBe(personId2); + }); }); }); diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/destroy-one-object-records-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/destroy-one-object-records-permissions.integration-spec.ts index 06139c959..02b7336c6 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/destroy-one-object-records-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/destroy-one-object-records-permissions.integration-spec.ts @@ -5,54 +5,126 @@ import { createOneOperationFactory } from 'test/integration/graphql/utils/create import { destroyOneOperationFactory } from 'test/integration/graphql/utils/destroy-one-operation-factory.util'; import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-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 { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; describe('destroyOneObjectRecordsPermissions', () => { - const personId = randomUUID(); - - beforeAll(async () => { - const createGraphqlOperation = createOneOperationFactory({ - objectMetadataSingularName: 'person', - gqlFields: PERSON_GQL_FIELDS, - data: { - id: personId, - }, - }); - - await makeGraphqlAPIRequest(createGraphqlOperation); - }); - - it('should throw a permission error when user does not have permission (guest role)', async () => { + describe('permissions V2 disabled', () => { const personId = randomUUID(); - const graphqlOperation = destroyOneOperationFactory({ - objectMetadataSingularName: 'person', - gqlFields: PERSON_GQL_FIELDS, - recordId: personId, + + beforeAll(async () => { + const createGraphqlOperation = createOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + data: { + id: personId, + }, + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); }); - const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + it('should throw a permission error when user does not have permission (guest role)', async () => { + const personId = randomUUID(); + const graphqlOperation = destroyOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + recordId: personId, + }); - expect(response.body.data).toStrictEqual({ destroyPerson: null }); - 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); + const response = + await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + expect(response.body.data).toStrictEqual({ destroyPerson: null }); + 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); + }); + + it('should destroy an object record when user has permission (admin role)', async () => { + const graphqlOperation = destroyOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + recordId: personId, + }); + + const response = await makeGraphqlAPIRequest(graphqlOperation); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.destroyPerson).toBeDefined(); + expect(response.body.data.destroyPerson.id).toBe(personId); + }); }); - it('should destroy an object record when user has permission (admin role)', async () => { - const graphqlOperation = destroyOneOperationFactory({ - objectMetadataSingularName: 'person', - gqlFields: PERSON_GQL_FIELDS, - recordId: personId, + describe('permissions V2 enabled', () => { + const personId = randomUUID(); + + beforeAll(async () => { + const createGraphqlOperation = createOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + data: { + id: personId, + }, + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); + + const enablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IsPermissionsV2Enabled', + true, + ); + + await makeGraphqlAPIRequest(enablePermissionsQuery); }); - const response = await makeGraphqlAPIRequest(graphqlOperation); + afterAll(async () => { + const disablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IsPermissionsV2Enabled', + false, + ); - expect(response.body.data).toBeDefined(); - expect(response.body.data.destroyPerson).toBeDefined(); - expect(response.body.data.destroyPerson.id).toBe(personId); + await makeGraphqlAPIRequest(disablePermissionsQuery); + }); + + it('should throw a permission error when user does not have permission (guest role)', async () => { + const personId = randomUUID(); + const graphqlOperation = destroyOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + recordId: personId, + }); + + const response = + await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + expect(response.body.data).toStrictEqual({ destroyPerson: null }); + 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); + }); + + it('should destroy an object record when user has permission (admin role)', async () => { + const graphqlOperation = destroyOneOperationFactory({ + objectMetadataSingularName: 'person', + gqlFields: PERSON_GQL_FIELDS, + recordId: personId, + }); + + const response = await makeGraphqlAPIRequest(graphqlOperation); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.destroyPerson).toBeDefined(); + expect(response.body.data.destroyPerson.id).toBe(personId); + }); }); }); diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/restore-many-object-records-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/restore-many-object-records-permissions.integration-spec.ts index d4cccbcf6..44d059e65 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/restore-many-object-records-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/restore-many-object-records-permissions.integration-spec.ts @@ -6,87 +6,192 @@ import { deleteManyOperationFactory } from 'test/integration/graphql/utils/delet import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util'; import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; import { restoreManyOperationFactory } from 'test/integration/graphql/utils/restore-many-operation-factory.util'; +import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util'; +import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; describe('restoreManyObjectRecordsPermissions', () => { - const personId1 = randomUUID(); - const personId2 = randomUUID(); + describe('permissions V2 disabled', () => { + const personId1 = randomUUID(); + const personId2 = randomUUID(); - beforeAll(async () => { - // Create people - const createGraphqlOperation = createManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - data: [ - { - id: personId1, + beforeAll(async () => { + // Create people + const createGraphqlOperation = createManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + data: [ + { + id: personId1, + }, + { + id: personId2, + }, + ], + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); + + // Delete people + const deleteGraphqlOperation = deleteManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, }, - { - id: personId2, - }, - ], + }); + + await makeGraphqlAPIRequest(deleteGraphqlOperation); }); - await makeGraphqlAPIRequest(createGraphqlOperation); - - // Delete people - const deleteGraphqlOperation = deleteManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - filter: { - id: { - in: [personId1, personId2], + it('should throw a permission error when user does not have permission (guest role)', async () => { + const graphqlOperation = restoreManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, }, - }, + }); + + const response = + await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + expect(response.body.data).toStrictEqual({ restorePeople: null }); + 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); }); - await makeGraphqlAPIRequest(deleteGraphqlOperation); + it('should restore multiple object records when user has permission (admin role)', async () => { + const graphqlOperation = restoreManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, + }, + }); + + const response = await makeGraphqlAPIRequest(graphqlOperation); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.restorePeople).toBeDefined(); + expect(response.body.data.restorePeople).toHaveLength(2); + expect(response.body.data.restorePeople[0].id).toBe(personId1); + expect(response.body.data.restorePeople[1].id).toBe(personId2); + }); }); - it('should throw a permission error when user does not have permission (guest role)', async () => { - const graphqlOperation = restoreManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - filter: { - id: { - in: [personId1, personId2], + describe('permissions V2 enabled', () => { + const personId1 = randomUUID(); + const personId2 = randomUUID(); + + beforeAll(async () => { + // Create people + const createGraphqlOperation = createManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + data: [ + { + id: personId1, + }, + { + id: personId2, + }, + ], + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); + + // Delete people + const deleteGraphqlOperation = deleteManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, }, - }, + }); + + await makeGraphqlAPIRequest(deleteGraphqlOperation); + + const enablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IsPermissionsV2Enabled', + true, + ); + + await makeGraphqlAPIRequest(enablePermissionsQuery); }); - const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + afterAll(async () => { + const disablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IsPermissionsV2Enabled', + false, + ); - expect(response.body.data).toStrictEqual({ restorePeople: null }); - 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); - }); - - it('should restore multiple object records when user has permission (admin role)', async () => { - const graphqlOperation = restoreManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - filter: { - id: { - in: [personId1, personId2], - }, - }, + await makeGraphqlAPIRequest(disablePermissionsQuery); }); - const response = await makeGraphqlAPIRequest(graphqlOperation); + it('should throw a permission error when user does not have permission (guest role)', async () => { + const graphqlOperation = restoreManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, + }, + }); - expect(response.body.data).toBeDefined(); - expect(response.body.data.restorePeople).toBeDefined(); - expect(response.body.data.restorePeople).toHaveLength(2); - expect(response.body.data.restorePeople[0].id).toBe(personId1); - expect(response.body.data.restorePeople[1].id).toBe(personId2); + const response = + await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + expect(response.body.data).toStrictEqual({ restorePeople: null }); + 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); + }); + + it('should restore multiple object records when user has permission (admin role)', async () => { + const graphqlOperation = restoreManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, + }, + }); + + const response = await makeGraphqlAPIRequest(graphqlOperation); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.restorePeople).toBeDefined(); + expect(response.body.data.restorePeople).toHaveLength(2); + expect(response.body.data.restorePeople[0].id).toBe(personId1); + expect(response.body.data.restorePeople[1].id).toBe(personId2); + }); }); }); diff --git a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/update-many-object-records-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/update-many-object-records-permissions.integration-spec.ts index a42831da8..d4ab07d88 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/update-many-object-records-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-records-permissions/update-many-object-records-permissions.integration-spec.ts @@ -2,81 +2,248 @@ import { randomUUID } from 'node:crypto'; import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants'; import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util'; +import { makeGraphqlAPIRequestWithApiKey } from 'test/integration/graphql/utils/make-graphql-api-request-with-api-key.util'; import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-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 { updateManyOperationFactory } from 'test/integration/graphql/utils/update-many-operation-factory.util'; +import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; describe('updateManyObjectRecordsPermissions', () => { - const personId1 = randomUUID(); - const personId2 = randomUUID(); + describe('permissions V2 disabled', () => { + const personId1 = randomUUID(); + const personId2 = randomUUID(); - beforeAll(async () => { - const createGraphqlOperation = createManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - data: [ - { - id: personId1, - }, - { - id: personId2, - }, - ], + beforeAll(async () => { + const createGraphqlOperation = createManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + data: [ + { + id: personId1, + }, + { + id: personId2, + }, + ], + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); }); - await makeGraphqlAPIRequest(createGraphqlOperation); + it('should throw a permission error when user does not have permission (guest role)', async () => { + const graphqlOperation = updateManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [randomUUID(), randomUUID()], + }, + }, + data: { + jobTitle: 'Architect', + }, + }); + + const response = + await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + + expect(response.body.data).toStrictEqual({ updatePeople: null }); + 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); + }); + + it('should update multiple object records when user has permission (admin role)', async () => { + const graphqlOperation = updateManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, + }, + data: { + jobTitle: 'Architect', + }, + }); + + const response = await makeGraphqlAPIRequest(graphqlOperation); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.updatePeople).toBeDefined(); + expect(response.body.data.updatePeople).toHaveLength(2); + expect(response.body.data.updatePeople[0].jobTitle).toBe('Architect'); + expect(response.body.data.updatePeople[1].jobTitle).toBe('Architect'); + }); }); - it('should throw a permission error when user does not have permission (guest role)', async () => { - const graphqlOperation = updateManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - filter: { - id: { - in: [randomUUID(), randomUUID()], - }, - }, - data: { - jobTitle: 'Architect', - }, + describe('permissions V2 enabled', () => { + beforeAll(async () => { + const enablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IsPermissionsV2Enabled', + true, + ); + + await makeGraphqlAPIRequest(enablePermissionsQuery); }); - const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); + afterAll(async () => { + const disablePermissionsQuery = updateFeatureFlagFactory( + SEED_APPLE_WORKSPACE_ID, + 'IsPermissionsV2Enabled', + false, + ); - expect(response.body.data).toStrictEqual({ updatePeople: null }); - 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); - }); - - it('should update multiple object records when user has permission (admin role)', async () => { - const graphqlOperation = updateManyOperationFactory({ - objectMetadataSingularName: 'person', - objectMetadataPluralName: 'people', - gqlFields: PERSON_GQL_FIELDS, - filter: { - id: { - in: [personId1, personId2], - }, - }, - data: { - jobTitle: 'Architect', - }, + await makeGraphqlAPIRequest(disablePermissionsQuery); }); - const response = await makeGraphqlAPIRequest(graphqlOperation); + it('should throw a permission error when user does not have permission (guest role)', async () => { + const personId1 = randomUUID(); + const personId2 = randomUUID(); + const createGraphqlOperation = createManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + data: [ + { + id: personId1, + }, + { + id: personId2, + }, + ], + }); - expect(response.body.data).toBeDefined(); - expect(response.body.data.updatePeople).toBeDefined(); - expect(response.body.data.updatePeople).toHaveLength(2); - expect(response.body.data.updatePeople[0].jobTitle).toBe('Architect'); - expect(response.body.data.updatePeople[1].jobTitle).toBe('Architect'); + await makeGraphqlAPIRequest(createGraphqlOperation); + + const updateGraphqlOperation = updateManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, + }, + data: { + jobTitle: 'Senior Developer', + }, + }); + + const response = await makeGraphqlAPIRequestWithGuestRole( + updateGraphqlOperation, + ); + + expect(response.body.data).toStrictEqual({ updatePeople: null }); + 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); + }); + + it('should update multiple object records when user has permission (admin role)', async () => { + const personId1 = randomUUID(); + const personId2 = randomUUID(); + const createGraphqlOperation = createManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + data: [ + { + id: personId1, + }, + { + id: personId2, + }, + ], + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); + + const updateGraphqlOperation = updateManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, + }, + data: { + jobTitle: 'Tech Lead', + }, + }); + + const response = await makeGraphqlAPIRequest(updateGraphqlOperation); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.updatePeople).toBeDefined(); + expect(response.body.data.updatePeople).toHaveLength(2); + expect(response.body.data.updatePeople[0].id).toBe(personId1); + expect(response.body.data.updatePeople[1].id).toBe(personId2); + expect(response.body.data.updatePeople[0].jobTitle).toBe('Tech Lead'); + expect(response.body.data.updatePeople[1].jobTitle).toBe('Tech Lead'); + }); + + it('should update multiple object records when executed by api key', async () => { + const personId1 = randomUUID(); + const personId2 = randomUUID(); + const createGraphqlOperation = createManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + data: [ + { + id: personId1, + }, + { + id: personId2, + }, + ], + }); + + await makeGraphqlAPIRequest(createGraphqlOperation); + + const updateGraphqlOperation = updateManyOperationFactory({ + objectMetadataSingularName: 'person', + objectMetadataPluralName: 'people', + gqlFields: PERSON_GQL_FIELDS, + filter: { + id: { + in: [personId1, personId2], + }, + }, + data: { + jobTitle: 'Product Manager', + }, + }); + + const response = await makeGraphqlAPIRequestWithApiKey( + updateGraphqlOperation, + ); + + expect(response.body.data).toBeDefined(); + expect(response.body.data.updatePeople).toBeDefined(); + expect(response.body.data.updatePeople).toHaveLength(2); + expect(response.body.data.updatePeople[0].id).toBe(personId1); + expect(response.body.data.updatePeople[1].id).toBe(personId2); + expect(response.body.data.updatePeople[0].jobTitle).toBe( + 'Product Manager', + ); + expect(response.body.data.updatePeople[1].jobTitle).toBe( + 'Product Manager', + ); + }); }); });