refactor(缺陷管理): 优化缺陷关联用例列表及模块树接口

This commit is contained in:
song-cc-rock 2024-01-17 15:24:06 +08:00 committed by Craftsman
parent 4fe77a27cf
commit f2df25ba0a
34 changed files with 315 additions and 110 deletions

View File

@ -1,7 +1,6 @@
package io.metersphere.functional.request;
package io.metersphere.request;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.system.dto.sdk.BaseCondition;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@ -10,7 +9,7 @@ import lombok.Data;
import java.util.List;
@Data
public class AssociateCaseModuleRequest extends BaseCondition {
public class AssociateCaseModuleRequest extends BaseProviderCondition {
@Schema(description = "模块ID(根据模块树查询时要把当前节点以及子节点都放在这里。)")
private List<@NotBlank String> moduleIds;
@ -24,16 +23,17 @@ public class AssociateCaseModuleRequest extends BaseCondition {
@Size(min = 1, max = 50, message = "{api_definition_module.project_id.length_range}")
private String projectId;
@Schema(description = "关键字")
private String keyword;
@Schema(description = "版本fk")
private String versionId;
@Schema(description = "版本引用fk")
private String refId;
@Schema(description = "关联用例的类型(API,SCENARIO,UI,PERFORMANCE)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_test_case_disassociate_request.type.not_blank}")
@Schema(description = "关联关系表里主ID eg:功能用例关联接口用例时为功能用例ID, 缺陷关联用例为缺陷ID")
@Size(min = 1, max = 50, message = "{relate_source_id_length_range}")
private String sourceId;
@Schema(description = "关联类型(FUNCTIONAL, API, SCENARIO, UI, PERFORMANCE)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{relate_source_type_not_blank}")
private String sourceType;
}

View File

@ -19,7 +19,7 @@ import java.util.Map;
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class TestCasePageProviderRequest implements Serializable {
public class TestCasePageProviderRequest extends BaseProviderCondition implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ -36,24 +36,7 @@ public class TestCasePageProviderRequest implements Serializable {
@Schema(description = "排序字段model中的字段 : asc/desc")
private Map<@Valid @Pattern(regexp = "^[A-Za-z]+$") String, @Valid @NotBlank String> sort;
@Schema(description = "关键字")
private String keyword;
@Schema(description = "匹配模式 所有/任一", allowableValues = {"AND", "OR"})
private String searchMode = "AND";
@Schema(description = "过滤字段")
private Map<String, List<String>> filter;
@Schema(description = "高级搜索")
private Map<String, Object> combine;
@Schema(description = "关联关系表里主ID eg:功能用例关联接口用例时为功能用例id")
@NotBlank(message = "{api_definition.project_id.not_blank}")
@Size(min = 1, max = 50, message = "{api_definition.project_id.length_range}")
private String sourceId;
@Schema(description = "接口pk(只在关联接口的时候用)")
@Schema(description = "接口pk")
private String apiDefinitionId;
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@ -69,14 +52,19 @@ public class TestCasePageProviderRequest implements Serializable {
@Schema(description = "模块ID")
private List<@NotBlank String> moduleIds;
@Schema(description = "版本fk")
@Schema(description = "版本fk(只在关联接口的时候用)")
private String versionId;
@Schema(description = "版本来源")
private String refId;
@Schema(description = "关联用例的类型(API,SCENARIO,UI,PERFORMANCE)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{associate_other_case_request.type.not_blank}")
@Schema(description = "关联关系表里主ID eg:功能用例关联接口用例时为功能用例id")
@NotBlank(message = "{relate_source_id_not_blank}")
@Size(min = 1, max = 50, message = "{relate_source_id_length_range}")
private String sourceId;
@Schema(description = "关联类型(FUNCTIONAL, API, SCENARIO, UI, PERFORMANCE)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{relate_source_type_not_blank}")
private String sourceType;

View File

@ -507,4 +507,9 @@ swagger_parse_error_with_auth=Swagger 解析失败,请确认认证信息是否
swagger_parse_error=Swagger 解析失败,请确认文件格式是否正确!
#测试计划
permission.test_plan.name=测试计划
permission.test_plan_module.name=测试计划模块
permission.test_plan_module.name=测试计划模块
#关联
relate_source_id_not_blank=关联来源ID不能为空
relate_source_id_length_range=关联来源ID必须在{min}和{max}之间
relate_source_type_not_blank=关联资源类型不能为空

View File

@ -524,4 +524,9 @@ excel.template.case_edit_type=Not mandatory, fill in STEP for step description,
excel.template.tag=Not mandatory labels should be separated by semicolons or commas
excel.template.text_description=Not mandatory, when the editing mode is STEP, the step description will be based on the identifier [1] [2] [3] To determine whether to split a cell into multiple steps, if not, it is a single step
excel.template.member=Not mandatory, please fill in the relevant personnel ID or email under this project
excel.template.not_required=Not required
excel.template.not_required=Not required
#关联
relate_source_id_not_blank=Source id cannot be empty
relate_source_id_length_range=The association source ID must be between {min} and {max}
relate_source_type_not_blank=The associated resource type cannot be empty

View File

@ -520,4 +520,9 @@ excel.template.case_edit_type=非必填步骤描述填写STEP文本描述
excel.template.tag=非必填,标签之间以分号或者逗号隔开
excel.template.text_description=非必填编辑模式为STEP时步骤描述会根据标识[1] [2] [3]...来判断是否将单元格拆分为多个步骤,没有则为一个步骤
excel.template.member=非必填请填写该项目下的相关人员ID或邮箱
excel.template.not_required=非必填
excel.template.not_required=非必填
#关联
relate_source_id_not_blank=关联来源ID不能为空
relate_source_id_length_range=关联来源ID必须在{min}和{max}之间
relate_source_type_not_blank=关联资源类型不能为空

View File

@ -520,4 +520,9 @@ excel.template.case_edit_type=非必填步驟描述填寫STEP文本描述
excel.template.tag=非必填,標簽之間以分號或者逗號隔開
excel.template.text_description=非必填編輯模式為STEP時步驟描述會根據標識[1] [2] [3]...來判斷是否將單元格拆分為多個步驟,沒有則為一個步驟
excel.template.member=非必填請填寫該項目下的相關人員ID或郵箱
excel.template.not_required=非必填
excel.template.not_required=非必填
#关联
relate_source_id_not_blank=關聯來源ID不能為空
relate_source_id_length_range=關聯來源ID必須在{min}和{max}之間
relate_source_type_not_blank=關聯資源類型不能為空

View File

@ -2,12 +2,12 @@ package io.metersphere.bug.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.bug.dto.request.BugRelateCaseModuleRequest;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import io.metersphere.bug.service.BugRelateCaseCommonService;
import io.metersphere.dto.TestCaseProviderDTO;
import io.metersphere.provider.BaseAssociateCaseProvider;
import io.metersphere.request.AssociateCaseModuleRequest;
import io.metersphere.request.AssociateOtherCaseRequest;
import io.metersphere.request.TestCasePageProviderRequest;
import io.metersphere.sdk.constants.PermissionConstants;
@ -41,26 +41,26 @@ public class BugRelateCaseController {
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
public Pager<List<TestCaseProviderDTO>> unRelatedPage(@Validated @RequestBody TestCasePageProviderRequest request) {
// 目前只保留功能用例的Provider接口, 后续其他用例根据RelateCaseType扩展
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(), null);
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize());
return PageUtils.setPageInfo(page, functionalCaseProvider.listUnRelatedTestCaseList(request));
}
@PostMapping("/un-relate/module/tree")
@Operation(summary = "缺陷管理-关联用例-未关联用例-模块树")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@CheckOwner(resourceId = "#request.projectId", resourceType = "project")
public List<BaseTreeNode> getTree(@RequestBody @Validated BugRelateCaseModuleRequest request) {
return bugRelateCaseCommonService.getRelateCaseTree(request);
}
@PostMapping("/un-relate/module/count")
@Operation(summary = "缺陷管理-关联用例-未关联用例-模块树数量")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@CheckOwner(resourceId = "#request.projectId", resourceType = "project")
public Map<String, Long> countTree(@RequestBody @Validated BugRelateCaseModuleRequest request) {
public Map<String, Long> countTree(@RequestBody @Validated TestCasePageProviderRequest request) {
return bugRelateCaseCommonService.countTree(request);
}
@PostMapping("/un-relate/module/tree")
@Operation(summary = "缺陷管理-关联用例-未关联用例-模块树")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)
@CheckOwner(resourceId = "#request.projectId", resourceType = "project")
public List<BaseTreeNode> getTree(@RequestBody @Validated AssociateCaseModuleRequest request) {
return bugRelateCaseCommonService.getRelateCaseTree(request);
}
@PostMapping("/relate")
@Operation(summary = "缺陷管理-关联用例-关联")
@RequiresPermissions(PermissionConstants.PROJECT_BUG_UPDATE)

View File

@ -16,10 +16,8 @@ import org.quartz.JobExecutionContext;
public class BugSyncJob extends BaseScheduleJob {
private final LicenseService licenseService;
private final XpackBugService xpackBugService;
private final BugSyncService bugSyncService;
private final XpackBugService xpackBugService;
public BugSyncJob() {
licenseService = CommonBeanFactory.getBean(LicenseService.class);

View File

@ -167,7 +167,7 @@
)
</when>
<!-- 自定义多选字段 -->
<when test="key.startsWith('custom_multiple_321421')">
<when test="key.startsWith('custom_multiple')">
and b.id in (
select bug_id from bug_custom_field where concat('custom_multiple_', field_id) = #{key}
and

View File

@ -1,12 +1,13 @@
package io.metersphere.bug.mapper;
import io.metersphere.bug.dto.request.BugRelateCaseModuleRequest;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseCountDTO;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import io.metersphere.dto.BugProviderDTO;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.request.AssociateBugPageRequest;
import io.metersphere.request.AssociateCaseModuleRequest;
import io.metersphere.request.TestCasePageProviderRequest;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import org.apache.ibatis.annotations.Param;
@ -23,7 +24,7 @@ public interface ExtBugRelateCaseMapper {
* @param deleted 是否删除状态
* @return 模块树集合
*/
List<BaseTreeNode> getRelateCaseModule(@Param("request") BugRelateCaseModuleRequest request, @Param("deleted") boolean deleted);
List<BaseTreeNode> getRelateCaseModule(@Param("request") AssociateCaseModuleRequest request, @Param("deleted") boolean deleted);
/**
* 获取缺陷关联的用例模块树数量
@ -31,7 +32,7 @@ public interface ExtBugRelateCaseMapper {
* @param deleted 是否删除状态
* @return 模块树数量
*/
List<ModuleCountDTO> countRelateCaseModuleTree(@Param("request") BugRelateCaseModuleRequest request, @Param("deleted") boolean deleted);
List<ModuleCountDTO> countRelateCaseModuleTree(@Param("request") TestCasePageProviderRequest request, @Param("deleted") boolean deleted);
/**
* 统计缺陷关联的用例数量

View File

@ -25,14 +25,14 @@
fcm.id as moduleId,
count(fc.id) as dataCount
from functional_case_module fcm left join functional_case fc on fc.module_id = fcm.id
where fc.deleted = #{deleted}
and fc.project_id = #{request.projectId}
and fc.version_id = #{request.versionId}
and fc.id not in
(
select brc.case_id from bug_relation_case brc where brc.bug_id = #{request.sourceId} and brc.case_type = #{request.sourceType}
)
<include refid="queryModuleWhereCondition"/>
where fc.deleted = #{deleted}
and fc.project_id = #{request.projectId}
and fc.version_id = #{request.versionId}
and fc.id not in
(
select brc.case_id from bug_relation_case brc where brc.bug_id = #{request.sourceId} and brc.case_type = #{request.sourceType}
)
<include refid="queryModuleWhereCondition"/>
group by fcm.id
</select>
@ -92,12 +92,12 @@
<!-- 待补充关联Case弹窗中的高级搜索条件 -->
<if test="request.keyword != null and request.keyword != ''">
and (
fc.id like concat('%', #{request.keyword}, '%') or fc.name like concat('%', #{request.keyword}, '%')
fc.num like concat('%', #{request.keyword}, '%') or fc.name like concat('%', #{request.keyword}, '%')
)
</if>
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
and fcm.module_id in
<foreach collection="moduleIds" item="moduleId" open="(" separator="," close=")">
<foreach collection="request.moduleIds" item="moduleId" open="(" separator="," close=")">
#{moduleId}
</foreach>
</if>

View File

@ -2,7 +2,6 @@ package io.metersphere.bug.service;
import io.metersphere.bug.domain.BugRelationCase;
import io.metersphere.bug.domain.BugRelationCaseExample;
import io.metersphere.bug.dto.request.BugRelateCaseModuleRequest;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import io.metersphere.bug.mapper.BugRelationCaseMapper;
@ -17,10 +16,13 @@ import io.metersphere.project.mapper.ProjectVersionMapper;
import io.metersphere.project.service.ModuleTreeService;
import io.metersphere.project.service.PermissionCheckService;
import io.metersphere.provider.BaseAssociateCaseProvider;
import io.metersphere.request.AssociateCaseModuleRequest;
import io.metersphere.request.AssociateOtherCaseRequest;
import io.metersphere.request.TestCasePageProviderRequest;
import io.metersphere.sdk.constants.CaseType;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.uid.IDGenerator;
@ -63,7 +65,7 @@ public class BugRelateCaseCommonService extends ModuleTreeService {
* @param request 请求参数
* @return 模块树集合
*/
public List<BaseTreeNode> getRelateCaseTree(BugRelateCaseModuleRequest request) {
public List<BaseTreeNode> getRelateCaseTree(AssociateCaseModuleRequest request) {
// 目前只保留功能用例的左侧模块树方法调用, 后续其他用例根据RelateCaseType扩展
List<BaseTreeNode> relateCaseModules = extBugRelateCaseMapper.getRelateCaseModule(request, false);
// 构建模块树层级数量为通用逻辑
@ -75,10 +77,12 @@ public class BugRelateCaseCommonService extends ModuleTreeService {
* @param request 请求参数
* @return 模块树集合
*/
public Map<String, Long> countTree(BugRelateCaseModuleRequest request) {
public Map<String, Long> countTree(TestCasePageProviderRequest request) {
// 目前只保留功能用例的左侧模块树方法调用, 后续其他用例根据RelateCaseType扩展
List<ModuleCountDTO> moduleCounts = extBugRelateCaseMapper.countRelateCaseModuleTree(request, false);
List<BaseTreeNode> relateCaseModules = extBugRelateCaseMapper.getRelateCaseModule(request, false);
AssociateCaseModuleRequest moduleRequest = new AssociateCaseModuleRequest();
BeanUtils.copyBean(moduleRequest, request);
List<BaseTreeNode> relateCaseModules = extBugRelateCaseMapper.getRelateCaseModule(moduleRequest, false);
List<BaseTreeNode> relateCaseModuleWithCount = buildTreeAndCountResource(relateCaseModules, moduleCounts, true, Translator.get("api_unplanned_request"));
Map<String, Long> moduleCountMap = getIdCountMapByBreadth(relateCaseModuleWithCount);
long total = getAllCount(moduleCounts);

View File

@ -159,7 +159,7 @@ public class BugSyncExtraService {
bytes = in.readAllBytes();
FileCenter.getDefaultRepository().saveFile(bytes, buildBugFileRequest(projectId, bugId, fileName));
} catch (Exception e) {
throw new MSException(e);
throw new MSException(e.getMessage());
}
// save bug attachment relation
BugLocalAttachment localAttachment = new BugLocalAttachment();
@ -174,7 +174,8 @@ public class BugSyncExtraService {
bugLocalAttachmentMapper.insert(localAttachment);
});
} catch (Exception e) {
LogUtils.error(e);
LogUtils.error(e.getMessage());
throw new MSException(e.getMessage());
}
}
@ -185,7 +186,7 @@ public class BugSyncExtraService {
* @param bugId 缺陷ID
* @param projectId 项目ID
*/
private void deleteSyncAttachmentFromMs(Set<String> platformAttachmentSet, List<BugFileDTO> allMsAttachments, String bugId, String projectId) {
public void deleteSyncAttachmentFromMs(Set<String> platformAttachmentSet, List<BugFileDTO> allMsAttachments, String bugId, String projectId) {
try {
// 删除MS中不存在的平台附件
if (!CollectionUtils.isEmpty(allMsAttachments)) {
@ -213,7 +214,7 @@ public class BugSyncExtraService {
BugFileDTO bugFileDTO = localFileMap.get(deleteLocalId);
FileCenter.getDefaultRepository().delete(buildBugFileRequest(projectId, bugId, bugFileDTO.getFileName()));
} catch (Exception e) {
throw new MSException(e);
throw new MSException(e.getMessage());
}
});
BugLocalAttachmentExample example = new BugLocalAttachmentExample();
@ -222,7 +223,8 @@ public class BugSyncExtraService {
}
}
} catch (Exception e) {
LogUtils.error(e);
LogUtils.error(e.getMessage());
throw new MSException(e.getMessage());
}
}

View File

@ -65,7 +65,7 @@ public class BugSyncService {
}
} catch (Exception e) {
bugSyncExtraService.deleteSyncKey(request.getProjectId());
throw new MSException(e);
throw new MSException(e.getMessage());
}
}

View File

@ -1,6 +1,8 @@
package io.metersphere.bug.config;
import io.metersphere.plugin.platform.spi.Platform;
import io.metersphere.provider.BaseAssociateCaseProvider;
import io.metersphere.system.service.LicenseService;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean;
@ -9,4 +11,10 @@ public class BugProviderConfiguration {
@MockBean
BaseAssociateCaseProvider baseAssociateCaseProvider;
@MockBean
LicenseService licenseService;
@MockBean
Platform platform;
}

View File

@ -30,7 +30,7 @@ public class AssociateBugProviderTests extends BaseTest {
@Test
@Order(1)
@Sql(scripts = {"/dml/init_bug_relation_case.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
public void getBugList() throws Exception {
public void getBugList() {
BugPageProviderRequest request = new BugPageProviderRequest();
request.setSourceId("wx_associate_case_id_1");
request.setProjectId("project_wx_associate_test");
@ -43,7 +43,7 @@ public class AssociateBugProviderTests extends BaseTest {
@Test
@Order(2)
public void getSelectBugs() throws Exception {
public void getSelectBugs() {
AssociateBugRequest request = new AssociateBugRequest();
request.setCaseId("wx_associate_case_id_1");
request.setProjectId("project_wx_associate_test");
@ -68,19 +68,19 @@ public class AssociateBugProviderTests extends BaseTest {
@Test
@Order(3)
public void testAssociateBug() throws Exception {
public void testAssociateBug() {
associateBugProvider.handleAssociateBug(List.of("bug_id_1", "bug_id_2"), "wx", "wx_associate_case_id_1");
}
@Test
@Order(4)
public void testDisassociateBug() throws Exception {
public void testDisassociateBug() {
associateBugProvider.disassociateBug("wx_test_id_1");
}
@Test
@Order(5)
public void testAssociateBugPage() throws Exception {
public void testAssociateBugPage(){
AssociateBugPageRequest request = new AssociateBugPageRequest();
request.setCurrent(1);
request.setPageSize(10);

View File

@ -4,7 +4,6 @@ import io.metersphere.bug.domain.BugComment;
import io.metersphere.bug.dto.request.BugCommentEditRequest;
import io.metersphere.bug.dto.response.BugCommentDTO;
import io.metersphere.bug.mapper.BugCommentMapper;
import io.metersphere.project.mapper.NotificationMapper;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
@ -31,8 +30,6 @@ public class BugCommentTests extends BaseTest {
@Resource
private BugCommentMapper bugCommentMapper;
@Resource
private NotificationMapper notificationMapper;
public static final String BUG_COMMENT_GET = "/bug/comment/get";
public static final String BUG_COMMENT_ADD = "/bug/comment/add";

View File

@ -24,6 +24,7 @@ import io.metersphere.project.service.FileService;
import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.constants.StorageType;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.util.FileAssociationSourceUtil;
import io.metersphere.sdk.util.JSON;
@ -42,12 +43,14 @@ import io.metersphere.system.utils.Pager;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*;
import org.mockito.Mockito;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@ -58,6 +61,8 @@ import java.nio.charset.StandardCharsets;
import java.util.*;
import static io.metersphere.sdk.constants.InternalUserRole.ADMIN;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
@ -530,7 +535,9 @@ public class BugControllerTests extends BaseTest {
this.requestGetWithOk(BUG_HEADER_HANDLER_OPTION + "/default-project-for-bug");
// 同步删除缺陷(default-bug-id-jira-sync)
// 一条模板不存在的缺陷(手动删除default-bug-id-jira-sync-1, 影响后续测试)
this.requestGetWithOk(BUG_SYNC + "/default-project-for-bug");
bugMapper.deleteByPrimaryKey("default-bug-id-jira-sync-1");
// 添加Jira缺陷
BugEditRequest addRequest = buildJiraBugRequest(false);
@ -590,13 +597,6 @@ public class BugControllerTests extends BaseTest {
MultiValueMap<String, Object> notIntegrationParam = getDefaultMultiPartParam(addRequest, file);
this.requestMultipart(BUG_ADD, notIntegrationParam).andExpect(status().is5xxServerError());
// 同步全量缺陷
BugSyncRequest request = new BugSyncRequest();
request.setProjectId("default-project-for-bug");
request.setPre(true);
request.setCreateTime(1702021500000L);
this.requestPostWithOk(BUG_SYNC_ALL, request);
// 执行同步全部
Project project = new Project();
project.setId("default-project-for-bug");
@ -674,6 +674,22 @@ public class BugControllerTests extends BaseTest {
bugSyncService.checkSyncStatus("default-project-for-bug");
// 覆盖空Msg
bugSyncService.checkSyncStatus("default-project-for-bug");
// 同步全量缺陷
BugSyncRequest syncRequest = new BugSyncRequest();
syncRequest.setProjectId("default-project-for-bug");
syncRequest.setPre(true);
syncRequest.setCreateTime(1702021500000L);
bugSyncExtraService.setSyncKey("default-project-for-bug");
this.requestPostWithOk(BUG_SYNC_ALL, request);
bugSyncExtraService.deleteSyncKey("default-project-for-bug");
Project project = projectMapper.selectByPrimaryKey("default-project-for-bug");
this.requestPostWithOk(BUG_SYNC_ALL, request);
BugService mockBugService = Mockito.mock(BugService.class);
Mockito.doThrow(new MSException("sync error!")).when(mockBugService).syncPlatformAllBugs(syncRequest, project);
ReflectionTestUtils.setField(bugSyncService, "bugService", mockBugService);
MSException msException = assertThrows(MSException.class, () -> bugSyncService.syncAllBugs(syncRequest));
assertEquals(msException.getMessage(), "sync error!");
}
/**

View File

@ -1,6 +1,5 @@
package io.metersphere.bug.controller;
import io.metersphere.bug.dto.request.BugRelateCaseModuleRequest;
import io.metersphere.bug.dto.request.BugRelatedCasePageRequest;
import io.metersphere.bug.dto.response.BugRelateCaseDTO;
import io.metersphere.bug.service.BugRelateCaseCommonService;
@ -63,11 +62,13 @@ public class BugRelateCaseControllerTests extends BaseTest {
@Test
@Order(1)
void testBugUnRelateCaseModule() throws Exception {
BugRelateCaseModuleRequest request = new BugRelateCaseModuleRequest();
TestCasePageProviderRequest request = new TestCasePageProviderRequest();
request.setProjectId("default-project-for-bug");
request.setVersionId("default_bug_version");
request.setSourceId("default-relate-bug-id'");
request.setSourceType("FUNCTIONAL");
request.setCurrent(1);
request.setPageSize(10);
this.requestPostWithOk(BUG_CASE_UN_RELATE_MODULE_TREE, request);
this.requestPostWithOk(BUG_CASE_UN_RELATE_MODULE_COUNT, request);
}

View File

@ -15,6 +15,7 @@ import org.springframework.test.web.servlet.MvcResult;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -32,7 +33,7 @@ public class BugTrashControllerTests extends BaseTest {
@Test
@Order(0)
@Sql(scripts = {"/dml/init_bug_trash.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
void testTrashPage() throws Exception{
void testTrashPage() throws Exception {
BugPageRequest bugRequest = new BugPageRequest();
bugRequest.setCurrent(1);
bugRequest.setPageSize(10);
@ -50,6 +51,9 @@ public class BugTrashControllerTests extends BaseTest {
Assertions.assertEquals(pageData.getCurrent(), bugRequest.getCurrent());
// 返回的数据量不超过规定要返回的数据量相同
Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(pageData.getList())).size() <= bugRequest.getPageSize());
// 排序
bugRequest.setSort(Map.of("status", "asc"));
this.requestPostWithOkAndReturn(BUG_TRASH_PAGE, bugRequest);
}
@Test

View File

@ -0,0 +1,50 @@
package io.metersphere.bug.job;
import io.metersphere.system.dto.sdk.LicenseDTO;
import io.metersphere.system.dto.sdk.LicenseInfoDTO;
import io.metersphere.system.service.LicenseService;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.mockito.Mockito;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.util.ReflectionTestUtils;
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BugSyncJobTests {
@Resource
private LicenseService licenseService;
@Test
void test() {
// set licenseService field to null by reflection
BugSyncJob noLicenseMockObj = new BugSyncJob();
ReflectionTestUtils.setField(noLicenseMockObj, "licenseService", null);
noLicenseMockObj.businessExecute(null);
// set mocLicenseService field
BugSyncJob syncJob = new BugSyncJob();
// mock license validate return null
Mockito.when(licenseService.validate()).thenReturn(null);
syncJob.businessExecute(null);
// mock license validate return empty info
Mockito.when(licenseService.validate()).thenReturn(new LicenseDTO());
syncJob.businessExecute(null);
// mock license validate return invalid && license info
LicenseDTO invalid = new LicenseDTO();
invalid.setStatus("invalid");
invalid.setLicense(new LicenseInfoDTO());
Mockito.when(licenseService.validate()).thenReturn(invalid);
syncJob.businessExecute(null);
// mock license validate return valid && license info
LicenseDTO valid = new LicenseDTO();
valid.setStatus("valid");
valid.setLicense(new LicenseInfoDTO());
Mockito.when(licenseService.validate()).thenReturn(valid);
syncJob.businessExecute(null);
}
}

View File

@ -0,0 +1,20 @@
package io.metersphere.bug.mock;
import io.metersphere.bug.dto.request.BugSyncRequest;
import io.metersphere.bug.service.XpackBugService;
import io.metersphere.project.domain.Project;
import org.springframework.stereotype.Service;
@Service
public class XpackBugMockServiceImpl implements XpackBugService {
@Override
public void syncPlatformBugsBySchedule() {
}
@Override
public void syncPlatformBugs(Project project, BugSyncRequest request) {
}
}

View File

@ -0,0 +1,77 @@
package io.metersphere.bug.service;
import io.metersphere.bug.dto.response.BugFileDTO;
import io.metersphere.plugin.platform.spi.Platform;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.MinioRepository;
import io.metersphere.system.base.BaseTest;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.mockito.Mockito;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import java.io.InputStream;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BugSyncExtraServiceTests extends BaseTest {
@Resource
Platform platform;
@MockBean
MinioRepository minioMock;
@Resource
private BugSyncExtraService bugSyncExtraService;
@Resource
private BugAttachmentService bugAttachmentService;
@Test
@Order(1)
@Sql(scripts = {"/dml/init_bug_sync_extra.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
void test() throws Exception {
List<BugFileDTO> allBugFile = bugAttachmentService.getAllBugFiles("bug-for-sync-extra");
// Mock minio delete exception
Mockito.doThrow(new MSException("delete minio error!")).when(minioMock).delete(Mockito.any());
MSException deleteException = assertThrows(MSException.class, () ->
bugSyncExtraService.deleteSyncAttachmentFromMs(Set.of("sync-extra-file-associate-B", "sync-extra-file-local-B", "sync-extra-file-local-A.txt"),
allBugFile, "bug-for-sync-extra", "project-for-sync-extra"));
assertEquals(deleteException.getMessage(), "delete minio error!");
// Reset minio mock
Mockito.reset(minioMock);
bugSyncExtraService.deleteSyncAttachmentFromMs(Set.of("sync-extra-file-associate-B", "sync-extra-file-local-B"),
allBugFile, "bug-for-sync-extra", "project-for-sync-extra");
// Mock null input stream and exception input stream
Mockito.doAnswer(invocation -> {
String fileKey = invocation.getArgument(0);
Consumer<InputStream> inputStreamHandler = invocation.getArgument(1);
if ("TEST-1".equals(fileKey)) {
inputStreamHandler.accept(null);
} else {
InputStream mockExceptionStream = Mockito.mock(InputStream.class);
Mockito.doThrow(new MSException("read bytes exception occurred!")).when(mockExceptionStream).readAllBytes();
inputStreamHandler.accept(mockExceptionStream);
}
return null;
}).when(platform).getAttachmentContent(Mockito.anyString(), Mockito.any());
// called twice for cover test
bugSyncExtraService.saveSyncAttachmentToMs(platform, "bug-for-sync-extra", "sync-extra-file-associate-B", "TEST-1", "project-for-sync-extra");
MSException msException = assertThrows(MSException.class, () ->
bugSyncExtraService.saveSyncAttachmentToMs(platform, "bug-for-sync-extra", "sync-extra-file-associate-B", "TEST-2", "project-for-sync-extra"));
assertEquals(msException.getMessage(), "read bytes exception occurred!");
}
}

View File

@ -8,14 +8,14 @@ INSERT INTO project (id, num, organization_id, name, description, create_user, u
INSERT INTO user_role_relation (id, user_id, role_id, source_id, organization_id, create_time, create_user) VALUES
(UUID(), 'admin', 'project_admin', 'default-project-for-bug', '100001', UNIX_TIMESTAMP() * 1000, 'admin');
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time,
update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags, platform_bug_id, deleted) VALUES
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time, update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags, platform_bug_id, deleted) VALUES
('default-bug-id', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 0),
('default-bug-id-tapd1', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'default-bug-template-id', 'Tapd', 'open', '["default-tag"]', null, 0),
('default-bug-id-tapd2', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug-no-local', 'default-bug-template-id', 'Tapd', 'open', '["default-tag"]', null, 0),
('default-bug-id-single', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug-single', 'default-bug-template-id', 'Tapd', 'open', '["default-tag"]', null, 0),
('default-bug-id-jira', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug-single', 'default-bug-template-id', 'Jira', 'open', '["default-tag"]', 'TES-TEST', 0),
('default-bug-id-jira-sync', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'jira', 'Jira', 'open', '["default-tag"]', 'TES-TEST', 0);
('default-bug-id-jira-sync', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'jira', 'Jira', 'open', '["default-tag"]', 'TES-TEST', 0),
('default-bug-id-jira-sync-1', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'default-project-for-bug', 'jira-test', 'Jira', 'open', '["default-tag"]', 'TES-TEST', 0);
INSERT INTO bug_custom_field (bug_id, field_id, value) VALUE ('default-bug-id', 'test_field', '["default", "default-1"]');

View File

@ -6,9 +6,4 @@ INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_
INSERT INTO bug_relation_case(id, case_id, bug_id, case_type, test_plan_id, test_plan_case_id, create_user, create_time, update_time)
VALUES ('wx_test_id_1', 'wx_1', 'bug_id_1', 'FUNCTIONAL', null, null, 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000),
('wx_test_id_2', 'wx_2', 'bug_id_1', 'FUNCTIONAL', 'test-plan-id', 'bug_relate_case', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000),
('wx_test_id_3', 'wx_3', 'bug_id_2', 'FUNCTIONAL', null, null, 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);
INSERT INTO `test_plan` (`id`, `project_id`, `group_id`, `module_id`, `type`, `name`, `status`, `create_time`,
`create_user`)
VALUES ('test-plan-id', 'wx_test', 'none', 'root', 'TEST_PLAN', 'cececec', 'PREPARED', UNIX_TIMESTAMP() * 1000,
'admin');
('wx_test_id_3', 'wx_3', 'bug_id_2', 'FUNCTIONAL', null, null, 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);

View File

@ -0,0 +1,20 @@
INSERT INTO project (id, num, organization_id, name, description, create_user, update_user, create_time, update_time) VALUE
('default-project-for-bug-tmp', null, '100001', '测试项目(缺陷)', '系统默认创建的项目(缺陷)', 'admin', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);
INSERT INTO project (id, num, organization_id, name, description, create_user, update_user, create_time, update_time) VALUE
('project-for-sync-extra', null, '100001', '测试项目(缺陷同步)', '系统默认创建的项目(缺陷)', 'admin', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000);
INSERT INTO bug (id, num, title, handle_users, handle_user, create_user, create_time, update_user, update_time, delete_user, delete_time, project_id, template_id, platform, status, tags, platform_bug_id, deleted) VALUE
('bug-for-sync-extra', 100000, 'default-bug', 'oasis', 'oasis', 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'project-for-sync-extra', 'bug-template-id', 'Local', 'open', '["default-tag"]', null, 0);
INSERT INTO file_metadata (id, name, type, size, create_time, update_time, project_id, storage, create_user, update_user,
tags, description, module_id, path, latest, ref_id, file_version) VALUE
('file-for-sync-extra', 'sync-extra-file-associate-A', 'xlsx', 100, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'project-for-sync-extra', 'MINIO', 'admin', 'admin',
'["default-tag"]', 'test-file', null, 'test-file', 1, 'default-bug-id', 1);
INSERT INTO file_association(id, source_type, source_id, file_id, file_ref_id, file_version, create_time, update_user, update_time, create_user) VALUE
('association-for-sync-extra', 'bug', 'bug-for-sync-extra', 'file-for-sync-extra', 'default-bug-id', 1, UNIX_TIMESTAMP() * 1000, 'admin', UNIX_TIMESTAMP() * 1000, 'admin');
INSERT INTO bug_local_attachment (id, bug_id, file_id, file_name, size, source, create_user, create_time) VALUE
('bug-local-attachment-for-sync-extra', 'bug-for-sync-extra', 'file-for-sync-extra', 'sync-extra-file-local-A.txt', 100, 'ATTACHMENT', 'admin', UNIX_TIMESTAMP() * 1000),
('bug-local-attachment-for-sync-extra-null', 'bug-for-sync-extra', 'file-for-sync-extra-null', '', 100, 'ATTACHMENT', 'admin', UNIX_TIMESTAMP() * 1000);

View File

@ -6,7 +6,6 @@ import io.metersphere.dto.BugProviderDTO;
import io.metersphere.dto.TestCaseProviderDTO;
import io.metersphere.functional.dto.FunctionalCaseTestDTO;
import io.metersphere.functional.dto.FunctionalCaseTestPlanDTO;
import io.metersphere.functional.request.AssociateCaseModuleRequest;
import io.metersphere.functional.request.AssociatePlanPageRequest;
import io.metersphere.functional.request.DisassociateOtherCaseRequest;
import io.metersphere.functional.request.FunctionalCaseTestRequest;

View File

@ -65,9 +65,10 @@ public interface ExtFunctionalCaseMapper {
* 获取缺陷未关联的功能用例列表
* @param request provider参数
* @param deleted 是否删除状态
* @param sort 排序
* @return 通用的列表Case集合
*/
List<TestCaseProviderDTO> listUnRelatedCaseWithBug(@Param("request") TestCasePageProviderRequest request, @Param("deleted") boolean deleted);
List<TestCaseProviderDTO> listUnRelatedCaseWithBug(@Param("request") TestCasePageProviderRequest request, @Param("deleted") boolean deleted, @Param("sort") String sort);
/**
* 根据关联条件获取关联的用例ID

View File

@ -656,7 +656,13 @@
select brc.case_id from bug_relation_case brc where brc.bug_id = #{request.sourceId} and brc.case_type = #{request.sourceType}
)
<include refid="queryByTestCaseProviderParam"/>
order by fc.create_time desc
order by
<if test="sort != null and sort != ''">
fc.${request.sort}
</if>
<if test="sort == null or sort == ''">
fc.create_time desc
</if>
</select>
<select id="getSelectIdsByAssociateParam" resultType="java.lang.String">
@ -677,7 +683,7 @@
<!-- 待补充关联Case弹窗中的高级搜索条件 -->
<if test="request.keyword != null and request.keyword != ''">
and (
fc.id like concat('%', #{request.keyword}, '%') or fc.name like concat('%', #{request.keyword}, '%')
fc.num like concat('%', #{request.keyword}, '%') or fc.name like concat('%', #{request.keyword}, '%')
)
</if>
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
@ -692,12 +698,12 @@
<!-- 待补充关联Case弹窗中的高级搜索条件 -->
<if test="request.condition.keyword != null and request.condition.keyword != ''">
and (
fc.id like concat('%', #{request.keyword}, '%') or fc.name like concat('%', #{request.keyword}, '%')
fc.num like concat('%', #{request.condition.keyword}, '%') or fc.name like concat('%', #{request.condition.keyword}, '%')
)
</if>
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
and fcm.module_id in
<foreach collection="moduleIds" item="moduleId" open="(" separator="," close=")">
<foreach collection="request.moduleIds" item="moduleId" open="(" separator="," close=")">
#{moduleId}
</foreach>
</if>

View File

@ -1,7 +1,7 @@
package io.metersphere.functional.mapper;
import io.metersphere.functional.request.AssociateCaseModuleRequest;
import io.metersphere.project.dto.NodeSortQueryParam;
import io.metersphere.request.AssociateCaseModuleRequest;
import io.metersphere.system.dto.sdk.BaseModule;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import org.apache.ibatis.annotations.Param;

View File

@ -19,7 +19,7 @@ public class AssociateCaseProvider implements BaseAssociateCaseProvider {
@Override
public List<TestCaseProviderDTO> listUnRelatedTestCaseList(TestCasePageProviderRequest testCasePageProviderRequest) {
return extFunctionalCaseMapper.listUnRelatedCaseWithBug(testCasePageProviderRequest, false);
return extFunctionalCaseMapper.listUnRelatedCaseWithBug(testCasePageProviderRequest, false, testCasePageProviderRequest.getSortString());
}
@Override

View File

@ -12,7 +12,6 @@ import io.metersphere.functional.dto.FunctionalCaseTestPlanDTO;
import io.metersphere.functional.mapper.ExtFunctionalCaseModuleMapper;
import io.metersphere.functional.mapper.ExtFunctionalCaseTestMapper;
import io.metersphere.functional.mapper.FunctionalCaseTestMapper;
import io.metersphere.functional.request.AssociateCaseModuleRequest;
import io.metersphere.functional.request.AssociatePlanPageRequest;
import io.metersphere.functional.request.DisassociateOtherCaseRequest;
import io.metersphere.functional.request.FunctionalCaseTestRequest;

View File

@ -15,7 +15,6 @@ import io.metersphere.functional.dto.FunctionalCaseTestDTO;
import io.metersphere.functional.dto.FunctionalCaseTestPlanDTO;
import io.metersphere.functional.mapper.FunctionalCaseMapper;
import io.metersphere.functional.mapper.FunctionalCaseTestMapper;
import io.metersphere.functional.request.AssociateCaseModuleRequest;
import io.metersphere.functional.request.AssociatePlanPageRequest;
import io.metersphere.functional.request.DisassociateOtherCaseRequest;
import io.metersphere.functional.request.FunctionalCaseTestRequest;

View File

@ -50,7 +50,7 @@
<sql id="filterMultipleWrapper">
<foreach collection="values" item="value" separator="or" open="(" close=")">
JSON_CONTAINS(value, #{value})
JSON_CONTAINS(`value`, JSON_ARRAY(#{value}))
</foreach>
</sql>
</mapper>