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 c300bbc6f0..08eaf33c77 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 @@ -211,6 +211,7 @@ public class PermissionConstants { /*------ start: FUNCTIONAL_CASE ------*/ + public static final String FUNCTIONAL_CASE_READ = "FUNCTIONAL_CASE:READ"; public static final String FUNCTIONAL_CASE_READ_ADD = "FUNCTIONAL_CASE:READ+ADD"; public static final String FUNCTIONAL_CASE_COMMENT_READ_ADD = "FUNCTIONAL_CASE_COMMENT:READ+ADD"; diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/controller/FunctionalCaseController.java b/backend/services/case-management/src/main/java/io/metersphere/functional/controller/FunctionalCaseController.java index 41f66ec1b3..0a6242cd58 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/controller/FunctionalCaseController.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/controller/FunctionalCaseController.java @@ -1,9 +1,13 @@ package io.metersphere.functional.controller; import io.metersphere.functional.domain.FunctionalCase; +import io.metersphere.functional.dto.FunctionalCaseDetailDTO; import io.metersphere.functional.request.FunctionalCaseAddRequest; import io.metersphere.functional.service.FunctionalCaseService; +import io.metersphere.project.service.ProjectTemplateService; import io.metersphere.sdk.constants.PermissionConstants; +import io.metersphere.sdk.constants.TemplateScene; +import io.metersphere.sdk.dto.TemplateDTO; import io.metersphere.system.log.annotation.Log; import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.utils.SessionUtils; @@ -28,8 +32,18 @@ public class FunctionalCaseController { @Resource private FunctionalCaseService functionalCaseService; + @Resource + private ProjectTemplateService projectTemplateService; - //TODO 获取模板列表 获取对应模板自定义字段 + //TODO 获取模板列表(多模板功能暂时不做) + + @GetMapping("/default/template/field/{projectId}") + @Operation(summary = "功能用例-获取默认模板自定义字段") + @RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_ADD) + public TemplateDTO getDefaultTemplateField(@PathVariable String projectId) { + TemplateDTO defaultTemplateDTO = projectTemplateService.getDefaultTemplateDTO(projectId, TemplateScene.FUNCTIONAL.name()); + return defaultTemplateDTO; + } @PostMapping("/add") @@ -41,4 +55,11 @@ public class FunctionalCaseController { return functionalCaseService.addFunctionalCase(request, files, userId); } + + @GetMapping("/detail/{functionalCaseId}") + @Operation(summary = "功能用例-查看用例详情") + @RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ) + public FunctionalCaseDetailDTO getFunctionalCaseDetail(@PathVariable String functionalCaseId) { + return functionalCaseService.getFunctionalCaseDetail(functionalCaseId); + } } diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/dto/FunctionalCaseDetailDTO.java b/backend/services/case-management/src/main/java/io/metersphere/functional/dto/FunctionalCaseDetailDTO.java new file mode 100644 index 0000000000..3a02679754 --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/dto/FunctionalCaseDetailDTO.java @@ -0,0 +1,78 @@ +package io.metersphere.functional.dto; + +import io.metersphere.sdk.dto.TemplateCustomFieldDTO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.List; + +/** + * @author wx + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class FunctionalCaseDetailDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "ID") + private String id; + + @Schema(description = "业务ID") + private Integer num; + + @Schema(description = "模块ID") + private String moduleId; + + @Schema(description = "项目ID") + private String projectId; + + @Schema(description = "模板ID") + private String templateId; + + @Schema(description = "用例名称") + private String name; + + @Schema(description = "评审状态") + private String reviewStatus; + + @Schema(description = "标签(JSON)") + private String tags; + + @Schema(description = "编辑模式") + private String caseEditType; + + @Schema(description = "版本") + private String versionId; + + @Schema(description = "是否是公共用例库") + private Boolean publicCase; + + @Schema(description = "是否是最新版") + private Boolean latest; + + @Schema(description = "创建人") + private String createUser; + + @Schema(description = "用例步骤(JSON),step_model 为 Step 时启用") + private String steps; + + @Schema(description = "步骤描述,step_model 为 Text 时启用") + private String textDescription; + + @Schema(description = "预期结果,step_model 为 Text 时启用") + private String expectedResult; + + @Schema(description = "前置条件") + private String prerequisite; + + @Schema(description = "备注") + private String description; + + @Schema(description = "自定义字段属性") + private List customFields; + + +} diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/result/FunctionalCaseResultCode.java b/backend/services/case-management/src/main/java/io/metersphere/functional/result/FunctionalCaseResultCode.java new file mode 100644 index 0000000000..2203f3226e --- /dev/null +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/result/FunctionalCaseResultCode.java @@ -0,0 +1,26 @@ +package io.metersphere.functional.result; + +import io.metersphere.sdk.exception.IResultCode; + +public enum FunctionalCaseResultCode implements IResultCode { + + FUNCTIONAL_CASE_NOT_FOUND(105001, "case_comment.case_is_null"); + + private final int code; + private final String message; + + FunctionalCaseResultCode(int code, String message) { + this.code = code; + this.message = message; + } + + @Override + public int getCode() { + return code; + } + + @Override + public String getMessage() { + return getTranslationMessage(this.message); + } +} diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseCustomFieldService.java b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseCustomFieldService.java index d4d1dad75d..43a6417292 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseCustomFieldService.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseCustomFieldService.java @@ -2,9 +2,11 @@ package io.metersphere.functional.service; import io.metersphere.functional.domain.FunctionalCaseCustomField; +import io.metersphere.functional.domain.FunctionalCaseCustomFieldExample; import io.metersphere.functional.dto.CaseCustomsFieldDTO; import io.metersphere.functional.mapper.FunctionalCaseCustomFieldMapper; import jakarta.annotation.Resource; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -35,4 +37,22 @@ public class FunctionalCaseCustomFieldService { functionalCaseCustomFieldMapper.insertSelective(customField); }); } + + + /** + * 获取用例保存的自定义字段值 + * + * @param fieldId + * @param caseId + * @return + */ + public FunctionalCaseCustomField getCustomField(String fieldId, String caseId) { + FunctionalCaseCustomFieldExample example = new FunctionalCaseCustomFieldExample(); + example.createCriteria().andCaseIdEqualTo(caseId).andFieldIdEqualTo(fieldId); + List functionalCaseCustomFields = functionalCaseCustomFieldMapper.selectByExample(example); + if (CollectionUtils.isNotEmpty(functionalCaseCustomFields)) { + return functionalCaseCustomFields.get(0); + } + return null; + } } \ No newline at end of file diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseService.java b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseService.java index 36062a3190..3c3a6106fd 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseService.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseService.java @@ -2,16 +2,19 @@ package io.metersphere.functional.service; import io.metersphere.functional.domain.FunctionalCase; import io.metersphere.functional.domain.FunctionalCaseBlob; +import io.metersphere.functional.domain.FunctionalCaseCustomField; import io.metersphere.functional.dto.CaseCustomsFieldDTO; +import io.metersphere.functional.dto.FunctionalCaseDetailDTO; import io.metersphere.functional.mapper.ExtFunctionalCaseMapper; import io.metersphere.functional.mapper.FunctionalCaseBlobMapper; import io.metersphere.functional.mapper.FunctionalCaseMapper; import io.metersphere.functional.request.FunctionalCaseAddRequest; -import io.metersphere.sdk.constants.FunctionalCaseExecuteResult; -import io.metersphere.sdk.constants.FunctionalCaseReviewStatus; -import io.metersphere.sdk.constants.HttpMethodConstants; -import io.metersphere.sdk.constants.StorageType; +import io.metersphere.functional.result.FunctionalCaseResultCode; +import io.metersphere.project.service.ProjectTemplateService; +import io.metersphere.sdk.constants.*; import io.metersphere.sdk.dto.LogDTO; +import io.metersphere.sdk.dto.TemplateCustomFieldDTO; +import io.metersphere.sdk.dto.TemplateDTO; import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.file.FileRequest; import io.metersphere.sdk.file.MinioRepository; @@ -58,6 +61,9 @@ public class FunctionalCaseService { @Resource private FunctionalCaseAttachmentService functionalCaseAttachmentService; + @Resource + private ProjectTemplateService projectTemplateService; + public FunctionalCase addFunctionalCase(FunctionalCaseAddRequest request, List files, String userId) { String caseId = IDGenerator.nextStr(); @@ -149,6 +155,52 @@ public class FunctionalCaseService { } + /** + * 查看用例获取详情 + * + * @param functionalCaseId + * @return + */ + public FunctionalCaseDetailDTO getFunctionalCaseDetail(String functionalCaseId) { + FunctionalCase functionalCase = functionalCaseMapper.selectByPrimaryKey(functionalCaseId); + if (functionalCase == null) { + throw new MSException(FunctionalCaseResultCode.FUNCTIONAL_CASE_NOT_FOUND); + } + FunctionalCaseDetailDTO functionalCaseDetailDTO = new FunctionalCaseDetailDTO(); + BeanUtils.copyBean(functionalCaseDetailDTO, functionalCase); + FunctionalCaseBlob caseBlob = functionalCaseBlobMapper.selectByPrimaryKey(functionalCaseId); + BeanUtils.copyBean(functionalCaseDetailDTO, caseBlob); + + //模板校验 获取自定义字段 + functionalCaseDetailDTO = checkTemplateCustomField(functionalCaseDetailDTO, functionalCase); + + return functionalCaseDetailDTO; + + } + + + /** + * 获取模板自定义字段 + * + * @param functionalCase + */ + private FunctionalCaseDetailDTO checkTemplateCustomField(FunctionalCaseDetailDTO functionalCaseDetailDTO, FunctionalCase functionalCase) { + TemplateDTO templateDTO = projectTemplateService.getTemplateDTOById(functionalCase.getTemplateId(), functionalCase.getProjectId(), TemplateScene.FUNCTIONAL.name()); + if (CollectionUtils.isNotEmpty(templateDTO.getCustomFields())) { + List customFields = templateDTO.getCustomFields(); + customFields.forEach(item -> { + FunctionalCaseCustomField caseCustomField = functionalCaseCustomFieldService.getCustomField(item.getFieldId(), functionalCase.getId()); + Optional.ofNullable(caseCustomField).ifPresentOrElse(customField -> { + item.setDefaultValue(customField.getValue()); + }, () -> { + }); + }); + functionalCaseDetailDTO.setCustomFields(customFields); + } + return functionalCaseDetailDTO; + } + + /** * 新增用例 日志 * diff --git a/backend/services/case-management/src/test/java/io/metersphere/functional/Application.java b/backend/services/case-management/src/test/java/io/metersphere/functional/Application.java index d766547753..7ffc8b07d1 100644 --- a/backend/services/case-management/src/test/java/io/metersphere/functional/Application.java +++ b/backend/services/case-management/src/test/java/io/metersphere/functional/Application.java @@ -20,7 +20,7 @@ import org.springframework.context.annotation.ComponentScan; MinioProperties.class }) @ServletComponentScan -@ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.functional", "io.metersphere.system"}) +@ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.system", "io.metersphere.project", "io.metersphere.functional"}) public class Application { public static void main(String[] args) { diff --git a/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseControllerTests.java b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseControllerTests.java index abe015b784..6e7ea2050d 100644 --- a/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseControllerTests.java +++ b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseControllerTests.java @@ -2,6 +2,7 @@ package io.metersphere.functional.controller; import io.metersphere.functional.dto.CaseCustomsFieldDTO; import io.metersphere.functional.request.FunctionalCaseAddRequest; +import io.metersphere.functional.result.FunctionalCaseResultCode; import io.metersphere.functional.utils.FileBaseUtils; import io.metersphere.sdk.util.JSON; import io.metersphere.system.base.BaseTest; @@ -27,19 +28,21 @@ import java.util.Objects; @AutoConfigureMockMvc public class FunctionalCaseControllerTests extends BaseTest { - public static final String FUNCTIONAL_CASE_URL = "/functional/case/add"; + public static final String FUNCTIONAL_CASE_ADD_URL = "/functional/case/add"; + public static final String DEFAULT_TEMPLATE_FIELD_URL = "/functional/case/default/template/field/"; + public static final String FUNCTIONAL_CASE_DETAIL_URL = "/functional/case//detail/"; @Test @Order(1) @Sql(scripts = {"/dml/init_file_metadata_test.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) - public void testTestPlanShare() throws Exception { + public void testFunctionalCaseAdd() throws Exception { //新增 FunctionalCaseAddRequest request = creatFunctionalCase(); LinkedMultiValueMap paramMap = new LinkedMultiValueMap<>(); List files = new ArrayList<>(); paramMap.add("request", JSON.toJSONString(request)); paramMap.add("files", files); - MvcResult mvcResult = this.requestMultipartWithOkAndReturn(FUNCTIONAL_CASE_URL, paramMap); + MvcResult mvcResult = this.requestMultipartWithOkAndReturn(FUNCTIONAL_CASE_ADD_URL, paramMap); // 获取返回值 String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class); @@ -61,7 +64,32 @@ public class FunctionalCaseControllerTests extends BaseTest { paramMap.add("request", JSON.toJSONString(request)); paramMap.add("files", files); - this.requestMultipartWithOkAndReturn(FUNCTIONAL_CASE_URL, paramMap); + this.requestMultipartWithOkAndReturn(FUNCTIONAL_CASE_ADD_URL, paramMap); + } + + + @Test + @Order(2) + public void testDefaultTemplateField() throws Exception { + MvcResult mvcResult = this.requestGetWithOkAndReturn(DEFAULT_TEMPLATE_FIELD_URL + DEFAULT_PROJECT_ID); + // 获取返回值 + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class); + // 返回请求正常 + Assertions.assertNotNull(resultHolder); + } + + + @Test + @Order(3) + public void testFunctionalCaseDetail() throws Exception { + assertErrorCode(this.requestGet(FUNCTIONAL_CASE_DETAIL_URL + "ERROR_TEST_FUNCTIONAL_CASE_ID"), FunctionalCaseResultCode.FUNCTIONAL_CASE_NOT_FOUND); + MvcResult mvcResult = this.requestGetWithOkAndReturn(FUNCTIONAL_CASE_DETAIL_URL + "TEST_FUNCTIONAL_CASE_ID"); + // 获取返回值 + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class); + // 返回请求正常 + Assertions.assertNotNull(resultHolder); } private List creatCustomsFields() { diff --git a/backend/services/case-management/src/test/resources/application.properties b/backend/services/case-management/src/test/resources/application.properties index 99987e6cc6..35945bdfcd 100644 --- a/backend/services/case-management/src/test/resources/application.properties +++ b/backend/services/case-management/src/test/resources/application.properties @@ -6,7 +6,7 @@ server.compression.enabled=true server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css,text/javascript,image/jpeg server.compression.min-response-size=2048 # -quartz.enabled=false +quartz.enabled=true quartz.scheduler-name=msScheduler quartz.thread-count=10 quartz.properties.org.quartz.jobStore.acquireTriggersWithinLock=true diff --git a/backend/services/case-management/src/test/resources/dml/init_file_metadata_test.sql b/backend/services/case-management/src/test/resources/dml/init_file_metadata_test.sql index ea4c8b58d6..7d5463e95d 100644 --- a/backend/services/case-management/src/test/resources/dml/init_file_metadata_test.sql +++ b/backend/services/case-management/src/test/resources/dml/init_file_metadata_test.sql @@ -1,6 +1,11 @@ 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) VALUES ('relate_file_meta_id_1', 'formItem', 'ts', 2502, 1698058347559, 1698058347559, '100001100001', 'MINIO', 'admin', 'admin', NULL, NULL, 'root', '100001100001/1127016598347779', b'1', '1127016598347779', '1127016598347779'); 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) VALUES ('relate_file_meta_id_2', 'formItem', 'ts', 2502, 1698058347559, 1698058347559, '100001100001', 'MINIO', 'admin', 'admin', NULL, NULL, 'root', '100001100001/1127016598347779', b'1', '1127016598347779', '1127016598347779'); +INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time) +VALUES ('TEST_FUNCTIONAL_CASE_ID', 1, 'TEST_MOUDLE_ID', '100001100001', '100001', '测试', 'UN_REVIEWED', NULL, 'STEP', 0, 'v1.0.0', 'v1.0.0', 'UN_EXECUTED', b'0', b'0', b'0', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL); +INSERT INTO functional_case_blob(id, steps, text_description, expected_result, prerequisite, description) VALUES ('TEST_FUNCTIONAL_CASE_ID', 'STEP', '1111', NULL, NULL, 'TEST'); + +INSERT INTO functional_case_custom_field(case_id, field_id, value) VALUES ('TEST_FUNCTIONAL_CASE_ID', '100548878725546079', '22'); diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/service/ProjectTemplateService.java b/backend/services/project-management/src/main/java/io/metersphere/project/service/ProjectTemplateService.java index be21fd5f3e..a21f759ec0 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/service/ProjectTemplateService.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/service/ProjectTemplateService.java @@ -31,7 +31,9 @@ import org.springframework.transaction.annotation.Transactional; import java.util.Comparator; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import static io.metersphere.project.enums.result.ProjectResultCode.PROJECT_TEMPLATE_PERMISSION; @@ -140,6 +142,26 @@ public class ProjectTemplateService extends BaseTemplateService { return getTemplateDTO(template); } + + /** + * 获取模板自定义字段 + * @param templateId + * @param projectId + * @param scene + * @return + */ + public TemplateDTO getTemplateDTOById(String templateId, String projectId, String scene) { + AtomicReference templateDTO = new AtomicReference<>(new TemplateDTO()); + Template template = templateMapper.selectByPrimaryKey(templateId); + Optional.ofNullable(template).ifPresentOrElse(item -> { + templateDTO.set(super.getTemplateDTO(template)); + }, () -> { + templateDTO.set(getDefaultTemplateDTO(projectId, scene)); + }); + return templateDTO.get(); + } + + /** * 获取内置模板 * @param projectId @@ -264,7 +286,6 @@ public class ProjectTemplateService extends BaseTemplateService { /** * 获取模板以及字段 - * !提供给其他模块调用 * @param id * @return */ diff --git a/backend/services/project-management/src/test/java/io/metersphere/project/controller/ProjectTemplateControllerTests.java b/backend/services/project-management/src/test/java/io/metersphere/project/controller/ProjectTemplateControllerTests.java index f63b725780..51e1491575 100644 --- a/backend/services/project-management/src/test/java/io/metersphere/project/controller/ProjectTemplateControllerTests.java +++ b/backend/services/project-management/src/test/java/io/metersphere/project/controller/ProjectTemplateControllerTests.java @@ -30,6 +30,8 @@ import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.*; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.web.servlet.MvcResult; import java.util.ArrayList; @@ -435,4 +437,12 @@ public class ProjectTemplateControllerTests extends BaseTest { } } } + + @Test + @Order(2) + @Sql(scripts = {"/dml/init_test_template.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) + public void getTemplateDTOById() throws Exception { + projectTemplateService.getTemplateDTOById("test_template_id", DEFAULT_PROJECT_ID, TemplateScene.FUNCTIONAL.name()); + projectTemplateService.getTemplateDTOById("test_template_id_1", "test_project_id_1", TemplateScene.FUNCTIONAL.name()); + } } \ No newline at end of file diff --git a/backend/services/project-management/src/test/resources/dml/init_test_template.sql b/backend/services/project-management/src/test/resources/dml/init_test_template.sql new file mode 100644 index 0000000000..9c7d46a2ec --- /dev/null +++ b/backend/services/project-management/src/test/resources/dml/init_test_template.sql @@ -0,0 +1,2 @@ +INSERT INTO template (id, name, remark, internal, update_time, create_time, create_user, scope_type, scope_id, enable_third_part, ref_id, scene) +VALUES ('test_template_id_1', 'functional_default', '', b'1', 1696992836000, 1696992836000, 'admin', 'ORGANIZATION', 'test_project_id_1', b'0', NULL, 'FUNCTIONAL'); \ No newline at end of file