feat(接口设置): 获取场景详情接口

This commit is contained in:
AgAngle 2024-01-24 16:03:15 +08:00 committed by 刘瑞斌
parent 21e3029e95
commit b5c20b102f
16 changed files with 374 additions and 82 deletions

View File

@ -111,10 +111,17 @@ public class ApiScenarioController {
apiScenarioService.deleteToGc(id);
}
@GetMapping("/get/{scenarioId}")
@Operation(summary = "接口测试-接口场景管理-获取场景详情")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_READ)
public ApiScenarioDetail get(@PathVariable String scenarioId) {
return apiScenarioService.get(scenarioId);
}
@PostMapping("/debug")
@Operation(summary = "接口测试-接口场景管理-场景调试")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_EXECUTE)
public String debug(@RequestBody ApiScenarioDebugRequest request) {
public String debug(@Validated @RequestBody ApiScenarioDebugRequest request) {
return apiScenarioService.debug(request);
}

View File

@ -3,6 +3,7 @@ package io.metersphere.api.dto.scenario;
import io.metersphere.api.constants.ApiScenarioStatus;
import io.metersphere.system.valid.EnumValue;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@ -57,6 +58,7 @@ public class ApiScenarioAddRequest {
private ScenarioConfig scenarioConfig;
@Schema(description = "步骤集合")
@Valid
private List<ApiScenarioStepRequest> steps;
/**

View File

@ -1,6 +1,7 @@
package io.metersphere.api.dto.scenario;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@ -29,6 +30,7 @@ public class ApiScenarioDebugRequest {
@Schema(description = "场景的通用配置")
private ScenarioConfig scenarioConfig;
@Valid
@Schema(description = "步骤集合")
private List<ApiScenarioStepRequest> steps;

View File

@ -5,9 +5,13 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false)
public class ApiScenarioDetail extends ApiScenario {
@Schema(description = "场景的通用配置")
private ScenarioConfig scenarioConfig;
@Schema(description = "步骤")
private List<ApiScenarioStepDTO> steps;
}

View File

@ -0,0 +1,57 @@
package io.metersphere.api.dto.scenario;
import io.metersphere.api.constants.ApiScenarioStepRefType;
import io.metersphere.api.constants.ApiScenarioStepType;
import io.metersphere.system.valid.EnumValue;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
/**
* 步骤解析使用的核心数据
* 用于步骤解析的统一处理
* @Author: jianxing
* @CreateTime: 2024-01-10 11:24
*/
@Data
public class ApiScenarioStepCommonDTO {
@Schema(description = "步骤id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_scenario_step.id.not_blank}")
@Size(max = 50, message = "{api_scenario_step.id.length_range}")
private String id;
@Schema(description = "启用/禁用")
private Boolean enable = true;
@Schema(description = "资源id")
private String resourceId;
/**
* @see ApiScenarioStepType
*/
@Schema(description = "步骤类型/API/CASE等")
@NotBlank
@EnumValue(enumClass = ApiScenarioStepType.class)
private String stepType;
/**
* 引用模式默认完全引用
* - 完全引用步骤状态不可调整
* - 部分引用步骤状态可调整
* @see ApiScenarioStepRefType
*/
@Schema(description = "引用/复制/自定义")
@EnumValue(enumClass = ApiScenarioStepRefType.class)
private String refType;
@Schema(description = "循环等组件基础数据")
private Object config;
@Valid
@Schema(description = "子步骤")
private List<? extends ApiScenarioStepCommonDTO> children;
}

View File

@ -0,0 +1,33 @@
package io.metersphere.api.dto.scenario;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2024-01-10 11:24
*/
@Data
public class ApiScenarioStepDTO extends ApiScenarioStepCommonDTO {
@Schema(description = "步骤名称")
private String name;
@Schema(description = "资源编号")
private String resourceNum;
@Schema(description = "项目fk")
private String projectId;
@Schema(description = "版本号")
private String versionId;
@Schema(description = "场景id")
private String scenarioId;
@Schema(description = "序号")
private Long sort;
@Schema(description = "父级fk")
private String parentId;
}

View File

@ -1,64 +1,25 @@
package io.metersphere.api.dto.scenario;
import io.metersphere.api.constants.ApiScenarioStepType;
import io.metersphere.system.valid.EnumValue;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* @Author: jianxing
* @CreateTime: 2024-01-10 11:24
*/
@Data
public class ApiScenarioStepRequest {
@Schema(description = "步骤id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_scenario_step.id.not_blank}")
@Size(max = 50, message = "{api_scenario_step.id.length_range}")
private String id;
public class ApiScenarioStepRequest extends ApiScenarioStepCommonDTO {
@Schema(description = "步骤名称")
@NotBlank
private String name;
@Schema(description = "启用/禁用")
private Boolean enable = true;
@Schema(description = "资源id")
private String resourceId;
@Schema(description = "资源编号")
private String resourceNum;
/**
* @see ApiScenarioStepType
*/
@Schema(description = "步骤类型/API/CASE等")
@NotBlank
@EnumValue(enumClass = ApiScenarioStepType.class)
private String stepType;
@Schema(description = "项目fk")
private String projectId;
@Schema(description = "版本号")
private String versionId;
/**
* 引用模式默认完全引用
* - 完全引用步骤状态不可调整
* - 部分引用步骤状态可调整
* @see io.metersphere.api.constants.ApiScenarioStepRefType
*/
@Schema(description = "引用/复制/自定义")
private String refType;
@Schema(description = "循环等组件基础数据")
private Map<Object, Object> config;
@Schema(description = "子步骤")
private List<ApiScenarioStepRequest> children;
}

View File

@ -5,6 +5,7 @@ import io.metersphere.system.valid.EnumValue;
import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@ -54,6 +55,7 @@ public class ApiScenarioUpdateRequest {
@Schema(description = "场景的通用配置")
private ScenarioConfig scenarioConfig;
@Valid
@Schema(description = "步骤集合")
private List<ApiScenarioStepRequest> steps;

View File

@ -1,5 +1,8 @@
package io.metersphere.api.mapper;
import io.metersphere.api.dto.scenario.ApiScenarioStepDTO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
@ -7,5 +10,7 @@ import java.util.List;
* @CreateTime: 2024-01-16 19:57
*/
public interface ExtApiScenarioStepMapper {
List<String> getStepIdsByScenarioId(String scenarioId);
List<String> getStepIdsByScenarioId(@Param("scenarioId") String scenarioId);
List<ApiScenarioStepDTO> getStepDTOByScenarioIds(@Param("scenarioIds") List<String> scenarioIds);
}

View File

@ -5,4 +5,12 @@
<select id="getStepIdsByScenarioId" resultType="java.lang.String">
SELECT id FROM api_scenario_step WHERE scenario_id = #{scenarioId}
</select>
<select id="getStepDTOByScenarioIds" resultType="io.metersphere.api.dto.scenario.ApiScenarioStepDTO">
select
<include refid="io.metersphere.api.mapper.ApiScenarioStepMapper.Base_Column_List"/>
from api_scenario_step where scenario_id in
<foreach collection="scenarioIds" item="scenarioId" open="(" separator="," close=")">
#{scenarioId}
</foreach>
</select>
</mapper>

View File

@ -1,6 +1,6 @@
package io.metersphere.api.parser.step;
import io.metersphere.api.dto.scenario.ApiScenarioStepRequest;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import org.apache.commons.lang3.StringUtils;
@ -11,7 +11,7 @@ import org.apache.commons.lang3.StringUtils;
*/
public class ApiCaseStepParser extends StepParser {
@Override
public AbstractMsTestElement parse(ApiScenarioStepRequest step, String resourceBlob, String stepDetail) {
public AbstractMsTestElement parse(ApiScenarioStepCommonDTO step, String resourceBlob, String stepDetail) {
if (isRef(step.getRefType())) {
return StringUtils.isBlank(resourceBlob) ? null : parse2MsTestElement(resourceBlob);
} else {

View File

@ -1,7 +1,7 @@
package io.metersphere.api.parser.step;
import io.metersphere.api.dto.request.MsScenario;
import io.metersphere.api.dto.scenario.ApiScenarioStepRequest;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.dto.scenario.ScenarioConfig;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.sdk.util.JSON;
@ -13,7 +13,7 @@ import org.apache.commons.lang3.StringUtils;
*/
public class ApiScenarioStepParser extends StepParser {
@Override
public AbstractMsTestElement parse(ApiScenarioStepRequest step, String resourceBlob, String stepDetail) {
public AbstractMsTestElement parse(ApiScenarioStepCommonDTO step, String resourceBlob, String stepDetail) {
MsScenario msScenario = new MsScenario();
if (isRef(step.getRefType())) {
if (StringUtils.isNotBlank(resourceBlob)) {

View File

@ -3,7 +3,7 @@ package io.metersphere.api.parser.step;
import io.metersphere.api.dto.request.http.KeyValueParam;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.dto.request.http.body.Body;
import io.metersphere.api.dto.scenario.ApiScenarioStepRequest;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import org.apache.commons.collections.CollectionUtils;
@ -17,7 +17,7 @@ import java.util.List;
*/
public class ApiStepParser extends StepParser {
@Override
public AbstractMsTestElement parse(ApiScenarioStepRequest step, String resourceBlob, String stepDetail) {
public AbstractMsTestElement parse(ApiScenarioStepCommonDTO step, String resourceBlob, String stepDetail) {
if (isRef(step.getRefType())) {
if (StringUtils.isBlank(resourceBlob)) {
return null;

View File

@ -1,6 +1,7 @@
package io.metersphere.api.parser.step;
import io.metersphere.api.constants.ApiScenarioStepRefType;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.dto.scenario.ApiScenarioStepRequest;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
@ -12,7 +13,7 @@ import org.apache.commons.lang3.StringUtils;
*/
public abstract class StepParser {
public abstract AbstractMsTestElement parse(ApiScenarioStepRequest step, String resourceBlob, String stepDetail);
public abstract AbstractMsTestElement parse(ApiScenarioStepCommonDTO step, String resourceBlob, String stepDetail);
protected boolean isRef(String refType) {
return StringUtils.equalsAny(refType, ApiScenarioStepRefType.REF.name(), ApiScenarioStepRefType.PARTIAL_REF.name());

View File

@ -479,7 +479,7 @@ public class ApiScenarioService {
if (step == null) {
return;
}
if (!isRef(step.getRefType()) || StringUtils.equals(step.getRefType(), ApiScenarioStepType.API.name())) {
if (!isRef(step.getRefType()) || isRefApi(step.getStepType(), step.getRefType())) {
// 非引用的步骤如果有编辑内容保存到blob表
// 如果引用的是接口定义也保存详情因为应用接口定义允许修改参数值
ApiScenarioStepBlob apiScenarioStepBlob = new ApiScenarioStepBlob();
@ -503,15 +503,14 @@ public class ApiScenarioService {
* @param steps
* @return
*/
private List<ApiScenarioStep> getApiScenarioSteps(ApiScenarioStepRequest parent,
List<ApiScenarioStepRequest> steps) {
private List<ApiScenarioStep> getApiScenarioSteps(ApiScenarioStepCommonDTO parent,
List<? extends ApiScenarioStepCommonDTO> steps) {
if (CollectionUtils.isEmpty(steps)) {
return Collections.emptyList();
}
List<ApiScenarioStep> apiScenarioSteps = new ArrayList<>();
long sort = 1;
for (ApiScenarioStepRequest step : steps) {
for (ApiScenarioStepCommonDTO step : steps) {
ApiScenarioStep apiScenarioStep = new ApiScenarioStep();
BeanUtils.copyBean(apiScenarioStep, step);
apiScenarioStep.setSort(sort++);
@ -540,13 +539,13 @@ public class ApiScenarioService {
* @param steps
* @return
*/
private List<ApiScenarioStepBlob> getPartialRefStepDetails(List<ApiScenarioStepRequest> steps) {
private List<ApiScenarioStepBlob> getPartialRefStepDetails(List<? extends ApiScenarioStepCommonDTO> steps) {
if (CollectionUtils.isEmpty(steps)) {
return Collections.emptyList();
}
List<ApiScenarioStepBlob> apiScenarioStepsDetails = new ArrayList<>();
for (ApiScenarioStepRequest step : steps) {
for (ApiScenarioStepCommonDTO step : steps) {
if (StringUtils.equals(step.getRefType(), ApiScenarioStepRefType.REF.name())) {
// 引用的步骤不解析子步骤
continue;
@ -572,12 +571,12 @@ public class ApiScenarioService {
* @param steps
* @return
*/
private Set<String> getEnableStepSet(List<ApiScenarioStepRequest> steps) {
private Set<String> getEnableStepSet(List<? extends ApiScenarioStepCommonDTO> steps) {
Set<String> enableSteps = new HashSet<>();
if (CollectionUtils.isEmpty(steps)) {
return Collections.emptySet();
}
for (ApiScenarioStepRequest step : steps) {
for (ApiScenarioStepCommonDTO step : steps) {
if (BooleanUtils.isTrue(step.getEnable())) {
enableSteps.add(step.getId());
}
@ -708,20 +707,19 @@ public class ApiScenarioService {
* @param stepDetailMap
*/
private void parseStep2MsElement(AbstractMsTestElement parentElement,
List<ApiScenarioStepRequest> steps,
List<? extends ApiScenarioStepCommonDTO> steps,
Map<String, String> resourceBlobMap,
Map<String, String> stepDetailMap) {
if (CollectionUtils.isNotEmpty(steps)) {
parentElement.setChildren(new LinkedList<>());
}
for (ApiScenarioStepRequest step : steps) {
for (ApiScenarioStepCommonDTO step : steps) {
StepParser stepParser = StepParserFactory.getStepParser(step.getStepType());
if (stepParser == null || BooleanUtils.isFalse(step.getEnable())) {
continue;
}
if (StringUtils.equals(step.getStepType(), ApiScenarioStepRefType.PARTIAL_REF.name())) {
setPartialRefStepEnable(step, stepDetailMap);
}
setPartialRefStepEnable(step, stepDetailMap);
// 将步骤详情解析生成对应的MsTestElement
AbstractMsTestElement msTestElement = stepParser.parse(step, resourceBlobMap.get(step.getResourceId()), stepDetailMap.get(step.getId()));
if (msTestElement != null) {
@ -734,18 +732,18 @@ public class ApiScenarioService {
}
/**
* 设置部分引用的步骤的启用状态
* 设置单个部分引用的步骤的启用状态
*
* @param step
* @param stepDetailMap
*/
private void setPartialRefStepEnable(ApiScenarioStepRequest step, Map<String, String> stepDetailMap) {
private void setPartialRefStepEnable(ApiScenarioStepCommonDTO step, Map<String, String> stepDetailMap) {
String stepDetail = stepDetailMap.get(step.getId());
if (StringUtils.isBlank(stepDetail)) {
return;
if (!isPartialRef(step) || StringUtils.isBlank(stepDetail)) {
return;
}
PartialRefStepDetail partialRefStepDetail = JSON.parseObject(stepDetail, PartialRefStepDetail.class);
setChildPartialRefEnable(step.getChildren(), partialRefStepDetail.getEnableStepIds(), stepDetailMap);
setChildPartialRefEnable(step.getChildren(), partialRefStepDetail.getEnableStepIds());
}
/**
@ -753,28 +751,31 @@ public class ApiScenarioService {
*
* @param steps
* @param enableStepIds
* @param stepDetailMap
*/
private void setChildPartialRefEnable(List<ApiScenarioStepRequest> steps, Set<String> enableStepIds, Map<String, String> stepDetailMap) {
for (ApiScenarioStepRequest step : steps) {
private void setChildPartialRefEnable(List<? extends ApiScenarioStepCommonDTO> steps, Set<String> enableStepIds) {
for (ApiScenarioStepCommonDTO step : steps) {
if (StringUtils.equals(step.getRefType(), ApiScenarioStepRefType.REF.name())) {
// 引用的启用不修改
continue;
}
// 非完全引用的步骤使用当前场景配置的启用状态
step.setEnable(enableStepIds.contains(step.getId()));
if (StringUtils.equals(step.getRefType(), ApiScenarioStepRefType.PARTIAL_REF.name())) {
// 如果是部分引用的场景重新获取详情再解析
setPartialRefStepEnable(step, stepDetailMap);
if (isPartialRef(step)) {
// 如果是部分引用的场景不递归解析了上层的递归会解析
continue;
}
// 非完全引用和部分引用的步骤递归设置子步骤
if (CollectionUtils.isNotEmpty(step.getChildren())) {
setChildPartialRefEnable(step.getChildren(), enableStepIds, stepDetailMap);
setChildPartialRefEnable(step.getChildren(), enableStepIds);
}
}
}
private boolean isPartialRef(ApiScenarioStepCommonDTO step) {
return StringUtils.equals(step.getStepType(), ApiScenarioStepType.API_SCENARIO.name()) &&
StringUtils.equals(step.getRefType(), ApiScenarioStepRefType.PARTIAL_REF.name());
}
private Map<String, String> getStepDetailMap(List<ApiScenarioStepRequest> steps, Map<String, Object> stepDetailsParam) {
List<String> needBlobStepIds = new ArrayList<>();
for (ApiScenarioStepRequest step : steps) {
@ -836,8 +837,8 @@ public class ApiScenarioService {
return apiScenarioBlobMapper.selectByExampleWithBLOBs(example);
}
private void buildRefResourceIdMap(List<ApiScenarioStepRequest> steps, Map<String, List<String>> refResourceIdMap) {
for (ApiScenarioStepRequest step : steps) {
private void buildRefResourceIdMap(List<? extends ApiScenarioStepCommonDTO> steps, Map<String, List<String>> refResourceIdMap) {
for (ApiScenarioStepCommonDTO step : steps) {
if (isRef(step.getRefType()) && BooleanUtils.isTrue(step.getEnable())) {
// 记录引用的步骤ID
List<String> resourceIds = refResourceIdMap.get(step.getStepType());
@ -888,4 +889,165 @@ public class ApiScenarioService {
update.setUpdateTime(System.currentTimeMillis());
apiScenarioMapper.updateByPrimaryKeySelective(update);
}
public ApiScenarioDetail get(String scenarioId) {
checkResourceExist(scenarioId);
ApiScenario apiScenario = apiScenarioMapper.selectByPrimaryKey(scenarioId);
ApiScenarioDetail apiScenarioDetail = BeanUtils.copyBean(new ApiScenarioDetail(), apiScenario);
ApiScenarioBlob apiScenarioBlob = apiScenarioBlobMapper.selectByPrimaryKey(scenarioId);
if (apiScenarioBlob != null) {
apiScenarioDetail.setScenarioConfig(JSON.parseObject(new String(apiScenarioBlob.getConfig()), ScenarioConfig.class));
}
// 获取所有步骤
List<ApiScenarioStepDTO> allSteps = getAllStepsByScenarioIds(List.of(scenarioId));
// 构造 mapkey 为场景IDvalue 为步骤列表
Map<String, List<ApiScenarioStepDTO>> scenarioStepMap = allSteps.stream()
.collect(Collectors.groupingBy(step -> Optional.ofNullable(step.getScenarioId()).orElse(StringUtils.EMPTY)));
// 查询部分引用的步骤详情和接口定义的步骤详情
Map<String, String> stepDetailMap = getStepDetailMap(allSteps);
// key 为父步骤IDvalue 为子步骤列表
Map<String, List<ApiScenarioStepDTO>> parentStepMap = scenarioStepMap.get(scenarioId)
.stream()
.collect(Collectors.groupingBy(step -> Optional.ofNullable(step.getParentId()).orElse(StringUtils.EMPTY)));
List<ApiScenarioStepDTO> steps = buildStepTree(parentStepMap.get(StringUtils.EMPTY), scenarioStepMap, scenarioStepMap);
// 设置部分引用的步骤的启用状态
setPartialRefStepsEnable(steps, stepDetailMap);
apiScenarioDetail.setSteps(steps);
return apiScenarioDetail;
}
/**
* 设置部分引用的步骤的启用状态
*
* @param steps
* @param stepDetailMap
*/
private void setPartialRefStepsEnable(List<? extends ApiScenarioStepCommonDTO> steps, Map<String, String> stepDetailMap) {
if (CollectionUtils.isNotEmpty(steps)) {
return;
}
for (ApiScenarioStepCommonDTO step : steps) {
setPartialRefStepEnable(step, stepDetailMap);
if (CollectionUtils.isNotEmpty(step.getChildren())) {
setPartialRefStepsEnable(step.getChildren(), stepDetailMap);
}
}
}
/**
* 查询部分引用的步骤详情 接口定义的步骤详情
*
* @param allSteps
*/
private Map<String, String> getStepDetailMap(List<ApiScenarioStepDTO> allSteps) {
List<String> stepDetailIds = allSteps.stream().filter(step -> isRefApi(step.getStepType(), step.getRefType())
|| StringUtils.equals(step.getRefType(), ApiScenarioStepRefType.PARTIAL_REF.name()))
.map(ApiScenarioStepDTO::getId)
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(stepDetailIds)) {
return new HashMap<>(0);
}
ApiScenarioBlobExample example = new ApiScenarioBlobExample();
example.createCriteria().andIdIn(stepDetailIds);
return apiScenarioBlobMapper.selectByExample(example).stream()
.collect(Collectors.toMap(ApiScenarioBlob::getId, blob -> new String(blob.getConfig())));
}
/**
* 判断步骤是否是引用的接口定义
* 引用的接口定义允许修改参数值需要特殊处理
*
* @param stepType
* @param refType
* @return
*/
private boolean isRefApi(String stepType, String refType) {
return StringUtils.equals(stepType, ApiScenarioStepType.API.name()) && StringUtils.equals(refType, ApiScenarioStepRefType.REF.name());
}
/**
* 递归构造步骤树
*
* @param steps 当前场景下当前层级的步骤
* @param parentStepMap 当前场景所有的步骤key 为父步骤IDvalue 为子步骤列表
* @param scenarioStepMap 所有场景步骤key 为场景IDvalue 为子步骤列表
*/
private List<ApiScenarioStepDTO> buildStepTree(List<ApiScenarioStepDTO> steps,
Map<String, List<ApiScenarioStepDTO>> parentStepMap,
Map<String, List<ApiScenarioStepDTO>> scenarioStepMap) {
if (CollectionUtils.isEmpty(steps)) {
return Collections.emptyList();
}
steps.forEach(step -> {
// 获取当前步骤的子步骤
List<ApiScenarioStepDTO> children = parentStepMap.get(step.getId());
if (CollectionUtils.isEmpty(children)) {
return;
}
if (isRefApiScenario(step)) {
// 如果当前步骤是引用的场景获取该场景的子步骤
Map<String, List<ApiScenarioStepDTO>> childStepMap = scenarioStepMap.get(step.getResourceId())
.stream()
.collect(Collectors.groupingBy(ApiScenarioStepDTO::getParentId));
step.setChildren(buildStepTree(children, childStepMap, scenarioStepMap));
} else {
step.setChildren(buildStepTree(children, parentStepMap, scenarioStepMap));
}
});
// 排序
return steps.stream()
.sorted(Comparator.comparing(ApiScenarioStepDTO::getSort))
.collect(Collectors.toList());
}
/**
* 判断步骤是否是引用的场景
* @param step
* @return
*/
private boolean isRefApiScenario(ApiScenarioStepDTO step) {
return isRef(step.getRefType()) && StringUtils.equals(step.getStepType(), ApiScenarioStepType.API_SCENARIO.name());
}
/**
* 递归获取所有的场景步骤
*
* @param scenarioIds
* @return
*/
private List<ApiScenarioStepDTO> getAllStepsByScenarioIds(List<String> scenarioIds) {
List<ApiScenarioStepDTO> steps = getStepDTOByScenarioIds(scenarioIds);
if (CollectionUtils.isEmpty(steps)) {
return steps;
}
// config 转换成对象
steps.stream().forEach(step -> {
if (step.getConfig() != null && StringUtils.isNotBlank(step.getConfig().toString())) {
step.setConfig(JSON.parseObject(step.getConfig().toString()));
}
});
// 获取步骤中引用的场景ID
List<String> childScenarioIds = steps.stream()
.filter(step -> isRefApiScenario(step))
.map(ApiScenarioStepDTO::getResourceId)
.collect(Collectors.toList());
// 嵌套获取引用的场景步骤
steps.addAll(getAllStepsByScenarioIds(childScenarioIds));
return steps;
}
private List<ApiScenarioStepDTO> getStepDTOByScenarioIds(List<String> scenarioIds) {
if (CollectionUtils.isEmpty(scenarioIds)) {
return Collections.emptyList();
}
return extApiScenarioStepMapper.getStepDTOByScenarioIds(scenarioIds);
}
}

View File

@ -94,7 +94,9 @@ public class ApiScenarioControllerTests extends BaseTest {
@Resource
private BaseResourcePoolTestService baseResourcePoolTestService;
private static ApiScenario addApiScenario;
private static List<ApiScenarioStepRequest> addApiScenarioSteps;
private static ApiScenario anOtherAddApiScenario;
private static List<ApiScenarioStepRequest> anOtherAddApiScenarioSteps;
private static ApiDefinition apiDefinition;
private static ApiTestCase apiTestCase;
@ -229,6 +231,7 @@ public class ApiScenarioControllerTests extends BaseTest {
stepRequest.setName(addApiScenario.getName());
stepRequest.setResourceId(addApiScenario.getId());
stepRequest.setRefType(ApiScenarioStepRefType.REF.name());
stepRequest.setStepType(ApiScenarioStepType.API_SCENARIO.name());
ApiScenarioStepRequest stepRequest2 = new ApiScenarioStepRequest();
stepRequest2.setId(IDGenerator.nextStr());
@ -236,12 +239,14 @@ public class ApiScenarioControllerTests extends BaseTest {
stepRequest2.setResourceId(addApiScenario.getId());
stepRequest2.setRefType(ApiScenarioStepRefType.PARTIAL_REF.name());
stepRequest2.setChildren(List.of(stepRequest));
stepRequest2.setStepType(ApiScenarioStepType.API_SCENARIO.name());
steps = List.of(stepRequest, stepRequest2);
request.setSteps(steps);
mvcResult = this.requestPostWithOkAndReturn(DEFAULT_ADD, request);
this.anOtherAddApiScenario = apiScenarioMapper.selectByPrimaryKey(getResultData(mvcResult, ApiScenario.class).getId());
assertUpdateApiScenario(request, request.getScenarioConfig(), anOtherAddApiScenario.getId());
assertUpdateSteps(steps, steptDetailMap);
anOtherAddApiScenarioSteps = steps;
// @@重名校验异常
assertErrorCode(this.requestPost(DEFAULT_ADD, request), API_SCENARIO_EXIST);
@ -314,6 +319,8 @@ public class ApiScenarioControllerTests extends BaseTest {
stepRequest.setName(apiTestCase.getName());
stepRequest.setResourceId(apiTestCase.getId());
stepRequest.setRefType(ApiScenarioStepRefType.REF.name());
stepRequest.setStepType(ApiScenarioStepType.API_CASE.name());
stepRequest.setProjectId(DEFAULT_PROJECT_ID);
stepRequest.setConfig(new HashMap<>());
ApiScenarioStepRequest stepRequest2 = new ApiScenarioStepRequest();
@ -322,17 +329,22 @@ public class ApiScenarioControllerTests extends BaseTest {
stepRequest2.setConfig(new HashMap<>());
stepRequest2.setEnable(true);
stepRequest2.setResourceId(apiTestCase.getId());
stepRequest.setName(apiTestCase.getName() + "2");
stepRequest2.setName(apiTestCase.getName() + "2");
stepRequest2.setStepType(ApiScenarioStepType.API_CASE.name());
stepRequest2.setRefType(ApiScenarioStepRefType.COPY.name());
stepRequest2.setProjectId(DEFAULT_PROJECT_ID);
ApiScenarioStepRequest stepRequest3 = new ApiScenarioStepRequest();
stepRequest3.setId(IDGenerator.nextStr());
stepRequest3.setVersionId(extBaseProjectVersionMapper.getDefaultVersion(DEFAULT_PROJECT_ID));
stepRequest3.setConfig(new HashMap<>());
stepRequest3.setEnable(true);
stepRequest3.setStepType(ApiScenarioStepType.API.name());
stepRequest3.setResourceId(apiDefinition.getId());
stepRequest.setName(apiDefinition.getName() + "3");
stepRequest3.setName(apiDefinition.getName() + "3");
stepRequest3.setRefType(ApiScenarioStepRefType.REF.name());
stepRequest3.setProjectId(DEFAULT_PROJECT_ID);
return new ArrayList<>() {{
add(stepRequest);
add(stepRequest2);
@ -409,6 +421,7 @@ public class ApiScenarioControllerTests extends BaseTest {
steps.get(0).setName("test name update");
this.requestPostWithOk(DEFAULT_UPDATE, request);
assertUpdateSteps(steps, steptDetailMap);
addApiScenarioSteps = steps;
// @@重名校验异常
request.setName(anOtherAddApiScenario.getName());
@ -475,6 +488,37 @@ public class ApiScenarioControllerTests extends BaseTest {
requestPostPermissionTest(PermissionConstants.PROJECT_API_SCENARIO_EXECUTE, DEBUG, request);
}
@Test
@Order(7)
public void get() throws Exception {
MvcResult mvcResult = this.requestGetWithOkAndReturn(DEFAULT_GET, addApiScenario.getId());
ApiScenarioDetail apiScenarioDetail = getResultData(mvcResult, ApiScenarioDetail.class);
// 验证数据
asserGetApiScenarioSteps(this.addApiScenarioSteps, apiScenarioDetail.getSteps());
mvcResult = this.requestGetWithOkAndReturn(DEFAULT_GET, anOtherAddApiScenario.getId());
apiScenarioDetail = getResultData(mvcResult, ApiScenarioDetail.class);
// 验证数据
Assertions.assertEquals(this.anOtherAddApiScenarioSteps.size(), apiScenarioDetail.getSteps().size());
// @@校验权限
requestGetPermissionTest(PermissionConstants.PROJECT_API_SCENARIO_READ, DEFAULT_GET, addApiScenario.getId());
}
private void asserGetApiScenarioSteps(List<? extends ApiScenarioStepCommonDTO> addApiScenarioSteps, List<? extends ApiScenarioStepCommonDTO> steps) {
if (addApiScenarioSteps == null || steps == null) {
Assertions.assertEquals(addApiScenarioSteps, null);
Assertions.assertEquals(steps, null);
return;
}
Assertions.assertEquals(addApiScenarioSteps.size(), steps.size());
for (int i = 0; i < addApiScenarioSteps.size(); i++) {
ApiScenarioStepRequest stepRequest = (ApiScenarioStepRequest) addApiScenarioSteps.get(i);
ApiScenarioStepDTO stepDTO = (ApiScenarioStepDTO) steps.get(i);
Assertions.assertEquals(BeanUtils.copyBean(new ApiScenarioStepCommonDTO(), stepRequest), BeanUtils.copyBean(new ApiScenarioStepCommonDTO(), stepDTO));
asserGetApiScenarioSteps(stepRequest.getChildren(), stepDTO.getChildren());
}
}
private String doUploadTempFile(MockMultipartFile file) throws Exception {
return JSON.parseObject(requestUploadFileWithOkAndReturn(UPLOAD_TEMP_FILE, file)
.getResponse()
@ -497,7 +541,8 @@ public class ApiScenarioControllerTests extends BaseTest {
public void deleteToGc() throws Exception {
// @@请求成功
this.requestGetWithOk(DELETE_TO_GC, addApiScenario.getId());
// todo 校验请求成功数据
ApiScenario apiScenario = apiScenarioMapper.selectByPrimaryKey(addApiScenario.getId());
Assertions.assertTrue(apiScenario.getDeleted());
// @@校验日志
checkLog(addApiScenario.getId(), OperationLogType.DELETE);
// @@校验权限
@ -509,7 +554,10 @@ public class ApiScenarioControllerTests extends BaseTest {
public void delete() throws Exception {
// @@请求成功
this.requestGetWithOk(DEFAULT_DELETE, addApiScenario.getId());
// todo 校验请求成功数据
ApiScenario apiScenario = apiScenarioMapper.selectByPrimaryKey(addApiScenario.getId());
ApiScenarioBlob apiScenarioBlob = apiScenarioBlobMapper.selectByPrimaryKey(addApiScenario.getId());
Assertions.assertNull(apiScenario);
Assertions.assertNull(apiScenarioBlob);
// @@校验日志
checkLog(addApiScenario.getId(), OperationLogType.DELETE);
// @@校验权限