From c07dd53a486aa3c25610fe0c6ae07c5436d3e547 Mon Sep 17 00:00:00 2001 From: neo773 <62795688+neo773@users.noreply.github.com> Date: Fri, 19 Jun 2026 04:51:50 +0530 Subject: [PATCH] Scope empty fixture workspaces to upgrade integration tests (#21778) The dev seeder activated Empty3/Empty4 workspaces without creating their DB schema, so every workspace-iterating job (e.g. the workflow cron trigger) logged 'relation does not exist' for those schemas on each run. ``` [1] query failed: SELECT * FROM workspace_4rdlooovb6mo66rdmgupv06zi."workflowAutomatedTrigger" WHERE type = 'CRON' [1] error: error: relation "workspace_4rdlooovb6mo66rdmgupv06zi.workflowAutomatedTrigger" does not exist [1] [Nest] 51868 - 18/06/2026, 5:07:04 pm ERROR [WorkflowCronTriggerCronJob] Error processing workspace 506915ec-21ca-431b-a04a-257eb216865e: QueryFailedError: relation "workspace_4rdlooovb6mo66rdmgupv06zi.workflowAutomatedTrigger" does not exist [1] Exception Captured ``` Review in cubic --- .../data-seed-dev-workspace.command.ts | 17 +--- .../constants/seeder-workspaces.constant.ts | 30 ------- .../dev-seeder/services/dev-seeder.service.ts | 80 ------------------- ...e-sequence-runner-integration-test.util.ts | 69 ++++++++++++++++ 4 files changed, 70 insertions(+), 126 deletions(-) diff --git a/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts index d66ae3c357..2bef6b74ac 100644 --- a/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts +++ b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts @@ -4,10 +4,7 @@ import { Command, CommandRunner, Option } from 'nest-commander'; import { SEED_APPLE_WORKSPACE_ID, - SEED_EMPTY_WORKSPACE_3_ID, - SEED_EMPTY_WORKSPACE_4_ID, SEED_YCOMBINATOR_WORKSPACE_ID, - SeededEmptyWorkspacesIds, SeededWorkspacesIds, } from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant'; import { DevSeederService } from 'src/engine/workspace-manager/dev-seeder/services/dev-seeder.service'; @@ -31,7 +28,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner { @Option({ flags: '--light', description: - 'Light seed: only seed the Apple workspace (skip YCombinator and the Empty3/Empty4 fixtures used by integration tests), skip demo custom objects (Pet, Survey, etc.) and limit records to 5 per object', + 'Light seed: only seed the Apple workspace (skip YCombinator), skip demo custom objects (Pet, Survey, etc.) and limit records to 5 per object', }) parseLight(): boolean { return true; @@ -41,28 +38,16 @@ export class DataSeedWorkspaceCommand extends CommandRunner { _passedParams: string[], options: DataSeedWorkspaceOptions, ): Promise { - // --light seeds a single workspace (Apple) for thin dev containers like - // twenty-app-dev. The default (no flag) seeds all four workspaces — Apple, - // YCombinator and the Empty3/Empty4 fixtures consumed by upgrade-sequence - // integration tests. const workspaceIds: SeededWorkspacesIds[] = options.light ? [SEED_APPLE_WORKSPACE_ID] : [SEED_APPLE_WORKSPACE_ID, SEED_YCOMBINATOR_WORKSPACE_ID]; - const emptyWorkspaceIds: SeededEmptyWorkspacesIds[] = options.light - ? [] - : [SEED_EMPTY_WORKSPACE_3_ID, SEED_EMPTY_WORKSPACE_4_ID]; - try { for (const workspaceId of workspaceIds) { await this.devSeederService.seedDev(workspaceId, { light: options.light, }); } - - for (const workspaceId of emptyWorkspaceIds) { - await this.devSeederService.seedEmptyWorkspace(workspaceId); - } } catch (error) { this.logger.error(error); this.logger.error(error.stack); diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant.ts index 7e0f374945..2909cd8786 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant.ts @@ -31,10 +31,6 @@ export type SeededWorkspacesIds = | typeof SEED_APPLE_WORKSPACE_ID | typeof SEED_YCOMBINATOR_WORKSPACE_ID; -export type SeededEmptyWorkspacesIds = - | typeof SEED_EMPTY_WORKSPACE_3_ID - | typeof SEED_EMPTY_WORKSPACE_4_ID; - export const SEEDER_CREATE_WORKSPACE_INPUT = { [SEED_APPLE_WORKSPACE_ID]: { id: SEED_APPLE_WORKSPACE_ID, @@ -58,29 +54,3 @@ export const SEEDER_CREATE_WORKSPACE_INPUT = { SeededWorkspacesIds, Omit >; - -// Empty workspaces with no users, metadata, or data — used by integration tests -// that need more than 2 workspaces (e.g. upgrade sequence runner tests). -export const SEEDER_CREATE_EMPTY_WORKSPACE_INPUT = { - [SEED_EMPTY_WORKSPACE_3_ID]: { - id: SEED_EMPTY_WORKSPACE_3_ID, - displayName: 'Empty3', - subdomain: 'empty3', - inviteHash: 'empty3.dev-invite-hash', - logo: '', - activationStatus: WorkspaceActivationStatus.PENDING_CREATION, - isTwoFactorAuthenticationEnforced: false, - }, - [SEED_EMPTY_WORKSPACE_4_ID]: { - id: SEED_EMPTY_WORKSPACE_4_ID, - displayName: 'Empty4', - subdomain: 'empty4', - inviteHash: 'empty4.dev-invite-hash', - logo: '', - activationStatus: WorkspaceActivationStatus.PENDING_CREATION, - isTwoFactorAuthenticationEnforced: false, - }, -} as const satisfies Record< - SeededEmptyWorkspacesIds, - Omit ->; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/services/dev-seeder.service.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/services/dev-seeder.service.ts index 24999287e2..07b5d0870e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/services/dev-seeder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/services/dev-seeder.service.ts @@ -22,9 +22,7 @@ import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/work import { seedBillingCustomers } from 'src/engine/workspace-manager/dev-seeder/core/billing/utils/seed-billing-customers.util'; import { seedBillingSubscriptions } from 'src/engine/workspace-manager/dev-seeder/core/billing/utils/seed-billing-subscriptions.util'; import { - type SeededEmptyWorkspacesIds, type SeededWorkspacesIds, - SEEDER_CREATE_EMPTY_WORKSPACE_INPUT, SEEDER_CREATE_WORKSPACE_INPUT, } from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant'; import { DevSeederPermissionsService } from 'src/engine/workspace-manager/dev-seeder/core/services/dev-seeder-permissions.service'; @@ -194,84 +192,6 @@ export class DevSeederService { await this.workspaceCacheStorageService.flush(workspaceId, undefined); } - public async seedEmptyWorkspace( - workspaceId: SeededEmptyWorkspacesIds, - ): Promise { - const appVersion = this.twentyConfigService.get('APP_VERSION') ?? 'unknown'; - const lastAttemptedInstanceCommand = - await this.upgradeMigrationService.getLastAttemptedInstanceCommandOrThrow(); - const initialCursor = - this.upgradeSequenceReaderService.getInitialCursorForNewWorkspace( - lastAttemptedInstanceCommand, - ); - - const createWorkspaceStaticInput = - SEEDER_CREATE_EMPTY_WORKSPACE_INPUT[workspaceId]; - const queryRunner = this.coreDataSource.createQueryRunner(); - - await queryRunner.connect(); - await queryRunner.startTransaction(); - - try { - const workspaceCustomApplicationId = v4(); - - await createWorkspace({ - queryRunner, - schemaName: 'core', - createWorkspaceInput: { - ...createWorkspaceStaticInput, - workspaceCustomApplicationId, - }, - }); - - await this.applicationService.createWorkspaceCustomApplication( - { - workspaceId, - applicationId: workspaceCustomApplicationId, - }, - queryRunner, - ); - - await this.applicationService.createTwentyStandardApplication( - { - workspaceId, - skipCacheInvalidation: true, - }, - queryRunner, - ); - - await queryRunner.commitTransaction(); - } catch (error) { - await queryRunner.rollbackTransaction(); - throw error; - } finally { - await queryRunner.release(); - } - - const { workspaceCustomFlatApplication } = - await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( - { - workspaceId, - }, - ); - - await this.devSeederPermissionsService.initMinimalPermissionsAndActivateWorkspace( - { - workspaceId, - workspaceCustomFlatApplication, - }, - ); - - await this.upgradeMigrationService.markAsWorkspaceInitial({ - name: initialCursor.name, - workspaceId, - executedByVersion: appVersion, - status: initialCursor.status, - }); - - await this.workspaceCacheStorageService.flush(workspaceId, undefined); - } - private async seedCoreSchema({ workspaceId, appVersion, diff --git a/packages/twenty-server/test/integration/upgrade/utils/upgrade-sequence-runner-integration-test.util.ts b/packages/twenty-server/test/integration/upgrade/utils/upgrade-sequence-runner-integration-test.util.ts index 2ea8b3861e..10cbd16ab1 100644 --- a/packages/twenty-server/test/integration/upgrade/utils/upgrade-sequence-runner-integration-test.util.ts +++ b/packages/twenty-server/test/integration/upgrade/utils/upgrade-sequence-runner-integration-test.util.ts @@ -3,8 +3,10 @@ import { getDataSourceToken, getRepositoryToken } from '@nestjs/typeorm'; import { config } from 'dotenv'; import { DataSource, type Repository } from 'typeorm'; +import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service'; +import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { InstanceCommandRunnerService } from 'src/engine/core-modules/upgrade/services/instance-command-runner.service'; import { UpgradeMigrationService } from 'src/engine/core-modules/upgrade/services/upgrade-migration.service'; @@ -24,6 +26,7 @@ import { SEED_EMPTY_WORKSPACE_4_ID, SEED_YCOMBINATOR_WORKSPACE_ID, } from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant'; +import { createWorkspace } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspace.util'; import { WorkspaceVersionService } from 'src/engine/workspace-manager/workspace-version/services/workspace-version.service'; jest.useRealTimers(); @@ -38,6 +41,70 @@ export const WS_2 = SEED_YCOMBINATOR_WORKSPACE_ID; export const WS_3 = SEED_EMPTY_WORKSPACE_3_ID; export const WS_4 = SEED_EMPTY_WORKSPACE_4_ID; +const FK_WORKSPACE_FIXTURES = [ + { + workspaceId: WS_3, + applicationId: 'f1c0ffee-0000-4000-8000-0000000000c3', + subdomain: 'upgrade-test-fixture-3', + }, + { + workspaceId: WS_4, + applicationId: 'f1c0ffee-0000-4000-8000-0000000000c4', + subdomain: 'upgrade-test-fixture-4', + }, +]; + +const seedEmptyWorkspaces = async (dataSource: DataSource) => { + const queryRunner = dataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + for (const { + workspaceId, + applicationId, + subdomain, + } of FK_WORKSPACE_FIXTURES) { + await createWorkspace({ + queryRunner, + schemaName: 'core', + createWorkspaceInput: { + id: workspaceId, + displayName: subdomain, + subdomain, + inviteHash: `${subdomain}.dev-invite-hash`, + logo: '', + activationStatus: WorkspaceActivationStatus.PENDING_CREATION, + isTwoFactorAuthenticationEnforced: false, + workspaceCustomApplicationId: applicationId, + }, + }); + + await queryRunner.manager + .createQueryBuilder() + .insert() + .into(ApplicationEntity) + .values({ + id: applicationId, + universalIdentifier: applicationId, + name: 'upgrade-test-fixture', + sourcePath: '', + workspaceId, + }) + .orIgnore() + .execute(); + } + + await queryRunner.commitTransaction(); + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + } +}; + const EXECUTED_BY_VERSION = '42.42.42'; const noopAsync = async () => {}; @@ -107,6 +174,8 @@ export const createUpgradeSequenceRunnerIntegrationTestModule = async () => { await dataSource.initialize(); + await seedEmptyWorkspaces(dataSource); + const migrationRepo: Repository = dataSource.getRepository(UpgradeMigrationEntity);