refactor(接口测试): 处理场景复制接口和场景步骤没有详情的情况

This commit is contained in:
AgAngle 2024-03-25 15:04:33 +08:00 committed by Craftsman
parent 78ee0c9cda
commit 3d78e72bc5
2 changed files with 189 additions and 51 deletions

View File

@ -14,4 +14,8 @@ public class ApiScenarioStepRequest extends ApiScenarioStepCommonDTO {
* 保存时需要根据这个字段查询原步骤详情保存 * 保存时需要根据这个字段查询原步骤详情保存
*/ */
private String copyFromStepId; private String copyFromStepId;
/**
* 记录当前步骤是否是新增的步骤
*/
private Boolean isNew;
} }

View File

@ -95,6 +95,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -125,8 +126,6 @@ public class ApiScenarioService extends MoveNodeService {
@Resource @Resource
private SqlSessionFactory sqlSessionFactory; private SqlSessionFactory sqlSessionFactory;
@Resource @Resource
private ProjectService projectService;
@Resource
private ExtBaseProjectVersionMapper extBaseProjectVersionMapper; private ExtBaseProjectVersionMapper extBaseProjectVersionMapper;
@Resource @Resource
private ApiFileResourceService apiFileResourceService; private ApiFileResourceService apiFileResourceService;
@ -418,16 +417,17 @@ public class ApiScenarioService extends MoveNodeService {
List<ApiScenarioCsvStep> csvSteps = new ArrayList<>(); List<ApiScenarioCsvStep> csvSteps = new ArrayList<>();
List<ApiScenarioStep> steps = getApiScenarioSteps(null, request.getSteps(), csvSteps); List<ApiScenarioStep> steps = getApiScenarioSteps(null, request.getSteps(), csvSteps);
steps.forEach(step -> step.setScenarioId(scenario.getId())); steps.forEach(step -> step.setScenarioId(scenario.getId()));
// 获取待添加的步骤详情 // 处理特殊的步骤详情
List<ApiScenarioStepBlob> apiScenarioStepsDetails = getPartialRefStepDetails(request.getSteps()); addSpecialStepDetails(request.getSteps(), request.getStepDetails());
apiScenarioStepsDetails.addAll(getUpdateStepDetails(steps, request.getStepDetails())); List<ApiScenarioStepBlob> apiScenarioStepBlobs = getUpdateStepBlobs(steps, request.getStepDetails());
apiScenarioStepsDetails.forEach(step -> step.setScenarioId(scenario.getId())); apiScenarioStepBlobs.forEach(step -> step.setScenarioId(scenario.getId()));
if (CollectionUtils.isNotEmpty(steps)) { if (CollectionUtils.isNotEmpty(steps)) {
apiScenarioStepMapper.batchInsert(steps); apiScenarioStepMapper.batchInsert(steps);
} }
if (CollectionUtils.isNotEmpty(apiScenarioStepsDetails)) {
apiScenarioStepBlobMapper.batchInsert(apiScenarioStepsDetails); if (CollectionUtils.isNotEmpty(apiScenarioStepBlobs)) {
apiScenarioStepBlobMapper.batchInsert(apiScenarioStepBlobs);
} }
saveStepCsv(steps, csvSteps); saveStepCsv(steps, csvSteps);
@ -678,9 +678,9 @@ public class ApiScenarioService extends MoveNodeService {
saveStepCsv(apiScenarioSteps, scenarioCsvSteps); saveStepCsv(apiScenarioSteps, scenarioCsvSteps);
// 获取待更新的步骤详情 // 获取待更新的步骤详情
List<ApiScenarioStepBlob> apiScenarioStepsDetails = getPartialRefStepDetails(request.getSteps()); addSpecialStepDetails(request.getSteps(), request.getStepDetails());
apiScenarioStepsDetails.addAll(getUpdateStepDetails(apiScenarioSteps, request.getStepDetails())); List<ApiScenarioStepBlob> updateStepBlobs = getUpdateStepBlobs(apiScenarioSteps, request.getStepDetails());
apiScenarioStepsDetails.forEach(step -> step.setScenarioId(scenario.getId())); updateStepBlobs.forEach(step -> step.setScenarioId(scenario.getId()));
List<String> stepIds = apiScenarioSteps.stream().map(ApiScenarioStep::getId).collect(Collectors.toList()); List<String> stepIds = apiScenarioSteps.stream().map(ApiScenarioStep::getId).collect(Collectors.toList());
List<String> originStepIds = extApiScenarioStepMapper.getStepIdsByScenarioId(scenario.getId()); List<String> originStepIds = extApiScenarioStepMapper.getStepIdsByScenarioId(scenario.getId());
@ -701,14 +701,14 @@ public class ApiScenarioService extends MoveNodeService {
Set<String> originStepDetailIds = new HashSet<>(extApiScenarioStepBlobMapper.getStepIdsByScenarioId(scenario.getId())); Set<String> originStepDetailIds = new HashSet<>(extApiScenarioStepBlobMapper.getStepIdsByScenarioId(scenario.getId()));
// 添加新增的步骤详情 // 添加新增的步骤详情
List<ApiScenarioStepBlob> addApiScenarioStepsDetails = apiScenarioStepsDetails.stream() List<ApiScenarioStepBlob> addApiScenarioStepsDetails = updateStepBlobs.stream()
.filter(step -> !originStepDetailIds.contains(step.getId())) .filter(step -> !originStepDetailIds.contains(step.getId()))
.collect(Collectors.toList()); .collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(addApiScenarioStepsDetails)) { if (CollectionUtils.isNotEmpty(addApiScenarioStepsDetails)) {
apiScenarioStepBlobMapper.batchInsert(addApiScenarioStepsDetails); apiScenarioStepBlobMapper.batchInsert(addApiScenarioStepsDetails);
} }
// 更新原有的步骤详情 // 更新原有的步骤详情
apiScenarioStepsDetails.stream() updateStepBlobs.stream()
.filter(step -> originStepDetailIds.contains(step.getId())) .filter(step -> originStepDetailIds.contains(step.getId()))
.forEach(apiScenarioStepBlobMapper::updateByPrimaryKeySelective); .forEach(apiScenarioStepBlobMapper::updateByPrimaryKeySelective);
} else if (MapUtils.isNotEmpty(request.getStepDetails())) { } else if (MapUtils.isNotEmpty(request.getStepDetails())) {
@ -718,15 +718,20 @@ public class ApiScenarioService extends MoveNodeService {
// 更新原有的步骤详情 // 更新原有的步骤详情
request.getStepDetails().forEach((stepId, stepDetail) -> { request.getStepDetails().forEach((stepId, stepDetail) -> {
if (originStepDetailIds.contains(stepId)) { if (originStepDetailIds.contains(stepId)) {
ApiScenarioStepBlob apiScenarioStepBlob = new ApiScenarioStepBlob(); ApiScenarioStepBlob apiScenarioStepBlob = getApiScenarioStepBlob(stepId, stepDetail);
apiScenarioStepBlob.setId(stepId);
apiScenarioStepBlob.setContent(JSON.toJSONString(stepDetail).getBytes());
apiScenarioStepBlobMapper.updateByPrimaryKeySelective(apiScenarioStepBlob); apiScenarioStepBlobMapper.updateByPrimaryKeySelective(apiScenarioStepBlob);
} }
}); });
} }
} }
private ApiScenarioStepBlob getApiScenarioStepBlob(String stepId, Object stepDetail) {
ApiScenarioStepBlob apiScenarioStepBlob = new ApiScenarioStepBlob();
apiScenarioStepBlob.setId(stepId);
apiScenarioStepBlob.setContent(JSON.toJSONString(stepDetail).getBytes());
return apiScenarioStepBlob;
}
private void deleteStepDetailByScenarioId(String scenarioId) { private void deleteStepDetailByScenarioId(String scenarioId) {
ApiScenarioStepBlobExample blobExample = new ApiScenarioStepBlobExample(); ApiScenarioStepBlobExample blobExample = new ApiScenarioStepBlobExample();
blobExample.createCriteria().andScenarioIdEqualTo(scenarioId); blobExample.createCriteria().andScenarioIdEqualTo(scenarioId);
@ -742,7 +747,7 @@ public class ApiScenarioService extends MoveNodeService {
/** /**
* 获取待更新的 ApiScenarioStepBlob 列表 * 获取待更新的 ApiScenarioStepBlob 列表
*/ */
private List<ApiScenarioStepBlob> getUpdateStepDetails(List<ApiScenarioStep> apiScenarioSteps, Map<String, Object> stepDetails) { private List<ApiScenarioStepBlob> getUpdateStepBlobs(List<ApiScenarioStep> apiScenarioSteps, Map<String, Object> stepDetails) {
if (MapUtils.isEmpty(stepDetails)) { if (MapUtils.isEmpty(stepDetails)) {
return Collections.emptyList(); return Collections.emptyList();
} }
@ -750,26 +755,54 @@ public class ApiScenarioService extends MoveNodeService {
Map<String, ApiScenarioStep> scenarioStepMap = apiScenarioSteps.stream() Map<String, ApiScenarioStep> scenarioStepMap = apiScenarioSteps.stream()
.collect(Collectors.toMap(ApiScenarioStep::getId, Function.identity())); .collect(Collectors.toMap(ApiScenarioStep::getId, Function.identity()));
List<ApiScenarioStepBlob> apiScenarioStepsDetails = new ArrayList<>(); List<ApiScenarioStepBlob> apiScenarioStepBlobs = new ArrayList<>();
stepDetails.forEach((stepId, stepDetail) -> { stepDetails.forEach((stepId, stepDetail) -> {
ApiScenarioStep step = scenarioStepMap.get(stepId); ApiScenarioStep step = scenarioStepMap.get(stepId);
if (step == null) { if (step == null) {
return; return;
} }
if (!isRef(step.getRefType()) || isRefApi(step.getStepType(), step.getRefType())) { if (hasDetail(step.getStepType(), step.getRefType())) {
// 非引用的步骤如果有编辑内容保存到blob表 // 非引用的步骤如果有编辑内容保存到blob表
// 如果引用的是接口定义也保存详情因为应用接口定义允许修改参数 // 如果引用的是接口定义也保存详情因为应用接口定义允许修改参数
ApiScenarioStepBlob apiScenarioStepBlob = new ApiScenarioStepBlob(); ApiScenarioStepBlob apiScenarioStepBlob = new ApiScenarioStepBlob();
apiScenarioStepBlob.setId(stepId); apiScenarioStepBlob.setId(stepId);
apiScenarioStepBlob.setContent(JSON.toJSONString(stepDetail).getBytes()); if (stepDetail instanceof byte[] detailBytes) {
apiScenarioStepsDetails.add(apiScenarioStepBlob); apiScenarioStepBlob.setContent(detailBytes);
} else {
apiScenarioStepBlob.setContent(JSON.toJSONString(stepDetail).getBytes());
}
apiScenarioStepBlobs.add(apiScenarioStepBlob);
} }
}); });
return apiScenarioStepsDetails; return apiScenarioStepBlobs;
}
private boolean isPartialRef(String refType) {
return StringUtils.equals(refType, ApiScenarioStepRefType.PARTIAL_REF.name());
}
private boolean isRefOrPartialRef(String refType) {
return StringUtils.equalsAny(refType, ApiScenarioStepRefType.REF.name(), ApiScenarioStepRefType.PARTIAL_REF.name());
} }
private boolean isRef(String refType) { private boolean isRef(String refType) {
return StringUtils.equalsAny(refType, ApiScenarioStepRefType.REF.name(), ApiScenarioStepRefType.PARTIAL_REF.name()); return StringUtils.equals(refType, ApiScenarioStepRefType.REF.name());
}
/**
* 是否有步骤详情
* 非完全引用的步骤
*
* @param stepType
* @param refType
* @return
*/
private boolean hasDetail(String stepType, String refType) {
return !isRef(refType) || isRefApi(stepType, refType);
}
private boolean hasDetail(ApiScenarioStepCommonDTO step) {
return hasDetail(step.getStepType(), step.getRefType());
} }
/** /**
@ -818,33 +851,134 @@ public class ApiScenarioService extends MoveNodeService {
} }
/** /**
* 解析步骤树结构 * 补充
* 获取待更新的 ApiScenarioStep 列表 * 部分引用的 detail
* copyFromStepId detail
* isNew 的资源的 detail
*/ */
private List<ApiScenarioStepBlob> getPartialRefStepDetails(List<? extends ApiScenarioStepCommonDTO> steps) { private void addSpecialStepDetails(List<ApiScenarioStepRequest> steps, Map<String, Object> stepDetails) {
if (CollectionUtils.isEmpty(steps)) { if (CollectionUtils.isEmpty(steps)) {
return Collections.emptyList(); return;
} }
List<ApiScenarioStepBlob> apiScenarioStepsDetails = new ArrayList<>();
for (ApiScenarioStepCommonDTO step : steps) { // key stepIdvalue copyFrom 的步骤ID
if (StringUtils.equals(step.getRefType(), ApiScenarioStepRefType.REF.name())) { Map<String, String> copyFromStepIdMap = new HashMap<>();
// 引用的步骤不解析子步骤 // key stepIdvalue copyFrom 的接口ID
continue; Map<String, String> isNewApiResourceMap = new HashMap<>();
// key stepIdvalue copyFrom 的接口用例ID
Map<String, String> isNewApiCaseResourceMap = new HashMap<>();
// 遍历步骤树
traversalStepTree(steps, (step) -> {
ApiScenarioStepRequest stepRequest = (ApiScenarioStepRequest) step;
// 补充部分引用的 detail
addPartialRefStepDetail(stepDetails, step);
// 如果该步骤没有步骤详情
if (stepDetails.get(stepRequest.getId()) == null && hasDetail(step)) {
if (isApiOrCase(step) && BooleanUtils.isTrue(((ApiScenarioStepRequest) step).getIsNew())) {
// 处理 isNew 的用例步骤
if (isApi(step.getStepType())) {
isNewApiResourceMap.put(step.getId(), step.getResourceId());
} else {
isNewApiCaseResourceMap.put(step.getId(), step.getResourceId());
}
} else if (StringUtils.isNotBlank(stepRequest.getCopyFromStepId())) {
// 处理 copyFromStep 的情况
if (stepDetails.containsKey(stepRequest.getCopyFromStepId())) {
// 如果有传 copyFromStepId 的详情优先使用
stepDetails.put(step.getId(), stepDetails.get(stepRequest.getCopyFromStepId()));
} else {
// 没有传则记录ID后续统一查库
copyFromStepIdMap.put(stepRequest.getId(), stepRequest.getCopyFromStepId());
}
}
} }
if (StringUtils.equals(step.getRefType(), ApiScenarioStepRefType.PARTIAL_REF.name())) {
// 如果是部分引用blob表保存启用的子步骤ID if (isRefOrPartialRef(step.getRefType())) {
Set<String> enableStepSet = getEnableStepSet(step.getChildren()); // 引用或者部分引用类型不解析子步骤
PartialRefStepDetail stepDetail = new PartialRefStepDetail(); return false;
stepDetail.setEnableStepIds(enableStepSet); }
ApiScenarioStepBlob apiScenarioStepBlob = new ApiScenarioStepBlob(); return true;
apiScenarioStepBlob.setId(step.getId()); });
apiScenarioStepBlob.setContent(JSON.toJSONString(stepDetail).getBytes());
apiScenarioStepsDetails.add(apiScenarioStepBlob); // 处理新copy的接口定义的步骤详情
putCopyStepDetails(stepDetails, isNewApiResourceMap, (subIds, copyFromBlobMap) -> {
apiDefinitionService.getBlobByIds(subIds)
.forEach(apiDefinitionBlob -> copyFromBlobMap.put(apiDefinitionBlob.getId(), apiDefinitionBlob.getRequest()));
});
// 处理新copy的接口用例的步骤详情
putCopyStepDetails(stepDetails, isNewApiCaseResourceMap, (subIds, copyFromBlobMap) -> {
apiTestCaseService.getBlobByIds(subIds)
.forEach(apiTestCaseBlob -> copyFromBlobMap.put(apiTestCaseBlob.getId(), apiTestCaseBlob.getRequest()));
});
// 处理新copy的步骤的步骤详情
putCopyStepDetails(stepDetails, isNewApiCaseResourceMap, (subIds, copyFromBlobMap) -> {
ApiScenarioStepBlobExample example = new ApiScenarioStepBlobExample();
example.createCriteria().andIdIn(subIds);
apiScenarioStepBlobMapper.selectByExampleWithBLOBs(example)
.forEach(scenarioStepBlob -> copyFromBlobMap.put(scenarioStepBlob.getId(), scenarioStepBlob.getContent()));
});
}
/**
* @param stepDetails
* @param copyFromIdMap key stepIDvalue 为复制的 resourceId 或者 stepId
* @param handlePutBlobMapFunc
*/
private void putCopyStepDetails(Map<String, Object> stepDetails, Map<String, String> copyFromIdMap, BiConsumer<List<String>, Map<String, byte[]>> handlePutBlobMapFunc) {
if (MapUtils.isEmpty(copyFromIdMap)) {
return;
}
// 处理新copy的步骤的步骤详情
Map<String, byte[]> copyFromBlobMap = new HashMap<>();
SubListUtils.dealForSubList(copyFromIdMap.values().stream().toList(), 50, (subIds) -> {
handlePutBlobMapFunc.accept(subIds, copyFromBlobMap);
});
copyFromIdMap.forEach((stepId, copyFromId) -> {
if (copyFromBlobMap.containsKey(copyFromId)) {
stepDetails.put(stepId, copyFromBlobMap.get(copyFromId));
}
});
}
/**
* 补充部分引用的 detail
*
* @param stepDetails
* @param step
*/
private void addPartialRefStepDetail(Map<String, Object> stepDetails, ApiScenarioStepCommonDTO step) {
if (isPartialRef(step)) {
// 如果是部分引用blob表保存启用的子步骤ID
Set<String> enableStepSet = getEnableStepSet(step.getChildren());
PartialRefStepDetail stepDetail = new PartialRefStepDetail();
stepDetail.setEnableStepIds(enableStepSet);
stepDetails.put(step.getId(), stepDetail);
}
}
private boolean isApiOrCase(ApiScenarioStepCommonDTO step) {
return isApi(step.getStepType()) || isApiCase(step.getStepType());
}
/**
* 遍历步骤树
*/
private void traversalStepTree(List<? extends ApiScenarioStepCommonDTO> steps, Function<ApiScenarioStepCommonDTO, Boolean> handleStepFunc) {
if (CollectionUtils.isEmpty(steps)) {
return;
}
for (ApiScenarioStepCommonDTO step : steps) {
Boolean isParseChild = handleStepFunc.apply(step);
if (BooleanUtils.isTrue(isParseChild)) {
traversalStepTree(step.getChildren(), handleStepFunc);
} }
apiScenarioStepsDetails.addAll(getPartialRefStepDetails(step.getChildren()));
} }
return apiScenarioStepsDetails;
} }
/** /**
@ -860,7 +994,7 @@ public class ApiScenarioService extends MoveNodeService {
enableSteps.add(step.getId()); enableSteps.add(step.getId());
} }
// 完全引用和部分引用不解析子步骤 // 完全引用和部分引用不解析子步骤
if (!isRef(step.getRefType())) { if (!isRefOrPartialRef(step.getRefType())) {
// 获取子步骤中 enable 的步骤 // 获取子步骤中 enable 的步骤
enableSteps.addAll(getEnableStepSet(step.getChildren())); enableSteps.addAll(getEnableStepSet(step.getChildren()));
} }
@ -1473,7 +1607,7 @@ public class ApiScenarioService extends MoveNodeService {
ApiScenarioStepCommonDTO step, ApiScenarioStepCommonDTO step,
AbstractMsTestElement msTestElement) { AbstractMsTestElement msTestElement) {
// 引用的场景设置场景参数 // 引用的场景设置场景参数
if (!isScenarioStep(step.getStepType()) || !isRef(step.getRefType()) || !(msTestElement instanceof MsScenario msScenario)) { if (!isScenarioStep(step.getStepType()) || !isRefOrPartialRef(step.getRefType()) || !(msTestElement instanceof MsScenario msScenario)) {
return; return;
} }
@ -1641,7 +1775,7 @@ public class ApiScenarioService extends MoveNodeService {
private void buildRefResourceIdMap(List<? extends ApiScenarioStepCommonDTO> steps, Map<String, List<String>> refResourceIdMap) { private void buildRefResourceIdMap(List<? extends ApiScenarioStepCommonDTO> steps, Map<String, List<String>> refResourceIdMap) {
for (ApiScenarioStepCommonDTO step : steps) { for (ApiScenarioStepCommonDTO step : steps) {
if (isRef(step.getRefType()) && BooleanUtils.isTrue(step.getEnable())) { if (isRefOrPartialRef(step.getRefType()) && BooleanUtils.isTrue(step.getEnable())) {
// 记录引用的步骤ID // 记录引用的步骤ID
List<String> resourceIds = refResourceIdMap.computeIfAbsent(step.getStepType(), k -> new ArrayList<>()); List<String> resourceIds = refResourceIdMap.computeIfAbsent(step.getStepType(), k -> new ArrayList<>());
resourceIds.add(step.getResourceId()); resourceIds.add(step.getResourceId());
@ -1789,7 +1923,7 @@ public class ApiScenarioService extends MoveNodeService {
if (CollectionUtils.isEmpty(children)) { if (CollectionUtils.isEmpty(children)) {
return; return;
} }
if (isRefApiScenario(step)) { if (isRefOrPartialScenario(step)) {
// 如果当前步骤是引用的场景获取该场景的子步骤 // 如果当前步骤是引用的场景获取该场景的子步骤
Map<String, List<ApiScenarioStepDTO>> childStepMap = scenarioStepMap.get(step.getResourceId()) Map<String, List<ApiScenarioStepDTO>> childStepMap = scenarioStepMap.get(step.getResourceId())
.stream() .stream()
@ -1808,8 +1942,8 @@ public class ApiScenarioService extends MoveNodeService {
/** /**
* 判断步骤是否是引用的场景 * 判断步骤是否是引用的场景
*/ */
private boolean isRefApiScenario(ApiScenarioStepDTO step) { private boolean isRefOrPartialScenario(ApiScenarioStepDTO step) {
return isRef(step.getRefType()) && isScenarioStep(step.getStepType()); return isRefOrPartialRef(step.getRefType()) && isScenarioStep(step.getStepType());
} }
/** /**
@ -1830,7 +1964,7 @@ public class ApiScenarioService extends MoveNodeService {
// 获取步骤中引用的场景ID // 获取步骤中引用的场景ID
List<String> childScenarioIds = steps.stream() List<String> childScenarioIds = steps.stream()
.filter(this::isRefApiScenario) .filter(this::isRefOrPartialScenario)
.map(ApiScenarioStepDTO::getResourceId) .map(ApiScenarioStepDTO::getResourceId)
.collect(Collectors.toList()); .collect(Collectors.toList());