feat(功能用例): 用例详情接口&模板自定义字段接口提供

This commit is contained in:
WangXu10 2023-10-26 14:49:33 +08:00 committed by Craftsman
parent 0065b4c28c
commit cd31a793be
13 changed files with 276 additions and 12 deletions

View File

@ -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";

View File

@ -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);
}
}

View File

@ -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<TemplateCustomFieldDTO> customFields;
}

View File

@ -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);
}
}

View File

@ -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<FunctionalCaseCustomField> functionalCaseCustomFields = functionalCaseCustomFieldMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(functionalCaseCustomFields)) {
return functionalCaseCustomFields.get(0);
}
return null;
}
}

View File

@ -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<MultipartFile> 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<TemplateCustomFieldDTO> 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;
}
/**
* 新增用例 日志
*

View File

@ -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) {

View File

@ -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<String, Object> paramMap = new LinkedMultiValueMap<>();
List<MockMultipartFile> 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<CaseCustomsFieldDTO> creatCustomsFields() {

View File

@ -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

View File

@ -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');

View File

@ -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> 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
*/

View File

@ -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());
}
}

View File

@ -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');