add tests on workspace deletion logic (#10530)

closes [#424](https://github.com/twentyhq/core-team-issues/issues/424)
This commit is contained in:
Etienne
2025-02-28 10:38:51 +01:00
committed by GitHub
parent 124e69447d
commit 8762c06ff2
3 changed files with 391 additions and 119 deletions

View File

@ -0,0 +1,228 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
import { CustomDomainService } from 'src/engine/core-modules/domain-manager/services/custom-domain.service';
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
import { EmailService } from 'src/engine/core-modules/email/email.service';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-queue-token.util';
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
import { UserService } from 'src/engine/core-modules/user/services/user.service';
import { User } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
describe('WorkspaceService', () => {
let service: WorkspaceService;
let userWorkspaceRepository: Repository<UserWorkspace>;
let userRepository: Repository<User>;
let workspaceRepository: Repository<Workspace>;
let workspaceCacheStorageService: WorkspaceCacheStorageService;
let messageQueueService: MessageQueueService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
WorkspaceService,
{
provide: getRepositoryToken(Workspace, 'core'),
useValue: {
findOne: jest.fn(),
softDelete: jest.fn(),
delete: jest.fn(),
},
},
{
provide: getRepositoryToken(UserWorkspace, 'core'),
useValue: {
find: jest.fn(),
softDelete: jest.fn(),
delete: jest.fn(),
},
},
{
provide: getRepositoryToken(User, 'core'),
useValue: {
softDelete: jest.fn(),
},
},
...[
WorkspaceManagerService,
WorkspaceManagerService,
UserWorkspaceService,
UserService,
DomainManagerService,
CustomDomainService,
BillingSubscriptionService,
BillingService,
EnvironmentService,
EmailService,
OnboardingService,
WorkspaceInvitationService,
PermissionsService,
FeatureFlagService,
ExceptionHandlerService,
PermissionsService,
].map((service) => ({
provide: service,
useValue: {},
})),
{
provide: WorkspaceCacheStorageService,
useValue: {
flush: jest.fn(),
},
},
{
provide: getQueueToken(MessageQueue.deleteCascadeQueue),
useValue: {
add: jest.fn(),
},
},
],
}).compile();
service = module.get<WorkspaceService>(WorkspaceService);
userWorkspaceRepository = module.get<Repository<UserWorkspace>>(
getRepositoryToken(UserWorkspace, 'core'),
);
userRepository = module.get<Repository<User>>(
getRepositoryToken(User, 'core'),
);
workspaceRepository = module.get<Repository<Workspace>>(
getRepositoryToken(Workspace, 'core'),
);
workspaceCacheStorageService = module.get<WorkspaceCacheStorageService>(
WorkspaceCacheStorageService,
);
messageQueueService = module.get<MessageQueueService>(
getQueueToken(MessageQueue.deleteCascadeQueue),
);
});
afterEach(() => {
jest.clearAllMocks();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('handleRemoveWorkspaceMember', () => {
it('should soft delete the user workspace record', async () => {
jest.spyOn(userWorkspaceRepository, 'find').mockResolvedValue([]);
await service.handleRemoveWorkspaceMember(
'workspace-id',
'user-id',
true,
);
expect(userWorkspaceRepository.softDelete).toHaveBeenCalledWith({
userId: 'user-id',
workspaceId: 'workspace-id',
});
expect(userWorkspaceRepository.delete).not.toHaveBeenCalled();
expect(userRepository.softDelete).toHaveBeenCalledWith('user-id');
});
it('should destroy the user workspace record', async () => {
jest.spyOn(userWorkspaceRepository, 'find').mockResolvedValue([]);
await service.handleRemoveWorkspaceMember(
'workspace-id',
'user-id',
false,
);
expect(userWorkspaceRepository.delete).toHaveBeenCalledWith({
userId: 'user-id',
workspaceId: 'workspace-id',
});
expect(userWorkspaceRepository.softDelete).not.toHaveBeenCalled();
expect(userRepository.softDelete).toHaveBeenCalledWith('user-id');
});
it('should not soft delete the user record if there are other user workspace records', async () => {
jest
.spyOn(userWorkspaceRepository, 'find')
.mockResolvedValue([
{ id: 'remaining-user-workspace-id' } as UserWorkspace,
]);
await service.handleRemoveWorkspaceMember(
'workspace-id',
'user-id',
false,
);
expect(userWorkspaceRepository.delete).toHaveBeenCalledWith({
userId: 'user-id',
workspaceId: 'workspace-id',
});
expect(userWorkspaceRepository.softDelete).not.toHaveBeenCalled();
expect(userRepository.softDelete).not.toHaveBeenCalled();
});
});
describe('deleteWorkspace', () => {
it('should delete the workspace', async () => {
const mockWorkspace = {
id: 'workspace-id',
metadataVersion: 0,
} as Workspace;
jest
.spyOn(workspaceRepository, 'findOne')
.mockResolvedValue(mockWorkspace);
jest.spyOn(userWorkspaceRepository, 'find').mockResolvedValue([]);
jest
.spyOn(service, 'deleteMetadataSchemaCacheAndUserWorkspace')
.mockResolvedValue({} as Workspace);
await service.deleteWorkspace(mockWorkspace.id, false);
expect(workspaceRepository.delete).toHaveBeenCalledWith(mockWorkspace.id);
expect(
service.deleteMetadataSchemaCacheAndUserWorkspace,
).toHaveBeenCalled();
expect(workspaceRepository.softDelete).not.toHaveBeenCalled();
expect(workspaceCacheStorageService.flush).toHaveBeenCalledWith(
mockWorkspace.id,
mockWorkspace.metadataVersion,
);
expect(messageQueueService.add).toHaveBeenCalled();
});
it('should soft delete the workspace', async () => {
const mockWorkspace = {
id: 'workspace-id',
metadataVersion: 0,
} as Workspace;
jest
.spyOn(workspaceRepository, 'findOne')
.mockResolvedValue(mockWorkspace);
jest.spyOn(userWorkspaceRepository, 'find').mockResolvedValue([]);
await service.deleteWorkspace(mockWorkspace.id, true);
expect(workspaceRepository.softDelete).toHaveBeenCalledWith({
id: mockWorkspace.id,
});
expect(workspaceRepository.delete).not.toHaveBeenCalled();
});
});
});

View File

@ -1,119 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
import { CustomDomainService } from 'src/engine/core-modules/domain-manager/services/custom-domain.service';
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
import { EmailService } from 'src/engine/core-modules/email/email.service';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-queue-token.util';
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
import { UserService } from 'src/engine/core-modules/user/services/user.service';
import { User } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
import { WorkspaceService } from './workspace.service';
describe('WorkspaceService', () => {
let service: WorkspaceService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
WorkspaceService,
{
provide: getRepositoryToken(Workspace, 'core'),
useValue: {},
},
{
provide: getRepositoryToken(UserWorkspace, 'core'),
useValue: {},
},
{
provide: getRepositoryToken(User, 'core'),
useValue: {},
},
{
provide: WorkspaceManagerService,
useValue: {},
},
{
provide: UserWorkspaceService,
useValue: {},
},
{
provide: UserService,
useValue: {},
},
{
provide: DomainManagerService,
useValue: {},
},
{
provide: CustomDomainService,
useValue: {},
},
{
provide: BillingSubscriptionService,
useValue: {},
},
{
provide: BillingService,
useValue: {},
},
{
provide: EnvironmentService,
useValue: {},
},
{
provide: EmailService,
useValue: {},
},
{
provide: OnboardingService,
useValue: {},
},
{
provide: WorkspaceInvitationService,
useValue: {},
},
{
provide: FeatureFlagService,
useValue: {},
},
{
provide: ExceptionHandlerService,
useValue: {},
},
{
provide: PermissionsService,
useValue: {},
},
{
provide: WorkspaceCacheStorageService,
useValue: {},
},
{
provide: getQueueToken(MessageQueue.deleteCascadeQueue),
useValue: {},
},
],
}).compile();
service = module.get<WorkspaceService>(WorkspaceService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,163 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
import { SeederService } from 'src/engine/seeder/seeder.service';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.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';
describe('WorkspaceManagerService', () => {
let service: WorkspaceManagerService;
let objectMetadataService: ObjectMetadataService;
let workspaceMigrationRepository: Repository<WorkspaceMigrationEntity>;
let dataSourceRepository: Repository<DataSourceEntity>;
let workspaceRelationMetadataRepository: Repository<RelationMetadataEntity>;
let workspaceFieldMetadataRepository: Repository<FieldMetadataEntity>;
let workspaceDataSourceService: WorkspaceDataSourceService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
WorkspaceManagerService,
WorkspaceMigrationService,
DataSourceService,
{
provide: getRepositoryToken(Workspace, 'core'),
useValue: {},
},
{
provide: getRepositoryToken(UserWorkspace, 'core'),
useValue: {},
},
{
provide: getRepositoryToken(FieldMetadataEntity, 'metadata'),
useValue: {
delete: jest.fn(),
},
},
{
provide: getRepositoryToken(RelationMetadataEntity, 'metadata'),
useValue: {
delete: jest.fn(),
},
},
{
provide: getRepositoryToken(ObjectMetadataEntity, 'metadata'),
useValue: {
delete: jest.fn(),
},
},
{
provide: getRepositoryToken(WorkspaceMigrationEntity, 'metadata'),
useValue: {
delete: jest.fn(),
},
},
{
provide: getRepositoryToken(DataSourceEntity, 'metadata'),
useValue: {
delete: jest.fn(),
},
},
{
provide: PermissionsService,
useValue: {},
},
{
provide: FeatureFlagService,
useValue: {},
},
{
provide: RoleService,
useValue: {},
},
{
provide: UserRoleService,
useValue: {},
},
{
provide: WorkspaceDataSourceService,
useValue: {
deleteWorkspaceDBSchema: jest.fn(),
},
},
{
provide: WorkspaceSyncMetadataService,
useValue: {},
},
{
provide: SeederService,
useValue: {},
},
{
provide: ObjectMetadataService,
useValue: {
deleteObjectsMetadata: jest.fn(),
},
},
],
}).compile();
service = module.get<WorkspaceManagerService>(WorkspaceManagerService);
objectMetadataService = module.get<ObjectMetadataService>(
ObjectMetadataService,
);
workspaceMigrationRepository = module.get<
Repository<WorkspaceMigrationEntity>
>(getRepositoryToken(WorkspaceMigrationEntity, 'metadata'));
dataSourceRepository = module.get<Repository<DataSourceEntity>>(
getRepositoryToken(DataSourceEntity, 'metadata'),
);
workspaceRelationMetadataRepository = module.get<
Repository<RelationMetadataEntity>
>(getRepositoryToken(RelationMetadataEntity, 'metadata'));
workspaceFieldMetadataRepository = module.get<
Repository<FieldMetadataEntity>
>(getRepositoryToken(FieldMetadataEntity, 'metadata'));
workspaceDataSourceService = module.get<WorkspaceDataSourceService>(
WorkspaceDataSourceService,
);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('delete', () => {
it('should delete all the workspace metadata tables and workspace schema', async () => {
await service.delete('workspace-id');
expect(objectMetadataService.deleteObjectsMetadata).toHaveBeenCalled();
expect(workspaceRelationMetadataRepository.delete).toHaveBeenCalledWith({
workspaceId: 'workspace-id',
});
expect(workspaceFieldMetadataRepository.delete).toHaveBeenCalledWith({
workspaceId: 'workspace-id',
});
expect(workspaceMigrationRepository.delete).toHaveBeenCalledWith({
workspaceId: 'workspace-id',
});
expect(dataSourceRepository.delete).toHaveBeenCalledWith({
workspaceId: 'workspace-id',
});
expect(
workspaceDataSourceService.deleteWorkspaceDBSchema,
).toHaveBeenCalled();
});
});
});