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 ``` <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21778?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -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<void> {
|
||||
// --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);
|
||||
|
||||
-30
@@ -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<CreateWorkspaceInput, 'workspaceCustomApplicationId'>
|
||||
>;
|
||||
|
||||
// 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<CreateWorkspaceInput, 'workspaceCustomApplicationId'>
|
||||
>;
|
||||
|
||||
-80
@@ -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<void> {
|
||||
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,
|
||||
|
||||
+69
@@ -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<UpgradeMigrationEntity> =
|
||||
dataSource.getRepository(UpgradeMigrationEntity);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user