From 2a51b922c621fc45ae63432daeb919c5e6d58ae2 Mon Sep 17 00:00:00 2001 From: guoyuqi Date: Wed, 22 Nov 2023 18:46:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=94=A8=E4=BE=8B=E8=AF=84=E5=AE=A1):=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=94=A8=E4=BE=8B=E8=AF=84=E5=AE=A1=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sdk/constants/PermissionConstants.java | 13 + .../constants/CaseReviewPassRule.java | 6 + .../constants/CaseReviewStatus.java | 8 + .../functional/constants/DemandPlatform.java | 5 + .../CaseReviewModuleController.java | 62 ++ .../mapper/ExtCaseReviewMapper.java | 16 + .../functional/mapper/ExtCaseReviewMapper.xml | 17 + .../mapper/ExtCaseReviewModuleMapper.java | 27 + .../mapper/ExtCaseReviewModuleMapper.xml | 64 ++ .../CaseReviewModuleCreateRequest.java | 33 + .../CaseReviewModuleUpdateRequest.java | 28 + .../functional/result/DemandPlatform.java | 5 - .../service/CaseReviewModuleService.java | 243 ++++++ .../service/DeleteCaseReviewService.java | 59 ++ .../CaseReviewModuleControllerTests.java | 764 ++++++++++++++++++ .../FunctionalCaseDemandControllerTests.java | 1 + .../log/constants/OperationLogModule.java | 3 + 17 files changed, 1349 insertions(+), 5 deletions(-) create mode 100644 backend/services/case-management/src/main/java/io/metersphere/functional/constants/CaseReviewPassRule.java create mode 100644 backend/services/case-management/src/main/java/io/metersphere/functional/constants/CaseReviewStatus.java create mode 100644 backend/services/case-management/src/main/java/io/metersphere/functional/constants/DemandPlatform.java create mode 100644 backend/services/case-management/src/main/java/io/metersphere/functional/controller/CaseReviewModuleController.java create mode 100644 backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewMapper.java create mode 100644 backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewMapper.xml create mode 100644 backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewModuleMapper.java create mode 100644 backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewModuleMapper.xml create mode 100644 backend/services/case-management/src/main/java/io/metersphere/functional/request/CaseReviewModuleCreateRequest.java create mode 100644 backend/services/case-management/src/main/java/io/metersphere/functional/request/CaseReviewModuleUpdateRequest.java delete mode 100644 backend/services/case-management/src/main/java/io/metersphere/functional/result/DemandPlatform.java create mode 100644 backend/services/case-management/src/main/java/io/metersphere/functional/service/CaseReviewModuleService.java create mode 100644 backend/services/case-management/src/main/java/io/metersphere/functional/service/DeleteCaseReviewService.java create mode 100644 backend/services/case-management/src/test/java/io/metersphere/functional/controller/CaseReviewModuleControllerTests.java diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/PermissionConstants.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/PermissionConstants.java index 79190ccd19..0b9ca5df4d 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/PermissionConstants.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/PermissionConstants.java @@ -218,6 +218,19 @@ public class PermissionConstants { + /*------ end: FUNCTIONAL_CASE ------*/ + + /*------ start: CASE_REVIEW ------*/ + public static final String CASE_REVIEW_READ = "CASE_REVIEW:READ"; + public static final String CASE_REVIEW_READ_ADD = "CASE_REVIEW:READ+ADD"; + public static final String CASE_REVIEW_READ_UPDATE = "CASE_REVIEW:READ+UPDATE"; + public static final String CASE_REVIEW_READ_DELETE = "CASE_REVIEW:READ+DELETE"; + public static final String CASE_REVIEW_READ_COMMENT = "CASE_REVIEW:READ+COMMENT"; + public static final String CASE_REVIEW_READ_EXPORT = "CASE_REVIEW:READ+EXPORT"; + public static final String CASE_REVIEW_READ_IMPORT = "CASE_REVIEW:READ+IMPORT"; + + + /*------ end: FUNCTIONAL_CASE ------*/ /*------ start: API_DEBUG ------*/ diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/constants/CaseReviewPassRule.java b/backend/services/case-management/src/main/java/io/metersphere/functional/constants/CaseReviewPassRule.java new file mode 100644 index 0000000000..ffb5e4fcfe --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/constants/CaseReviewPassRule.java @@ -0,0 +1,6 @@ +package io.metersphere.functional.constants; + +public enum CaseReviewPassRule { + SINGLE, + MULTIPLE +} diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/constants/CaseReviewStatus.java b/backend/services/case-management/src/main/java/io/metersphere/functional/constants/CaseReviewStatus.java new file mode 100644 index 0000000000..c7fa3a910b --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/constants/CaseReviewStatus.java @@ -0,0 +1,8 @@ +package io.metersphere.functional.constants; + +public enum CaseReviewStatus { + PREPARED, + UNDERWAY, + COMPLETED, + ARCHIVED; +} diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/constants/DemandPlatform.java b/backend/services/case-management/src/main/java/io/metersphere/functional/constants/DemandPlatform.java new file mode 100644 index 0000000000..3dfbe360c0 --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/constants/DemandPlatform.java @@ -0,0 +1,5 @@ +package io.metersphere.functional.constants; + +public enum DemandPlatform { + LOCAL; +} diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/controller/CaseReviewModuleController.java b/backend/services/case-management/src/main/java/io/metersphere/functional/controller/CaseReviewModuleController.java new file mode 100644 index 0000000000..36256ca69d --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/controller/CaseReviewModuleController.java @@ -0,0 +1,62 @@ +package io.metersphere.functional.controller; + +import io.metersphere.functional.request.CaseReviewModuleCreateRequest; +import io.metersphere.functional.request.CaseReviewModuleUpdateRequest; +import io.metersphere.functional.service.CaseReviewModuleService; +import io.metersphere.sdk.constants.PermissionConstants; +import io.metersphere.system.dto.sdk.BaseTreeNode; +import io.metersphere.system.dto.sdk.request.NodeMoveRequest; +import io.metersphere.system.utils.SessionUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "用例管理-用例评审-模块") +@RestController +@RequestMapping("/case/review/module") +public class CaseReviewModuleController { + + @Resource + private CaseReviewModuleService caseReviewModuleService; + + @GetMapping("/tree/{projectId}") + @Operation(summary = "用例管理-用例评审-模块-获取模块树") + @RequiresPermissions(PermissionConstants.CASE_REVIEW_READ) + public List getTree(@PathVariable String projectId) { + return caseReviewModuleService.getTree(projectId); + } + + @PostMapping("/add") + @Operation(summary = "用例管理-用例评审-模块-添加模块") + @RequiresPermissions(PermissionConstants.CASE_REVIEW_READ_ADD) + public void add(@RequestBody @Validated CaseReviewModuleCreateRequest request) { + caseReviewModuleService.add(request, SessionUtils.getUserId()); + } + + @PostMapping("/update") + @Operation(summary = "用例管理-用例评审-模块-修改模块") + @RequiresPermissions(PermissionConstants.CASE_REVIEW_READ_UPDATE) + public void list(@RequestBody @Validated CaseReviewModuleUpdateRequest request) { + caseReviewModuleService.update(request, SessionUtils.getUserId()); + } + + @PostMapping("/move") + @Operation(summary = "用例管理-用例评审-模块-移动模块") + @RequiresPermissions(PermissionConstants.CASE_REVIEW_READ_UPDATE) + public void moveNode(@Validated @RequestBody NodeMoveRequest request) { + caseReviewModuleService.moveNode(request, SessionUtils.getUserId()); + } + + @GetMapping("/delete/{moduleId}") + @Operation(summary = "用例管理-用例评审-模块-删除模块") + @RequiresPermissions(PermissionConstants.CASE_REVIEW_READ_DELETE) + public void deleteNode(@PathVariable String moduleId) { + caseReviewModuleService.deleteModule(moduleId); + } + +} diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewMapper.java b/backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewMapper.java new file mode 100644 index 0000000000..d435704a94 --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewMapper.java @@ -0,0 +1,16 @@ +package io.metersphere.functional.mapper; + +import io.metersphere.functional.domain.CaseReview; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @author wx + */ +public interface ExtCaseReviewMapper { + + List checkCaseByModuleIds(@Param("moduleIds") List deleteIds); + + +} diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewMapper.xml b/backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewMapper.xml new file mode 100644 index 0000000000..36c311ed14 --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewMapper.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewModuleMapper.java b/backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewModuleMapper.java new file mode 100644 index 0000000000..872b10340a --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewModuleMapper.java @@ -0,0 +1,27 @@ +package io.metersphere.functional.mapper; + +import io.metersphere.project.dto.NodeSortQueryParam; +import io.metersphere.system.dto.sdk.BaseModule; +import io.metersphere.system.dto.sdk.BaseTreeNode; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ExtCaseReviewModuleMapper { + List selectBaseByProjectId(@Param("projectId")String projectId); + + List selectBaseByIds(@Param("ids") List ids); + + List selectChildrenIdsByParentIds(@Param("ids") List deleteIds); + + List selectChildrenIdsSortByPos(String parentId); + + Long getMaxPosByParentId(String parentId); + + BaseModule selectBaseModuleById(String dragNodeId); + + BaseModule selectModuleByParentIdAndPosOperator(NodeSortQueryParam nodeSortQueryParam); + + List selectIdAndParentIdByProjectId(String projectId); + +} diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewModuleMapper.xml b/backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewModuleMapper.xml new file mode 100644 index 0000000000..efed49bbce --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/mapper/ExtCaseReviewModuleMapper.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/request/CaseReviewModuleCreateRequest.java b/backend/services/case-management/src/main/java/io/metersphere/functional/request/CaseReviewModuleCreateRequest.java new file mode 100644 index 0000000000..75a18109eb --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/request/CaseReviewModuleCreateRequest.java @@ -0,0 +1,33 @@ +package io.metersphere.functional.request; + +import io.metersphere.sdk.constants.ModuleConstants; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author guoyuqi + */ +@Data +public class CaseReviewModuleCreateRequest implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{project.id.not_blank}") + private String projectId; + + @Schema(description = "模块名称", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "{file_module.name.not_blank}") + private String name; + + @Schema(description = "父模块ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "{parent.node.not_blank}") + private String parentId = ModuleConstants.ROOT_NODE_PARENT_ID; +} + diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/request/CaseReviewModuleUpdateRequest.java b/backend/services/case-management/src/main/java/io/metersphere/functional/request/CaseReviewModuleUpdateRequest.java new file mode 100644 index 0000000000..96fa97dc55 --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/request/CaseReviewModuleUpdateRequest.java @@ -0,0 +1,28 @@ +package io.metersphere.functional.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author guoyuqi + */ +@Data +public class CaseReviewModuleUpdateRequest implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "模块ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{file_module.id.not_blank}") + private String id; + + @Schema(description = "模块名称", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "{file_module.name.not_blank}") + private String name; +} + diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/result/DemandPlatform.java b/backend/services/case-management/src/main/java/io/metersphere/functional/result/DemandPlatform.java deleted file mode 100644 index 0f3deebda3..0000000000 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/result/DemandPlatform.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.metersphere.functional.result; - -public enum DemandPlatform { - LOCAL; -} diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/service/CaseReviewModuleService.java b/backend/services/case-management/src/main/java/io/metersphere/functional/service/CaseReviewModuleService.java new file mode 100644 index 0000000000..e70448d5e5 --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/service/CaseReviewModuleService.java @@ -0,0 +1,243 @@ +/** + * @filename:CaseReviewModuleServiceImpl 2023年5月17日 + * @project ms V3.x + * Copyright(c) 2018 wx Co. Ltd. + * All right reserved. + */ +package io.metersphere.functional.service; + + +import io.metersphere.functional.domain.*; +import io.metersphere.functional.mapper.*; +import io.metersphere.functional.request.CaseReviewModuleCreateRequest; +import io.metersphere.functional.request.CaseReviewModuleUpdateRequest; +import io.metersphere.project.dto.ModuleCountDTO; +import io.metersphere.project.dto.NodeSortDTO; +import io.metersphere.project.service.ModuleTreeService; +import io.metersphere.sdk.constants.HttpMethodConstants; +import io.metersphere.sdk.constants.ModuleConstants; +import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.JSON; +import io.metersphere.sdk.util.Translator; +import io.metersphere.system.dto.sdk.BaseTreeNode; +import io.metersphere.system.dto.sdk.request.NodeMoveRequest; +import io.metersphere.system.log.constants.OperationLogModule; +import io.metersphere.system.log.constants.OperationLogType; +import io.metersphere.system.log.dto.LogDTO; +import io.metersphere.system.log.service.OperationLogService; +import io.metersphere.system.uid.IDGenerator; +import jakarta.annotation.Resource; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + + +@Service +@Transactional(rollbackFor = Exception.class) +public class CaseReviewModuleService extends ModuleTreeService { + @Resource + private CaseReviewModuleMapper caseReviewModuleMapper; + @Resource + private SqlSessionFactory sqlSessionFactory; + @Resource + private ExtCaseReviewModuleMapper extCaseReviewModuleMapper; + @Resource + private ExtCaseReviewMapper extCaseReviewMapper; + @Resource + private DeleteCaseReviewService deleteCaseReviewService; + + + @Resource + private OperationLogService operationLogService; + + public List getTree(String projectId) { + List fileModuleList = extCaseReviewModuleMapper.selectBaseByProjectId(projectId); + return super.buildTreeAndCountResource(fileModuleList, true, Translator.get("default.module")); + } + + public void add(CaseReviewModuleCreateRequest request, String userId) { + CaseReviewModule caseReviewModule = new CaseReviewModule(); + caseReviewModule.setId(IDGenerator.nextStr()); + caseReviewModule.setName(request.getName()); + caseReviewModule.setParentId(request.getParentId()); + caseReviewModule.setProjectId(request.getProjectId()); + this.checkDataValidity(caseReviewModule); + caseReviewModule.setCreateTime(System.currentTimeMillis()); + caseReviewModule.setUpdateTime(caseReviewModule.getCreateTime()); + caseReviewModule.setPos(this.countPos(request.getParentId())); + caseReviewModule.setCreateUser(userId); + caseReviewModule.setUpdateUser(userId); + caseReviewModuleMapper.insert(caseReviewModule); + } + + public void update(CaseReviewModuleUpdateRequest request, String userId) { + CaseReviewModule updateModule = caseReviewModuleMapper.selectByPrimaryKey(request.getId()); + if (updateModule == null) { + throw new MSException(Translator.get("case_module.not.exist")); + } + updateModule.setName(request.getName()); + this.checkDataValidity(updateModule); + updateModule.setUpdateTime(System.currentTimeMillis()); + updateModule.setUpdateUser(userId); + updateModule.setCreateUser(null); + updateModule.setCreateTime(null); + caseReviewModuleMapper.updateByPrimaryKeySelective(updateModule); + } + + public void moveNode(NodeMoveRequest request, String userId) { + NodeSortDTO nodeSortDTO = super.getNodeSortDTO(request, + extCaseReviewModuleMapper::selectBaseModuleById, + extCaseReviewModuleMapper::selectModuleByParentIdAndPosOperator); + + CaseReviewModuleExample example = new CaseReviewModuleExample(); + example.createCriteria().andParentIdEqualTo(nodeSortDTO.getParent().getId()).andIdEqualTo(request.getDragNodeId()); + //节点换到了别的节点下,要先更新parent节点再计算sort + if (caseReviewModuleMapper.countByExample(example) == 0) { + CaseReviewModule caseReviewModule = new CaseReviewModule(); + caseReviewModule.setId(request.getDragNodeId()); + caseReviewModule.setParentId(nodeSortDTO.getParent().getId()); + caseReviewModule.setUpdateUser(userId); + caseReviewModule.setUpdateTime(System.currentTimeMillis()); + caseReviewModuleMapper.updateByPrimaryKeySelective(caseReviewModule); + } + super.sort(nodeSortDTO); + } + + public void deleteModule(String moduleId) { + CaseReviewModule deleteModule = caseReviewModuleMapper.selectByPrimaryKey(moduleId); + if (deleteModule != null) { + List caseReviews = this.deleteModuleByIds(Collections.singletonList(moduleId), new ArrayList<>(), deleteModule.getProjectId()); + batchDelLog(caseReviews, deleteModule.getProjectId()); + } + } + + public void batchDelLog(List caseReviews, String projectId) { + List dtoList = new ArrayList<>(); + caseReviews.forEach(item -> { + LogDTO dto = new LogDTO( + projectId, + "", + item.getId(), + item.getCreateUser(), + OperationLogType.DELETE.name(), + OperationLogModule.CASE_REVIEW, + item.getName()); + + dto.setPath("/case/review/module/delete/"); + dto.setMethod(HttpMethodConstants.GET.name()); + dto.setOriginalValue(JSON.toJSONBytes(item)); + dtoList.add(dto); + }); + operationLogService.batchAdd(dtoList); + } + + public List deleteModuleByIds(ListdeleteIds, ListcaseReviews, String projectId){ + if (CollectionUtils.isEmpty(deleteIds)) { + return caseReviews; + } + CaseReviewModuleExample caseReviewModuleExample = new CaseReviewModuleExample(); + caseReviewModuleExample.createCriteria().andIdIn(deleteIds); + caseReviewModuleMapper.deleteByExample(caseReviewModuleExample); + List caseReviewList = extCaseReviewMapper.checkCaseByModuleIds(deleteIds); + if (CollectionUtils.isNotEmpty(caseReviewList)) { + caseReviews.addAll(caseReviewList); + } + deleteCaseReviewService.deleteCaseReviewResource(deleteIds, projectId); + List childrenIds = extCaseReviewModuleMapper.selectChildrenIdsByParentIds(deleteIds); + if (CollectionUtils.isNotEmpty(childrenIds)) { + deleteModuleByIds(childrenIds, caseReviews, projectId); + } + return caseReviews; + } + + private Long countPos(String parentId) { + Long maxPos = extCaseReviewModuleMapper.getMaxPosByParentId(parentId); + if (maxPos == null) { + return LIMIT_POS; + } else { + return maxPos + LIMIT_POS; + } + } + + /** + * 查找当前项目下模块每个节点对应的资源统计 + * + */ + public Map getModuleCountMap(String projectId, List moduleCountDTOList) { + + //构建模块树,并计算每个节点下的所有数量(包含子节点) + List treeNodeList = this.getTreeOnlyIdsAndResourceCount(projectId, moduleCountDTOList); + //通过广度遍历的方式构建返回值 + return super.getIdCountMapByBreadth(treeNodeList); + } + + public List getTreeOnlyIdsAndResourceCount(String projectId, List moduleCountDTOList) { + //节点内容只有Id和parentId + List fileModuleList = extCaseReviewModuleMapper.selectIdAndParentIdByProjectId(projectId); + return super.buildTreeAndCountResource(fileModuleList, moduleCountDTOList, true, Translator.get("default.module")); + } + + /** + * 检查数据的合法性 + */ + private void checkDataValidity(CaseReviewModule caseReviewModule) { + CaseReviewModuleExample example = new CaseReviewModuleExample(); + if (!StringUtils.equals(caseReviewModule.getParentId(), ModuleConstants.ROOT_NODE_PARENT_ID)) { + //检查父ID是否存在 + example.createCriteria().andIdEqualTo(caseReviewModule.getParentId()); + if (caseReviewModuleMapper.countByExample(example) == 0) { + throw new MSException(Translator.get("parent.node.not_blank")); + } + example.clear(); + + if (StringUtils.isNotBlank(caseReviewModule.getProjectId())) { + //检查项目ID是否和父节点ID一致 + example.createCriteria().andProjectIdEqualTo(caseReviewModule.getProjectId()).andIdEqualTo(caseReviewModule.getParentId()); + if (caseReviewModuleMapper.countByExample(example) == 0) { + throw new MSException(Translator.get("project.cannot.match.parent")); + } + example.clear(); + } + } + example.createCriteria().andParentIdEqualTo(caseReviewModule.getParentId()).andNameEqualTo(caseReviewModule.getName()).andIdNotEqualTo(caseReviewModule.getId()); + if (caseReviewModuleMapper.countByExample(example) > 0) { + throw new MSException(Translator.get("node.name.repeat")); + } + example.clear(); + } + + @Override + public void updatePos(String id, long pos) { + CaseReviewModule updateModule = new CaseReviewModule(); + updateModule.setPos(pos); + updateModule.setId(id); + caseReviewModuleMapper.updateByPrimaryKeySelective(updateModule); + } + + @Override + public void refreshPos(String parentId) { + List childrenIdSortByPos = extCaseReviewModuleMapper.selectChildrenIdsSortByPos(parentId); + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + CaseReviewModuleMapper batchUpdateMapper = sqlSession.getMapper(CaseReviewModuleMapper.class); + for (int i = 0; i < childrenIdSortByPos.size(); i++) { + String nodeId = childrenIdSortByPos.get(i); + CaseReviewModule updateModule = new CaseReviewModule(); + updateModule.setId(nodeId); + updateModule.setPos((i + 1) * LIMIT_POS); + batchUpdateMapper.updateByPrimaryKeySelective(updateModule); + } + sqlSession.flushStatements(); + SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); + } + +} \ No newline at end of file diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/service/DeleteCaseReviewService.java b/backend/services/case-management/src/main/java/io/metersphere/functional/service/DeleteCaseReviewService.java new file mode 100644 index 0000000000..d3a559623e --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/service/DeleteCaseReviewService.java @@ -0,0 +1,59 @@ +package io.metersphere.functional.service; + +import io.metersphere.functional.domain.*; +import io.metersphere.functional.mapper.*; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * @author guoyuqi + */ +@Service +@Transactional(rollbackFor = Exception.class) +public class DeleteCaseReviewService { + + @Resource + private CaseReviewMapper caseReviewMapper; + @Resource + private CaseReviewUserMapper caseReviewUserMapper; + @Resource + private CaseReviewFunctionalCaseMapper caseReviewFunctionalCaseMapper; + @Resource + private CaseReviewFunctionalCaseArchiveMapper caseReviewFunctionalCaseArchiveMapper; + @Resource + private CaseReviewFollowerMapper caseReviewFollowerMapper; + @Resource + private CaseReviewHistoryMapper caseReviewHistoryMapper; + + public void deleteCaseReviewResource(List ids, String projectId) { + //TODO 删除各种关联关系? 1.关联用例(功能/接口/场景/ui/性能)? 2.评审和评审人 3. 归档的用例 4. 关注人 5.评审历史 6. 操作记录 + //1.刪除评审与功能用例关联关系 + CaseReviewFunctionalCaseExample caseReviewFunctionalCaseExample = new CaseReviewFunctionalCaseExample(); + caseReviewFunctionalCaseExample.createCriteria().andReviewIdIn(ids); + caseReviewFunctionalCaseMapper.deleteByExample(caseReviewFunctionalCaseExample); + //2. 删除评审和评审人 + CaseReviewUserExample caseReviewUserExample = new CaseReviewUserExample(); + caseReviewUserExample.createCriteria().andReviewIdIn(ids); + caseReviewUserMapper.deleteByExample(caseReviewUserExample); + //3. 删除归档的用例 + CaseReviewFunctionalCaseArchiveExample archiveExample = new CaseReviewFunctionalCaseArchiveExample(); + archiveExample.createCriteria().andReviewIdIn(ids); + caseReviewFunctionalCaseArchiveMapper.deleteByExample(archiveExample); + //4.删除关注人 + CaseReviewFollowerExample caseReviewFollowerExample = new CaseReviewFollowerExample(); + caseReviewFollowerExample.createCriteria().andReviewIdIn(ids); + caseReviewFollowerMapper.deleteByExample(caseReviewFollowerExample); + //5.删除评审历史 + CaseReviewHistoryExample caseReviewHistoryExample = new CaseReviewHistoryExample(); + caseReviewHistoryExample.createCriteria().andReviewIdIn(ids); + caseReviewHistoryMapper.deleteByExample(caseReviewHistoryExample); + //TODO: 6.删除操作记录 + //删除评审 + CaseReviewExample caseReviewExample = new CaseReviewExample(); + caseReviewExample.createCriteria().andIdIn(ids).andProjectIdEqualTo(projectId); + caseReviewMapper.deleteByExample(caseReviewExample); + } +} diff --git a/backend/services/case-management/src/test/java/io/metersphere/functional/controller/CaseReviewModuleControllerTests.java b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/CaseReviewModuleControllerTests.java new file mode 100644 index 0000000000..05deeb9992 --- /dev/null +++ b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/CaseReviewModuleControllerTests.java @@ -0,0 +1,764 @@ +package io.metersphere.functional.controller; + +import io.metersphere.functional.constants.CaseReviewPassRule; +import io.metersphere.functional.constants.CaseReviewStatus; +import io.metersphere.functional.domain.CaseReview; +import io.metersphere.functional.domain.CaseReviewModule; +import io.metersphere.functional.domain.CaseReviewModuleExample; +import io.metersphere.functional.mapper.CaseReviewMapper; +import io.metersphere.functional.mapper.CaseReviewModuleMapper; +import io.metersphere.functional.request.CaseReviewModuleCreateRequest; +import io.metersphere.functional.request.CaseReviewModuleUpdateRequest; +import io.metersphere.functional.service.CaseReviewModuleService; +import io.metersphere.project.domain.Project; +import io.metersphere.project.mapper.ProjectMapper; +import io.metersphere.sdk.constants.ModuleConstants; +import io.metersphere.sdk.constants.SessionConstants; +import io.metersphere.sdk.util.JSON; +import io.metersphere.system.base.BaseTest; +import io.metersphere.system.controller.handler.ResultHolder; +import io.metersphere.system.dto.sdk.BaseTreeNode; +import io.metersphere.system.dto.sdk.request.NodeMoveRequest; +import io.metersphere.system.log.constants.OperationLogType; +import io.metersphere.system.uid.IDGenerator; +import jakarta.annotation.Resource; +import org.apache.commons.collections4.CollectionUtils; +import org.junit.jupiter.api.*; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@AutoConfigureMockMvc +public class CaseReviewModuleControllerTests extends BaseTest { + + private static Project project; + private static final String URL_MODULE_TREE = "/case/review/module/tree/"; + private static final String URL_MODULE_TREE_ADD = "/case/review/module/add"; + private static final String URL_MODULE_TREE_UPDATE = "/case/review/module/update"; + + private static List preliminaryTreeNodes = new ArrayList<>(); + + private static final String URL_MODULE_TREE_MOVE = "/case/review/module/move"; + private static final String URL_MODULE_TREE_DELETE = "/case/review/module/delete/"; + + + @Resource + private CaseReviewModuleService caseReviewModuleService; + @Resource + private CaseReviewModuleMapper caseReviewModuleMapper; + @Resource + private CaseReviewMapper caseReviewMapper; + @Resource + private ProjectMapper projectMapper; + + @BeforeEach + public void initTestData() { + if (project == null) { + Project initProject = new Project(); + initProject.setId(IDGenerator.nextStr()); + initProject.setNum(null); + initProject.setOrganizationId("100001"); + initProject.setName("caseReviewModuleTestProject"); + initProject.setDescription("caseReviewModuleTestProject"); + initProject.setCreateUser("admin"); + initProject.setUpdateUser("admin"); + initProject.setCreateTime(System.currentTimeMillis()); + initProject.setUpdateTime(System.currentTimeMillis()); + initProject.setEnable(true); + initProject.setModuleSetting("[\"apiTest\",\"uiTest\"]"); + projectMapper.insertSelective(initProject); + project = projectMapper.selectByPrimaryKey(initProject.getId()); + } + } + + @Test + @Order(1) + public void emptyDataTest() throws Exception { + //空数据下,检查模块树 + List treeNodes = this.getCaseReviewModuleTreeNode(); + //检查有没有默认节点 + boolean hasNode = false; + for (BaseTreeNode baseTreeNode : treeNodes) { + if (StringUtils.equals(baseTreeNode.getId(), ModuleConstants.DEFAULT_NODE_ID)) { + hasNode = true; + } + Assertions.assertNotNull(baseTreeNode.getParentId()); + } + Assertions.assertTrue(hasNode); + } + + @Test + @Order(2) + public void addModuleTestSuccess() throws Exception { + //根目录下创建节点(a1) + CaseReviewModuleCreateRequest request = new CaseReviewModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1"); + MvcResult mvcResult = this.requestPostWithOkAndReturn(URL_MODULE_TREE_ADD, request); + String returnId = mvcResult.getResponse().getContentAsString(); + Assertions.assertNotNull(returnId); + List treeNodes = this.getCaseReviewModuleTreeNode(); + BaseTreeNode a1Node = null; + for (BaseTreeNode baseTreeNode : treeNodes) { + if (StringUtils.equals(baseTreeNode.getName(), request.getName())) { + a1Node = baseTreeNode; + } + Assertions.assertNotNull(baseTreeNode.getParentId()); + } + Assertions.assertNotNull(a1Node); + + //根目录下创建节点a2和a3,在a1下创建子节点a1-b1 + request = new CaseReviewModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a2"); + this.requestPostWithOkAndReturn(URL_MODULE_TREE_ADD, request); + + request.setName("a3"); + this.requestPostWithOkAndReturn(URL_MODULE_TREE_ADD, request); + + request = new CaseReviewModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1-b1"); + request.setParentId(a1Node.getId()); + this.requestPostWithOkAndReturn(URL_MODULE_TREE_ADD, request); + + treeNodes = this.getCaseReviewModuleTreeNode(); + BaseTreeNode a1b1Node = null; + BaseTreeNode a2Node = null; + for (BaseTreeNode baseTreeNode : treeNodes) { + Assertions.assertNotNull(baseTreeNode.getParentId()); + if (StringUtils.equals(baseTreeNode.getName(), "a1") && CollectionUtils.isNotEmpty(baseTreeNode.getChildren())) { + for (BaseTreeNode childNode : baseTreeNode.getChildren()) { + if (StringUtils.equals(childNode.getName(), "a1-b1")) { + a1b1Node = childNode; + } + Assertions.assertNotNull(childNode.getParentId()); + } + } else if (StringUtils.equals(baseTreeNode.getName(), "a2")) { + a2Node = baseTreeNode; + } + } + Assertions.assertNotNull(a2Node); + Assertions.assertNotNull(a1b1Node); + + //a1节点下可以继续添加a1节点 + request = new CaseReviewModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1"); + request.setParentId(a1Node.getId()); + this.requestPostWithOkAndReturn(URL_MODULE_TREE_ADD, request); + + //继续创建a1下继续创建a1-a1-b1, + treeNodes = this.getCaseReviewModuleTreeNode(); + BaseTreeNode a1ChildNode = null; + for (BaseTreeNode baseTreeNode : treeNodes) { + Assertions.assertNotNull(baseTreeNode.getParentId()); + if (StringUtils.equals(baseTreeNode.getName(), "a1") && CollectionUtils.isNotEmpty(baseTreeNode.getChildren())) { + for (BaseTreeNode childNode : baseTreeNode.getChildren()) { + Assertions.assertNotNull(childNode.getParentId()); + if (StringUtils.equals(childNode.getName(), "a1")) { + a1ChildNode = childNode; + } + } + } + } + Assertions.assertNotNull(a1ChildNode); + + //a1的子节点a1下继续创建节点a1-a1-c1 + request = new CaseReviewModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1-a1-c1"); + request.setParentId(a1ChildNode.getId()); + this.requestPostWithOkAndReturn(URL_MODULE_TREE_ADD, request); + treeNodes = this.getCaseReviewModuleTreeNode(); + BaseTreeNode a1a1c1Node = null; + for (BaseTreeNode baseTreeNode : treeNodes) { + Assertions.assertNotNull(baseTreeNode.getParentId()); + if (StringUtils.equals(baseTreeNode.getName(), "a1") && CollectionUtils.isNotEmpty(baseTreeNode.getChildren())) { + for (BaseTreeNode secondNode : baseTreeNode.getChildren()) { + Assertions.assertNotNull(secondNode.getParentId()); + if (StringUtils.equals(secondNode.getName(), "a1") && CollectionUtils.isNotEmpty(secondNode.getChildren())) { + for (BaseTreeNode thirdNode : secondNode.getChildren()) { + Assertions.assertNotNull(thirdNode.getParentId()); + if (StringUtils.equals(thirdNode.getName(), "a1-a1-c1")) { + a1a1c1Node = thirdNode; + } + } + } + } + } + } + Assertions.assertNotNull(a1a1c1Node); + + //子节点a1-b1下继续创建节点a1-b1-c1 + request = new CaseReviewModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1-b1-c1"); + request.setParentId(a1b1Node.getId()); + this.requestPostWithOkAndReturn(URL_MODULE_TREE_ADD, request); + treeNodes = this.getCaseReviewModuleTreeNode(); + BaseTreeNode a1b1c1Node = null; + for (BaseTreeNode baseTreeNode : treeNodes) { + if (StringUtils.equals(baseTreeNode.getName(), "a1") && CollectionUtils.isNotEmpty(baseTreeNode.getChildren())) { + for (BaseTreeNode secondNode : baseTreeNode.getChildren()) { + if (StringUtils.equals(secondNode.getName(), "a1-b1") && CollectionUtils.isNotEmpty(secondNode.getChildren())) { + for (BaseTreeNode thirdNode : secondNode.getChildren()) { + if (StringUtils.equals(thirdNode.getName(), "a1-b1-c1")) { + a1b1c1Node = thirdNode; + } + } + } + } + } + } + Assertions.assertNotNull(a1b1c1Node); + preliminaryTreeNodes = treeNodes; + + } + + @Test + @Order(3) + public void addModuleTestError() throws Exception { + this.preliminaryData(); + + BaseTreeNode a1Node = getNodeByName(preliminaryTreeNodes, "a1"); + assert a1Node != null; + + //参数校验 + CaseReviewModuleCreateRequest request = new CaseReviewModuleCreateRequest(); + request.setProjectId(project.getId()); + this.requestPost(URL_MODULE_TREE_ADD, request).andExpect(status().isBadRequest()); + request = new CaseReviewModuleCreateRequest(); + request.setName("none"); + this.requestPost(URL_MODULE_TREE_ADD, request).andExpect(status().isBadRequest()); + request = new CaseReviewModuleCreateRequest(); + this.requestPost(URL_MODULE_TREE_ADD, request).andExpect(status().isBadRequest()); + request = new CaseReviewModuleCreateRequest(); + request.setParentId(null); + this.requestPost(URL_MODULE_TREE_ADD, request).andExpect(status().isBadRequest()); + + //父节点ID不存在的 + request = new CaseReviewModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("ParentIsUUID"); + request.setParentId(IDGenerator.nextStr()); + this.requestPost(URL_MODULE_TREE_ADD, request).andExpect(status().is5xxServerError()); + + //添加重复的a1节点 + request = new CaseReviewModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1"); + this.requestPost(URL_MODULE_TREE_ADD, request).andExpect(status().is5xxServerError()); + + //a1节点下添加重复的a1-b1节点 + request = new CaseReviewModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1-b1"); + request.setParentId(a1Node.getId()); + this.requestPost(URL_MODULE_TREE_ADD, request).andExpect(status().is5xxServerError()); + + //子节点的项目ID和父节点的不匹配 + request = new CaseReviewModuleCreateRequest(); + request.setProjectId(IDGenerator.nextStr()); + request.setName("RandomUUID"); + request.setParentId(a1Node.getId()); + this.requestPost(URL_MODULE_TREE_ADD, request).andExpect(status().is5xxServerError()); + + //项目ID和父节点的不匹配 + request = new CaseReviewModuleCreateRequest(); + request.setProjectId("100001100001"); + request.setName("RandomUUID"); + request.setParentId(a1Node.getId()); + this.requestPost(URL_MODULE_TREE_ADD, request).andExpect(status().is5xxServerError()); + } + + public static BaseTreeNode getNodeByName(List preliminaryTreeNodes, String nodeName) { + for (BaseTreeNode firstLevelNode : preliminaryTreeNodes) { + if (StringUtils.equals(firstLevelNode.getName(), nodeName)) { + return firstLevelNode; + } + if (CollectionUtils.isNotEmpty(firstLevelNode.getChildren())) { + for (BaseTreeNode secondLevelNode : firstLevelNode.getChildren()) { + if (StringUtils.equals(secondLevelNode.getName(), nodeName)) { + return secondLevelNode; + } + if (CollectionUtils.isNotEmpty(secondLevelNode.getChildren())) { + for (BaseTreeNode thirdLevelNode : secondLevelNode.getChildren()) { + if (StringUtils.equals(thirdLevelNode.getName(), nodeName)) { + return thirdLevelNode; + } + } + } + } + } + } + return null; + } + + @Test + @Order(4) + public void updateModuleTestSuccess() throws Exception { + if (CollectionUtils.isEmpty(preliminaryTreeNodes)) { + this.addModuleTestSuccess(); + } + //更改名称 + BaseTreeNode a1Node = null; + for (BaseTreeNode node : preliminaryTreeNodes) { + if (StringUtils.equals(node.getName(), "a1")) { + for (BaseTreeNode a1ChildrenNode : node.getChildren()) { + if (StringUtils.equals(a1ChildrenNode.getName(), "a1")) { + a1Node = a1ChildrenNode; + } + } + } + } + assert a1Node != null; + CaseReviewModuleUpdateRequest updateRequest = new CaseReviewModuleUpdateRequest(); + updateRequest.setId(a1Node.getId()); + updateRequest.setName("a1-a1"); + this.requestPostWithOkAndReturn(URL_MODULE_TREE_UPDATE, updateRequest); + + preliminaryTreeNodes = this.getCaseReviewModuleTreeNode(); + } + + @Test + @Order(5) + public void updateModuleTestError() throws Exception { + BaseTreeNode a1Node = getNodeByName(preliminaryTreeNodes, "a1-a1"); + assert a1Node != null; + //反例-参数校验 + CaseReviewModuleUpdateRequest updateRequest = new CaseReviewModuleUpdateRequest(); + this.requestPost(URL_MODULE_TREE_UPDATE, updateRequest).andExpect(status().isBadRequest()); + + //id不存在 + updateRequest = new CaseReviewModuleUpdateRequest(); + updateRequest.setId(IDGenerator.nextStr()); + updateRequest.setName(IDGenerator.nextStr()); + this.requestPost(URL_MODULE_TREE_UPDATE, updateRequest).andExpect(status().is5xxServerError()); + + //名称重复 a1-a1改为a1-b1 + updateRequest = new CaseReviewModuleUpdateRequest(); + updateRequest.setId(a1Node.getId()); + updateRequest.setName("a1-b1"); + this.requestPost(URL_MODULE_TREE_UPDATE, updateRequest).andExpect(status().is5xxServerError()); + } + + @Test + @Order(6) + public void moveTest() throws Exception { + this.preliminaryData(); + /* + *默认节点 + | + ·a1 + + | | + | ·a1-b1 + + | | | + | | ·a1-b1-c1 + | | + | *a1-a1 +(创建的时候是a1,通过修改改为a1-a1) + | | + | ·a1-a1-c1 + | + ·a2 + | + ·a3 + */ + BaseTreeNode a1Node = getNodeByName(preliminaryTreeNodes, "a1"); + BaseTreeNode a2Node = getNodeByName(preliminaryTreeNodes, "a2"); + BaseTreeNode a3Node = getNodeByName(preliminaryTreeNodes, "a3"); + BaseTreeNode a1a1Node = getNodeByName(preliminaryTreeNodes, "a1-a1"); + BaseTreeNode a1b1Node = getNodeByName(preliminaryTreeNodes, "a1-b1"); + + //父节点内移动-移动到首位 a1挪到a3后面 + NodeMoveRequest request = new NodeMoveRequest(); + { + request.setDragNodeId(a1Node.getId()); + request.setDropNodeId(a3Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a3Node.getId(), a1Node.getId(), null, false); + } + //父节点内移动-移动到末位 在上面的基础上,a1挪到a2上面 + { + request = new NodeMoveRequest(); + request.setDragNodeId(a1Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(-1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a1Node.getId(), a2Node.getId(), null, false); + } + + //父节点内移动-移动到中位 a1移动到a2-a3中间 + { + request = new NodeMoveRequest(); + request.setDragNodeId(a1Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a2Node.getId(), a1Node.getId(), a3Node.getId(), false); + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a1Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(-1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a1Node.getId(), a2Node.getId(), null, false); + } + + //跨节点移动-移动到首位 a3移动到a1-b1前面,然后移动回来; + { + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1b1Node.getId()); + request.setDropPosition(-1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a3Node.getId(), a1b1Node.getId(), null, false); + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + + //跨节点移动-移动到末尾 a3移动到a1-a1后面,然后移动回来; + { + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1a1Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a1a1Node.getId(), a3Node.getId(), null, false); + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + + //跨节点移动-移动到中位 a3移动到a1-b1和a1-a1中间,然后移动回来; + { + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1b1Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a1b1Node.getId(), a3Node.getId(), a1a1Node.getId(), false); + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + + //父节点内移动-a3移动到首位pos小于2,是否触发计算函数 (先手动更改a1的pos为2,然后移动a3到a1前面) + { + //更改pos + CaseReviewModule updateModule = new CaseReviewModule(); + updateModule.setId(a1Node.getId()); + updateModule.setPos(2L); + caseReviewModuleMapper.updateByPrimaryKeySelective(updateModule); + + //开始移动 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1Node.getId()); + request.setDropPosition(-1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a3Node.getId(), a1Node.getId(), null, true); + + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + + //父节点内移动-移动到中位,前后节点pos差不大于2,是否触发计算函数(在上面的 a3-a1-a2的基础上, 先手动更改a1pos为3*64,a2的pos为3*64+2,然后移动a3到a1和a2中间) + { + //更改pos + CaseReviewModule updateModule = new CaseReviewModule(); + updateModule.setId(a1Node.getId()); + updateModule.setPos(3 * 64L); + caseReviewModuleMapper.updateByPrimaryKeySelective(updateModule); + updateModule.setId(a2Node.getId()); + updateModule.setPos(3 * 64 + 2L); + caseReviewModuleMapper.updateByPrimaryKeySelective(updateModule); + + //开始移动 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a1Node.getId(), a3Node.getId(), a2Node.getId(), true); + + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + //跨节点移动-移动到首位pos小于2,是否触发计算函数(先手动更改a1-b1的pos为2,然后移动a3到a1-b1前面,最后再移动回来) + { + //更改pos + CaseReviewModule updateModule = new CaseReviewModule(); + updateModule.setId(a1b1Node.getId()); + updateModule.setPos(2L); + caseReviewModuleMapper.updateByPrimaryKeySelective(updateModule); + + //开始移动 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1b1Node.getId()); + request.setDropPosition(-1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a3Node.getId(), a1b1Node.getId(), null, true); + + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + //跨节点移动-移动到中位,前后节点pos差不大于2,是否触发计算函数先手动更改a1-a1的pos为a1-b1+2,然后移动a3到a1-a1前面,最后再移动回来) + { + //更改pos + CaseReviewModule updateModule = new CaseReviewModule(); + updateModule.setId(a1b1Node.getId()); + updateModule.setPos(3 * 64L); + caseReviewModuleMapper.updateByPrimaryKeySelective(updateModule); + updateModule.setId(a1a1Node.getId()); + updateModule.setPos(3 * 64 + 2L); + caseReviewModuleMapper.updateByPrimaryKeySelective(updateModule); + + //开始移动 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1a1Node.getId()); + request.setDropPosition(-1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a1b1Node.getId(), a3Node.getId(), a1a1Node.getId(), true); + + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + //移动到没有子节点的节点下 a3移动到a2下 + { + //开始移动 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(0); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + CaseReviewModule a3Module = caseReviewModuleMapper.selectByPrimaryKey(a3Node.getId()); + Assertions.assertEquals(a3Module.getParentId(), a2Node.getId()); + + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_MODULE_TREE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + + } + + @Test + @Order(7) + public void moveTestError() throws Exception { + this.preliminaryData(); + BaseTreeNode a1Node = getNodeByName(preliminaryTreeNodes, "a1"); + BaseTreeNode a2Node = getNodeByName(preliminaryTreeNodes, "a2"); + //drag节点为空 + NodeMoveRequest request = new NodeMoveRequest(); + request.setDragNodeId(null); + request.setDropNodeId(a1Node.getId()); + request.setDropPosition(1); + this.requestPost(URL_MODULE_TREE_MOVE, request).andExpect(status().isBadRequest()); + //drag节点不存在 + request = new NodeMoveRequest(); + request.setDragNodeId(IDGenerator.nextStr()); + request.setDropNodeId(a1Node.getId()); + request.setDropPosition(1); + this.requestPost(URL_MODULE_TREE_MOVE, request).andExpect(status().is5xxServerError()); + + //drop节点为空 + request = new NodeMoveRequest(); + request.setDragNodeId(a1Node.getId()); + request.setDropNodeId(null); + request.setDropPosition(1); + this.requestPost(URL_MODULE_TREE_MOVE, request).andExpect(status().isBadRequest()); + + //drop节点不存在 + request = new NodeMoveRequest(); + request.setDragNodeId(a1Node.getId()); + request.setDropNodeId(IDGenerator.nextStr()); + request.setDropPosition(1); + this.requestPost(URL_MODULE_TREE_MOVE, request).andExpect(status().is5xxServerError()); + + //position为0的时候节点不存在 + request = new NodeMoveRequest(); + request.setDragNodeId(a1Node.getId()); + request.setDropNodeId(IDGenerator.nextStr()); + request.setDropPosition(0); + this.requestPost(URL_MODULE_TREE_MOVE, request).andExpect(status().is5xxServerError()); + + //dragNode和dropNode一样 + request = new NodeMoveRequest(); + request.setDragNodeId(a1Node.getId()); + request.setDropNodeId(a1Node.getId()); + request.setDropPosition(1); + this.requestPost(URL_MODULE_TREE_MOVE, request).andExpect(status().is5xxServerError()); + + //position不是-1 0 1 + request = new NodeMoveRequest(); + request.setDragNodeId(a1Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(4); + this.requestPost(URL_MODULE_TREE_MOVE, request).andExpect(status().is5xxServerError()); + } + + @Test + @Order(8) + public void deleteModuleTestSuccess() throws Exception { + this.preliminaryData(); + + // 删除没有用例的节点a1-b1-c1 检查是否级联删除根节点 + BaseTreeNode a1b1Node = getNodeByName(this.getCaseReviewModuleTreeNode(), "a1-b1"); + assert a1b1Node != null; + this.requestGetWithOk(URL_MODULE_TREE_DELETE+a1b1Node.getId()); + this.checkModuleIsEmpty(a1b1Node.getId()); + + // 删除有用例的节点 a1-a1 检查是否级联删除根节点 + //创建数据 + BaseTreeNode a1a1Node = getNodeByName(this.getCaseReviewModuleTreeNode(), "a1-a1"); + CaseReview name = createCaseReview(a1a1Node, "name"); + CaseReview caseReview = caseReviewMapper.selectByPrimaryKey(name.getId()); + Assertions.assertNotNull(caseReview); + this.requestGetWithOk(URL_MODULE_TREE_DELETE+a1a1Node.getId()); + this.checkModuleIsEmpty(a1a1Node.getId()); + CaseReview caseReviewDel = caseReviewMapper.selectByPrimaryKey(name.getId()); + Assertions.assertNotNull(caseReviewDel); + + //删除不存在的节点 + this.requestGetWithOk(URL_MODULE_TREE_DELETE+IDGenerator.nextNum()); + // 测试删除根节点 + this.requestGetWithOk(URL_MODULE_TREE_DELETE+ModuleConstants.DEFAULT_NODE_ID); + + //service层判断:测试删除空集合 + caseReviewModuleService.deleteModuleByIds(new ArrayList<>(),new ArrayList<>(), project.getId()); + + checkLog(caseReview.getId(), OperationLogType.DELETE, URL_MODULE_TREE_DELETE); + + + } + + private CaseReview createCaseReview(BaseTreeNode a1a1Node, String name) { + CaseReview caseReview = new CaseReview(); + caseReview.setId(IDGenerator.nextStr()); + caseReview.setName(name); + caseReview.setModuleId(a1a1Node.getId()); + caseReview.setProjectId(project.getId()); + caseReview.setStatus(CaseReviewStatus.PREPARED.toString()); + caseReview.setReviewPassRule(CaseReviewPassRule.SINGLE.toString()); + caseReview.setCreateUser("gyq"); + caseReview.setCreateTime(System.currentTimeMillis()); + caseReview.setUpdateUser("gyq"); + caseReview.setUpdateTime(System.currentTimeMillis()); + caseReviewMapper.insertSelective(caseReview); + return caseReview; + } + + private void checkModuleIsEmpty(String id) { + CaseReviewModuleExample example = new CaseReviewModuleExample(); + example.createCriteria().andParentIdEqualTo(id); + Assertions.assertEquals(caseReviewModuleMapper.countByExample(example), 0); + } + + private List getCaseReviewModuleTreeNode() throws Exception { + MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(URL_MODULE_TREE+"/"+project.getId()).header(SessionConstants.HEADER_TOKEN, sessionId) + .header(SessionConstants.CSRF_TOKEN, csrfToken) + .header(SessionConstants.CURRENT_PROJECT, project.getId()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andReturn(); + String returnData = result.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class); + return JSON.parseArray(JSON.toJSONString(resultHolder.getData()), BaseTreeNode.class); + } + + private void preliminaryData() throws Exception { + if (CollectionUtils.isEmpty(preliminaryTreeNodes)) { + /* + 这里需要获取修改过的树的结构。期望的最终结构是这样的(*为测试用例中挂载文件的节点, · 为空节点): + + *默认节点 + | + ·a1 + + | | + | ·a1-b1 + + | | | + | | ·a1-b1-c1 + | | + | *a1-a1 +(创建的时候是a1,通过修改改为a1-a1) + | | | + | | ·a1-a1-c1(用于测试文件移动) + | + ·a2 + | + ·a3 + */ + this.updateModuleTestSuccess(); + } + } + + private void checkModulePos(String firstNode, String secondNode, String thirdNode, boolean isRecalculate) { + CaseReviewModule firstModule = caseReviewModuleMapper.selectByPrimaryKey(firstNode); + CaseReviewModule secondModule = caseReviewModuleMapper.selectByPrimaryKey(secondNode); + CaseReviewModule thirdModule = null; + Assertions.assertTrue(firstModule.getPos() < secondModule.getPos()); + if (StringUtils.isNotBlank(thirdNode)) { + thirdModule = caseReviewModuleMapper.selectByPrimaryKey(thirdNode); + Assertions.assertTrue(secondModule.getPos() < thirdModule.getPos()); + } + if (isRecalculate) { + int limitPos = 64; + Assertions.assertEquals(0, firstModule.getPos() % limitPos); + Assertions.assertEquals(0, secondModule.getPos() % limitPos); + if (thirdModule != null) { + Assertions.assertEquals(0, thirdModule.getPos() % limitPos); + } + } + } +} diff --git a/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseDemandControllerTests.java b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseDemandControllerTests.java index 8db8fbd57b..64915c4099 100644 --- a/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseDemandControllerTests.java +++ b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseDemandControllerTests.java @@ -247,6 +247,7 @@ public class FunctionalCaseDemandControllerTests extends BaseTest { DemandDTO demandDTO2 = new DemandDTO(); demandDTO2.setDemandId("100002"); demandDTO2.setDemandName("手动加入Tapd1"); + demandDTO2.setDemandUrl("https://www.tapd.cn/55049933/prong/stories/view/1155049933001012783"); demandList.add(demandDTO2); DemandDTO demandDTO3 = new DemandDTO(); demandDTO3.setDemandId("100003"); diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/log/constants/OperationLogModule.java b/backend/services/system-setting/src/main/java/io/metersphere/system/log/constants/OperationLogModule.java index 225d477bec..28927a050d 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/log/constants/OperationLogModule.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/log/constants/OperationLogModule.java @@ -100,6 +100,9 @@ public class OperationLogModule { //用例 public static final String FUNCTIONAL_CASE = "FUNCTIONAL_CASE"; + public static final String CASE_REVIEW = "CASE_REVIEW"; + + //接口调试 public static final String API_DEBUG = "API_DEBUG"; //接口管理-环境