[permissions - seeds] Give tim@apple.dev restricted rights (#12768)

Let's introduce an object-limited role for Tim, to test and/or spot
incompatibilities with restricted permissions in the future.
Our main user tim@apple.dev is now assigned a role that has all settings
permissions, and all object permissions except for update on Pets (to
test read-only view) and read on Rockets.
Since we still need an admin user for each workspace we are introducing
a new member, Jane, who has the admin role

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Marie
2025-06-23 15:46:53 +02:00
committed by GitHub
parent 8f0c9facf2
commit 2cb2f528df
12 changed files with 246 additions and 108 deletions

View File

@ -97,7 +97,7 @@ export const mockedTimelineActivities: Array<TimelineActivity> = [
createdAt: '2023-04-26T10:12:42.33625+00:00',
workspaceMember: {
__typename: 'WorkspaceMember',
id: '20202020-1553-45c6-a028-5a9064cce07f',
id: '20202020-463f-435b-828c-107e007a2711',
avatarUrl: '',
locale: 'en',
name: {
@ -108,7 +108,7 @@ export const mockedTimelineActivities: Array<TimelineActivity> = [
userEmail: 'jane@doe.com',
colorScheme: 'Light',
},
workspaceMemberId: '20202020-1553-45c6-a028-5a9064cce07f',
workspaceMemberId: '20202020-463f-435b-828c-107e007a2711',
deletedAt: null,
__typename: 'TimelineActivity',
},

View File

@ -3,7 +3,7 @@ import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
export const mockWorkspaceMembers: WorkspaceMember[] = [
{
id: '20202020-1553-45c6-a028-5a9064cce07f',
id: '20202020-463f-435b-828c-107e007a2711',
name: {
firstName: 'Jane',
lastName: 'Doe',

View File

@ -75,7 +75,7 @@ const jestConfig: JestConfigWithTsJest = {
APP_PORT: 4000,
NODE_ENV: NodeEnvironment.TEST,
ADMIN_ACCESS_TOKEN:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMDY4Ny00YzQxLWI3MDctZWQxYmZjYTk3MmE3IiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtOWUzYi00NmQ0LWE1NTYtODhiOWRkYzJiMDM1IiwiaWF0IjoxNzM5NTQ3NjYxLCJleHAiOjMzMjk3MTQ3NjYxfQ.fbOM9yhr3jWDicPZ1n771usUURiPGmNdeFApsgrbxOw',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUwNDEyODczLCJleHAiOjE3NTEyNzY4NzN9.cUq9Q_ugJWzP_REUVq5XYpYz9y_yPI-yRIPo5PjvT1k',
EXPIRED_ACCESS_TOKEN:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMDY4Ny00YzQxLWI3MDctZWQxYmZjYTk3MmE3IiwiaWF0IjoxNzM4MzIzODc5LCJleHAiOjE3MzgzMjU2Nzl9.m73hHVpnw5uGNGrSuKxn6XtKEUK3Wqkp4HsQdYfZiHo',
INVALID_ACCESS_TOKEN:

View File

@ -92,71 +92,6 @@ export class UserService extends TypeOrmQueryService<User> {
});
}
private async deleteUserFromWorkspace({
userId,
workspaceId,
}: {
userId: string;
workspaceId: string;
}) {
const workspaceMemberRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
);
const workspaceMembers = await workspaceMemberRepository.find();
if (workspaceMembers.length > 1) {
const userWorkspace =
await this.userWorkspaceService.getUserWorkspaceForUserOrThrow({
userId,
workspaceId,
});
await this.userRoleService.validateUserWorkspaceIsNotUniqueAdminOrThrow({
workspaceId,
userWorkspaceId: userWorkspace.id,
});
}
const workspaceMember = workspaceMembers.filter(
(member: WorkspaceMemberWorkspaceEntity) => member.userId === userId,
)?.[0];
assert(workspaceMember, 'WorkspaceMember not found');
await workspaceMemberRepository.delete({ userId });
const objectMetadata = await this.objectMetadataRepository.findOneOrFail({
where: {
nameSingular: 'workspaceMember',
workspaceId,
},
});
if (workspaceMembers.length === 1) {
await this.workspaceService.deleteWorkspace(workspaceId);
return;
}
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'workspaceMember',
action: DatabaseEventAction.DELETED,
events: [
{
recordId: workspaceMember.id,
objectMetadata,
properties: {
before: workspaceMember,
},
},
],
workspaceId,
});
}
async deleteUser(userId: string): Promise<User> {
const user = await this.userRepository.findOne({
where: {
@ -167,29 +102,97 @@ export class UserService extends TypeOrmQueryService<User> {
userValidator.assertIsDefinedOrThrow(user);
await Promise.all(
const prepareForUserDeletionInWorkspaces = await Promise.all(
user.workspaces.map(async (userWorkspace) => {
try {
await this.deleteUserFromWorkspace({
userId,
workspaceId: userWorkspace.workspaceId,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (
error instanceof PermissionsException &&
error.code === PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN
) {
throw new PermissionsException(
PermissionsExceptionMessage.CANNOT_DELETE_LAST_ADMIN_USER,
PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER,
const { workspaceId } = userWorkspace;
const workspaceMemberRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
);
const workspaceMembers = await workspaceMemberRepository.find();
if (workspaceMembers.length > 1) {
try {
await this.userRoleService.validateUserWorkspaceIsNotUniqueAdminOrThrow(
{
workspaceId,
userWorkspaceId: userWorkspace.id,
},
);
} catch (error) {
if (
error instanceof PermissionsException &&
error.code === PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN
) {
throw new PermissionsException(
PermissionsExceptionMessage.CANNOT_DELETE_LAST_ADMIN_USER,
PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER,
);
}
throw error;
}
throw error;
}
const workspaceMember = workspaceMembers.find(
(member: WorkspaceMemberWorkspaceEntity) => member.userId === userId,
);
assert(workspaceMember, 'WorkspaceMember not found');
return {
workspaceId,
workspaceMemberRepository,
workspaceMembers,
workspaceMember,
};
}),
);
await Promise.all(
prepareForUserDeletionInWorkspaces.map(
async ({
workspaceId,
workspaceMemberRepository,
workspaceMembers,
workspaceMember,
}) => {
await workspaceMemberRepository.delete({ userId });
const objectMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: {
nameSingular: 'workspaceMember',
workspaceId,
},
});
if (workspaceMembers.length === 1) {
await this.workspaceService.deleteWorkspace(workspaceId);
return;
}
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'workspaceMember',
action: DatabaseEventAction.DELETED,
events: [
{
recordId: workspaceMember.id,
objectMetadata,
properties: {
before: workspaceMember,
},
},
],
workspaceId,
});
},
),
);
return user;
}

View File

@ -5,6 +5,8 @@ import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { Repository } from 'typeorm';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { USER_WORKSPACE_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-user-workspaces.util';
@ -22,6 +24,9 @@ export class DevSeederPermissionsService {
private readonly userRoleService: UserRoleService,
@InjectRepository(Workspace, 'core')
private readonly workspaceRepository: Repository<Workspace>,
private readonly objectPermissionService: ObjectPermissionService,
@InjectRepository(ObjectMetadataEntity, 'core')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
) {}
public async initPermissions(workspaceId: string) {
@ -30,11 +35,15 @@ export class DevSeederPermissionsService {
});
let adminUserWorkspaceId: string | undefined;
let memberUserWorkspaceId: string | undefined;
let memberUserWorkspaceIds: string[] = [];
let limitedUserWorkspaceId: string | undefined;
let guestUserWorkspaceId: string | undefined;
if (workspaceId === SEED_APPLE_WORKSPACE_ID) {
adminUserWorkspaceId = USER_WORKSPACE_DATA_SEED_IDS.TIM;
memberUserWorkspaceId = USER_WORKSPACE_DATA_SEED_IDS.JONY;
adminUserWorkspaceId = USER_WORKSPACE_DATA_SEED_IDS.JANE;
limitedUserWorkspaceId = USER_WORKSPACE_DATA_SEED_IDS.TIM;
memberUserWorkspaceIds = [USER_WORKSPACE_DATA_SEED_IDS.JONY];
guestUserWorkspaceId = USER_WORKSPACE_DATA_SEED_IDS.PHIL;
// Create guest role only in this workspace
const guestRole = await this.roleService.createGuestRole({
@ -43,11 +52,25 @@ export class DevSeederPermissionsService {
await this.userRoleService.assignRoleToUserWorkspace({
workspaceId,
userWorkspaceId: USER_WORKSPACE_DATA_SEED_IDS.PHIL,
userWorkspaceId: guestUserWorkspaceId,
roleId: guestRole.id,
});
const limitedRole =
await this.createLimitedRoleForSeedWorkspace(workspaceId);
await this.userRoleService.assignRoleToUserWorkspace({
workspaceId,
userWorkspaceId: limitedUserWorkspaceId,
roleId: limitedRole.id,
});
} else if (workspaceId === SEED_YCOMBINATOR_WORKSPACE_ID) {
adminUserWorkspaceId = USER_WORKSPACE_DATA_SEED_IDS.TIM_ACME;
memberUserWorkspaceIds = [
USER_WORKSPACE_DATA_SEED_IDS.JONY_ACME,
USER_WORKSPACE_DATA_SEED_IDS.JANE_ACME,
USER_WORKSPACE_DATA_SEED_IDS.PHIL_ACME,
];
}
if (adminUserWorkspaceId) {
@ -67,12 +90,73 @@ export class DevSeederPermissionsService {
activationStatus: WorkspaceActivationStatus.ACTIVE,
});
if (memberUserWorkspaceId) {
await this.userRoleService.assignRoleToUserWorkspace({
workspaceId,
userWorkspaceId: memberUserWorkspaceId,
roleId: memberRole.id,
});
if (memberUserWorkspaceIds) {
for (const memberUserWorkspaceId of memberUserWorkspaceIds) {
await this.userRoleService.assignRoleToUserWorkspace({
workspaceId,
userWorkspaceId: memberUserWorkspaceId,
roleId: memberRole.id,
});
}
}
}
private async createLimitedRoleForSeedWorkspace(workspaceId: string) {
const customRole = await this.roleService.createRole({
workspaceId,
input: {
label: 'Object-restricted',
description:
'All permissions except read on Rockets and update on Pets',
icon: 'custom',
canUpdateAllSettings: true,
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: true,
canSoftDeleteAllObjectRecords: true,
canDestroyAllObjectRecords: true,
},
});
const petObjectMetadata = await this.objectMetadataRepository.findOneOrFail(
{
where: {
nameSingular: 'pet',
workspaceId,
},
},
);
const rocketObjectMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: {
nameSingular: 'rocket',
workspaceId,
},
});
await this.objectPermissionService.upsertObjectPermissions({
workspaceId,
input: {
roleId: customRole.id,
objectPermissions: [
{
objectMetadataId: petObjectMetadata.id,
canReadObjectRecords: true,
canUpdateObjectRecords: false,
canSoftDeleteObjectRecords: false,
canDestroyObjectRecords: false,
},
{
objectMetadataId: rocketObjectMetadata.id,
canReadObjectRecords: false,
canUpdateObjectRecords: false,
canSoftDeleteObjectRecords: false,
canDestroyObjectRecords: false,
},
],
},
});
return customRole;
}
}

View File

@ -10,9 +10,11 @@ import {
const tableName = 'userWorkspace';
export const USER_WORKSPACE_DATA_SEED_IDS = {
JANE: '20202020-1e7c-43d9-a5db-685b5069d816',
TIM: '20202020-9e3b-46d4-a556-88b9ddc2b035',
JONY: '20202020-3957-4908-9c36-2929a23f8353',
PHIL: '20202020-7169-42cf-bc47-1cfef15264b1',
JANE_ACME: '20202020-ae8d-41ea-9469-f74f5d4b002e',
TIM_ACME: '20202020-e10a-4c27-a90b-b08c57b02d44',
JONY_ACME: '20202020-e10a-4c27-a90b-b08c57b02d45',
PHIL_ACME: '20202020-e10a-4c27-a90b-b08c57b02d46',
@ -33,6 +35,11 @@ export const seedUserWorkspaces = async (
userId: USER_DATA_SEED_IDS.TIM,
workspaceId,
},
{
id: USER_WORKSPACE_DATA_SEED_IDS.JANE,
userId: USER_DATA_SEED_IDS.JANE,
workspaceId,
},
{
id: USER_WORKSPACE_DATA_SEED_IDS.JONY,
userId: USER_DATA_SEED_IDS.JONY,
@ -63,6 +70,11 @@ export const seedUserWorkspaces = async (
userId: USER_DATA_SEED_IDS.PHIL,
workspaceId,
},
{
id: USER_WORKSPACE_DATA_SEED_IDS.JANE_ACME,
userId: USER_DATA_SEED_IDS.JANE,
workspaceId,
},
];
}
await dataSource

View File

@ -3,6 +3,7 @@ import { DataSource } from 'typeorm';
const tableName = 'user';
export const USER_DATA_SEED_IDS = {
JANE: '20202020-e6b5-4680-8a32-b8209737156b',
TIM: '20202020-9e3b-46d4-a556-88b9ddc2b034',
JONY: '20202020-3957-4908-9c36-2929a23f8357',
PHIL: '20202020-7169-42cf-bc47-1cfef15264b8',
@ -57,6 +58,17 @@ export const seedUsers = async (dataSource: DataSource, schemaName: string) => {
canAccessFullAdminPanel: true,
isEmailVerified: true,
},
{
id: USER_DATA_SEED_IDS.JANE,
firstName: 'Jane',
lastName: 'Austen',
email: 'jane.austen@apple.dev',
passwordHash:
'$2b$10$3LwXjJRtLsfx4hLuuXhxt.3mWgismTiZFCZSG3z9kDrSfsrBl0fT6', // tim@apple.dev
canImpersonate: true,
canAccessFullAdminPanel: true,
isEmailVerified: true,
},
])
.execute();
};

View File

@ -25,6 +25,7 @@ export const WORKSPACE_MEMBER_DATA_SEED_IDS = {
TIM: '20202020-0687-4c41-b707-ed1bfca972a7',
JONY: '20202020-77d5-4cb6-b60a-f4a835a85d61',
PHIL: '20202020-1553-45c6-a028-5a9064cce07f',
JANE: '20202020-463f-435b-828c-107e007a2711',
};
export const WORKSPACE_MEMBER_DATA_SEEDS: WorkspaceMemberDataSeed[] = [
@ -55,4 +56,13 @@ export const WORKSPACE_MEMBER_DATA_SEEDS: WorkspaceMemberDataSeed[] = [
userEmail: 'phil.schiler@apple.dev',
userId: USER_DATA_SEED_IDS.PHIL,
},
{
id: WORKSPACE_MEMBER_DATA_SEED_IDS.JANE,
nameFirstName: 'Jane',
nameLastName: 'Austen',
locale: 'en',
colorScheme: 'Light',
userEmail: 'jane.austen@apple.dev',
userId: USER_DATA_SEED_IDS.JANE,
},
];

View File

@ -6,7 +6,9 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module';
import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
@ -30,7 +32,8 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
UserRoleModule,
FeatureFlagModule,
WorkspaceSyncMetadataModule,
TypeOrmModule.forFeature([Workspace], 'core'),
TypeOrmModule.forFeature([Workspace, ObjectMetadataEntity], 'core'),
ObjectPermissionModule,
],
exports: [DevSeederService],
providers: [

View File

@ -88,7 +88,7 @@ describe('roles permissions', () => {
expect(resp.status).toBe(200);
expect(resp.body.errors).toBeUndefined();
expect(resp.body.data.getRoles).toHaveLength(3);
expect(resp.body.data.getRoles).toHaveLength(4);
expect(resp.body.data.getRoles).toEqual(
expect.arrayContaining([
{
@ -107,10 +107,10 @@ describe('roles permissions', () => {
label: 'Admin',
workspaceMembers: [
{
id: '20202020-0687-4c41-b707-ed1bfca972a7',
id: '20202020-463f-435b-828c-107e007a2711',
name: {
firstName: 'Tim',
lastName: 'Apple',
firstName: 'Jane',
lastName: 'Austen',
},
},
],
@ -127,6 +127,18 @@ describe('roles permissions', () => {
},
],
},
{
label: 'Object-restricted',
workspaceMembers: [
{
id: '20202020-0687-4c41-b707-ed1bfca972a7',
name: {
firstName: 'Tim',
lastName: 'Apple',
},
},
],
},
]),
);
});
@ -171,7 +183,7 @@ describe('roles permissions', () => {
const query = {
query: `
mutation UpdateWorkspaceMemberRole {
updateWorkspaceMemberRole(workspaceMemberId: "${WORKSPACE_MEMBER_DATA_SEED_IDS.TIM}", roleId: "test-role-id") {
updateWorkspaceMemberRole(workspaceMemberId: "${WORKSPACE_MEMBER_DATA_SEED_IDS.JANE}", roleId: "test-role-id") {
id
}
}

View File

@ -1,14 +1,14 @@
import { TEST_COMPANY_1_ID } from 'test/integration/constants/test-company-ids.constants';
import {
TEST_PERSON_1_ID,
TEST_PERSON_2_ID,
} from 'test/integration/constants/test-person-ids.constants';
import { TEST_PRIMARY_LINK_URL } from 'test/integration/constants/test-primary-link-url.constant';
import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util';
import { deleteAllRecords } from 'test/integration/utils/delete-all-records';
import { TEST_COMPANY_1_ID } from 'test/integration/constants/test-company-ids.constants';
import { TEST_PRIMARY_LINK_URL } from 'test/integration/constants/test-primary-link-url.constant';
import { TIM_ACCOUNT_ID } from 'test/integration/graphql/integration.constants';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { WORKSPACE_MEMBER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
describe('Core REST API Create Many endpoint', () => {
beforeEach(async () => {
@ -112,12 +112,12 @@ describe('Core REST API Create Many endpoint', () => {
expect(createdPeople[0].createdBy.source).toBe(FieldActorSource.MANUAL);
expect(createdPeople[0].createdBy.workspaceMemberId).toBe(
TIM_ACCOUNT_ID,
WORKSPACE_MEMBER_DATA_SEED_IDS.JANE,
);
expect(createdPeople[1].createdBy.source).toBe(FieldActorSource.MANUAL);
expect(createdPeople[1].createdBy.workspaceMemberId).toBe(
TIM_ACCOUNT_ID,
WORKSPACE_MEMBER_DATA_SEED_IDS.JANE,
);
});
});

View File

@ -4,12 +4,12 @@ import {
TEST_PRIMARY_LINK_URL,
TEST_PRIMARY_LINK_URL_WIITHOUT_TRAILING_SLASH,
} from 'test/integration/constants/test-primary-link-url.constant';
import { TIM_ACCOUNT_ID } from 'test/integration/graphql/integration.constants';
import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util';
import { deleteAllRecords } from 'test/integration/utils/delete-all-records';
import { generateRecordName } from 'test/integration/utils/generate-record-name';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { WORKSPACE_MEMBER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
describe('Core REST API Create One endpoint', () => {
beforeEach(async () => {
@ -94,7 +94,9 @@ describe('Core REST API Create One endpoint', () => {
const createdPerson = res.body.data.createPerson;
expect(createdPerson.createdBy.source).toBe(FieldActorSource.MANUAL);
expect(createdPerson.createdBy.workspaceMemberId).toBe(TIM_ACCOUNT_ID);
expect(createdPerson.createdBy.workspaceMemberId).toBe(
WORKSPACE_MEMBER_DATA_SEED_IDS.JANE,
);
});
});