[permission] Override query builders db-executing methods (#11714)

closes https://github.com/twentyhq/core-team-issues/issues/843
This commit is contained in:
Marie
2025-04-24 18:20:21 +02:00
committed by GitHub
parent e55ecb4dcd
commit e750ef28a1
16 changed files with 1546 additions and 408 deletions

View File

@ -69,6 +69,7 @@ export const validateQueryIsPermittedOrThrow = (
); );
} }
break; break;
case 'restore':
case 'soft-delete': case 'soft-delete':
if (!permissionsForEntity?.canSoftDelete) { if (!permissionsForEntity?.canSoftDelete) {
throw new PermissionsException( throw new PermissionsException(

View File

@ -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<T> {
private objectRecordsPermissions: ObjectRecordsPermissions;
private shouldBypassPermissionChecks: boolean;
private internalContext: WorkspaceInternalContext;
constructor(
queryBuilder: DeleteQueryBuilder<T>,
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<DeleteResult> {
validateQueryIsPermittedOrThrow(
this.expressionMap,
this.objectRecordsPermissions,
this.internalContext.objectMetadataMaps,
this.shouldBypassPermissionChecks,
);
return super.execute();
}
override select(): WorkspaceSelectQueryBuilder<T> {
throw new Error('This builder cannot morph into a select builder');
}
override update(): WorkspaceUpdateQueryBuilder<T>;
override update(
updateSet: QueryDeepPartialEntity<T>,
): WorkspaceUpdateQueryBuilder<T>;
override update(
_updateSet?: QueryDeepPartialEntity<T>,
): WorkspaceUpdateQueryBuilder<T> {
throw new Error('This builder cannot morph into an update builder');
}
override insert(): InsertQueryBuilder<T> {
throw new Error('This builder cannot morph into an insert builder');
}
override softDelete(): WorkspaceSoftDeleteQueryBuilder<T> {
throw new Error('This builder cannot morph into a soft delete builder');
}
override restore(): WorkspaceSoftDeleteQueryBuilder<T> {
throw new Error('This builder cannot morph into a soft delete builder');
}
}

View File

@ -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<T> {
private objectRecordsPermissions: ObjectRecordsPermissions;
private shouldBypassPermissionChecks: boolean;
private internalContext: WorkspaceInternalContext;
constructor(
queryBuilder: InsertQueryBuilder<T>,
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<any> {
validateQueryIsPermittedOrThrow(
this.expressionMap,
this.objectRecordsPermissions,
this.internalContext.objectMetadataMaps,
this.shouldBypassPermissionChecks,
);
return super.execute();
}
override select(): WorkspaceSelectQueryBuilder<T> {
throw new Error('This builder cannot morph into a select builder');
}
override update(): WorkspaceUpdateQueryBuilder<T> {
throw new Error('This builder cannot morph into an update builder');
}
override delete(): WorkspaceDeleteQueryBuilder<T> {
throw new Error('This builder cannot morph into a delete builder');
}
override softDelete(): WorkspaceSoftDeleteQueryBuilder<T> {
throw new Error('This builder cannot morph into a soft delete builder');
}
override restore(): WorkspaceSoftDeleteQueryBuilder<T> {
throw new Error('This builder cannot morph into a soft delete builder');
}
}

View File

@ -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<T> {
constructor(
queryBuilder: SelectQueryBuilder<T>,
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;
}
}

View File

@ -1,10 +1,12 @@
import { ObjectRecordsPermissions } from 'twenty-shared/types'; 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 { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util'; 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'; import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
export class WorkspaceSelectQueryBuilder< export class WorkspaceSelectQueryBuilder<
@ -25,6 +27,29 @@ export class WorkspaceSelectQueryBuilder<
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; 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<T[]> {
this.validatePermissions();
return super.execute();
}
override getMany(): Promise<T[]> {
this.validatePermissions();
return super.getMany();
}
override update(): WorkspaceUpdateQueryBuilder<T>; override update(): WorkspaceUpdateQueryBuilder<T>;
override update( override update(
@ -33,7 +58,7 @@ export class WorkspaceSelectQueryBuilder<
override update( override update(
updateSet?: QueryDeepPartialEntity<T>, updateSet?: QueryDeepPartialEntity<T>,
): UpdateQueryBuilder<T> { ): WorkspaceUpdateQueryBuilder<T> {
const updateQueryBuilder = updateSet const updateQueryBuilder = updateSet
? super.update(updateSet) ? super.update(updateSet)
: super.update(); : super.update();
@ -46,25 +71,45 @@ export class WorkspaceSelectQueryBuilder<
); );
} }
override execute(): Promise<T[]> { override delete(): WorkspaceDeleteQueryBuilder<T> {
validateQueryIsPermittedOrThrow( const deleteQueryBuilder = super.delete();
this.expressionMap,
return new WorkspaceDeleteQueryBuilder<T>(
deleteQueryBuilder,
this.objectRecordsPermissions, this.objectRecordsPermissions,
this.internalContext.objectMetadataMaps, this.internalContext,
this.shouldBypassPermissionChecks, this.shouldBypassPermissionChecks,
); );
return super.execute();
} }
override getMany(): Promise<T[]> { override softDelete(): WorkspaceSoftDeleteQueryBuilder<T> {
const softDeleteQueryBuilder = super.softDelete();
return new WorkspaceSoftDeleteQueryBuilder<T>(
softDeleteQueryBuilder,
this.objectRecordsPermissions,
this.internalContext,
this.shouldBypassPermissionChecks,
);
}
override restore(): WorkspaceSoftDeleteQueryBuilder<T> {
const restoreQueryBuilder = super.restore();
return new WorkspaceSoftDeleteQueryBuilder<T>(
restoreQueryBuilder,
this.objectRecordsPermissions,
this.internalContext,
this.shouldBypassPermissionChecks,
);
}
private validatePermissions(): void {
validateQueryIsPermittedOrThrow( validateQueryIsPermittedOrThrow(
this.expressionMap, this.expressionMap,
this.objectRecordsPermissions, this.objectRecordsPermissions,
this.internalContext.objectMetadataMaps, this.internalContext.objectMetadataMaps,
this.shouldBypassPermissionChecks, this.shouldBypassPermissionChecks,
); );
return super.getMany();
} }
} }

View File

@ -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<T> {
private objectRecordsPermissions: ObjectRecordsPermissions;
private shouldBypassPermissionChecks: boolean;
private internalContext: WorkspaceInternalContext;
constructor(
queryBuilder: SoftDeleteQueryBuilder<T>,
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<UpdateResult> {
validateQueryIsPermittedOrThrow(
this.expressionMap,
this.objectRecordsPermissions,
this.internalContext.objectMetadataMaps,
this.shouldBypassPermissionChecks,
);
return super.execute();
}
override select(): WorkspaceSelectQueryBuilder<T> {
throw new Error('This builder cannot morph into a select builder');
}
override update(): WorkspaceUpdateQueryBuilder<T> {
throw new Error('This builder cannot morph into an update builder');
}
override insert(): InsertQueryBuilder<T> {
throw new Error('This builder cannot morph into an insert builder');
}
override delete(): WorkspaceDeleteQueryBuilder<T> {
throw new Error('This builder cannot morph into a delete builder');
}
}

View File

@ -4,15 +4,18 @@ import { ObjectLiteral, UpdateQueryBuilder, UpdateResult } from 'typeorm';
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util'; 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< export class WorkspaceUpdateQueryBuilder<
Entity extends ObjectLiteral, T extends ObjectLiteral,
> extends UpdateQueryBuilder<Entity> { > extends UpdateQueryBuilder<T> {
private objectRecordsPermissions: ObjectRecordsPermissions; private objectRecordsPermissions: ObjectRecordsPermissions;
private shouldBypassPermissionChecks: boolean; private shouldBypassPermissionChecks: boolean;
private internalContext: WorkspaceInternalContext; private internalContext: WorkspaceInternalContext;
constructor( constructor(
queryBuilder: UpdateQueryBuilder<Entity>, queryBuilder: UpdateQueryBuilder<T>,
objectRecordsPermissions: ObjectRecordsPermissions, objectRecordsPermissions: ObjectRecordsPermissions,
internalContext: WorkspaceInternalContext, internalContext: WorkspaceInternalContext,
shouldBypassPermissionChecks: boolean, shouldBypassPermissionChecks: boolean,
@ -23,6 +26,17 @@ export class WorkspaceUpdateQueryBuilder<
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; 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<UpdateResult> { override execute(): Promise<UpdateResult> {
validateQueryIsPermittedOrThrow( validateQueryIsPermittedOrThrow(
this.expressionMap, this.expressionMap,
@ -33,4 +47,20 @@ export class WorkspaceUpdateQueryBuilder<
return super.execute(); return super.execute();
} }
override select(): WorkspaceSelectQueryBuilder<T> {
throw new Error('This builder cannot morph into a select builder');
}
override delete(): WorkspaceDeleteQueryBuilder<T> {
throw new Error('This builder cannot morph into a delete builder');
}
override softDelete(): WorkspaceSoftDeleteQueryBuilder<T> {
throw new Error('This builder cannot morph into a soft delete builder');
}
override restore(): WorkspaceSoftDeleteQueryBuilder<T> {
throw new Error('This builder cannot morph into a soft delete builder');
}
} }

View File

@ -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 { 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 { 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 { 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 { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspace-entities.storage';
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@ -58,11 +58,11 @@ export class WorkspaceRepository<
override createQueryBuilder<U extends T>( override createQueryBuilder<U extends T>(
alias?: string, alias?: string,
queryRunner?: QueryRunner, queryRunner?: QueryRunner,
): WorkspaceQueryBuilder<U> { ): WorkspaceSelectQueryBuilder<U> {
const queryBuilder = super.createQueryBuilder( const queryBuilder = super.createQueryBuilder(
alias, alias,
queryRunner, queryRunner,
) as unknown as WorkspaceQueryBuilder<U>; ) as unknown as WorkspaceSelectQueryBuilder<U>;
const isPermissionsV2Enabled = const isPermissionsV2Enabled =
this.featureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled]; this.featureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled];
@ -73,7 +73,7 @@ export class WorkspaceRepository<
throw new Error('Object records permissions are required'); throw new Error('Object records permissions are required');
} }
return new WorkspaceQueryBuilder( return new WorkspaceSelectQueryBuilder(
queryBuilder, queryBuilder,
this.objectRecordsPermissions, this.objectRecordsPermissions,
this.internalContext, this.internalContext,

View File

@ -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'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('createManyObjectRecordsPermissions', () => { describe('createManyObjectRecordsPermissions', () => {
it('should throw a permission error when user does not have permission (guest role)', async () => { describe('permissions V2 disabled', () => {
const graphqlOperation = createManyOperationFactory({ it('should throw a permission error when user does not have permission (guest role)', async () => {
objectMetadataSingularName: 'person', const graphqlOperation = createManyOperationFactory({
objectMetadataPluralName: 'people', objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS, objectMetadataPluralName: 'people',
data: [ gqlFields: PERSON_GQL_FIELDS,
{ data: [
id: randomUUID(), {
}, id: randomUUID(),
{ },
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 }); const graphqlOperation = createManyOperationFactory({
expect(response.body.errors).toBeDefined(); objectMetadataSingularName: 'person',
expect(response.body.errors[0].message).toBe( objectMetadataPluralName: 'people',
PermissionsExceptionMessage.PERMISSION_DENIED, gqlFields: PERSON_GQL_FIELDS,
); data: [
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN); {
}); id: personId1,
},
{
id: personId2,
},
],
});
it('should create multiple object records when user has permission (admin role)', async () => { const response = await makeGraphqlAPIRequest(graphqlOperation);
const personId1 = randomUUID();
const personId2 = randomUUID();
const graphqlOperation = createManyOperationFactory({ expect(response.body.data).toBeDefined();
objectMetadataSingularName: 'person', expect(response.body.data.createPeople).toBeDefined();
objectMetadataPluralName: 'people', expect(response.body.data.createPeople).toHaveLength(2);
gqlFields: PERSON_GQL_FIELDS, expect(response.body.data.createPeople[0].id).toBe(personId1);
data: [ expect(response.body.data.createPeople[1].id).toBe(personId2);
{
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);
}); });
// 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);
// });
// });
}); });

View File

@ -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'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('createOneObjectRecordsPermissions', () => { describe('createOneObjectRecordsPermissions', () => {
it('should throw a permission error when user does not have permission (guest role)', async () => { describe('permissions V2 disabled', () => {
const graphqlOperation = createOneOperationFactory({ it('should throw a permission error when user does not have permission (guest role)', async () => {
objectMetadataSingularName: 'person', const graphqlOperation = createOneOperationFactory({
gqlFields: PERSON_GQL_FIELDS, objectMetadataSingularName: 'person',
data: { gqlFields: PERSON_GQL_FIELDS,
id: randomUUID(), 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 }); const response = await makeGraphqlAPIRequest(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);
});
it('should create an object record when user has permission (admin role)', async () => { expect(response.body.data).toBeDefined();
const personId = randomUUID(); expect(response.body.data.createPerson).toBeDefined();
const graphqlOperation = createOneOperationFactory({ expect(response.body.data.createPerson.id).toBe(personId);
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);
}); });
// 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);
// });
// });
}); });

View File

@ -3,72 +3,205 @@ import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants'; 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 { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util';
import { deleteManyOperationFactory } from 'test/integration/graphql/utils/delete-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 { 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 { 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 { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('deleteManyObjectRecordsPermissions', () => { describe('deleteManyObjectRecordsPermissions', () => {
it('should throw a permission error when user does not have permission (guest role)', async () => { describe('permissions V2 disabled', () => {
const graphqlOperation = deleteManyOperationFactory({ it('should throw a permission error when user does not have permission (guest role)', async () => {
objectMetadataSingularName: 'person', const graphqlOperation = deleteManyOperationFactory({
objectMetadataPluralName: 'people', objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS, objectMetadataPluralName: 'people',
filter: { gqlFields: PERSON_GQL_FIELDS,
id: { filter: {
in: [randomUUID(), randomUUID()], 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 }); const createGraphqlOperation = createManyOperationFactory({
expect(response.body.errors).toBeDefined(); objectMetadataSingularName: 'person',
expect(response.body.errors[0].message).toBe( objectMetadataPluralName: 'people',
PermissionsExceptionMessage.PERMISSION_DENIED, gqlFields: PERSON_GQL_FIELDS,
); data: [
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN); {
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 () => { describe('permissions V2 enabled', () => {
const personId1 = randomUUID(); beforeAll(async () => {
const personId2 = randomUUID(); const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsV2Enabled',
true,
);
const createGraphqlOperation = createManyOperationFactory({ await makeGraphqlAPIRequest(enablePermissionsQuery);
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
id: personId1,
},
{
id: personId2,
},
],
}); });
await makeGraphqlAPIRequest(createGraphqlOperation); afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsV2Enabled',
false,
);
const deleteGraphqlOperation = deleteManyOperationFactory({ await makeGraphqlAPIRequest(disablePermissionsQuery);
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
filter: {
id: {
in: [personId1, personId2],
},
},
}); });
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(); const response =
expect(response.body.data.deletePeople).toBeDefined(); await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.data.deletePeople).toHaveLength(2);
expect(response.body.data.deletePeople[0].id).toBe(personId1); expect(response.body.data).toStrictEqual({ deletePeople: null });
expect(response.body.data.deletePeople[1].id).toBe(personId2); 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);
});
}); });
}); });

View File

@ -3,56 +3,178 @@ import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants'; 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 { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
import { deleteOneOperationFactory } from 'test/integration/graphql/utils/delete-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 { 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 { 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 { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('deleteOneObjectRecordsPermissions', () => { describe('deleteOneObjectRecordsPermissions', () => {
const personId = randomUUID(); describe('permissions V2 disabled', () => {
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 () => {
const personId = randomUUID(); const personId = randomUUID();
const graphqlOperation = deleteOneOperationFactory({
objectMetadataSingularName: 'person', beforeAll(async () => {
gqlFields: PERSON_GQL_FIELDS, const createOnePersonRecordOperation = createOneOperationFactory({
recordId: personId, 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 }); const response =
expect(response.body.errors).toBeDefined(); await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED, expect(response.body.data).toStrictEqual({ deletePerson: null });
); expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN); 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 () => { describe('permissions V2 enabled', () => {
const deleteGraphqlOperation = deleteOneOperationFactory({ const personId = randomUUID();
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS, beforeAll(async () => {
recordId: personId, 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(); await makeGraphqlAPIRequest(disablePermissionsQuery);
expect(response.body.data.deletePerson).toBeDefined(); });
expect(response.body.data.deletePerson.id).toBe(personId);
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);
});
}); });
}); });

View File

@ -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 { 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 { 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 { 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 { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('destroyManyObjectRecordsPermissions', () => { describe('destroyManyObjectRecordsPermissions', () => {
it('should throw a permission error when user does not have permission (guest role)', async () => { describe('permissions V2 disabled', () => {
const graphqlOperation = destroyManyOperationFactory({ it('should throw a permission error when user does not have permission (guest role)', async () => {
objectMetadataSingularName: 'person', const graphqlOperation = destroyManyOperationFactory({
objectMetadataPluralName: 'people', objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS, objectMetadataPluralName: 'people',
filter: { gqlFields: PERSON_GQL_FIELDS,
id: { filter: {
in: [randomUUID(), randomUUID()], 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 }); const createGraphqlOperation = createManyOperationFactory({
expect(response.body.errors).toBeDefined(); objectMetadataSingularName: 'person',
expect(response.body.errors[0].message).toBe( objectMetadataPluralName: 'people',
PermissionsExceptionMessage.PERMISSION_DENIED, gqlFields: PERSON_GQL_FIELDS,
); data: [
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN); {
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 () => { describe('permissions V2 enabled', () => {
const personId1 = randomUUID(); beforeAll(async () => {
const personId2 = randomUUID(); const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsV2Enabled',
true,
);
const createGraphqlOperation = createManyOperationFactory({ await makeGraphqlAPIRequest(enablePermissionsQuery);
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
id: personId1,
},
{
id: personId2,
},
],
}); });
await makeGraphqlAPIRequest(createGraphqlOperation); afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsV2Enabled',
false,
);
const graphqlOperation = destroyManyOperationFactory({ await makeGraphqlAPIRequest(disablePermissionsQuery);
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
filter: {
id: {
in: [personId1, personId2],
},
},
}); });
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(); const response =
expect(response.body.data.destroyPeople).toBeDefined(); await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.data.destroyPeople).toHaveLength(2);
expect(response.body.data.destroyPeople[0].id).toBe(personId1); expect(response.body.data).toStrictEqual({ destroyPeople: null });
expect(response.body.data.destroyPeople[1].id).toBe(personId2); 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);
});
}); });
}); });

View File

@ -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 { 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 { 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 { 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 { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('destroyOneObjectRecordsPermissions', () => { describe('destroyOneObjectRecordsPermissions', () => {
const personId = randomUUID(); describe('permissions V2 disabled', () => {
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 () => {
const personId = randomUUID(); const personId = randomUUID();
const graphqlOperation = destroyOneOperationFactory({
objectMetadataSingularName: 'person', beforeAll(async () => {
gqlFields: PERSON_GQL_FIELDS, const createGraphqlOperation = createOneOperationFactory({
recordId: personId, 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 }); const response =
expect(response.body.errors).toBeDefined(); await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED, expect(response.body.data).toStrictEqual({ destroyPerson: null });
); expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN); 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 () => { describe('permissions V2 enabled', () => {
const graphqlOperation = destroyOneOperationFactory({ const personId = randomUUID();
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS, beforeAll(async () => {
recordId: personId, 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(); await makeGraphqlAPIRequest(disablePermissionsQuery);
expect(response.body.data.destroyPerson).toBeDefined(); });
expect(response.body.data.destroyPerson.id).toBe(personId);
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);
});
}); });
}); });

View File

@ -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 { 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 { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import { restoreManyOperationFactory } from 'test/integration/graphql/utils/restore-many-operation-factory.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 { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('restoreManyObjectRecordsPermissions', () => { describe('restoreManyObjectRecordsPermissions', () => {
const personId1 = randomUUID(); describe('permissions V2 disabled', () => {
const personId2 = randomUUID(); const personId1 = randomUUID();
const personId2 = randomUUID();
beforeAll(async () => { beforeAll(async () => {
// Create people // Create people
const createGraphqlOperation = createManyOperationFactory({ const createGraphqlOperation = createManyOperationFactory({
objectMetadataSingularName: 'person', objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people', objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS, gqlFields: PERSON_GQL_FIELDS,
data: [ data: [
{ {
id: personId1, 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); it('should throw a permission error when user does not have permission (guest role)', async () => {
const graphqlOperation = restoreManyOperationFactory({
// Delete people objectMetadataSingularName: 'person',
const deleteGraphqlOperation = deleteManyOperationFactory({ objectMetadataPluralName: 'people',
objectMetadataSingularName: 'person', gqlFields: PERSON_GQL_FIELDS,
objectMetadataPluralName: 'people', filter: {
gqlFields: PERSON_GQL_FIELDS, id: {
filter: { in: [personId1, personId2],
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 () => { describe('permissions V2 enabled', () => {
const graphqlOperation = restoreManyOperationFactory({ const personId1 = randomUUID();
objectMetadataSingularName: 'person', const personId2 = randomUUID();
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS, beforeAll(async () => {
filter: { // Create people
id: { const createGraphqlOperation = createManyOperationFactory({
in: [personId1, personId2], 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 }); await makeGraphqlAPIRequest(disablePermissionsQuery);
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); 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(); const response =
expect(response.body.data.restorePeople).toBeDefined(); await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.data.restorePeople).toHaveLength(2);
expect(response.body.data.restorePeople[0].id).toBe(personId1); expect(response.body.data).toStrictEqual({ restorePeople: null });
expect(response.body.data.restorePeople[1].id).toBe(personId2); 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);
});
}); });
}); });

View File

@ -2,81 +2,248 @@ import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants'; 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 { 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 { 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 { 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 { 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 { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('updateManyObjectRecordsPermissions', () => { describe('updateManyObjectRecordsPermissions', () => {
const personId1 = randomUUID(); describe('permissions V2 disabled', () => {
const personId2 = randomUUID(); const personId1 = randomUUID();
const personId2 = randomUUID();
beforeAll(async () => { beforeAll(async () => {
const createGraphqlOperation = createManyOperationFactory({ const createGraphqlOperation = createManyOperationFactory({
objectMetadataSingularName: 'person', objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people', objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS, gqlFields: PERSON_GQL_FIELDS,
data: [ data: [
{ {
id: personId1, id: personId1,
}, },
{ {
id: personId2, 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 () => { describe('permissions V2 enabled', () => {
const graphqlOperation = updateManyOperationFactory({ beforeAll(async () => {
objectMetadataSingularName: 'person', const enablePermissionsQuery = updateFeatureFlagFactory(
objectMetadataPluralName: 'people', SEED_APPLE_WORKSPACE_ID,
gqlFields: PERSON_GQL_FIELDS, 'IsPermissionsV2Enabled',
filter: { true,
id: { );
in: [randomUUID(), randomUUID()],
}, await makeGraphqlAPIRequest(enablePermissionsQuery);
},
data: {
jobTitle: 'Architect',
},
}); });
const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsV2Enabled',
false,
);
expect(response.body.data).toStrictEqual({ updatePeople: null }); await makeGraphqlAPIRequest(disablePermissionsQuery);
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); 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(); await makeGraphqlAPIRequest(createGraphqlOperation);
expect(response.body.data.updatePeople).toBeDefined();
expect(response.body.data.updatePeople).toHaveLength(2); const updateGraphqlOperation = updateManyOperationFactory({
expect(response.body.data.updatePeople[0].jobTitle).toBe('Architect'); objectMetadataSingularName: 'person',
expect(response.body.data.updatePeople[1].jobTitle).toBe('Architect'); 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',
);
});
}); });
}); });