From 6f2ff06a35db7c5ec4419182408d8fac92357173 Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:47:31 +0100 Subject: [PATCH] Refactor workspace creation (#16689) # Introduction Created an env variable to will inject a feature flag during any new workspace init to be created through v2 --- .../src/generated-metadata/graphql.ts | 3 +- .../twenty-front/src/generated/graphql.ts | 3 +- packages/twenty-server/.env.test | 1 + .../enums/feature-flag-key.enum.ts | 1 + .../twenty-config/config-variables.ts | 10 + .../workspace/services/workspace.service.ts | 12 +- .../workspace-entity-manager.spec.ts | 2 +- .../workspace-manager.service.spec.ts | 7 + .../workspace-manager.module.ts | 2 + .../workspace-manager.service.ts | 79 +++-- ...-workspace-creation-v1.integration-spec.ts | 281 ++++++++++++++++++ 11 files changed, 368 insertions(+), 33 deletions(-) create mode 100644 packages/twenty-server/test/integration/graphql/suites/workspace/successful-user-and-workspace-creation-v1.integration-spec.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 575df342bf..21df1267b9 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1319,7 +1319,8 @@ export enum FeatureFlagKey { IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED', IS_TIMELINE_ACTIVITY_MIGRATED = 'IS_TIMELINE_ACTIVITY_MIGRATED', IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED', - IS_WORKFLOW_RUN_STOPPAGE_ENABLED = 'IS_WORKFLOW_RUN_STOPPAGE_ENABLED' + IS_WORKFLOW_RUN_STOPPAGE_ENABLED = 'IS_WORKFLOW_RUN_STOPPAGE_ENABLED', + IS_WORKSPACE_CREATION_V2_ENABLED = 'IS_WORKSPACE_CREATION_V2_ENABLED' } export type Field = { diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 73ad142850..54d317958c 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -1302,7 +1302,8 @@ export enum FeatureFlagKey { IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED', IS_TIMELINE_ACTIVITY_MIGRATED = 'IS_TIMELINE_ACTIVITY_MIGRATED', IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED', - IS_WORKFLOW_RUN_STOPPAGE_ENABLED = 'IS_WORKFLOW_RUN_STOPPAGE_ENABLED' + IS_WORKFLOW_RUN_STOPPAGE_ENABLED = 'IS_WORKFLOW_RUN_STOPPAGE_ENABLED', + IS_WORKSPACE_CREATION_V2_ENABLED = 'IS_WORKSPACE_CREATION_V2_ENABLED' } export type Field = { diff --git a/packages/twenty-server/.env.test b/packages/twenty-server/.env.test index 8c1de92b96..842d0415c0 100644 --- a/packages/twenty-server/.env.test +++ b/packages/twenty-server/.env.test @@ -26,3 +26,4 @@ AUTH_MICROSOFT_CALLBACK_URL=http://localhost:3000/auth/microsoft/redirect AUTH_MICROSOFT_APIS_CALLBACK_URL=http://localhost:3000/auth/microsoft-apis/get-access-token CLICKHOUSE_URL=http://default:clickhousePassword@localhost:8123/twenty +IS_WORKSPACE_CREATION_V2_ENABLED=true diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts index 7f427d0c88..7f75fb3e85 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts @@ -16,4 +16,5 @@ export enum FeatureFlagKey { IS_TIMELINE_ACTIVITY_MIGRATED = 'IS_TIMELINE_ACTIVITY_MIGRATED', IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED = 'IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED', IS_READ_ON_REPLICA_ENABLED = 'IS_READ_ON_REPLICA_ENABLED', + IS_WORKSPACE_CREATION_V2_ENABLED = 'IS_WORKSPACE_CREATION_V2_ENABLED', } diff --git a/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts b/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts index 0dfe87abca..b441b023a8 100644 --- a/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts +++ b/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts @@ -1393,6 +1393,16 @@ export class ConfigVariables { @IsOptional() IS_MAPS_AND_ADDRESS_AUTOCOMPLETE_ENABLED = false; + @ConfigVariablesMetadata({ + group: ConfigVariablesGroup.OTHER, + description: + 'Enable or disable workspace creation through v2 workspaceMigration', + type: ConfigVariableType.BOOLEAN, + isEnvOnly: true, + }) + @IsOptional() + IS_WORKSPACE_CREATION_V2_ENABLED = false; + @ConfigVariablesMetadata({ group: ConfigVariablesGroup.OTHER, isSensitive: true, diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts index 3f06758a8c..70c67e4755 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts @@ -17,6 +17,7 @@ import { DnsManagerService } from 'src/engine/core-modules/dns-manager/services/ import { CustomDomainManagerService } from 'src/engine/core-modules/domain/custom-domain-manager/services/custom-domain-manager.service'; import { SubdomainManagerService } from 'src/engine/core-modules/domain/subdomain-manager/services/subdomain-manager.service'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { FileWorkspaceFolderDeletionJob, @@ -242,8 +243,17 @@ export class WorkspaceService extends TypeOrmQueryService { activationStatus: WorkspaceActivationStatus.ONGOING_CREATION, }); + const isV2SyncEnabled = this.twentyConfigService.get( + 'IS_WORKSPACE_CREATION_V2_ENABLED', + ); + await this.featureFlagService.enableFeatureFlags( - DEFAULT_FEATURE_FLAGS, + [ + ...DEFAULT_FEATURE_FLAGS, + ...(isV2SyncEnabled + ? [FeatureFlagKey.IS_WORKSPACE_CREATION_V2_ENABLED] + : []), + ], workspace.id, ); diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts index 79be790e92..389b9af89b 100644 --- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts @@ -205,7 +205,6 @@ describe('WorkspaceEntityManager', () => { IS_AI_ENABLED: false, IS_APPLICATION_ENABLED: false, IS_IMAP_SMTP_CALDAV_ENABLED: false, - IS_MORPH_RELATION_ENABLED: false, IS_PAGE_LAYOUT_ENABLED: false, IS_RECORD_PAGE_LAYOUT_ENABLED: false, IS_PUBLIC_DOMAIN_ENABLED: false, @@ -215,6 +214,7 @@ describe('WorkspaceEntityManager', () => { IS_TIMELINE_ACTIVITY_MIGRATED: false, IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED: false, IS_READ_ON_REPLICA_ENABLED: false, + IS_WORKSPACE_CREATION_V2_ENABLED: false, }, eventEmitterService: { emitMutationEvent: jest.fn(), diff --git a/packages/twenty-server/src/engine/workspace-manager/__tests__/workspace-manager.service.spec.ts b/packages/twenty-server/src/engine/workspace-manager/__tests__/workspace-manager.service.spec.ts index c0dfedfbb9..5aacc97b43 100644 --- a/packages/twenty-server/src/engine/workspace-manager/__tests__/workspace-manager.service.spec.ts +++ b/packages/twenty-server/src/engine/workspace-manager/__tests__/workspace-manager.service.spec.ts @@ -23,6 +23,7 @@ import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace- import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; +import { TwentyStandardApplicationService } from 'src/engine/workspace-manager/twenty-standard-application/services/twenty-standard-application.service'; import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service'; import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service'; @@ -130,6 +131,12 @@ describe('WorkspaceManagerService', () => { deleteWorkspaceAllObjectMetadata: jest.fn(), }, }, + { + provide: TwentyStandardApplicationService, + useValue: { + synchronizeTwentyStandardApplicationOrThrow: jest.fn(), + }, + }, { provide: AgentService, useValue: { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.module.ts index 48e51081b7..3ba364f9ce 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.module.ts @@ -20,6 +20,7 @@ import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/works import { DevSeederModule } from 'src/engine/workspace-manager/dev-seeder/dev-seeder.module'; import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module'; import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module'; +import { TwentyStandardApplicationModule } from 'src/engine/workspace-manager/twenty-standard-application/twenty-standard-application.module'; import { WorkspaceManagerService } from './workspace-manager.service'; @@ -35,6 +36,7 @@ import { WorkspaceManagerService } from './workspace-manager.service'; FeatureFlagModule, PermissionsModule, AiAgentModule, + TwentyStandardApplicationModule, WorkspaceManyOrAllFlatEntityMapsCacheModule, TypeOrmModule.forFeature([UserWorkspaceEntity, WorkspaceEntity]), RoleModule, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts index fd072c830e..dcb659b603 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts @@ -5,6 +5,8 @@ import { DataSource, Repository } from 'typeorm'; import { ApplicationService } from 'src/engine/core-modules/application/application.service'; import { FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { fromApplicationEntityToFlatApplication } from 'src/engine/core-modules/application/utils/from-application-entity-to-flat-application.util'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -21,6 +23,7 @@ import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspac import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { prefillCoreViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-core-views'; import { standardObjectsPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data'; +import { TwentyStandardApplicationService } from 'src/engine/workspace-manager/twenty-standard-application/services/twenty-standard-application.service'; import { ADMIN_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role'; import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service'; @@ -35,12 +38,11 @@ export class WorkspaceManagerService { private readonly workspaceMigrationService: WorkspaceMigrationService, private readonly objectMetadataService: ObjectMetadataService, private readonly dataSourceService: DataSourceService, - private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService, @InjectRepository(UserWorkspaceEntity) private readonly userWorkspaceRepository: Repository, private readonly roleService: RoleService, private readonly userRoleService: UserRoleService, - private readonly featureFlagService: FeatureFlagService, + private readonly twentyStandardApplicationService: TwentyStandardApplicationService, @InjectRepository(WorkspaceEntity) private readonly workspaceRepository: Repository, @InjectRepository(RoleEntity) @@ -50,6 +52,8 @@ export class WorkspaceManagerService { protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, private readonly applicationService: ApplicationService, private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, + private readonly featureFlagService: FeatureFlagService, + private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService, ) {} public async init({ @@ -87,12 +91,39 @@ export class WorkspaceManagerService { workspaceId, }); - // TODO later replace by twenty-standard installation aka workspaceMigration run - await this.workspaceSyncMetadataService.synchronize({ - workspaceId, - dataSourceId: dataSourceMetadata.id, - featureFlags, - }); + const isV2SyncEnabled = + featureFlags[FeatureFlagKey.IS_WORKSPACE_CREATION_V2_ENABLED]; + + if (isV2SyncEnabled) { + await this.twentyStandardApplicationService.synchronizeTwentyStandardApplicationOrThrow( + { + workspaceId, + }, + ); + } else { + await this.workspaceSyncMetadataService.synchronize({ + workspaceId, + dataSourceId: dataSourceMetadata.id, + featureFlags, + }); + + const prefillStandardObjectsStart = performance.now(); + + await this.prefillWorkspaceWithStandardObjectsRecords({ + dataSourceMetadata, + workspaceId, + featureFlags, + twentyStandardFlatApplication: fromApplicationEntityToFlatApplication( + twentyStandardApplication, + ), + }); + + const prefillStandardObjectsEnd = performance.now(); + + this.logger.log( + `Prefill standard objects took ${prefillStandardObjectsEnd - prefillStandardObjectsStart}ms`, + ); + } const dataSourceMetadataCreationEnd = performance.now(); @@ -112,29 +143,19 @@ export class WorkspaceManagerService { userId, workspaceCustomFlatApplication, }); - - const prefillStandardObjectsStart = performance.now(); - - await this.prefillWorkspaceWithStandardObjectsRecords( - dataSourceMetadata, - workspaceId, - featureFlags, - twentyStandardApplication, - ); - - const prefillStandardObjectsEnd = performance.now(); - - this.logger.log( - `Prefill standard objects took ${prefillStandardObjectsEnd - prefillStandardObjectsStart}ms`, - ); } - private async prefillWorkspaceWithStandardObjectsRecords( - dataSourceMetadata: DataSourceEntity, - workspaceId: string, - featureFlags: Record, - twentyStandardFlatApplication: FlatApplication, - ) { + private async prefillWorkspaceWithStandardObjectsRecords({ + dataSourceMetadata, + workspaceId, + featureFlags, + twentyStandardFlatApplication, + }: { + dataSourceMetadata: DataSourceEntity; + workspaceId: string; + featureFlags: Record; + twentyStandardFlatApplication: FlatApplication; + }) { const { flatObjectMetadataMaps, flatFieldMetadataMaps } = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { diff --git a/packages/twenty-server/test/integration/graphql/suites/workspace/successful-user-and-workspace-creation-v1.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/workspace/successful-user-and-workspace-creation-v1.integration-spec.ts new file mode 100644 index 0000000000..40e25a2d37 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/workspace/successful-user-and-workspace-creation-v1.integration-spec.ts @@ -0,0 +1,281 @@ +import { randomUUID } from 'crypto'; + +import { activateWorkspace } from 'test/integration/graphql/utils/activate-workspace.util'; +import { deleteUser } from 'test/integration/graphql/utils/delete-user.util'; +import { findManyApplications } from 'test/integration/graphql/utils/find-many-applications.util'; +import { getAuthTokensFromLoginToken } from 'test/integration/graphql/utils/get-auth-tokens-from-login-token.util'; +import { getCurrentUser } from 'test/integration/graphql/utils/get-current-user.util'; +import { signUpInNewWorkspace } from 'test/integration/graphql/utils/sign-up-in-new-workspace.util'; +import { signUp } from 'test/integration/graphql/utils/sign-up.util'; +import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; +import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; +import { isDefined } from 'twenty-shared/utils'; +import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; + +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; + +describe('Successful user and workspace creation', () => { + let createdUserAccessToken: string | undefined; + + afterEach(async () => { + if (!isDefined(createdUserAccessToken)) { + return; + } + + await deleteUser({ + accessToken: createdUserAccessToken, + expectToFail: false, + }); + }); + + beforeAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_CREATION_V2_ENABLED, + value: false, + }); + }); + + afterAll(async () => { + await updateFeatureFlag({ + expectToFail: false, + featureFlag: FeatureFlagKey.IS_WORKSPACE_CREATION_V2_ENABLED, + value: true, + }); + }); + + it('should sign up a new user and create a new workspace successfully', async () => { + const uniqueEmail = `test-${randomUUID()}@example.com`; + + const { data } = await signUp({ + input: { + email: uniqueEmail, + password: 'Test123!@#', + }, + + expectToFail: false, + }); + + createdUserAccessToken = + data.signUp.tokens.accessOrWorkspaceAgnosticToken.token; + + // Mark email as verified to bypass email verification requirement + await testDataSource.query( + 'UPDATE core."user" SET "isEmailVerified" = true WHERE email = $1', + [uniqueEmail], + ); + + const { + data: { signUpInNewWorkspace: signUpInNewWorkspaceData }, + } = await signUpInNewWorkspace({ + accessToken: createdUserAccessToken, + expectToFail: false, + }); + + const { + data: { getAuthTokensFromLoginToken: authTokensData }, + } = await getAuthTokensFromLoginToken({ + origin: signUpInNewWorkspaceData.workspace.workspaceUrls.subdomainUrl, + loginToken: signUpInNewWorkspaceData.loginToken.token, + expectToFail: false, + }); + + const newWorkspaceAccessToken = + authTokensData.tokens.accessOrWorkspaceAgnosticToken.token; + + const { + data: { activateWorkspace: activateWorkspaceData }, + } = await activateWorkspace({ + accessToken: newWorkspaceAccessToken, + displayName: '42 answer', + expectToFail: false, + }); + + expect(activateWorkspaceData.activationStatus).toBe( + WorkspaceActivationStatus.ACTIVE, + ); + + const { + data: { currentUser }, + } = await getCurrentUser({ + accessToken: newWorkspaceAccessToken, + expectToFail: false, + }); + + jestExpectToBeDefined(currentUser.currentWorkspace); + const { inviteHash: _, ...expectedCurrentWorkspace } = + activateWorkspaceData; + + jestExpectToBeDefined(currentUser.currentWorkspace); + expect(currentUser.currentWorkspace).toMatchObject( + expectedCurrentWorkspace, + ); + + const { + data: { findManyApplications: findManyApplicationsData }, + } = await findManyApplications({ + accessToken: newWorkspaceAccessToken, + expectToFail: false, + }); + + expect(findManyApplicationsData.length).toBe(2); + const twentyStandardApp = findManyApplicationsData.find( + (application) => + application.universalIdentifier === + TWENTY_STANDARD_APPLICATION.universalIdentifier, + ); + + jestExpectToBeDefined(twentyStandardApp); + const { + sourcePath: _sourcePath, + sourceType: _sourceType, + ...expectedStandardTwentyApplication + } = TWENTY_STANDARD_APPLICATION; + + expect(twentyStandardApp).toMatchObject(expectedStandardTwentyApplication); + + const workpsaceCustomApplication = findManyApplicationsData.find( + (application) => + application.id === + currentUser.currentWorkspace?.workspaceCustomApplicationId, + ); + + jestExpectToBeDefined(workpsaceCustomApplication); + expect(workpsaceCustomApplication.universalIdentifier).toEqual( + workpsaceCustomApplication.id, + ); + }); + + it('should delete workspace and related metadata entities when last user is deleted', async () => { + const uniqueEmail = `test-delete-${randomUUID()}@example.com`; + + const { data } = await signUp({ + input: { + email: uniqueEmail, + password: 'Test123!@#', + }, + expectToFail: false, + }); + + createdUserAccessToken = + data.signUp.tokens.accessOrWorkspaceAgnosticToken.token; + + // Mark email as verified to bypass email verification requirement + await testDataSource.query( + 'UPDATE core."user" SET "isEmailVerified" = true WHERE email = $1', + [uniqueEmail], + ); + + const { + data: { signUpInNewWorkspace: signUpInNewWorkspaceData }, + } = await signUpInNewWorkspace({ + accessToken: createdUserAccessToken, + expectToFail: false, + }); + + const workspaceId = signUpInNewWorkspaceData.workspace.id; + + const { + data: { getAuthTokensFromLoginToken: authTokensData }, + } = await getAuthTokensFromLoginToken({ + origin: signUpInNewWorkspaceData.workspace.workspaceUrls.subdomainUrl, + loginToken: signUpInNewWorkspaceData.loginToken.token, + expectToFail: false, + }); + + const newWorkspaceAccessToken = + authTokensData.tokens.accessOrWorkspaceAgnosticToken.token; + + await activateWorkspace({ + accessToken: newWorkspaceAccessToken, + displayName: 'Test Workspace for Deletion', + expectToFail: false, + }); + + await createOneObjectMetadata({ + input: { + nameSingular: 'workspaceEviction', + namePlural: 'workspaceEvictions', + labelPlural: 'whatevers', + labelSingular: 'whatever', + isLabelSyncedWithName: false, + }, + token: newWorkspaceAccessToken, + expectToFail: false, + }); + + const workspaceBeforeDeletion = await testDataSource.query( + 'SELECT * FROM core.workspace WHERE id = $1', + [workspaceId], + ); + + expect(workspaceBeforeDeletion).toHaveLength(1); + + const tablesToVerify = [ + 'dataSource', + 'objectMetadata', + 'fieldMetadata', + 'indexMetadata', + 'searchFieldMetadata', + 'workspaceMigration', + 'role', + 'roleTarget', + 'objectPermission', + 'fieldPermission', + 'permissionFlag', + 'serverlessFunction', + 'serverlessFunctionLayer', + 'agent', + 'remoteServer', + 'remoteTable', + 'databaseEventTrigger', + 'view', + 'viewField', + 'viewFilter', + 'viewFilterGroup', + 'viewGroup', + 'viewSort', + 'cronTrigger', + 'routeTrigger', + ]; + + let totalRecordsBefore = 0; + + for (const table of tablesToVerify) { + const result = await testDataSource.query( + `SELECT COUNT(*) as count FROM core."${table}" WHERE "workspaceId" = $1`, + [workspaceId], + ); + + totalRecordsBefore += parseInt(result[0].count); + } + + expect(totalRecordsBefore).toBeGreaterThan(0); + + await deleteUser({ + accessToken: createdUserAccessToken, + expectToFail: false, + }); + + createdUserAccessToken = undefined; + + const workspaceAfterDeletion = await testDataSource.query( + 'SELECT * FROM core.workspace WHERE id = $1', + [workspaceId], + ); + + expect(workspaceAfterDeletion).toHaveLength(0); + + for (const table of tablesToVerify) { + const result = await testDataSource.query( + `SELECT COUNT(*) as count FROM core."${table}" WHERE "workspaceId" = $1`, + [workspaceId], + ); + const count = parseInt(result[0].count); + + expect({ count, table }).toEqual({ count: 0, table }); + } + }); +});