diff --git a/packages/twenty-server/src/engine/metadata-modules/view-field/controllers/view-field.controller.ts b/packages/twenty-server/src/engine/metadata-modules/view-field/controllers/view-field.controller.ts index 56c1d467b7..09c0eef700 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-field/controllers/view-field.controller.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-field/controllers/view-field.controller.ts @@ -13,11 +13,14 @@ import { import { isDefined } from 'twenty-shared/utils'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { CreateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input'; import { UpdateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/update-view-field.input'; +import { ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto'; import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity'; import { generateViewFieldExceptionMessage, @@ -27,13 +30,18 @@ import { ViewFieldExceptionMessageKey, } from 'src/engine/metadata-modules/view-field/exceptions/view-field.exception'; import { ViewFieldRestApiExceptionFilter } from 'src/engine/metadata-modules/view-field/filters/view-field-rest-api-exception.filter'; +import { ViewFieldV2Service } from 'src/engine/metadata-modules/view-field/services/view-field-v2.service'; import { ViewFieldService } from 'src/engine/metadata-modules/view-field/services/view-field.service'; @Controller('rest/metadata/viewFields') @UseGuards(WorkspaceAuthGuard) @UseFilters(ViewFieldRestApiExceptionFilter) export class ViewFieldController { - constructor(private readonly viewFieldService: ViewFieldService) {} + constructor( + private readonly viewFieldService: ViewFieldService, + private readonly viewFieldV2Service: ViewFieldV2Service, + private readonly featureFlagService: FeatureFlagService, + ) {} @Get() async findMany( @@ -77,25 +85,79 @@ export class ViewFieldController { @Param('id') id: string, @Body() input: UpdateViewFieldInput['update'], @AuthWorkspace() workspace: WorkspaceEntity, - ): Promise { + ): Promise { + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + if (isWorkspaceMigrationV2Enabled) { + return await this.viewFieldV2Service.updateOne({ + updateViewFieldInput: { id, update: input }, + workspaceId: workspace.id, + }); + } + const updatedViewField = await this.viewFieldService.update( id, workspace.id, input, ); - return updatedViewField; + // Convert ViewFieldEntity to ViewFieldDTO for consistency + return { + id: updatedViewField.id, + fieldMetadataId: updatedViewField.fieldMetadataId, + isVisible: updatedViewField.isVisible, + size: updatedViewField.size, + position: updatedViewField.position, + aggregateOperation: updatedViewField.aggregateOperation, + viewId: updatedViewField.viewId, + workspaceId: updatedViewField.workspaceId, + createdAt: updatedViewField.createdAt, + updatedAt: updatedViewField.updatedAt, + deletedAt: updatedViewField.deletedAt, + }; } @Post() async create( @Body() input: CreateViewFieldInput, @AuthWorkspace() workspace: WorkspaceEntity, - ): Promise { - return this.viewFieldService.create({ + ): Promise { + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + if (isWorkspaceMigrationV2Enabled) { + return await this.viewFieldV2Service.createOne({ + createViewFieldInput: input, + workspaceId: workspace.id, + }); + } + + const createdViewField = await this.viewFieldService.create({ ...input, workspaceId: workspace.id, }); + + // Convert ViewFieldEntity to ViewFieldDTO for consistency + return { + id: createdViewField.id, + fieldMetadataId: createdViewField.fieldMetadataId, + isVisible: createdViewField.isVisible, + size: createdViewField.size, + position: createdViewField.position, + aggregateOperation: createdViewField.aggregateOperation, + viewId: createdViewField.viewId, + workspaceId: createdViewField.workspaceId, + createdAt: createdViewField.createdAt, + updatedAt: createdViewField.updatedAt, + deletedAt: createdViewField.deletedAt, + }; } @Delete(':id') @@ -103,6 +165,21 @@ export class ViewFieldController { @Param('id') id: string, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise<{ success: boolean }> { + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + if (isWorkspaceMigrationV2Enabled) { + const deletedViewField = await this.viewFieldV2Service.deleteOne({ + deleteViewFieldInput: { id }, + workspaceId: workspace.id, + }); + + return { success: isDefined(deletedViewField) }; + } + const deletedViewField = await this.viewFieldService.delete( id, workspace.id, diff --git a/packages/twenty-server/src/engine/metadata-modules/view-field/filters/view-field-rest-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/view-field/filters/view-field-rest-api-exception.filter.ts index 8d3831d9e8..57b82b31b9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-field/filters/view-field-rest-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-field/filters/view-field-rest-api-exception.filter.ts @@ -2,47 +2,91 @@ import { type ArgumentsHost, Catch, type ExceptionFilter, + Injectable, } from '@nestjs/common'; import { type Response } from 'express'; +import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service'; +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { ViewFieldException, ViewFieldExceptionCode, } from 'src/engine/metadata-modules/view-field/exceptions/view-field.exception'; -import { type CustomException } from 'src/utils/custom-exception'; +import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; +import { fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util'; +import { + type CustomException, + UnknownException, +} from 'src/utils/custom-exception'; -@Catch(ViewFieldException) +@Injectable() +@Catch(ViewFieldException, WorkspaceMigrationBuilderExceptionV2) export class ViewFieldRestApiExceptionFilter implements ExceptionFilter { constructor( private readonly httpExceptionHandlerService: HttpExceptionHandlerService, + private readonly i18nService: I18nService, ) {} - catch(exception: ViewFieldException, host: ArgumentsHost) { + catch( + exception: ViewFieldException | WorkspaceMigrationBuilderExceptionV2, + host: ArgumentsHost, + ) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); - switch (exception.code) { - case ViewFieldExceptionCode.VIEW_FIELD_NOT_FOUND: - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 404, - ); - case ViewFieldExceptionCode.INVALID_VIEW_FIELD_DATA: - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 400, - ); - default: - // TODO: change to 500 when we have input validation - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 400, + if (exception instanceof WorkspaceMigrationBuilderExceptionV2) { + const i18n = this.i18nService.getI18nInstance(SOURCE_LOCALE); + const { errors, summary } = + fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError( + exception, + i18n, ); + + return response.status(400).json({ + statusCode: 400, + error: 'METADATA_VALIDATION_ERROR', + message: exception.message || 'Validation failed', + errors, + summary, + }); } + + if (exception instanceof ViewFieldException) { + switch (exception.code) { + case ViewFieldExceptionCode.VIEW_FIELD_NOT_FOUND: + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 404, + ); + case ViewFieldExceptionCode.INVALID_VIEW_FIELD_DATA: + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 400, + ); + default: + // TODO: change to 500 when we have input validation + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 400, + ); + } + } + + // Fallback for any other exception type + const unknownException = new UnknownException( + 'Internal server error', + 'INTERNAL_ERROR', + ); + + return this.httpExceptionHandlerService.handleError( + unknownException as CustomException, + response, + 500, + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/filters/view-filter-group-rest-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/filters/view-filter-group-rest-api-exception.filter.ts index b018da1871..31848bb388 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/filters/view-filter-group-rest-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/filters/view-filter-group-rest-api-exception.filter.ts @@ -2,47 +2,91 @@ import { type ArgumentsHost, Catch, type ExceptionFilter, + Injectable, } from '@nestjs/common'; import { type Response } from 'express'; +import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service'; +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { ViewFilterGroupException, ViewFilterGroupExceptionCode, } from 'src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception'; -import { type CustomException } from 'src/utils/custom-exception'; +import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; +import { fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util'; +import { + type CustomException, + UnknownException, +} from 'src/utils/custom-exception'; -@Catch(ViewFilterGroupException) +@Injectable() +@Catch(ViewFilterGroupException, WorkspaceMigrationBuilderExceptionV2) export class ViewFilterGroupRestApiExceptionFilter implements ExceptionFilter { constructor( private readonly httpExceptionHandlerService: HttpExceptionHandlerService, + private readonly i18nService: I18nService, ) {} - catch(exception: ViewFilterGroupException, host: ArgumentsHost) { + catch( + exception: ViewFilterGroupException | WorkspaceMigrationBuilderExceptionV2, + host: ArgumentsHost, + ) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); - switch (exception.code) { - case ViewFilterGroupExceptionCode.VIEW_FILTER_GROUP_NOT_FOUND: - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 404, - ); - case ViewFilterGroupExceptionCode.INVALID_VIEW_FILTER_GROUP_DATA: - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 400, - ); - default: - // TODO: change to 500 when we have input validation - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 400, + if (exception instanceof WorkspaceMigrationBuilderExceptionV2) { + const i18n = this.i18nService.getI18nInstance(SOURCE_LOCALE); + const { errors, summary } = + fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError( + exception, + i18n, ); + + return response.status(400).json({ + statusCode: 400, + error: 'METADATA_VALIDATION_ERROR', + message: exception.message || 'Validation failed', + errors, + summary, + }); } + + if (exception instanceof ViewFilterGroupException) { + switch (exception.code) { + case ViewFilterGroupExceptionCode.VIEW_FILTER_GROUP_NOT_FOUND: + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 404, + ); + case ViewFilterGroupExceptionCode.INVALID_VIEW_FILTER_GROUP_DATA: + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 400, + ); + default: + // TODO: change to 500 when we have input validation + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 400, + ); + } + } + + // Fallback for any other exception type + const unknownException = new UnknownException( + 'Internal server error', + 'INTERNAL_ERROR', + ); + + return this.httpExceptionHandlerService.handleError( + unknownException as CustomException, + response, + 500, + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter/controllers/view-filter.controller.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter/controllers/view-filter.controller.ts index fca95dffa2..cab161e027 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter/controllers/view-filter.controller.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter/controllers/view-filter.controller.ts @@ -13,6 +13,8 @@ import { import { isDefined } from 'twenty-shared/utils'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; @@ -27,13 +29,18 @@ import { ViewFilterExceptionMessageKey, } from 'src/engine/metadata-modules/view-filter/exceptions/view-filter.exception'; import { ViewFilterRestApiExceptionFilter } from 'src/engine/metadata-modules/view-filter/filters/view-filter-rest-api-exception.filter'; +import { ViewFilterV2Service } from 'src/engine/metadata-modules/view-filter/services/view-filter-v2.service'; import { ViewFilterService } from 'src/engine/metadata-modules/view-filter/services/view-filter.service'; @Controller('rest/metadata/viewFilters') @UseGuards(WorkspaceAuthGuard) @UseFilters(ViewFilterRestApiExceptionFilter) export class ViewFilterController { - constructor(private readonly viewFilterService: ViewFilterService) {} + constructor( + private readonly viewFilterService: ViewFilterService, + private readonly viewFilterV2Service: ViewFilterV2Service, + private readonly featureFlagService: FeatureFlagService, + ) {} @Get() async findMany( @@ -77,6 +84,19 @@ export class ViewFilterController { @Body() input: CreateViewFilterInput, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + if (isWorkspaceMigrationV2Enabled) { + return await this.viewFilterV2Service.createOne({ + createViewFilterInput: input, + workspaceId: workspace.id, + }); + } + return this.viewFilterService.create({ ...input, workspaceId: workspace.id, @@ -94,6 +114,19 @@ export class ViewFilterController { update: input.update ?? input, }; + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + if (isWorkspaceMigrationV2Enabled) { + return await this.viewFilterV2Service.updateOne({ + updateViewFilterInput: updateInput, + workspaceId: workspace.id, + }); + } + const updatedViewFilter = await this.viewFilterService.update( updateInput.id, workspace.id, @@ -108,6 +141,21 @@ export class ViewFilterController { @Param('id') id: string, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise<{ success: boolean }> { + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + if (isWorkspaceMigrationV2Enabled) { + const deletedViewFilter = await this.viewFilterV2Service.deleteOne({ + deleteViewFilterInput: { id }, + workspaceId: workspace.id, + }); + + return { success: isDefined(deletedViewFilter) }; + } + const deletedViewFilter = await this.viewFilterService.delete( id, workspace.id, diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter/filters/view-filter-rest-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter/filters/view-filter-rest-api-exception.filter.ts index f389e987f5..8b4ae2a6eb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter/filters/view-filter-rest-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter/filters/view-filter-rest-api-exception.filter.ts @@ -2,47 +2,91 @@ import { type ArgumentsHost, Catch, type ExceptionFilter, + Injectable, } from '@nestjs/common'; import { type Response } from 'express'; +import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service'; +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { ViewFilterException, ViewFilterExceptionCode, } from 'src/engine/metadata-modules/view-filter/exceptions/view-filter.exception'; -import { type CustomException } from 'src/utils/custom-exception'; +import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; +import { fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util'; +import { + type CustomException, + UnknownException, +} from 'src/utils/custom-exception'; -@Catch(ViewFilterException) +@Injectable() +@Catch(ViewFilterException, WorkspaceMigrationBuilderExceptionV2) export class ViewFilterRestApiExceptionFilter implements ExceptionFilter { constructor( private readonly httpExceptionHandlerService: HttpExceptionHandlerService, + private readonly i18nService: I18nService, ) {} - catch(exception: ViewFilterException, host: ArgumentsHost) { + catch( + exception: ViewFilterException | WorkspaceMigrationBuilderExceptionV2, + host: ArgumentsHost, + ) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); - switch (exception.code) { - case ViewFilterExceptionCode.VIEW_FILTER_NOT_FOUND: - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 404, - ); - case ViewFilterExceptionCode.INVALID_VIEW_FILTER_DATA: - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 400, - ); - default: - // TODO: change to 500 when we have input validation - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 400, + if (exception instanceof WorkspaceMigrationBuilderExceptionV2) { + const i18n = this.i18nService.getI18nInstance(SOURCE_LOCALE); + const { errors, summary } = + fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError( + exception, + i18n, ); + + return response.status(400).json({ + statusCode: 400, + error: 'METADATA_VALIDATION_ERROR', + message: exception.message || 'Validation failed', + errors, + summary, + }); } + + if (exception instanceof ViewFilterException) { + switch (exception.code) { + case ViewFilterExceptionCode.VIEW_FILTER_NOT_FOUND: + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 404, + ); + case ViewFilterExceptionCode.INVALID_VIEW_FILTER_DATA: + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 400, + ); + default: + // TODO: change to 500 when we have input validation + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 400, + ); + } + } + + // Fallback for any other exception type + const unknownException = new UnknownException( + 'Internal server error', + 'INTERNAL_ERROR', + ); + + return this.httpExceptionHandlerService.handleError( + unknownException as CustomException, + response, + 500, + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/controllers/view-group.controller.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/controllers/view-group.controller.ts index ad796c85e2..375ffa2bc2 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/controllers/view-group.controller.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-group/controllers/view-group.controller.ts @@ -13,6 +13,8 @@ import { import { isDefined } from 'twenty-shared/utils'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; @@ -27,13 +29,18 @@ import { ViewGroupExceptionMessageKey, } from 'src/engine/metadata-modules/view-group/exceptions/view-group.exception'; import { ViewGroupRestApiExceptionFilter } from 'src/engine/metadata-modules/view-group/filters/view-group-rest-api-exception.filter'; +import { ViewGroupV2Service } from 'src/engine/metadata-modules/view-group/services/view-group-v2.service'; import { ViewGroupService } from 'src/engine/metadata-modules/view-group/services/view-group.service'; @Controller('rest/metadata/viewGroups') @UseGuards(WorkspaceAuthGuard) @UseFilters(ViewGroupRestApiExceptionFilter) export class ViewGroupController { - constructor(private readonly viewGroupService: ViewGroupService) {} + constructor( + private readonly viewGroupService: ViewGroupService, + private readonly viewGroupV2Service: ViewGroupV2Service, + private readonly featureFlagService: FeatureFlagService, + ) {} @Get() async findMany( @@ -77,6 +84,19 @@ export class ViewGroupController { @Body() input: CreateViewGroupInput, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + if (isWorkspaceMigrationV2Enabled) { + return await this.viewGroupV2Service.createOne({ + createViewGroupInput: input, + workspaceId: workspace.id, + }); + } + return this.viewGroupService.create({ ...input, workspaceId: workspace.id, @@ -89,6 +109,24 @@ export class ViewGroupController { @Body() input: UpdateViewGroupInput, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + if (isWorkspaceMigrationV2Enabled) { + const updateInput = { + id, + update: input.update ?? input, + }; + + return await this.viewGroupV2Service.updateOne({ + updateViewGroupInput: updateInput, + workspaceId: workspace.id, + }); + } + const updatedViewGroup = await this.viewGroupService.update( id, workspace.id, @@ -103,6 +141,21 @@ export class ViewGroupController { @Param('id') id: string, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise<{ success: boolean }> { + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + if (isWorkspaceMigrationV2Enabled) { + const deletedViewGroup = await this.viewGroupV2Service.deleteOne({ + deleteViewGroupInput: { id }, + workspaceId: workspace.id, + }); + + return { success: isDefined(deletedViewGroup) }; + } + const deletedViewGroup = await this.viewGroupService.delete( id, workspace.id, diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/exceptions/view-group.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/exceptions/view-group.exception.ts index 0346a5db7f..4c8335ff06 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/exceptions/view-group.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-group/exceptions/view-group.exception.ts @@ -1,6 +1,5 @@ -import { msg } from '@lingui/core/macro'; import { type MessageDescriptor } from '@lingui/core'; -import { assertUnreachable } from 'twenty-shared/utils'; +import { msg } from '@lingui/core/macro'; import { CustomException } from 'src/utils/custom-exception'; @@ -44,7 +43,7 @@ export const generateViewGroupExceptionMessage = ( case ViewGroupExceptionMessageKey.FIELD_METADATA_ID_REQUIRED: return 'FieldMetadataId is required'; default: - assertUnreachable(key); + return 'unknown'; } }; @@ -58,5 +57,8 @@ export const generateViewGroupUserFriendlyExceptionMessage = ( return msg`ViewId is required to create a view group.`; case ViewGroupExceptionMessageKey.FIELD_METADATA_ID_REQUIRED: return msg`FieldMetadataId is required to create a view group.`; + default: { + return msg`unknown`; + } } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/filters/view-group-rest-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/filters/view-group-rest-api-exception.filter.ts index 1a4b4f145c..14c1eefcd7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/filters/view-group-rest-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-group/filters/view-group-rest-api-exception.filter.ts @@ -2,47 +2,90 @@ import { type ArgumentsHost, Catch, type ExceptionFilter, + Injectable, } from '@nestjs/common'; import { type Response } from 'express'; +import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service'; +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { ViewGroupException, ViewGroupExceptionCode, } from 'src/engine/metadata-modules/view-group/exceptions/view-group.exception'; -import { type CustomException } from 'src/utils/custom-exception'; +import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; +import { fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util'; +import { + type CustomException, + UnknownException, +} from 'src/utils/custom-exception'; -@Catch(ViewGroupException) +@Injectable() +@Catch(ViewGroupException, WorkspaceMigrationBuilderExceptionV2) export class ViewGroupRestApiExceptionFilter implements ExceptionFilter { constructor( private readonly httpExceptionHandlerService: HttpExceptionHandlerService, + private readonly i18nService: I18nService, ) {} - catch(exception: ViewGroupException, host: ArgumentsHost) { + catch( + exception: ViewGroupException | WorkspaceMigrationBuilderExceptionV2, + host: ArgumentsHost, + ) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); - switch (exception.code) { - case ViewGroupExceptionCode.VIEW_GROUP_NOT_FOUND: - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 404, - ); - case ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA: - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 400, - ); - default: - // TODO: change to 500 when we have input validation - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 400, + if (exception instanceof WorkspaceMigrationBuilderExceptionV2) { + const i18n = this.i18nService.getI18nInstance(SOURCE_LOCALE); + const { errors, summary } = + fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError( + exception, + i18n, ); + + return response.status(400).json({ + statusCode: 400, + error: 'METADATA_VALIDATION_ERROR', + message: exception.message || 'Validation failed', + errors, + summary, + }); } + + if (exception instanceof ViewGroupException) { + switch (exception.code) { + case ViewGroupExceptionCode.VIEW_GROUP_NOT_FOUND: + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 404, + ); + case ViewGroupExceptionCode.INVALID_VIEW_GROUP_DATA: + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 400, + ); + default: + // TODO: change to 500 when we have input validation + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 400, + ); + } + } + + const unknownException = new UnknownException( + 'Internal server error', + 'INTERNAL_ERROR', + ); + + return this.httpExceptionHandlerService.handleError( + unknownException as CustomException, + response, + 500, + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-sort/filters/view-sort-rest-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/view-sort/filters/view-sort-rest-api-exception.filter.ts index a25f122ac1..7a4fdfa640 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-sort/filters/view-sort-rest-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-sort/filters/view-sort-rest-api-exception.filter.ts @@ -2,47 +2,91 @@ import { type ArgumentsHost, Catch, type ExceptionFilter, + Injectable, } from '@nestjs/common'; import { type Response } from 'express'; +import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service'; +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { ViewSortException, ViewSortExceptionCode, } from 'src/engine/metadata-modules/view-sort/exceptions/view-sort.exception'; -import { type CustomException } from 'src/utils/custom-exception'; +import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; +import { fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util'; +import { + type CustomException, + UnknownException, +} from 'src/utils/custom-exception'; -@Catch(ViewSortException) +@Injectable() +@Catch(ViewSortException, WorkspaceMigrationBuilderExceptionV2) export class ViewSortRestApiExceptionFilter implements ExceptionFilter { constructor( private readonly httpExceptionHandlerService: HttpExceptionHandlerService, + private readonly i18nService: I18nService, ) {} - catch(exception: ViewSortException, host: ArgumentsHost) { + catch( + exception: ViewSortException | WorkspaceMigrationBuilderExceptionV2, + host: ArgumentsHost, + ) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); - switch (exception.code) { - case ViewSortExceptionCode.VIEW_SORT_NOT_FOUND: - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 404, - ); - case ViewSortExceptionCode.INVALID_VIEW_SORT_DATA: - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 400, - ); - default: - // TODO: change to 500 when we have input validation - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 400, + if (exception instanceof WorkspaceMigrationBuilderExceptionV2) { + const i18n = this.i18nService.getI18nInstance(SOURCE_LOCALE); + const { errors, summary } = + fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError( + exception, + i18n, ); + + return response.status(400).json({ + statusCode: 400, + error: 'METADATA_VALIDATION_ERROR', + message: exception.message || 'Validation failed', + errors, + summary, + }); } + + if (exception instanceof ViewSortException) { + switch (exception.code) { + case ViewSortExceptionCode.VIEW_SORT_NOT_FOUND: + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 404, + ); + case ViewSortExceptionCode.INVALID_VIEW_SORT_DATA: + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 400, + ); + default: + // TODO: change to 500 when we have input validation + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 400, + ); + } + } + + // Fallback for any other exception type + const unknownException = new UnknownException( + 'Internal server error', + 'INTERNAL_ERROR', + ); + + return this.httpExceptionHandlerService.handleError( + unknownException as CustomException, + response, + 500, + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view/controllers/view.controller.ts b/packages/twenty-server/src/engine/metadata-modules/view/controllers/view.controller.ts index 0bb196a6a7..71bc98424e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view/controllers/view.controller.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view/controllers/view.controller.ts @@ -14,6 +14,8 @@ import { import { type APP_LOCALES } from 'twenty-shared/translations'; import { isDefined } from 'twenty-shared/utils'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; @@ -31,6 +33,7 @@ import { ViewExceptionMessageKey, } from 'src/engine/metadata-modules/view/exceptions/view.exception'; import { ViewRestApiExceptionFilter } from 'src/engine/metadata-modules/view/filters/view-rest-api-exception.filter'; +import { ViewV2Service } from 'src/engine/metadata-modules/view/services/view-v2.service'; import { ViewService } from 'src/engine/metadata-modules/view/services/view.service'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; @@ -40,6 +43,8 @@ import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/works export class ViewController { constructor( private readonly viewService: ViewService, + private readonly viewV2Service: ViewV2Service, + private readonly featureFlagService: FeatureFlagService, private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService, private readonly i18nService: I18nService, ) {} @@ -98,10 +103,25 @@ export class ViewController { @AuthWorkspace() workspace: WorkspaceEntity, @RequestLocale() locale?: keyof typeof APP_LOCALES, ): Promise { - const view = await this.viewService.create({ - ...input, - workspaceId: workspace.id, - }); + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + let view: ViewDTO; + + if (isWorkspaceMigrationV2Enabled) { + view = await this.viewV2Service.createOne({ + createViewInput: input, + workspaceId: workspace.id, + }); + } else { + view = await this.viewService.create({ + ...input, + workspaceId: workspace.id, + }); + } const processedViews = await this.processViewsWithTemplates( [view], @@ -119,7 +139,25 @@ export class ViewController { @RequestLocale() locale: keyof typeof APP_LOCALES | undefined, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { - const updatedView = await this.viewService.update(id, workspace.id, input); + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + let updatedView: ViewDTO; + + if (isWorkspaceMigrationV2Enabled) { + updatedView = await this.viewV2Service.updateOne({ + updateViewInput: { + ...input, + id, + }, + workspaceId: workspace.id, + }); + } else { + updatedView = await this.viewService.update(id, workspace.id, input); + } const processedViews = await this.processViewsWithTemplates( [updatedView], @@ -135,7 +173,22 @@ export class ViewController { @Param('id') id: string, @AuthWorkspace() workspace: WorkspaceEntity, ): Promise<{ success: boolean }> { - const deletedView = await this.viewService.delete(id, workspace.id); + const isWorkspaceMigrationV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED, + workspace.id, + ); + + let deletedView: ViewDTO | null; + + if (isWorkspaceMigrationV2Enabled) { + deletedView = await this.viewV2Service.deleteOne({ + deleteViewInput: { id }, + workspaceId: workspace.id, + }); + } else { + deletedView = await this.viewService.delete(id, workspace.id); + } return { success: isDefined(deletedView) }; } diff --git a/packages/twenty-server/src/engine/metadata-modules/view/filters/view-rest-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/view/filters/view-rest-api-exception.filter.ts index 756675f8ba..d2710688c9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view/filters/view-rest-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view/filters/view-rest-api-exception.filter.ts @@ -2,47 +2,91 @@ import { type ArgumentsHost, Catch, type ExceptionFilter, + Injectable, } from '@nestjs/common'; import { type Response } from 'express'; +import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service'; +import { I18nService } from 'src/engine/core-modules/i18n/i18n.service'; import { ViewException, ViewExceptionCode, } from 'src/engine/metadata-modules/view/exceptions/view.exception'; -import { type CustomException } from 'src/utils/custom-exception'; +import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; +import { fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util'; +import { + type CustomException, + UnknownException, +} from 'src/utils/custom-exception'; -@Catch(ViewException) +@Injectable() +@Catch(ViewException, WorkspaceMigrationBuilderExceptionV2) export class ViewRestApiExceptionFilter implements ExceptionFilter { constructor( private readonly httpExceptionHandlerService: HttpExceptionHandlerService, + private readonly i18nService: I18nService, ) {} - catch(exception: ViewException, host: ArgumentsHost) { + catch( + exception: ViewException | WorkspaceMigrationBuilderExceptionV2, + host: ArgumentsHost, + ) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); - switch (exception.code) { - case ViewExceptionCode.VIEW_NOT_FOUND: - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 404, - ); - case ViewExceptionCode.INVALID_VIEW_DATA: - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 400, - ); - default: - // TODO: change to 500 when we have input validation - return this.httpExceptionHandlerService.handleError( - exception as CustomException, - response, - 400, + if (exception instanceof WorkspaceMigrationBuilderExceptionV2) { + const i18n = this.i18nService.getI18nInstance(SOURCE_LOCALE); + const { errors, summary } = + fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseError( + exception, + i18n, ); + + return response.status(400).json({ + statusCode: 400, + error: 'METADATA_VALIDATION_ERROR', + message: exception.message || 'Validation failed', + errors, + summary, + }); } + + if (exception instanceof ViewException) { + switch (exception.code) { + case ViewExceptionCode.VIEW_NOT_FOUND: + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 404, + ); + case ViewExceptionCode.INVALID_VIEW_DATA: + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 400, + ); + default: + // TODO: change to 500 when we have input validation + return this.httpExceptionHandlerService.handleError( + exception as CustomException, + response, + 400, + ); + } + } + + // Fallback for any other exception type + const unknownException = new UnknownException( + 'Internal server error', + 'INTERNAL_ERROR', + ); + + return this.httpExceptionHandlerService.handleError( + unknownException as CustomException, + response, + 500, + ); } } diff --git a/packages/twenty-server/test/integration/constants/test-view-ids.constants.ts b/packages/twenty-server/test/integration/constants/test-view-ids.constants.ts deleted file mode 100644 index d77bb590b5..0000000000 --- a/packages/twenty-server/test/integration/constants/test-view-ids.constants.ts +++ /dev/null @@ -1,21 +0,0 @@ -export const TEST_VIEW_1_ID = '20202020-05e2-439a-a768-d10e2945759e'; -export const TEST_VIEW_2_ID = '20202020-d172-433b-ba1b-b03ad2560f09'; -export const TEST_VIEW_3_ID = '20202020-2bbd-4e25-b141-24f11da50e0a'; - -export const TEST_OBJECT_METADATA_1_ID = '20202020-1436-4a52-b386-74a0142fb1d1'; -export const TEST_OBJECT_METADATA_2_ID = '20202020-887f-4bc0-9011-2a81a3e852c3'; - -export const TEST_FIELD_METADATA_1_ID = '20202020-ac79-494f-a0a5-e456c72dcc6f'; -export const TEST_FIELD_METADATA_2_ID = '20202020-be78-4d14-a413-ea4d8562e6f0'; - -export const TEST_NOT_EXISTING_VIEW_ID = '20202020-ce26-4249-93a0-b71d405d3775'; -export const TEST_NOT_EXISTING_VIEW_FIELD_ID = - '20202020-6be8-40ec-82fa-5c9ae1930915'; -export const TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID = - '20202020-e214-44fa-a39a-d81447b2c44f'; -export const TEST_NOT_EXISTING_VIEW_FILTER_ID = - '20202020-034a-433d-b6e6-5ee7aaf5aaa6'; -export const TEST_NOT_EXISTING_VIEW_SORT_ID = - '20202020-e0dd-49d6-94f9-a23416fc5a50'; -export const TEST_NOT_EXISTING_VIEW_GROUP_ID = - '20202020-d1df-43be-9cf7-187dbffa08d7'; diff --git a/packages/twenty-server/test/integration/graphql/suites/view/create-view.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/create-view.integration-spec.ts index 60dfae0cfd..98d6b21bd3 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/create-view.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/create-view.integration-spec.ts @@ -2,10 +2,7 @@ import { createOneObjectMetadata } from 'test/integration/metadata/suites/object import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util'; -import { - assertViewStructure, - cleanupViewRecords, -} from 'test/integration/utils/view-test.util'; +import { assertViewStructure } from 'test/integration/utils/view-test.util'; import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in'; import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum'; @@ -46,11 +43,6 @@ describe('Create core view', () => { expectToFail: false, input: { idToDelete: testObjectMetadataId }, }); - await cleanupViewRecords(); - }); - - beforeEach(async () => { - await cleanupViewRecords(); }); it('should create a new view with all properties', async () => { diff --git a/packages/twenty-server/test/integration/graphql/suites/view/delete-view.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/delete-view.integration-spec.ts index d5f66b2447..590a79de94 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/delete-view.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/delete-view.integration-spec.ts @@ -1,4 +1,3 @@ -import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants'; import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; @@ -6,7 +5,8 @@ import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util'; import { deleteOneCoreView } from 'test/integration/metadata/suites/view/utils/delete-one-core-view.util'; import { findOneCoreView } from 'test/integration/metadata/suites/view/utils/find-one-core-view.util'; -import { cleanupViewRecords } from 'test/integration/utils/view-test.util'; + +const TEST_NOT_EXISTING_VIEW_ID = '20202020-0000-4000-8000-000000000000'; describe('Delete core view', () => { let testObjectMetadataId: string; @@ -44,11 +44,6 @@ describe('Delete core view', () => { expectToFail: false, input: { idToDelete: testObjectMetadataId }, }); - await cleanupViewRecords(); - }); - - beforeEach(async () => { - await cleanupViewRecords(); }); it('should delete an existing view', async () => { diff --git a/packages/twenty-server/test/integration/graphql/suites/view/destroy-view.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/destroy-view.integration-spec.ts index 1fb826d768..2707c61649 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/destroy-view.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/destroy-view.integration-spec.ts @@ -1,4 +1,3 @@ -import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants'; import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; @@ -7,7 +6,8 @@ import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/c import { deleteOneCoreView } from 'test/integration/metadata/suites/view/utils/delete-one-core-view.util'; import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util'; import { findOneCoreView } from 'test/integration/metadata/suites/view/utils/find-one-core-view.util'; -import { cleanupViewRecords } from 'test/integration/utils/view-test.util'; + +const TEST_NOT_EXISTING_VIEW_ID = '20202020-0000-4000-8000-000000000000'; describe('Destroy core view', () => { let testObjectMetadataId: string; @@ -45,11 +45,6 @@ describe('Destroy core view', () => { expectToFail: false, input: { idToDelete: testObjectMetadataId }, }); - await cleanupViewRecords(); - }); - - beforeEach(async () => { - await cleanupViewRecords(); }); it.only('should destroy an existing view', async () => { diff --git a/packages/twenty-server/test/integration/graphql/suites/view/successful-find-view.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/successful-find-view.integration-spec.ts index 6300960991..70561f74d6 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/successful-find-view.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/successful-find-view.integration-spec.ts @@ -13,8 +13,7 @@ import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum'; -// TODO prastoin deprecate cleanupViewRecords that breaks this test suite -describe.skip('successful find view with all sub-relations (e2e)', () => { +describe('successful find view with all sub-relations (e2e)', () => { let companyObjectMetadataId: string; const COMPREHENSIVE_VIEW_GQL_FIELDS = ` diff --git a/packages/twenty-server/test/integration/graphql/suites/view/update-view.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/update-view.integration-spec.ts index 794480bf36..15085a733d 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/update-view.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/update-view.integration-spec.ts @@ -1,14 +1,14 @@ -import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants'; import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { createOneCoreView } from 'test/integration/metadata/suites/view/utils/create-one-core-view.util'; import { updateOneCoreView } from 'test/integration/metadata/suites/view/utils/update-one-core-view.util'; -import { cleanupViewRecords } from 'test/integration/utils/view-test.util'; import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum'; +const TEST_NOT_EXISTING_VIEW_ID = '20202020-0000-4000-8000-000000000000'; + describe('Update core view', () => { let testObjectMetadataId: string; @@ -45,11 +45,6 @@ describe('Update core view', () => { expectToFail: false, input: { idToDelete: testObjectMetadataId }, }); - await cleanupViewRecords(); - }); - - beforeEach(async () => { - await cleanupViewRecords(); }); it('should update an existing view', async () => { diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-field/failing-delete-view-field-v2.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-field/failing-delete-view-field-v2.integration-spec.ts index 763c2d4905..13313161df 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-field/failing-delete-view-field-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-field/failing-delete-view-field-v2.integration-spec.ts @@ -1,4 +1,3 @@ -import { TEST_NOT_EXISTING_VIEW_FIELD_ID } from 'test/integration/constants/test-view-ids.constants'; import { cleanupViewFieldTestV2, setupViewFieldTestV2, @@ -13,6 +12,8 @@ import { import { type DeleteViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/delete-view-field.input'; +const TEST_NOT_EXISTING_VIEW_FIELD_ID = '20202020-0000-4000-8000-000000000001'; + describe('View Field Resolver - Failing Delete Operation - v2', () => { let testSetup: ViewFieldTestSetup; diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-field/failing-destroy-view-field-v2.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-field/failing-destroy-view-field-v2.integration-spec.ts index 67e1f0fcae..bf65d7500c 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-field/failing-destroy-view-field-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-field/failing-destroy-view-field-v2.integration-spec.ts @@ -1,4 +1,3 @@ -import { TEST_NOT_EXISTING_VIEW_FIELD_ID } from 'test/integration/constants/test-view-ids.constants'; import { cleanupViewFieldTestV2, setupViewFieldTestV2, @@ -13,6 +12,8 @@ import { import { type DestroyViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/destroy-view-field.input'; +const TEST_NOT_EXISTING_VIEW_FIELD_ID = '20202020-0000-4000-8000-000000000001'; + describe('View Field Resolver - Failing Destroy Operation - v2', () => { let testSetup: ViewFieldTestSetup; diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-field/failing-update-view-field-v2.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-field/failing-update-view-field-v2.integration-spec.ts index 3ae9325f7f..7814c7ccf0 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-field/failing-update-view-field-v2.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-field/failing-update-view-field-v2.integration-spec.ts @@ -1,4 +1,3 @@ -import { TEST_NOT_EXISTING_VIEW_FIELD_ID } from 'test/integration/constants/test-view-ids.constants'; import { cleanupViewFieldTestV2, setupViewFieldTestV2, @@ -13,6 +12,8 @@ import { import { type UpdateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/update-view-field.input'; +const TEST_NOT_EXISTING_VIEW_FIELD_ID = '20202020-0000-4000-8000-000000000001'; + describe('View Field Resolver - Failing Update Operation - v2', () => { let testSetup: ViewFieldTestSetup; diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-field/view-field-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-field/view-field-resolver.integration-spec.ts index 28db7c3a45..949fa7691c 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-field/view-field-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-field/view-field-resolver.integration-spec.ts @@ -1,4 +1,3 @@ -import { TEST_NOT_EXISTING_VIEW_FIELD_ID } from 'test/integration/constants/test-view-ids.constants'; import { findViewFieldsOperationFactory } from 'test/integration/graphql/utils/find-view-fields-operation-factory.util'; import { assertGraphQLErrorResponse, @@ -19,10 +18,8 @@ import { createOneCoreViewField } from 'test/integration/metadata/suites/view-fi import { deleteOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/delete-one-core-view-field.util'; import { destroyOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/destroy-one-core-view-field.util'; import { updateOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/update-one-core-view-field.util'; -import { - assertViewFieldStructure, - cleanupViewRecords, -} from 'test/integration/utils/view-test.util'; +import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util'; +import { assertViewFieldStructure } from 'test/integration/utils/view-test.util'; import { FieldMetadataType } from 'twenty-shared/types'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; @@ -33,6 +30,8 @@ import { ViewFieldExceptionMessageKey, } from 'src/engine/metadata-modules/view-field/exceptions/view-field.exception'; +const TEST_NOT_EXISTING_VIEW_FIELD_ID = '20202020-0000-4000-8000-000000000001'; + describe('View Field Resolver', () => { let testViewId: string; let testObjectMetadataId: string; @@ -104,7 +103,6 @@ describe('View Field Resolver', () => { expectToFail: false, input: { idToDelete: testObjectMetadataId }, }); - await cleanupViewRecords(); }); afterAll(async () => { @@ -116,8 +114,6 @@ describe('View Field Resolver', () => { }); beforeEach(async () => { - await cleanupViewRecords(); - const view = await createTestViewWithGraphQL({ name: 'Test View for Fields', objectMetadataId: testObjectMetadataId, @@ -126,6 +122,13 @@ describe('View Field Resolver', () => { testViewId = view.id; }); + afterEach(async () => { + await destroyOneCoreView({ + viewId: testViewId, + expectToFail: false, + }); + }); + describe('getCoreViewFields', () => { it('should return empty array when no view fields exist', async () => { const operation = findViewFieldsOperationFactory({ viewId: testViewId }); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-filter-group-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-filter-group-resolver.integration-spec.ts index c1875b8ab8..26478ff6bb 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-filter-group-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-filter-group-resolver.integration-spec.ts @@ -1,4 +1,3 @@ -import { TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID } from 'test/integration/constants/test-view-ids.constants'; import { createViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/create-view-filter-group-operation-factory.util'; import { deleteViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/delete-view-filter-group-operation-factory.util'; import { destroyViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/destroy-view-filter-group-operation-factory.util'; @@ -18,23 +17,28 @@ import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-g import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { - assertViewFilterGroupStructure, - cleanupViewRecords, -} from 'test/integration/utils/view-test.util'; +import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util'; +import { assertViewFilterGroupStructure } from 'test/integration/utils/view-test.util'; +import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { type ViewFilterGroupDTO } from 'src/engine/metadata-modules/view-filter-group/dtos/view-filter-group.dto'; import { ViewFilterGroupLogicalOperator } from 'src/engine/metadata-modules/view-filter-group/enums/view-filter-group-logical-operator'; import { generateViewFilterGroupExceptionMessage, ViewFilterGroupExceptionMessageKey, } from 'src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception'; +const TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID = + '20202020-0000-4000-8000-000000000002'; + describe('View Filter Group Resolver', () => { let testViewId: string; let testObjectMetadataId: string; + let createdViewFilterGroup: string[] = []; + beforeAll(async () => { const { data: { @@ -68,12 +72,9 @@ describe('View Filter Group Resolver', () => { expectToFail: false, input: { idToDelete: testObjectMetadataId }, }); - await cleanupViewRecords(); }); beforeEach(async () => { - await cleanupViewRecords(); - const view = await createTestViewWithGraphQL({ name: 'Test View for Filter Groups', objectMetadataId: testObjectMetadataId, @@ -82,6 +83,23 @@ describe('View Filter Group Resolver', () => { testViewId = view.id; }); + afterEach(async () => { + // Clean up all created view filter groups + for (const filterGroupId of createdViewFilterGroup) { + const destroyOperation = destroyViewFilterGroupOperationFactory({ + viewFilterGroupId: filterGroupId, + }); + + await makeGraphqlAPIRequest(destroyOperation); + } + createdViewFilterGroup = []; + + await destroyOneCoreView({ + viewId: testViewId, + expectToFail: false, + }); + }); + describe('getCoreViewFilterGroups', () => { it('should return empty array when no view filter groups exist', async () => { const operation = findViewFilterGroupsOperationFactory({ @@ -101,14 +119,23 @@ describe('View Filter Group Resolver', () => { data: filterGroupData, }); - await makeGraphqlAPIRequest(createOperation); + const createResponse = await makeGraphqlAPIRequest(createOperation); + const createdFilterGroupId = + createResponse.body.data.createCoreViewFilterGroup.id; + + createdViewFilterGroup.push(createdFilterGroupId); const getOperation = findViewFilterGroupsOperationFactory(); const response = await makeGraphqlAPIRequest(getOperation); assertGraphQLSuccessfulResponse(response); - expect(response.body.data.getCoreViewFilterGroups).toHaveLength(1); - expect(response.body.data.getCoreViewFilterGroups[0]).toMatchObject({ + + const foundFilterGroup = response.body.data.getCoreViewFilterGroups.find( + (el: ViewFilterGroupDTO) => el.id === createdFilterGroupId, + ); + + jestExpectToBeDefined(foundFilterGroup); + expect(foundFilterGroup).toMatchObject({ logicalOperator: ViewFilterGroupLogicalOperator.AND, viewId: testViewId, }); @@ -122,7 +149,11 @@ describe('View Filter Group Resolver', () => { data: filterGroupData, }); - await makeGraphqlAPIRequest(createOperation); + const createResponse = await makeGraphqlAPIRequest(createOperation); + const createdFilterGroupId = + createResponse.body.data.createCoreViewFilterGroup.id; + + createdViewFilterGroup.push(createdFilterGroupId); const getOperation = findViewFilterGroupsOperationFactory({ viewId: testViewId, @@ -130,14 +161,16 @@ describe('View Filter Group Resolver', () => { const response = await makeGraphqlAPIRequest(getOperation); assertGraphQLSuccessfulResponse(response); - expect(response.body.data.getCoreViewFilterGroups).toHaveLength(1); - assertViewFilterGroupStructure( - response.body.data.getCoreViewFilterGroups[0], - { - logicalOperator: ViewFilterGroupLogicalOperator.OR, - viewId: testViewId, - }, + + const foundFilterGroup = response.body.data.getCoreViewFilterGroups.find( + (group: ViewFilterGroupDTO) => group.id === createdFilterGroupId, ); + + jestExpectToBeDefined(foundFilterGroup); + assertViewFilterGroupStructure(foundFilterGroup, { + logicalOperator: ViewFilterGroupLogicalOperator.OR, + viewId: testViewId, + }); }); it('should return nested filter groups with parent relationships', async () => { @@ -150,6 +183,8 @@ describe('View Filter Group Resolver', () => { const parentResponse = await makeGraphqlAPIRequest(parentOperation); const parentId = parentResponse.body.data.createCoreViewFilterGroup.id; + createdViewFilterGroup.push(parentId); + const childData = createViewFilterGroupData(testViewId, { parentViewFilterGroupId: parentId, logicalOperator: ViewFilterGroupLogicalOperator.OR, @@ -158,7 +193,10 @@ describe('View Filter Group Resolver', () => { data: childData, }); - await makeGraphqlAPIRequest(childOperation); + const childResponse = await makeGraphqlAPIRequest(childOperation); + const childId = childResponse.body.data.createCoreViewFilterGroup.id; + + createdViewFilterGroup.push(childId); const getOperation = findViewFilterGroupsOperationFactory({ viewId: testViewId, @@ -166,15 +204,17 @@ describe('View Filter Group Resolver', () => { const response = await makeGraphqlAPIRequest(getOperation); assertGraphQLSuccessfulResponse(response); - expect(response.body.data.getCoreViewFilterGroups).toHaveLength(2); const parentGroup = response.body.data.getCoreViewFilterGroups.find( - (group: any) => group.parentViewFilterGroupId === null, + (group: ViewFilterGroupDTO) => group.id === parentId, ); const childGroup = response.body.data.getCoreViewFilterGroups.find( - (group: any) => group.parentViewFilterGroupId !== null, + (group: ViewFilterGroupDTO) => group.id === childId, ); + jestExpectToBeDefined(parentGroup); + jestExpectToBeDefined(childGroup); + assertViewFilterGroupStructure(parentGroup, { logicalOperator: ViewFilterGroupLogicalOperator.AND, parentViewFilterGroupId: null, @@ -209,6 +249,8 @@ describe('View Filter Group Resolver', () => { const filterGroupId = createResponse.body.data.createCoreViewFilterGroup.id; + createdViewFilterGroup.push(filterGroupId); + const getOperation = findViewFilterGroupOperationFactory({ viewFilterGroupId: filterGroupId, }); @@ -237,6 +279,10 @@ describe('View Filter Group Resolver', () => { const response = await makeGraphqlAPIRequest(operation); assertGraphQLSuccessfulResponse(response); + + createdViewFilterGroup.push( + response.body.data.createCoreViewFilterGroup.id, + ); assertViewFilterGroupStructure( response.body.data.createCoreViewFilterGroup, { @@ -256,6 +302,10 @@ describe('View Filter Group Resolver', () => { const response = await makeGraphqlAPIRequest(operation); assertGraphQLSuccessfulResponse(response); + + createdViewFilterGroup.push( + response.body.data.createCoreViewFilterGroup.id, + ); assertViewFilterGroupStructure( response.body.data.createCoreViewFilterGroup, { @@ -274,6 +324,10 @@ describe('View Filter Group Resolver', () => { const response = await makeGraphqlAPIRequest(operation); assertGraphQLSuccessfulResponse(response); + + createdViewFilterGroup.push( + response.body.data.createCoreViewFilterGroup.id, + ); assertViewFilterGroupStructure( response.body.data.createCoreViewFilterGroup, { @@ -292,6 +346,8 @@ describe('View Filter Group Resolver', () => { const parentResponse = await makeGraphqlAPIRequest(parentOperation); const parentId = parentResponse.body.data.createCoreViewFilterGroup.id; + createdViewFilterGroup.push(parentId); + const childData = createViewFilterGroupData(testViewId, { parentViewFilterGroupId: parentId, logicalOperator: ViewFilterGroupLogicalOperator.OR, @@ -302,6 +358,10 @@ describe('View Filter Group Resolver', () => { const childResponse = await makeGraphqlAPIRequest(childOperation); assertGraphQLSuccessfulResponse(childResponse); + + createdViewFilterGroup.push( + childResponse.body.data.createCoreViewFilterGroup.id, + ); assertViewFilterGroupStructure( childResponse.body.data.createCoreViewFilterGroup, { @@ -324,6 +384,8 @@ describe('View Filter Group Resolver', () => { const filterGroupId = createResponse.body.data.createCoreViewFilterGroup.id; + createdViewFilterGroup.push(filterGroupId); + const updateInput = updateViewFilterGroupData({ logicalOperator: ViewFilterGroupLogicalOperator.OR, }); @@ -350,6 +412,8 @@ describe('View Filter Group Resolver', () => { const parentResponse = await makeGraphqlAPIRequest(parentOperation); const parentId = parentResponse.body.data.createCoreViewFilterGroup.id; + createdViewFilterGroup.push(parentId); + const childData = createViewFilterGroupData(testViewId, { logicalOperator: ViewFilterGroupLogicalOperator.OR, }); @@ -359,6 +423,8 @@ describe('View Filter Group Resolver', () => { const childResponse = await makeGraphqlAPIRequest(childOperation); const childId = childResponse.body.data.createCoreViewFilterGroup.id; + createdViewFilterGroup.push(childId); + const updateInput = updateViewFilterGroupData({ parentViewFilterGroupId: parentId, }); @@ -408,6 +474,8 @@ describe('View Filter Group Resolver', () => { const filterGroupId = createResponse.body.data.createCoreViewFilterGroup.id; + createdViewFilterGroup.push(filterGroupId); + const deleteOperation = deleteViewFilterGroupOperationFactory({ viewFilterGroupId: filterGroupId, }); @@ -453,6 +521,8 @@ describe('View Filter Group Resolver', () => { const filterGroupId = createResponse.body.data.createCoreViewFilterGroup.id; + createdViewFilterGroup.push(filterGroupId); + const destroyOperation = destroyViewFilterGroupOperationFactory({ viewFilterGroupId: filterGroupId, }); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-filter-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-filter-resolver.integration-spec.ts index a0a9dae58d..df15570515 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-filter-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-filter-resolver.integration-spec.ts @@ -9,7 +9,7 @@ import { deleteOneCoreViewFilter } from 'test/integration/metadata/suites/view-f import { destroyOneCoreViewFilter } from 'test/integration/metadata/suites/view-filter/utils/destroy-one-core-view-filter.util'; import { findCoreViewFilters } from 'test/integration/metadata/suites/view-filter/utils/find-core-view-filters.util'; import { updateOneCoreViewFilter } from 'test/integration/metadata/suites/view-filter/utils/update-one-core-view-filter.util'; -import { cleanupViewRecords } from 'test/integration/utils/view-test.util'; +import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util'; import { FieldMetadataType, ViewFilterOperand } from 'twenty-shared/types'; const TEST_NOT_EXISTING_VIEW_FILTER_ID = '20202020-52c5-4152-8c09-76a845fb8ece'; @@ -69,12 +69,9 @@ describe('View Filter Resolver', () => { expectToFail: false, input: { idToDelete: testObjectMetadataId }, }); - await cleanupViewRecords(); }); beforeEach(async () => { - await cleanupViewRecords(); - const view = await createTestViewWithGraphQL({ name: 'Test View for Filters', objectMetadataId: testObjectMetadataId, @@ -83,6 +80,13 @@ describe('View Filter Resolver', () => { testViewId = view.id; }); + afterEach(async () => { + await destroyOneCoreView({ + viewId: testViewId, + expectToFail: false, + }); + }); + describe('getCoreViewFilters', () => { it('should return empty array when no view filters exist', async () => { const { data, errors } = await findCoreViewFilters({ diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-group-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-group-resolver.integration-spec.ts index 6efeee7520..4959c4f56f 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-group-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-group-resolver.integration-spec.ts @@ -1,4 +1,3 @@ -import { TEST_NOT_EXISTING_VIEW_GROUP_ID } from 'test/integration/constants/test-view-ids.constants'; import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; import { createTestViewWithGraphQL } from 'test/integration/graphql/utils/view-graphql.util'; import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; @@ -10,12 +9,12 @@ import { deleteOneCoreViewGroup } from 'test/integration/metadata/suites/view-gr import { destroyOneCoreViewGroup } from 'test/integration/metadata/suites/view-group/utils/destroy-one-core-view-group.util'; import { findCoreViewGroups } from 'test/integration/metadata/suites/view-group/utils/find-core-view-groups.util'; import { updateOneCoreViewGroup } from 'test/integration/metadata/suites/view-group/utils/update-one-core-view-group.util'; -import { - assertViewGroupStructure, - cleanupViewRecords, -} from 'test/integration/utils/view-test.util'; +import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util'; +import { assertViewGroupStructure } from 'test/integration/utils/view-test.util'; import { FieldMetadataType } from 'twenty-shared/types'; +const TEST_NOT_EXISTING_VIEW_GROUP_ID = '20202020-0000-4000-8000-000000000003'; + describe('View Group Resolver', () => { let testViewId: string; let testObjectMetadataId: string; @@ -71,12 +70,9 @@ describe('View Group Resolver', () => { expectToFail: false, input: { idToDelete: testObjectMetadataId }, }); - await cleanupViewRecords(); }); beforeEach(async () => { - await cleanupViewRecords(); - const view = await createTestViewWithGraphQL({ name: 'Test View for Groups', objectMetadataId: testObjectMetadataId, @@ -85,6 +81,13 @@ describe('View Group Resolver', () => { testViewId = view.id; }); + afterEach(async () => { + await destroyOneCoreView({ + viewId: testViewId, + expectToFail: false, + }); + }); + describe('getCoreViewGroups', () => { it('should return empty array when no view groups exist', async () => { const { data } = await findCoreViewGroups({ diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-sort-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-sort-resolver.integration-spec.ts index 89a4ee59c5..0c0249776c 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-sort-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-sort-resolver.integration-spec.ts @@ -1,4 +1,3 @@ -import { TEST_NOT_EXISTING_VIEW_SORT_ID } from 'test/integration/constants/test-view-ids.constants'; import { createViewSortOperationFactory } from 'test/integration/graphql/utils/create-view-sort-operation-factory.util'; import { deleteViewSortOperationFactory } from 'test/integration/graphql/utils/delete-view-sort-operation-factory.util'; import { destroyViewSortOperationFactory } from 'test/integration/graphql/utils/destroy-view-sort-operation-factory.util'; @@ -18,10 +17,8 @@ import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-m import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; -import { - assertViewSortStructure, - cleanupViewRecords, -} from 'test/integration/utils/view-test.util'; +import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util'; +import { assertViewSortStructure } from 'test/integration/utils/view-test.util'; import { FieldMetadataType } from 'twenty-shared/types'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; @@ -31,6 +28,8 @@ import { ViewSortExceptionMessageKey, } from 'src/engine/metadata-modules/view-sort/exceptions/view-sort.exception'; +const TEST_NOT_EXISTING_VIEW_SORT_ID = '20202020-0000-4000-8000-000000000004'; + describe('View Sort Resolver', () => { let testViewId: string; let testObjectMetadataId: string; @@ -86,12 +85,9 @@ describe('View Sort Resolver', () => { expectToFail: false, input: { idToDelete: testObjectMetadataId }, }); - await cleanupViewRecords(); }); beforeEach(async () => { - await cleanupViewRecords(); - const view = await createTestViewWithGraphQL({ name: 'Test View for Sorts', objectMetadataId: testObjectMetadataId, @@ -100,6 +96,13 @@ describe('View Sort Resolver', () => { testViewId = view.id; }); + afterEach(async () => { + await destroyOneCoreView({ + viewId: testViewId, + expectToFail: false, + }); + }); + describe('getCoreViewSorts', () => { it('should return empty array when no view sorts exist', async () => { const operation = findViewSortsOperationFactory({ viewId: testViewId }); diff --git a/packages/twenty-server/test/integration/rest/suites/__snapshots__/view-group.integration-spec.ts.snap b/packages/twenty-server/test/integration/rest/suites/__snapshots__/view-group.integration-spec.ts.snap new file mode 100644 index 0000000000..6e3cc69a0a --- /dev/null +++ b/packages/twenty-server/test/integration/rest/suites/__snapshots__/view-group.integration-spec.ts.snap @@ -0,0 +1,52 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`View Group REST API POST /metadata/viewGroups should fail to create view group with missing required fields 1`] = ` +{ + "error": "METADATA_VALIDATION_ERROR", + "errors": { + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [ + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_group", + }, + ], + }, + "message": "Multiple validation errors occurred while creating view group", + "statusCode": 400, + "summary": { + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, +} +`; diff --git a/packages/twenty-server/test/integration/rest/suites/view-field.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/view-field.integration-spec.ts index 4663c3f73d..2df9313622 100644 --- a/packages/twenty-server/test/integration/rest/suites/view-field.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/view-field.integration-spec.ts @@ -1,7 +1,3 @@ -import { - TEST_NOT_EXISTING_VIEW_FIELD_ID, - TEST_VIEW_1_ID, -} from 'test/integration/constants/test-view-ids.constants'; import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; @@ -15,14 +11,13 @@ import { import { createTestViewFieldWithRestApi, createTestViewWithRestApi, - deleteTestViewFieldWithRestApi, } from 'test/integration/rest/utils/view-rest-api.util'; -import { - assertViewFieldStructure, - cleanupViewRecords, -} from 'test/integration/utils/view-test.util'; +import { assertViewFieldStructure } from 'test/integration/utils/view-test.util'; +import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; import { FieldMetadataType } from 'twenty-shared/types'; +import { destroyOneCoreViewField } from 'test/integration/metadata/suites/view-field/utils/destroy-one-core-view-field.util'; +import { type ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto'; import { generateViewFieldExceptionMessage, ViewFieldExceptionMessageKey, @@ -31,6 +26,8 @@ import { describe('View Field REST API', () => { let testObjectMetadataId: string; let testFieldMetadataId: string; + let testViewId: string; + let testViewFieldId: string | undefined; beforeAll(async () => { const { @@ -40,11 +37,11 @@ describe('View Field REST API', () => { } = await createOneObjectMetadata({ expectToFail: false, input: { - nameSingular: 'myTestObject', - namePlural: 'myTestObjects', - labelSingular: 'My Test Object', - labelPlural: 'My Test Objects', - icon: 'Icon123', + nameSingular: 'testViewFieldObject', + namePlural: 'testViewFieldObjects', + labelSingular: 'Test View Field Object', + labelPlural: 'Test View Field Objects', + icon: 'IconField', }, }); @@ -74,6 +71,13 @@ describe('View Field REST API', () => { }); testFieldMetadataId = fieldMetadataId; + + const testView = await createTestViewWithRestApi({ + name: 'Test View for Field Integration', + objectMetadataId: testObjectMetadataId, + }); + + testViewId = testView.id; }); afterAll(async () => { @@ -92,24 +96,23 @@ describe('View Field REST API', () => { }); }); - beforeEach(async () => { - await cleanupViewRecords(); + afterEach(async () => { + if (!testViewFieldId) return; - await createTestViewWithRestApi({ - name: 'Test View for Fields', - objectMetadataId: testObjectMetadataId, + await destroyOneCoreViewField({ + expectToFail: false, + input: { + id: testViewFieldId, + }, }); - }); - - afterAll(async () => { - await cleanupViewRecords(); + testViewFieldId = undefined; }); describe('GET /metadata/viewFields', () => { it('should return empty array when no view fields exist', async () => { const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewFields?viewId=${TEST_VIEW_1_ID}`, + path: `/metadata/viewFields?viewId=${testViewId}`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -130,86 +133,95 @@ describe('View Field REST API', () => { it('should return view fields for a specific view after creating one', async () => { const viewField = await createTestViewFieldWithRestApi({ + viewId: testViewId, + fieldMetadataId: testFieldMetadataId, position: 0, isVisible: true, size: 150, - fieldMetadataId: testFieldMetadataId, }); + testViewFieldId = viewField.id; + const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewFields?viewId=${TEST_VIEW_1_ID}`, + path: `/metadata/viewFields?viewId=${testViewId}`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); assertRestApiSuccessfulResponse(response); expect(Array.isArray(response.body)).toBe(true); - expect(response.body).toHaveLength(1); - const returnedViewField = response.body[0]; + const returnedViewField = response.body.find( + (el: ViewFieldDTO) => el.id === viewField.id, + ); + + jestExpectToBeDefined(returnedViewField); assertViewFieldStructure(returnedViewField, { id: viewField.id, fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, position: 0, isVisible: true, size: 150, }); - - await deleteTestViewFieldWithRestApi(viewField.id); }); }); describe('POST /metadata/viewFields', () => { it('should create a new view field', async () => { const viewField = await createTestViewFieldWithRestApi({ + viewId: testViewId, + fieldMetadataId: testFieldMetadataId, position: 1, isVisible: true, size: 200, - fieldMetadataId: testFieldMetadataId, }); + testViewFieldId = viewField.id; + assertViewFieldStructure(viewField, { fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, position: 1, isVisible: true, size: 200, }); - - await deleteTestViewFieldWithRestApi(viewField.id); }); it('should create a hidden view field', async () => { const hiddenField = await createTestViewFieldWithRestApi({ + viewId: testViewId, + fieldMetadataId: testFieldMetadataId, position: 2, isVisible: false, size: 100, - fieldMetadataId: testFieldMetadataId, }); + testViewFieldId = hiddenField.id; + assertViewFieldStructure(hiddenField, { fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, position: 2, isVisible: false, size: 100, }); - - await deleteTestViewFieldWithRestApi(hiddenField.id); }); }); describe('GET /metadata/viewFields/:id', () => { it('should return a view field by id', async () => { const viewField = await createTestViewFieldWithRestApi({ + viewId: testViewId, + fieldMetadataId: testFieldMetadataId, position: 0, isVisible: true, size: 150, - fieldMetadataId: testFieldMetadataId, }); + testViewFieldId = viewField.id; + const response = await makeRestAPIRequest({ method: 'get', path: `/metadata/viewFields/${viewField.id}`, @@ -220,16 +232,14 @@ describe('View Field REST API', () => { assertViewFieldStructure(response.body, { id: viewField.id, fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, }); - - await deleteTestViewFieldWithRestApi(viewField.id); }); it('should return empty object for non-existent view field', async () => { const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewFields/${TEST_NOT_EXISTING_VIEW_FIELD_ID}`, + path: `/metadata/viewFields/20202020-f891-4d2a-8b23-c1e4d7f6a9b2`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -240,12 +250,15 @@ describe('View Field REST API', () => { describe('PATCH /metadata/viewFields/:id', () => { it('should update an existing view field', async () => { const viewField = await createTestViewFieldWithRestApi({ + viewId: testViewId, + fieldMetadataId: testFieldMetadataId, position: 0, isVisible: true, size: 150, - fieldMetadataId: testFieldMetadataId, }); + testViewFieldId = viewField.id; + const updateData = { position: 5, isVisible: false, @@ -266,10 +279,8 @@ describe('View Field REST API', () => { isVisible: false, size: 300, fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, }); - - await deleteTestViewFieldWithRestApi(viewField.id); }); it('should return 404 error when updating non-existent view field', async () => { @@ -281,7 +292,7 @@ describe('View Field REST API', () => { const response = await makeRestAPIRequest({ method: 'patch', - path: `/metadata/viewFields/${TEST_NOT_EXISTING_VIEW_FIELD_ID}`, + path: `/metadata/viewFields/20202020-f891-4d2a-8b23-c1e4d7f6a9b2`, body: updateData, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -291,7 +302,7 @@ describe('View Field REST API', () => { 404, generateViewFieldExceptionMessage( ViewFieldExceptionMessageKey.VIEW_FIELD_NOT_FOUND, - TEST_NOT_EXISTING_VIEW_FIELD_ID, + '20202020-f891-4d2a-8b23-c1e4d7f6a9b2', ), ); }); @@ -300,12 +311,15 @@ describe('View Field REST API', () => { describe('DELETE /metadata/viewFields/:id', () => { it('should delete an existing view field', async () => { const viewField = await createTestViewFieldWithRestApi({ + viewId: testViewId, + fieldMetadataId: testFieldMetadataId, position: 0, isVisible: true, size: 150, - fieldMetadataId: testFieldMetadataId, }); + testViewFieldId = viewField.id; + const deleteResponse = await makeRestAPIRequest({ method: 'delete', path: `/metadata/viewFields/${viewField.id}`, @@ -327,7 +341,7 @@ describe('View Field REST API', () => { it('should return 404 error when deleting non-existent view field', async () => { const response = await makeRestAPIRequest({ method: 'delete', - path: `/metadata/viewFields/${TEST_NOT_EXISTING_VIEW_FIELD_ID}`, + path: `/metadata/viewFields/20202020-f891-4d2a-8b23-c1e4d7f6a9b2`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); diff --git a/packages/twenty-server/test/integration/rest/suites/view-filter-group.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/view-filter-group.integration-spec.ts index 3de41fad0a..3dd4b1bea5 100644 --- a/packages/twenty-server/test/integration/rest/suites/view-filter-group.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/view-filter-group.integration-spec.ts @@ -1,7 +1,4 @@ -import { - TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID, - TEST_VIEW_1_ID, -} from 'test/integration/constants/test-view-ids.constants'; +import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; @@ -16,12 +13,11 @@ import { createTestViewWithRestApi, deleteTestViewFilterGroupWithRestApi, } from 'test/integration/rest/utils/view-rest-api.util'; -import { generateRecordName } from 'test/integration/utils/generate-record-name'; -import { - assertViewFilterGroupStructure, - cleanupViewRecords, -} from 'test/integration/utils/view-test.util'; +import { assertViewFilterGroupStructure } from 'test/integration/utils/view-test.util'; +import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; +import { FieldMetadataType } from 'twenty-shared/types'; +import { type ViewFilterGroupDTO } from 'src/engine/metadata-modules/view-filter-group/dtos/view-filter-group.dto'; import { ViewFilterGroupLogicalOperator } from 'src/engine/metadata-modules/view-filter-group/enums/view-filter-group-logical-operator'; import { generateViewFilterGroupExceptionMessage, @@ -30,6 +26,8 @@ import { describe('View Filter Group REST API', () => { let testObjectMetadataId: string; + let testViewId: string; + let testViewFilterGroupId: string | undefined; beforeAll(async () => { const { @@ -38,15 +36,40 @@ describe('View Filter Group REST API', () => { }, } = await createOneObjectMetadata({ input: { - nameSingular: 'myTestObject', - namePlural: 'myTestObjects', - labelSingular: 'My Test Object', - labelPlural: 'My Test Objects', - icon: 'Icon123', + nameSingular: 'testViewFilterGroupObject', + namePlural: 'testViewFilterGroupObjects', + labelSingular: 'Test View Filter Group Object', + labelPlural: 'Test View Filter Group Objects', + icon: 'IconFilterGroup', }, }); testObjectMetadataId = objectMetadataId; + + const createFieldInput = { + name: 'testField', + label: 'Test Field', + type: FieldMetadataType.TEXT, + objectMetadataId: testObjectMetadataId, + isLabelSyncedWithName: true, + }; + + await createOneFieldMetadata({ + input: createFieldInput, + gqlFields: ` + id + name + label + isLabelSyncedWithName + `, + }); + + const testView = await createTestViewWithRestApi({ + name: 'Test View for Filter Group Integration', + objectMetadataId: testObjectMetadataId, + }); + + testViewId = testView.id; }); afterAll(async () => { @@ -64,24 +87,18 @@ describe('View Filter Group REST API', () => { }); }); - beforeEach(async () => { - await cleanupViewRecords(); + afterEach(async () => { + if (!testViewFilterGroupId) return; - await createTestViewWithRestApi({ - name: generateRecordName('Test View for Filter Groups'), - objectMetadataId: testObjectMetadataId, - }); - }); - - afterAll(async () => { - await cleanupViewRecords(); + await deleteTestViewFilterGroupWithRestApi(testViewFilterGroupId); + testViewFilterGroupId = undefined; }); describe('GET /metadata/viewFilterGroups', () => { it('should return empty array when no view filter groups exist', async () => { const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewFilterGroups?viewId=${TEST_VIEW_1_ID}`, + path: `/metadata/viewFilterGroups?viewId=${testViewId}`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -102,33 +119,39 @@ describe('View Filter Group REST API', () => { it('should return view filter groups for a specific view after creating one', async () => { const viewFilterGroup = await createTestViewFilterGroupWithRestApi({ + viewId: testViewId, logicalOperator: ViewFilterGroupLogicalOperator.AND, }); + testViewFilterGroupId = viewFilterGroup.id; + const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewFilterGroups?viewId=${TEST_VIEW_1_ID}`, + path: `/metadata/viewFilterGroups?viewId=${testViewId}`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); assertRestApiSuccessfulResponse(response); expect(Array.isArray(response.body)).toBe(true); - expect(response.body).toHaveLength(1); - const returnedViewFilterGroup = response.body[0]; + const returnedViewFilterGroup = response.body.find( + (el: ViewFilterGroupDTO) => el.id === viewFilterGroup.id, + ); + + jestExpectToBeDefined(returnedViewFilterGroup); assertViewFilterGroupStructure(returnedViewFilterGroup, { id: viewFilterGroup.id, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, logicalOperator: ViewFilterGroupLogicalOperator.AND, }); - await deleteTestViewFilterGroupWithRestApi(viewFilterGroup.id); + testViewFilterGroupId = viewFilterGroup.id; }); it('should return nested filter groups with parent relationships', async () => { const parentData = { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, logicalOperator: 'AND', objectMetadataId: testObjectMetadataId, }; @@ -143,7 +166,7 @@ describe('View Filter Group REST API', () => { const parentId = parentResponse.body.id; const childData = { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, parentViewFilterGroupId: parentId, logicalOperator: 'OR', objectMetadataId: testObjectMetadataId, @@ -160,7 +183,7 @@ describe('View Filter Group REST API', () => { const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewFilterGroups?viewId=${TEST_VIEW_1_ID}`, + path: `/metadata/viewFilterGroups?viewId=${testViewId}`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -204,11 +227,14 @@ describe('View Filter Group REST API', () => { describe('POST /metadata/viewFilterGroups', () => { it('should create a new filter group with AND operator', async () => { const viewFilterGroup = await createTestViewFilterGroupWithRestApi({ + viewId: testViewId, logicalOperator: ViewFilterGroupLogicalOperator.AND, }); + testViewFilterGroupId = viewFilterGroup.id; + assertViewFilterGroupStructure(viewFilterGroup, { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, logicalOperator: ViewFilterGroupLogicalOperator.AND, }); expect(viewFilterGroup.parentViewFilterGroupId).toBeNull(); @@ -216,21 +242,22 @@ describe('View Filter Group REST API', () => { it('should create a filter group with OR operator', async () => { const orGroup = await createTestViewFilterGroupWithRestApi({ + viewId: testViewId, logicalOperator: ViewFilterGroupLogicalOperator.OR, }); + testViewFilterGroupId = orGroup.id; + assertViewFilterGroupStructure(orGroup, { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, logicalOperator: ViewFilterGroupLogicalOperator.OR, }); expect(orGroup.parentViewFilterGroupId).toBeNull(); - - await deleteTestViewFilterGroupWithRestApi(orGroup.id); }); it('should create a filter group with NOT operator', async () => { const viewFilterGroupData = { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, logicalOperator: 'NOT', }; @@ -254,7 +281,7 @@ describe('View Filter Group REST API', () => { it('should create a nested filter group with parent relationship', async () => { const parentData = { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, logicalOperator: 'AND', objectMetadataId: testObjectMetadataId, }; @@ -269,7 +296,7 @@ describe('View Filter Group REST API', () => { const parentId = parentResponse.body.id; const childData = { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, parentViewFilterGroupId: parentId, logicalOperator: 'OR', objectMetadataId: testObjectMetadataId, @@ -289,7 +316,7 @@ describe('View Filter Group REST API', () => { describe('GET /metadata/viewFilterGroups/:id', () => { it('should return a view filter group by id', async () => { const viewFilterGroupData = { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, logicalOperator: 'NOT', objectMetadataId: testObjectMetadataId, }; @@ -312,14 +339,14 @@ describe('View Filter Group REST API', () => { assertRestApiSuccessfulResponse(response); expect(response.body).toBeDefined(); expect(response.body.id).toBe(viewFilterGroupId); - expect(response.body.viewId).toBe(TEST_VIEW_1_ID); + expect(response.body.viewId).toBe(testViewId); expect(response.body.logicalOperator).toBe('NOT'); }); it('should return empty object for non-existent view filter group', async () => { const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewFilterGroups/${TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID}`, + path: `/metadata/viewFilterGroups/20202020-e214-44fa-a39a-d81447b2c44f`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -330,7 +357,7 @@ describe('View Filter Group REST API', () => { describe('PATCH /metadata/viewFilterGroups/:id', () => { it('should update an existing filter group', async () => { const viewFilterGroupData = { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, logicalOperator: 'AND', objectMetadataId: testObjectMetadataId, }; @@ -360,12 +387,12 @@ describe('View Filter Group REST API', () => { expect(response.body).toBeDefined(); expect(response.body.id).toBe(viewFilterGroupId); expect(response.body.logicalOperator).toBe('OR'); - expect(response.body.viewId).toBe(TEST_VIEW_1_ID); + expect(response.body.viewId).toBe(testViewId); }); it('should update parent relationship of filter group', async () => { const parentData = { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, logicalOperator: 'AND', objectMetadataId: testObjectMetadataId, }; @@ -380,7 +407,7 @@ describe('View Filter Group REST API', () => { const parentId = parentResponse.body.id; const childData = { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, logicalOperator: 'OR', objectMetadataId: testObjectMetadataId, }; @@ -421,7 +448,7 @@ describe('View Filter Group REST API', () => { const response = await makeRestAPIRequest({ method: 'patch', - path: `/metadata/viewFilterGroups/${TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID}`, + path: `/metadata/viewFilterGroups/20202020-e214-44fa-a39a-d81447b2c44f`, body: updateData, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -431,7 +458,7 @@ describe('View Filter Group REST API', () => { 404, generateViewFilterGroupExceptionMessage( ViewFilterGroupExceptionMessageKey.VIEW_FILTER_GROUP_NOT_FOUND, - TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID, + '20202020-e214-44fa-a39a-d81447b2c44f', ), ); }); @@ -440,7 +467,7 @@ describe('View Filter Group REST API', () => { describe('DELETE /metadata/viewFilterGroups/:id', () => { it('should delete an existing filter group', async () => { const viewFilterGroupData = { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, logicalOperator: 'AND', objectMetadataId: testObjectMetadataId, }; @@ -476,7 +503,7 @@ describe('View Filter Group REST API', () => { it('should return 404 error when deleting non-existent filter group', async () => { const response = await makeRestAPIRequest({ method: 'delete', - path: `/metadata/viewFilterGroups/${TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID}`, + path: `/metadata/viewFilterGroups/20202020-e214-44fa-a39a-d81447b2c44f`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); diff --git a/packages/twenty-server/test/integration/rest/suites/view-filter.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/view-filter.integration-spec.ts index 7587c8137c..08269be415 100644 --- a/packages/twenty-server/test/integration/rest/suites/view-filter.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/view-filter.integration-spec.ts @@ -1,11 +1,8 @@ -import { - TEST_NOT_EXISTING_VIEW_FILTER_ID, - TEST_VIEW_1_ID, -} from 'test/integration/constants/test-view-ids.constants'; import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { destroyOneCoreViewFilter } from 'test/integration/metadata/suites/view-filter/utils/destroy-one-core-view-filter.util'; import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util'; import { assertRestApiErrorNotFoundResponse, @@ -14,17 +11,18 @@ import { import { createTestViewFilterWithRestApi, createTestViewWithRestApi, - deleteTestViewFilterWithRestApi, } from 'test/integration/rest/utils/view-rest-api.util'; -import { - assertViewFilterStructure, - cleanupViewRecords, -} from 'test/integration/utils/view-test.util'; +import { assertViewFilterStructure } from 'test/integration/utils/view-test.util'; +import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; import { FieldMetadataType, ViewFilterOperand } from 'twenty-shared/types'; +import { type ViewFilterDTO } from 'src/engine/metadata-modules/view-filter/dtos/view-filter.dto'; + describe('View Filter REST API', () => { let testObjectMetadataId: string; let testFieldMetadataId: string; + let testViewId: string; + let testViewFilterId: string | undefined; beforeAll(async () => { const { @@ -33,11 +31,11 @@ describe('View Filter REST API', () => { }, } = await createOneObjectMetadata({ input: { - nameSingular: 'myTestObject', - namePlural: 'myTestObjects', - labelSingular: 'My Test Object', - labelPlural: 'My Test Objects', - icon: 'Icon123', + nameSingular: 'testViewFilterObject', + namePlural: 'testViewFilterObjects', + labelSingular: 'Test View Filter Object', + labelPlural: 'Test View Filter Objects', + icon: 'IconFilter', }, }); @@ -66,6 +64,13 @@ describe('View Filter REST API', () => { }); testFieldMetadataId = fieldMetadataId; + + const testView = await createTestViewWithRestApi({ + name: 'Test View for Filter Integration', + objectMetadataId: testObjectMetadataId, + }); + + testViewId = testView.id; }); afterAll(async () => { @@ -83,24 +88,23 @@ describe('View Filter REST API', () => { }); }); - beforeEach(async () => { - await cleanupViewRecords(); + afterEach(async () => { + if (!testViewFilterId) return; - await createTestViewWithRestApi({ - name: 'Test View for Filters', - objectMetadataId: testObjectMetadataId, + await destroyOneCoreViewFilter({ + input: { + id: testViewFilterId, + }, + expectToFail: false, }); - }); - - afterAll(async () => { - await cleanupViewRecords(); + testViewFilterId = undefined; }); describe('GET /metadata/viewFilters', () => { it('should return empty array when no view filters exist', async () => { const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewFilters?viewId=${TEST_VIEW_1_ID}`, + path: `/metadata/viewFilters?viewId=${testViewId}`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -121,96 +125,108 @@ describe('View Filter REST API', () => { it('should return view filters for a specific view after creating one', async () => { const viewFilter = await createTestViewFilterWithRestApi({ + viewId: testViewId, operand: ViewFilterOperand.CONTAINS, value: 'test', fieldMetadataId: testFieldMetadataId, }); + testViewFilterId = viewFilter.id; + const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewFilters?viewId=${TEST_VIEW_1_ID}`, + path: `/metadata/viewFilters?viewId=${testViewId}`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); assertRestApiSuccessfulResponse(response); expect(Array.isArray(response.body)).toBe(true); - expect(response.body).toHaveLength(1); - const returnedViewFilter = response.body[0]; + const returnedViewFilter = response.body.find( + (el: ViewFilterDTO) => el.id === viewFilter.id, + ); + + jestExpectToBeDefined(returnedViewFilter); assertViewFilterStructure(returnedViewFilter, { id: viewFilter.id, fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, operand: ViewFilterOperand.CONTAINS, value: 'test', }); - - await deleteTestViewFilterWithRestApi(viewFilter.id); }); }); describe('POST /metadata/viewFilters', () => { it('should create a new view filter with string value', async () => { const viewFilter = await createTestViewFilterWithRestApi({ + viewId: testViewId, operand: ViewFilterOperand.IS, value: 'test value', fieldMetadataId: testFieldMetadataId, }); + testViewFilterId = viewFilter.id; + assertViewFilterStructure(viewFilter, { fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, operand: ViewFilterOperand.IS, value: 'test value', }); - await deleteTestViewFilterWithRestApi(viewFilter.id); + testViewFilterId = viewFilter.id; }); it('should create a view filter with numeric value', async () => { const numericFilter = await createTestViewFilterWithRestApi({ + viewId: testViewId, operand: ViewFilterOperand.GREATER_THAN_OR_EQUAL, value: '100', fieldMetadataId: testFieldMetadataId, }); + testViewFilterId = numericFilter.id; + assertViewFilterStructure(numericFilter, { fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, operand: ViewFilterOperand.GREATER_THAN_OR_EQUAL, value: '100', }); - - await deleteTestViewFilterWithRestApi(numericFilter.id); }); it('should create a view filter with boolean value', async () => { const booleanFilter = await createTestViewFilterWithRestApi({ + viewId: testViewId, operand: ViewFilterOperand.IS, value: 'true', fieldMetadataId: testFieldMetadataId, }); + testViewFilterId = booleanFilter.id; + assertViewFilterStructure(booleanFilter, { fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, operand: ViewFilterOperand.IS, value: 'true', }); - - await deleteTestViewFilterWithRestApi(booleanFilter.id); }); }); describe('GET /metadata/viewFilters/:id', () => { it('should return a view filter by id', async () => { const viewFilter = await createTestViewFilterWithRestApi({ + viewId: testViewId, operand: ViewFilterOperand.IS, value: 'test', fieldMetadataId: testFieldMetadataId, }); + testViewFilterId = viewFilter.id; + const response = await makeRestAPIRequest({ method: 'get', path: `/metadata/viewFilters/${viewFilter.id}`, @@ -221,18 +237,18 @@ describe('View Filter REST API', () => { assertViewFilterStructure(response.body, { id: viewFilter.id, fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, operand: ViewFilterOperand.IS, value: 'test', }); - await deleteTestViewFilterWithRestApi(viewFilter.id); + testViewFilterId = viewFilter.id; }); it('should return empty object for non-existent view filter', async () => { const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewFilters/${TEST_NOT_EXISTING_VIEW_FILTER_ID}`, + path: `/metadata/viewFilters/20202020-5262-419d-ab77-575bfaf3db28`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -243,11 +259,14 @@ describe('View Filter REST API', () => { describe('PATCH /metadata/viewFilters/:id', () => { it('should update an existing view filter', async () => { const viewFilter = await createTestViewFilterWithRestApi({ + viewId: testViewId, operand: ViewFilterOperand.IS, value: 'original', fieldMetadataId: testFieldMetadataId, }); + testViewFilterId = viewFilter.id; + const updateData = { operand: ViewFilterOperand.IS_NOT, value: 'updated', @@ -266,10 +285,10 @@ describe('View Filter REST API', () => { operand: ViewFilterOperand.IS_NOT, value: 'updated', fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, }); - await deleteTestViewFilterWithRestApi(viewFilter.id); + testViewFilterId = viewFilter.id; }); it('should return 404 error when updating non-existent view filter', async () => { @@ -280,7 +299,7 @@ describe('View Filter REST API', () => { const response = await makeRestAPIRequest({ method: 'patch', - path: `/metadata/viewFilters/${TEST_NOT_EXISTING_VIEW_FILTER_ID}`, + path: `/metadata/viewFilters/20202020-d8db-4dfb-b654-01b872851b37`, body: updateData, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -292,11 +311,14 @@ describe('View Filter REST API', () => { describe('DELETE /metadata/viewFilters/:id', () => { it('should delete an existing view filter', async () => { const viewFilter = await createTestViewFilterWithRestApi({ + viewId: testViewId, operand: ViewFilterOperand.IS, value: 'to delete', fieldMetadataId: testFieldMetadataId, }); + testViewFilterId = viewFilter.id; + const deleteResponse = await makeRestAPIRequest({ method: 'delete', path: `/metadata/viewFilters/${viewFilter.id}`, @@ -318,7 +340,7 @@ describe('View Filter REST API', () => { it('should return 404 error when deleting non-existent view filter', async () => { const response = await makeRestAPIRequest({ method: 'delete', - path: `/metadata/viewFilters/${TEST_NOT_EXISTING_VIEW_FILTER_ID}`, + path: `/metadata/viewFilters/20202020-b8a3-4885-ae28-b89c2a4942d8`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); diff --git a/packages/twenty-server/test/integration/rest/suites/view-group.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/view-group.integration-spec.ts index 131b6bc73f..87be8c8756 100644 --- a/packages/twenty-server/test/integration/rest/suites/view-group.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/view-group.integration-spec.ts @@ -1,38 +1,30 @@ -import { - TEST_NOT_EXISTING_VIEW_GROUP_ID, - TEST_VIEW_1_ID, -} from 'test/integration/constants/test-view-ids.constants'; import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { destroyOneCoreViewGroup } from 'test/integration/metadata/suites/view-group/utils/destroy-one-core-view-group.util'; import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util'; import { assertRestApiErrorNotFoundResponse, - assertRestApiErrorResponse, assertRestApiSuccessfulResponse, } from 'test/integration/rest/utils/rest-test-assertions.util'; import { createTestViewGroupWithRestApi, createTestViewWithRestApi, - deleteTestViewGroupWithRestApi, } from 'test/integration/rest/utils/view-rest-api.util'; -import { generateRecordName } from 'test/integration/utils/generate-record-name'; -import { - assertViewGroupStructure, - cleanupViewRecords, -} from 'test/integration/utils/view-test.util'; +import { assertViewGroupStructure } from 'test/integration/utils/view-test.util'; +import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any'; +import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; import { FieldMetadataType } from 'twenty-shared/types'; +import { type ViewGroupDTO } from 'src/engine/metadata-modules/view-group/dtos/view-group.dto'; import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; -import { - generateViewGroupExceptionMessage, - ViewGroupExceptionMessageKey, -} from 'src/engine/metadata-modules/view-group/exceptions/view-group.exception'; describe('View Group REST API', () => { let testObjectMetadataId: string; let testFieldMetadataId: string; + let testViewId: string; + let testViewGroupId: string | undefined; beforeAll(async () => { const { @@ -42,11 +34,11 @@ describe('View Group REST API', () => { } = await createOneObjectMetadata({ expectToFail: false, input: { - nameSingular: 'myTestObject', - namePlural: 'myTestObjects', - labelSingular: 'My Test Object', - labelPlural: 'My Test Objects', - icon: 'Icon123', + nameSingular: 'testViewGroupObject', + namePlural: 'testViewGroupObjects', + labelSingular: 'Test View Group Object', + labelPlural: 'Test View Group Objects', + icon: 'IconGroup', }, }); @@ -76,6 +68,13 @@ describe('View Group REST API', () => { }); testFieldMetadataId = fieldMetadataId; + + const testView = await createTestViewWithRestApi({ + name: 'Test View for Group Integration', + objectMetadataId: testObjectMetadataId, + }); + + testViewId = testView.id; }); afterAll(async () => { @@ -94,24 +93,23 @@ describe('View Group REST API', () => { }); }); - beforeEach(async () => { - await cleanupViewRecords(); + afterEach(async () => { + if (!testViewGroupId) return; - await createTestViewWithRestApi({ - name: generateRecordName('Test View for Groups'), - objectMetadataId: testObjectMetadataId, + await destroyOneCoreViewGroup({ + input: { + id: testViewGroupId, + }, + expectToFail: false, }); - }); - - afterAll(async () => { - await cleanupViewRecords(); + testViewGroupId = undefined; }); describe('GET /metadata/viewGroups', () => { it('should return empty array when no view groups exist', async () => { const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewGroups?viewId=${TEST_VIEW_1_ID}`, + path: `/metadata/viewGroups?viewId=${testViewId}`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -132,51 +130,59 @@ describe('View Group REST API', () => { it('should return view groups for a specific view after creating one', async () => { const viewGroup = await createTestViewGroupWithRestApi({ + viewId: testViewId, + fieldMetadataId: testFieldMetadataId, fieldValue: 'test-field-value', isVisible: true, position: 0, - fieldMetadataId: testFieldMetadataId, }); + testViewGroupId = viewGroup.id; + const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewGroups?viewId=${TEST_VIEW_1_ID}`, + path: `/metadata/viewGroups?viewId=${testViewId}`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); assertRestApiSuccessfulResponse(response); expect(Array.isArray(response.body)).toBe(true); - expect(response.body).toHaveLength(1); - const returnedViewGroup = response.body[0]; + const returnedViewGroup = response.body.find( + (el: ViewGroupDTO) => el.id === viewGroup.id, + ); + + jestExpectToBeDefined(returnedViewGroup); assertViewGroupStructure(returnedViewGroup, { id: viewGroup.id, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, fieldValue: 'test-field-value', isVisible: true, position: 0, }); - await deleteTestViewGroupWithRestApi(viewGroup.id); + testViewGroupId = viewGroup.id; }); it('should return multiple view groups for a view', async () => { const viewGroup1 = await createTestViewGroupWithRestApi({ + viewId: testViewId, + fieldMetadataId: testFieldMetadataId, fieldValue: 'group-1', position: 0, - fieldMetadataId: testFieldMetadataId, }); const viewGroup2 = await createTestViewGroupWithRestApi({ + viewId: testViewId, + fieldMetadataId: testFieldMetadataId, fieldValue: 'group-2', position: 1, - fieldMetadataId: testFieldMetadataId, }); const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewGroups?viewId=${TEST_VIEW_1_ID}`, + path: `/metadata/viewGroups?viewId=${testViewId}`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -201,19 +207,21 @@ describe('View Group REST API', () => { position: 1, }); - await deleteTestViewGroupWithRestApi(viewGroup1.id); - await deleteTestViewGroupWithRestApi(viewGroup2.id); + testViewGroupId = viewGroup2.id; }); }); describe('GET /metadata/viewGroups/:id', () => { it('should return a specific view group by id', async () => { const viewGroup = await createTestViewGroupWithRestApi({ + viewId: testViewId, + fieldMetadataId: testFieldMetadataId, fieldValue: 'specific-group', isVisible: false, - fieldMetadataId: testFieldMetadataId, }); + testViewGroupId = viewGroup.id; + const response = await makeRestAPIRequest({ method: 'get', path: `/metadata/viewGroups/${viewGroup.id}`, @@ -223,19 +231,19 @@ describe('View Group REST API', () => { assertRestApiSuccessfulResponse(response); assertViewGroupStructure(response.body, { id: viewGroup.id, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, fieldValue: 'specific-group', isVisible: false, }); - await deleteTestViewGroupWithRestApi(viewGroup.id); + testViewGroupId = viewGroup.id; }); }); - describe('POST /metadata/viewGroups', () => { + describe.only('POST /metadata/viewGroups', () => { it('should create a new view group', async () => { const viewGroupData = { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, fieldMetadataId: testFieldMetadataId, fieldValue: 'new-group-value', isVisible: true, @@ -251,18 +259,18 @@ describe('View Group REST API', () => { assertRestApiSuccessfulResponse(response, 201); assertViewGroupStructure(response.body, { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, fieldValue: 'new-group-value', isVisible: true, position: 5, }); - await deleteTestViewGroupWithRestApi(response.body.id); + testViewGroupId = response.body.id; }); it('should create view group with minimal required fields', async () => { const viewGroupData = { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, fieldMetadataId: testFieldMetadataId, fieldValue: 'minimal-group', }; @@ -276,18 +284,18 @@ describe('View Group REST API', () => { assertRestApiSuccessfulResponse(response, 201); assertViewGroupStructure(response.body, { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, fieldValue: 'minimal-group', isVisible: true, position: 0, }); - await deleteTestViewGroupWithRestApi(response.body.id); + testViewGroupId = response.body.id; }); it('should fail to create view group with missing required fields', async () => { const invalidData = { - viewId: TEST_VIEW_1_ID, + viewId: testViewId, }; const response = await makeRestAPIRequest({ @@ -297,12 +305,12 @@ describe('View Group REST API', () => { bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); - assertRestApiErrorResponse( - response, - 400, - generateViewGroupExceptionMessage( - ViewGroupExceptionMessageKey.INVALID_VIEW_GROUP_DATA, - ), + expect(response.status).toBe(400); + + const errorResponse = JSON.parse(response.text); + + expect(errorResponse).toMatchSnapshot( + extractRecordIdsAndDatesAsExpectAny(errorResponse), ); }); }); @@ -310,12 +318,15 @@ describe('View Group REST API', () => { describe('PATCH /metadata/viewGroups/:id', () => { it('should update an existing view group', async () => { const viewGroup = await createTestViewGroupWithRestApi({ + viewId: testViewId, + fieldMetadataId: testFieldMetadataId, fieldValue: 'original-value', isVisible: true, position: 1, - fieldMetadataId: testFieldMetadataId, }); + testViewGroupId = viewGroup.id; + const updateData = { fieldValue: 'updated-value', isVisible: false, @@ -332,23 +343,24 @@ describe('View Group REST API', () => { assertRestApiSuccessfulResponse(response); assertViewGroupStructure(response.body, { id: viewGroup.id, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, fieldValue: 'updated-value', isVisible: false, position: 2, }); - - await deleteTestViewGroupWithRestApi(viewGroup.id); }); it('should update only specific fields', async () => { const viewGroup = await createTestViewGroupWithRestApi({ + viewId: testViewId, + fieldMetadataId: testFieldMetadataId, fieldValue: 'original-value', isVisible: true, position: 1, - fieldMetadataId: testFieldMetadataId, }); + testViewGroupId = viewGroup.id; + const updateData = { fieldValue: 'partially-updated', }; @@ -367,8 +379,6 @@ describe('View Group REST API', () => { isVisible: true, position: 1, }); - - await deleteTestViewGroupWithRestApi(viewGroup.id); }); it('should return 404 for non-existent view group', async () => { @@ -378,7 +388,7 @@ describe('View Group REST API', () => { const response = await makeRestAPIRequest({ method: 'patch', - path: `/metadata/viewGroups/${TEST_NOT_EXISTING_VIEW_GROUP_ID}`, + path: `/metadata/viewGroups/20202020-9c8b-4a7e-9f2d-1a2b3c4d5e6f`, body: updateData, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -390,10 +400,13 @@ describe('View Group REST API', () => { describe('DELETE /metadata/viewGroups/:id', () => { it('should delete an existing view group', async () => { const viewGroup = await createTestViewGroupWithRestApi({ - fieldValue: 'to-be-deleted', + viewId: testViewId, fieldMetadataId: testFieldMetadataId, + fieldValue: 'to-be-deleted', }); + testViewGroupId = viewGroup.id; + const deleteResponse = await makeRestAPIRequest({ method: 'delete', path: `/metadata/viewGroups/${viewGroup.id}`, @@ -407,7 +420,7 @@ describe('View Group REST API', () => { it('should return 404 for non-existent view group', async () => { const response = await makeRestAPIRequest({ method: 'delete', - path: `/metadata/viewGroups/${TEST_NOT_EXISTING_VIEW_GROUP_ID}`, + path: `/metadata/viewGroups/20202020-9c8b-4a7e-9f2d-1a2b3c4d5e6f`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -416,10 +429,13 @@ describe('View Group REST API', () => { it('should return success even when group is already deleted', async () => { const viewGroup = await createTestViewGroupWithRestApi({ - fieldValue: 'double-delete-test', + viewId: testViewId, fieldMetadataId: testFieldMetadataId, + fieldValue: 'double-delete-test', }); + testViewGroupId = viewGroup.id; + const deleteResponse = await makeRestAPIRequest({ method: 'delete', path: `/metadata/viewGroups/${viewGroup.id}`, diff --git a/packages/twenty-server/test/integration/rest/suites/view-sort.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/view-sort.integration-spec.ts index d02bf9e475..c2a1f0534c 100644 --- a/packages/twenty-server/test/integration/rest/suites/view-sort.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/view-sort.integration-spec.ts @@ -1,7 +1,3 @@ -import { - TEST_NOT_EXISTING_VIEW_SORT_ID, - TEST_VIEW_1_ID, -} from 'test/integration/constants/test-view-ids.constants'; import { createOneFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata.util'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; @@ -15,14 +11,13 @@ import { import { createTestViewSortWithRestApi, createTestViewWithRestApi, + deleteTestViewSortWithRestApi, } from 'test/integration/rest/utils/view-rest-api.util'; -import { generateRecordName } from 'test/integration/utils/generate-record-name'; -import { - assertViewSortStructure, - cleanupViewRecords, -} from 'test/integration/utils/view-test.util'; +import { assertViewSortStructure } from 'test/integration/utils/view-test.util'; +import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; import { FieldMetadataType } from 'twenty-shared/types'; +import { type ViewSortDTO } from 'src/engine/metadata-modules/view-sort/dtos/view-sort.dto'; import { ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction'; import { generateViewSortExceptionMessage, @@ -32,6 +27,8 @@ import { describe('View Sort REST API', () => { let testObjectMetadataId: string; let testFieldMetadataId: string; + let testViewId: string; + let testViewSortId: string | undefined; beforeAll(async () => { const { @@ -40,11 +37,11 @@ describe('View Sort REST API', () => { }, } = await createOneObjectMetadata({ input: { - nameSingular: 'myTestObject', - namePlural: 'myTestObjects', - labelSingular: 'My Test Object', - labelPlural: 'My Test Objects', - icon: 'Icon123', + nameSingular: 'testViewSortObject', + namePlural: 'testViewSortObjects', + labelSingular: 'Test View Sort Object', + labelPlural: 'Test View Sort Objects', + icon: 'IconSort', }, }); @@ -73,6 +70,13 @@ describe('View Sort REST API', () => { }); testFieldMetadataId = fieldMetadataId; + + const testView = await createTestViewWithRestApi({ + name: 'Test View for Sort Integration', + objectMetadataId: testObjectMetadataId, + }); + + testViewId = testView.id; }); afterAll(async () => { @@ -90,24 +94,18 @@ describe('View Sort REST API', () => { }); }); - beforeEach(async () => { - await cleanupViewRecords(); + afterEach(async () => { + if (!testViewSortId) return; - await createTestViewWithRestApi({ - name: generateRecordName('Test View for Sorts'), - objectMetadataId: testObjectMetadataId, - }); - }); - - afterAll(async () => { - await cleanupViewRecords(); + await deleteTestViewSortWithRestApi(testViewSortId); + testViewSortId = undefined; }); describe('GET /metadata/viewSorts', () => { it('should return empty array when no view sorts exist', async () => { const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewSorts?viewId=${TEST_VIEW_1_ID}`, + path: `/metadata/viewSorts?viewId=${testViewId}`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -128,26 +126,32 @@ describe('View Sort REST API', () => { it('should return view sorts for a specific view after creating one', async () => { const viewSort = await createTestViewSortWithRestApi({ - direction: ViewSortDirection.ASC, + viewId: testViewId, fieldMetadataId: testFieldMetadataId, + direction: ViewSortDirection.ASC, }); + testViewSortId = viewSort.id; + const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewSorts?viewId=${TEST_VIEW_1_ID}`, + path: `/metadata/viewSorts?viewId=${testViewId}`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); assertRestApiSuccessfulResponse(response); expect(Array.isArray(response.body)).toBe(true); - expect(response.body).toHaveLength(1); - const returnedViewSort = response.body[0]; + const returnedViewSort = response.body.find( + (el: ViewSortDTO) => el.id === viewSort.id, + ); + + jestExpectToBeDefined(returnedViewSort); assertViewSortStructure(returnedViewSort, { id: viewSort.id, fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, direction: ViewSortDirection.ASC, }); }); @@ -156,26 +160,32 @@ describe('View Sort REST API', () => { describe('POST /metadata/viewSorts', () => { it('should create a new view sort with ASC direction', async () => { const viewSort = await createTestViewSortWithRestApi({ - direction: ViewSortDirection.ASC, + viewId: testViewId, fieldMetadataId: testFieldMetadataId, + direction: ViewSortDirection.ASC, }); + testViewSortId = viewSort.id; + assertViewSortStructure(viewSort, { fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, direction: ViewSortDirection.ASC, }); }); it('should create a view sort with DESC direction', async () => { const descSort = await createTestViewSortWithRestApi({ - direction: ViewSortDirection.DESC, + viewId: testViewId, fieldMetadataId: testFieldMetadataId, + direction: ViewSortDirection.DESC, }); + testViewSortId = descSort.id; + assertViewSortStructure(descSort, { fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, direction: ViewSortDirection.DESC, }); }); @@ -184,10 +194,13 @@ describe('View Sort REST API', () => { describe('GET /metadata/viewSorts/:id', () => { it('should return a view sort by id', async () => { const viewSort = await createTestViewSortWithRestApi({ - direction: ViewSortDirection.ASC, + viewId: testViewId, fieldMetadataId: testFieldMetadataId, + direction: ViewSortDirection.ASC, }); + testViewSortId = viewSort.id; + const response = await makeRestAPIRequest({ method: 'get', path: `/metadata/viewSorts/${viewSort.id}`, @@ -198,7 +211,7 @@ describe('View Sort REST API', () => { assertViewSortStructure(response.body, { id: viewSort.id, fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, direction: ViewSortDirection.ASC, }); }); @@ -206,7 +219,7 @@ describe('View Sort REST API', () => { it('should return empty object for non-existent view sort', async () => { const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/viewSorts/${TEST_NOT_EXISTING_VIEW_SORT_ID}`, + path: `/metadata/viewSorts/20202020-a1b2-4c3d-8e9f-123456789abc`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -217,10 +230,13 @@ describe('View Sort REST API', () => { describe('PATCH /metadata/viewSorts/:id', () => { it('should update an existing view sort', async () => { const viewSort = await createTestViewSortWithRestApi({ - direction: ViewSortDirection.ASC, + viewId: testViewId, fieldMetadataId: testFieldMetadataId, + direction: ViewSortDirection.ASC, }); + testViewSortId = viewSort.id; + const updateData = { direction: ViewSortDirection.DESC, }; @@ -237,7 +253,7 @@ describe('View Sort REST API', () => { id: viewSort.id, direction: ViewSortDirection.DESC, fieldMetadataId: testFieldMetadataId, - viewId: TEST_VIEW_1_ID, + viewId: testViewId, }); }); @@ -248,7 +264,7 @@ describe('View Sort REST API', () => { const response = await makeRestAPIRequest({ method: 'patch', - path: `/metadata/viewSorts/${TEST_NOT_EXISTING_VIEW_SORT_ID}`, + path: `/metadata/viewSorts/20202020-a1b2-4c3d-8e9f-123456789abc`, body: updateData, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -260,10 +276,13 @@ describe('View Sort REST API', () => { describe('DELETE /metadata/viewSorts/:id', () => { it('should delete an existing view sort', async () => { const viewSort = await createTestViewSortWithRestApi({ - direction: ViewSortDirection.ASC, + viewId: testViewId, fieldMetadataId: testFieldMetadataId, + direction: ViewSortDirection.ASC, }); + testViewSortId = viewSort.id; + const deleteResponse = await makeRestAPIRequest({ method: 'delete', path: `/metadata/viewSorts/${viewSort.id}`, @@ -285,7 +304,7 @@ describe('View Sort REST API', () => { it('should return 404 error when deleting non-existent view sort', async () => { const response = await makeRestAPIRequest({ method: 'delete', - path: `/metadata/viewSorts/${TEST_NOT_EXISTING_VIEW_SORT_ID}`, + path: `/metadata/viewSorts/20202020-a1b2-4c3d-8e9f-123456789abc`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -294,7 +313,7 @@ describe('View Sort REST API', () => { 404, generateViewSortExceptionMessage( ViewSortExceptionMessageKey.VIEW_SORT_NOT_FOUND, - TEST_NOT_EXISTING_VIEW_SORT_ID, + '20202020-a1b2-4c3d-8e9f-123456789abc', ), ); }); diff --git a/packages/twenty-server/test/integration/rest/suites/view.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/view.integration-spec.ts index e3ad554769..bd712344d6 100644 --- a/packages/twenty-server/test/integration/rest/suites/view.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/view.integration-spec.ts @@ -1,21 +1,15 @@ -import { TEST_NOT_EXISTING_VIEW_ID } from 'test/integration/constants/test-view-ids.constants'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; +import { destroyOneCoreView } from 'test/integration/metadata/suites/view/utils/destroy-one-core-view.util'; import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util'; import { assertRestApiErrorNotFoundResponse, assertRestApiSuccessfulResponse, } from 'test/integration/rest/utils/rest-test-assertions.util'; -import { - createTestViewWithRestApi, - deleteTestViewWithRestApi, -} from 'test/integration/rest/utils/view-rest-api.util'; +import { createTestViewWithRestApi } from 'test/integration/rest/utils/view-rest-api.util'; import { generateRecordName } from 'test/integration/utils/generate-record-name'; -import { - assertViewStructure, - cleanupViewRecords, -} from 'test/integration/utils/view-test.util'; +import { assertViewStructure } from 'test/integration/utils/view-test.util'; import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum'; import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in'; @@ -23,6 +17,7 @@ import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum' describe('View REST API', () => { let testObjectMetadataId: string; + let testViewId: string | undefined; beforeAll(async () => { const { @@ -31,11 +26,11 @@ describe('View REST API', () => { }, } = await createOneObjectMetadata({ input: { - nameSingular: 'myTestObject', - namePlural: 'myTestObjects', - labelSingular: 'My Test Object', - labelPlural: 'My Test Objects', - icon: 'Icon123', + nameSingular: 'testViewObject', + namePlural: 'testViewObjects', + labelSingular: 'Test View Object', + labelPlural: 'Test View Objects', + icon: 'IconView', }, }); @@ -57,12 +52,14 @@ describe('View REST API', () => { }); }); - beforeEach(async () => { - await cleanupViewRecords(); - }); + afterEach(async () => { + if (!testViewId) return; - afterAll(async () => { - await cleanupViewRecords(); + await destroyOneCoreView({ + viewId: testViewId, + expectToFail: false, + }); + testViewId = undefined; }); describe('GET /metadata/views', () => { @@ -107,6 +104,8 @@ describe('View REST API', () => { objectMetadataId: testObjectMetadataId, }); + testViewId = view.id; + assertViewStructure(view, { name: viewName, objectMetadataId: testObjectMetadataId, @@ -132,6 +131,8 @@ describe('View REST API', () => { objectMetadataId: testObjectMetadataId, }); + testViewId = kanbanView.id; + assertViewStructure(kanbanView, { name: viewName, type: ViewType.KANBAN, @@ -139,8 +140,6 @@ describe('View REST API', () => { openRecordIn: ViewOpenRecordIn.SIDE_PANEL, objectMetadataId: testObjectMetadataId, }); - - await deleteTestViewWithRestApi(kanbanView.id); }); }); @@ -158,6 +157,8 @@ describe('View REST API', () => { objectMetadataId: testObjectMetadataId, }); + testViewId = view.id; + const response = await makeRestAPIRequest({ method: 'get', path: `/metadata/views/${view.id}`, @@ -175,7 +176,7 @@ describe('View REST API', () => { it('should return empty object for non-existent view', async () => { const response = await makeRestAPIRequest({ method: 'get', - path: `/metadata/views/${TEST_NOT_EXISTING_VIEW_ID}`, + path: `/metadata/views/20202020-b7a4-4f8e-9c1d-2e3f4a5b6c7d`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -197,6 +198,8 @@ describe('View REST API', () => { objectMetadataId: testObjectMetadataId, }); + testViewId = view.id; + const updatedName = generateRecordName('Updated View'); const updateData = { name: updatedName, @@ -231,7 +234,7 @@ describe('View REST API', () => { const response = await makeRestAPIRequest({ method: 'patch', - path: `/metadata/views/${TEST_NOT_EXISTING_VIEW_ID}`, + path: `/metadata/views/20202020-b7a4-4f8e-9c1d-2e3f4a5b6c7d`, body: updateData, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); @@ -254,6 +257,8 @@ describe('View REST API', () => { objectMetadataId: testObjectMetadataId, }); + testViewId = view.id; + const deleteResponse = await makeRestAPIRequest({ method: 'delete', path: `/metadata/views/${view.id}`, @@ -275,7 +280,7 @@ describe('View REST API', () => { it('should return 404 error when deleting non-existent view', async () => { const response = await makeRestAPIRequest({ method: 'delete', - path: `/metadata/views/${TEST_NOT_EXISTING_VIEW_ID}`, + path: `/metadata/views/20202020-b7a4-4f8e-9c1d-2e3f4a5b6c7d`, bearer: APPLE_JANE_ADMIN_ACCESS_TOKEN, }); diff --git a/packages/twenty-server/test/integration/rest/utils/view-rest-api.util.ts b/packages/twenty-server/test/integration/rest/utils/view-rest-api.util.ts index fda87f50f8..8373425c74 100644 --- a/packages/twenty-server/test/integration/rest/utils/view-rest-api.util.ts +++ b/packages/twenty-server/test/integration/rest/utils/view-rest-api.util.ts @@ -1,8 +1,3 @@ -import { - TEST_FIELD_METADATA_1_ID, - TEST_OBJECT_METADATA_1_ID, - TEST_VIEW_1_ID, -} from 'test/integration/constants/test-view-ids.constants'; import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util'; import { generateRecordName } from 'test/integration/utils/generate-record-name'; @@ -48,19 +43,21 @@ export const findViewFilterWithRestApi = async ( }; export const createTestViewWithRestApi = async ( - overrides: Partial = {}, + params: { + objectMetadataId: string; + } & Partial>, ): Promise => { + const { objectMetadataId, name, ...restParams } = params; const viewData = { - id: TEST_VIEW_1_ID, - name: generateRecordName('Test View'), - objectMetadataId: TEST_OBJECT_METADATA_1_ID, + name: name || generateRecordName('Test View'), + objectMetadataId, icon: 'IconTable', type: ViewType.TABLE, key: 'INDEX', position: 0, isCompact: false, openRecordIn: ViewOpenRecordIn.SIDE_PANEL, - ...overrides, + ...restParams, }; const response = await makeRestAPIRequest({ @@ -80,15 +77,19 @@ export const createTestViewWithRestApi = async ( }; export const createTestViewFieldWithRestApi = async ( - overrides: Partial = {}, + params: { + viewId: string; + fieldMetadataId: string; + } & Partial>, ): Promise => { + const { viewId, fieldMetadataId, ...restParams } = params; const viewFieldData = { - viewId: TEST_VIEW_1_ID, - fieldMetadataId: TEST_FIELD_METADATA_1_ID, + viewId, + fieldMetadataId, position: 0, isVisible: true, size: 150, - ...overrides, + ...restParams, }; const response = await makeRestAPIRequest({ @@ -108,14 +109,18 @@ export const createTestViewFieldWithRestApi = async ( }; export const createTestViewFilterWithRestApi = async ( - overrides: Partial = {}, + params: { + viewId: string; + fieldMetadataId: string; + } & Partial>, ): Promise => { + const { viewId, fieldMetadataId, operand, value, ...restParams } = params; const viewFilterData = { - viewId: TEST_VIEW_1_ID, - fieldMetadataId: TEST_FIELD_METADATA_1_ID, - operand: 'Is', - value: 'test-value', - ...overrides, + viewId, + fieldMetadataId, + operand: operand || 'Is', + value: value || 'test-value', + ...restParams, }; const response = await makeRestAPIRequest({ @@ -135,13 +140,17 @@ export const createTestViewFilterWithRestApi = async ( }; export const createTestViewSortWithRestApi = async ( - overrides: Partial = {}, + params: { + viewId: string; + fieldMetadataId: string; + } & Partial>, ): Promise => { + const { viewId, fieldMetadataId, direction, ...restParams } = params; const viewSortData = { - viewId: TEST_VIEW_1_ID, - fieldMetadataId: TEST_FIELD_METADATA_1_ID, - direction: 'ASC', - ...overrides, + viewId, + fieldMetadataId, + direction: direction || 'ASC', + ...restParams, }; const response = await makeRestAPIRequest({ @@ -161,15 +170,19 @@ export const createTestViewSortWithRestApi = async ( }; export const createTestViewGroupWithRestApi = async ( - overrides: Partial = {}, + params: { + viewId: string; + fieldMetadataId: string; + } & Partial>, ): Promise => { + const { viewId, fieldMetadataId, fieldValue, ...restParams } = params; const viewGroupData = { - viewId: TEST_VIEW_1_ID, - fieldMetadataId: TEST_FIELD_METADATA_1_ID, + viewId, + fieldMetadataId, isVisible: true, - fieldValue: 'test-group-value', + fieldValue: fieldValue || 'test-group-value', position: 0, - ...overrides, + ...restParams, }; const response = await makeRestAPIRequest({ @@ -189,12 +202,15 @@ export const createTestViewGroupWithRestApi = async ( }; export const createTestViewFilterGroupWithRestApi = async ( - overrides: Partial = {}, + params: { + viewId: string; + } & Partial>, ): Promise => { + const { viewId, logicalOperator, ...restParams } = params; const viewFilterGroupData = { - viewId: TEST_VIEW_1_ID, - logicalOperator: 'AND', - ...overrides, + viewId, + logicalOperator: logicalOperator || 'AND', + ...restParams, }; const response = await makeRestAPIRequest({ diff --git a/packages/twenty-server/test/integration/utils/view-test.util.ts b/packages/twenty-server/test/integration/utils/view-test.util.ts index f002364c2f..8dc0702ddb 100644 --- a/packages/twenty-server/test/integration/utils/view-test.util.ts +++ b/packages/twenty-server/test/integration/utils/view-test.util.ts @@ -6,10 +6,6 @@ import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/ent import { type ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity'; import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; -export const cleanupViewRecords = async (): Promise => { - await global.testDataSource.query(`DELETE from "core"."view"`); -}; - export const assertViewStructure = ( view: ViewEntity, expectedFields?: Partial,