fix(billing) - fix orphaned stripe subs 2/2 (#20916)

Fixes https://sonarly.com/issue/40688

Should have been included in
https://github.com/twentyhq/twenty/commit/0edd8d400c646cd6a40ff0fea5342a0f61645d5e
This commit is contained in:
Etienne
2026-05-26 18:28:21 +02:00
committed by GitHub
parent f19647617a
commit 5eb79e7797
3 changed files with 18 additions and 18 deletions
@@ -55,7 +55,10 @@ describe('UserService', () => {
},
{
provide: WorkspaceService,
useValue: { deleteWorkspace: jest.fn() },
useValue: {
deleteWorkspace: jest.fn(),
suspendWorkspace: jest.fn(),
},
},
{
provide: WorkspaceDomainsService,
@@ -386,7 +389,8 @@ describe('UserService', () => {
const res = await service.deleteUser('u2');
expect(workspaceService.deleteWorkspace).toHaveBeenCalledWith('w2');
expect(workspaceService.suspendWorkspace).toHaveBeenCalledWith('w2');
expect(workspaceService.deleteWorkspace).toHaveBeenCalledWith('w2', true);
expect(res).toMatchObject({ id: 'u2' });
});
@@ -10,10 +10,12 @@ import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
import { type QueryRunner, In, IsNull, Not, Repository } from 'typeorm';
import { CoreEntityCacheService } from 'src/engine/core-entity-cache/services/core-entity-cache.service';
import {
AuthException,
AuthExceptionCode,
} from 'src/engine/core-modules/auth/auth.exception';
import { type AuthContextUser } from 'src/engine/core-modules/auth/types/auth-context.type';
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
import { EmailVerificationTrigger } from 'src/engine/core-modules/email-verification/email-verification.constants';
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
@@ -27,7 +29,7 @@ import {
UpdateWorkspaceMemberEmailJob,
UpdateWorkspaceMemberEmailJobData,
} from 'src/engine/core-modules/user/jobs/update-workspace-member-email.job';
import { type AuthContextUser } from 'src/engine/core-modules/auth/types/auth-context.type';
import { WorkspaceMemberTranspiler } from 'src/engine/core-modules/user/services/workspace-member-transpiler.service';
import { UserEntity } from 'src/engine/core-modules/user/user.entity';
import { UserExceptionCode } from 'src/engine/core-modules/user/user.exception';
import { userValidator } from 'src/engine/core-modules/user/user.validate';
@@ -39,11 +41,9 @@ import {
PermissionsExceptionMessage,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { CoreEntityCacheService } from 'src/engine/core-entity-cache/services/core-entity-cache.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { WorkspaceMemberTranspiler } from 'src/engine/core-modules/user/services/workspace-member-transpiler.service';
// oxlint-disable-next-line twenty/inject-workspace-repository
export class UserService extends TypeOrmQueryService<UserEntity> {
@@ -322,7 +322,8 @@ export class UserService extends TypeOrmQueryService<UserEntity> {
const userWorkspaceId = userWorkspace.id;
if (workspaceMembers.length === 1) {
await this.workspaceService.deleteWorkspace(workspaceId);
await this.workspaceService.suspendWorkspace(workspaceId);
await this.workspaceService.deleteWorkspace(workspaceId, true);
return;
}
@@ -131,7 +131,7 @@ describe('Successful user and workspace creation', () => {
);
});
it('should delete workspace and related metadata entities when last user is deleted', async () => {
it('should suspend and soft-delete workspace when last user is deleted', async () => {
const uniqueEmail = `test-delete-${randomUUID()}@example.com`;
const { data } = await signUp({
@@ -253,16 +253,11 @@ describe('Successful user and workspace creation', () => {
[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 });
}
expect(workspaceAfterDeletion).toHaveLength(1);
expect(workspaceAfterDeletion[0].activationStatus).toBe(
WorkspaceActivationStatus.SUSPENDED,
);
expect(workspaceAfterDeletion[0].suspendedAt).not.toBeNull();
expect(workspaceAfterDeletion[0].deletedAt).not.toBeNull();
});
});