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
```
---
.../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);