feat(接口测试): Metersphere场景格式导入-文件数据解析部分

This commit is contained in:
Jianguo-Genius 2024-10-18 18:23:50 +08:00 committed by Craftsman
parent 025082fdfc
commit 6cec903a59
10 changed files with 1223 additions and 55 deletions

View File

@ -0,0 +1,40 @@
package io.metersphere.api.dto.converter;
import io.metersphere.api.domain.ApiScenarioCsv;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.scenario.ApiScenarioImportDetail;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 接口导入数据准备结果
*/
@Data
public class ApiScenarioImportParseResult {
@Schema(description = "导入的场景")
List<ApiScenarioImportDetail> importScenarioList = new ArrayList<>();
@Schema(description = "有关联关系的场景")
List<ApiScenarioImportDetail> relatedScenarioList = new ArrayList<>();
@Schema(description = "场景CSV相关的数据")
private List<ApiScenarioCsv> apiScenarioCsvList = new ArrayList<>();
@Schema(description = "所有场景步骤内容")
private Map<String, String> scenarioStepBlobMap = new HashMap<>();
@Schema(description = "有关联的接口定义")
private List<ApiDefinitionDetail> relatedApiDefinitions = new ArrayList<>();
@Schema(description = "有关联的接口用例")
private List<ApiTestCaseDTO> relatedApiTestCaseList = new ArrayList<>();
}

View File

@ -8,9 +8,6 @@ import lombok.Data;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/**
* 接口导入数据准备结果
*/
@Data @Data
public class ApiScenarioPreImportAnalysisResult { public class ApiScenarioPreImportAnalysisResult {

View File

@ -0,0 +1,15 @@
package io.metersphere.api.dto.converter;
import io.metersphere.api.dto.scenario.ApiScenarioStepRequest;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class ApiScenarioStepParseResult {
private List<ApiScenarioStepRequest> stepList = new ArrayList<>();
private Map<String, Object> stepDetails = new HashMap<>();
}

View File

@ -5,6 +5,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -16,7 +18,7 @@ public class ApiScenarioImportDetail extends ApiScenario {
@Schema(description = "模块路径") @Schema(description = "模块路径")
private String modulePath; private String modulePath;
@Schema(description = "步骤详情") @Schema(description = "步骤详情")
private Map<String, Object> stepDetails; private Map<String, Object> stepDetails = new HashMap<>();
@Schema(description = "步骤集合") @Schema(description = "步骤集合")
private List<ApiScenarioStepRequest> steps; private List<ApiScenarioStepRequest> steps = new ArrayList<>();
} }

View File

@ -1,11 +1,10 @@
package io.metersphere.api.parser; package io.metersphere.api.parser;
import io.metersphere.api.dto.scenario.ApiScenarioImportDetail; import io.metersphere.api.dto.converter.ApiScenarioImportParseResult;
import io.metersphere.api.dto.scenario.ApiScenarioImportRequest; import io.metersphere.api.dto.scenario.ApiScenarioImportRequest;
import java.io.InputStream; import java.io.InputStream;
import java.util.List;
public interface ApiScenarioImportParser { public interface ApiScenarioImportParser {
@ -16,6 +15,6 @@ public interface ApiScenarioImportParser {
* @param request 导入的请求参数 * @param request 导入的请求参数
* @return 解析后的数据 * @return 解析后的数据
*/ */
List<ApiScenarioImportDetail> parse(InputStream source, ApiScenarioImportRequest request) throws Exception; ApiScenarioImportParseResult parse(InputStream source, ApiScenarioImportRequest request) throws Exception;
} }

View File

@ -3,6 +3,8 @@ package io.metersphere.api.parser.api.dataimport;
import io.metersphere.api.constants.ApiScenarioStatus; import io.metersphere.api.constants.ApiScenarioStatus;
import io.metersphere.api.constants.ApiScenarioStepRefType; import io.metersphere.api.constants.ApiScenarioStepRefType;
import io.metersphere.api.constants.ApiScenarioStepType; import io.metersphere.api.constants.ApiScenarioStepType;
import io.metersphere.api.dto.converter.ApiScenarioImportParseResult;
import io.metersphere.api.dto.converter.ApiScenarioStepParseResult;
import io.metersphere.api.dto.request.MsJMeterComponent; import io.metersphere.api.dto.request.MsJMeterComponent;
import io.metersphere.api.dto.request.MsThreadGroup; import io.metersphere.api.dto.request.MsThreadGroup;
import io.metersphere.api.dto.request.controller.*; import io.metersphere.api.dto.request.controller.*;
@ -19,7 +21,6 @@ import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils; import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -36,14 +37,16 @@ public class JmeterParserApiScenario implements ApiScenarioImportParser {
@Override @Override
public List<ApiScenarioImportDetail> parse(InputStream inputSource, ApiScenarioImportRequest request) throws Exception { public ApiScenarioImportParseResult parse(InputStream inputSource, ApiScenarioImportRequest request) throws Exception {
try { try {
Object scriptWrapper = MsSaveService.loadElement(inputSource); Object scriptWrapper = MsSaveService.loadElement(inputSource);
HashTree hashTree = this.getHashTree(scriptWrapper); HashTree hashTree = this.getHashTree(scriptWrapper);
MsTestElementParser parser = new MsTestElementParser(); MsTestElementParser parser = new MsTestElementParser();
AbstractMsTestElement msTestElement = parser.parse(hashTree); AbstractMsTestElement msTestElement = parser.parse(hashTree);
Map<String, String> polymorphicNameMap = parser.getPolymorphicNameMap(request.getProjectId()); Map<String, String> polymorphicNameMap = parser.getPolymorphicNameMap(request.getProjectId());
return this.parseImportFile(request.getProjectId(), msTestElement, polymorphicNameMap); return new ApiScenarioImportParseResult() {{
this.setImportScenarioList(parseImportFile(request.getProjectId(), msTestElement, polymorphicNameMap));
}};
} catch (Exception e) { } catch (Exception e) {
LogUtils.error(e); LogUtils.error(e);
throw new MSException("当前JMX版本不兼容"); throw new MSException("当前JMX版本不兼容");
@ -184,11 +187,6 @@ public class JmeterParserApiScenario implements ApiScenarioImportParser {
} }
} }
@Data
class ApiScenarioStepParseResult {
private List<ApiScenarioStepRequest> stepList = new ArrayList<>();
private Map<String, Object> stepDetails = new HashMap<>();
}
class ProtocolConfig { class ProtocolConfig {
String id; String id;

View File

@ -1,29 +1,118 @@
package io.metersphere.api.parser.api.dataimport; package io.metersphere.api.parser.api.dataimport;
import io.metersphere.api.dto.scenario.ApiScenarioImportDetail; import io.metersphere.api.dto.converter.ApiScenarioImportParseResult;
import io.metersphere.api.dto.scenario.ApiScenarioImportRequest; import io.metersphere.api.dto.converter.ApiScenarioStepParseResult;
import io.metersphere.api.dto.export.MetersphereApiScenarioExportResponse;
import io.metersphere.api.dto.scenario.*;
import io.metersphere.api.parser.ApiScenarioImportParser; import io.metersphere.api.parser.ApiScenarioImportParser;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.uid.IDGenerator;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class MetersphereParserApiScenario implements ApiScenarioImportParser { public class MetersphereParserApiScenario implements ApiScenarioImportParser {
@Override @Override
public List<ApiScenarioImportDetail> parse(InputStream source, ApiScenarioImportRequest request) throws Exception { public ApiScenarioImportParseResult parse(InputStream source, ApiScenarioImportRequest request) throws Exception {
return null; MetersphereApiScenarioExportResponse metersphereApiScenarioExportResponse = null;
// MetersphereApiExportResponse metersphereApiExportResponse = null; try {
// try { metersphereApiScenarioExportResponse = ApiDataUtils.parseObject(source, MetersphereApiScenarioExportResponse.class);
// metersphereApiExportResponse = ApiDataUtils.parseObject(source, MetersphereApiExportResponse.class); } catch (Exception e) {
// } catch (Exception e) { LogUtils.error(e.getMessage(), e);
// LogUtils.error(e.getMessage(), e); throw new MSException(e.getMessage());
// throw new MSException(e.getMessage()); }
// } if (metersphereApiScenarioExportResponse == null) {
// if (metersphereApiExportResponse == null) { throw new MSException("解析失败,请确认是否是正确的文件");
// throw new MSException("解析失败,请确认选择的是 Metersphere 格式!"); }
// } return this.genApiScenarioPreImportAnalysisResult(metersphereApiScenarioExportResponse);
// return this.genApiDefinitionImport(metersphereApiExportResponse.getApiDefinitions());
} }
private ApiScenarioImportParseResult genApiScenarioPreImportAnalysisResult(MetersphereApiScenarioExportResponse metersphereApiScenarioExportResponse) {
ApiScenarioImportParseResult returnResult = new ApiScenarioImportParseResult();
returnResult.setRelatedApiDefinitions(metersphereApiScenarioExportResponse.getRelatedApiDefinitions());
returnResult.setRelatedApiTestCaseList(metersphereApiScenarioExportResponse.getRelatedApiTestCaseList());
returnResult.setApiScenarioCsvList(metersphereApiScenarioExportResponse.getApiScenarioCsvList());
List<ApiScenarioDetail> exportScenarioList = metersphereApiScenarioExportResponse.getExportScenarioList();
List<ApiScenarioDetail> relatedScenarioList = metersphereApiScenarioExportResponse.getRelatedScenarioList();
Map<String, List<ApiScenarioStepDTO>> scenarioStepMap =
metersphereApiScenarioExportResponse.getScenarioStepList().stream().collect(Collectors.groupingBy(ApiScenarioStepDTO::getScenarioId));
Map<String, String> scenarioStepBlobMap = metersphereApiScenarioExportResponse.getScenarioStepBlobMap();
for (ApiScenarioDetail apiScenarioDetail : exportScenarioList) {
returnResult.getImportScenarioList().add(
this.parseApiScenarioStep(apiScenarioDetail, scenarioStepMap.getOrDefault(apiScenarioDetail.getId(), new ArrayList<>()), scenarioStepBlobMap));
}
for (ApiScenarioDetail apiScenarioDetail : relatedScenarioList) {
returnResult.getRelatedScenarioList().add(
this.parseApiScenarioStep(apiScenarioDetail, scenarioStepMap.getOrDefault(apiScenarioDetail.getId(), new ArrayList<>()), scenarioStepBlobMap));
}
return returnResult;
}
private ApiScenarioImportDetail parseApiScenarioStep(ApiScenarioDetail apiScenarioDetail, List<ApiScenarioStepDTO> apiScenarioStepDTOList, Map<String, String> scenarioStepBlobMap) {
ApiScenarioImportDetail apiScenarioImportDetail = new ApiScenarioImportDetail();
BeanUtils.copyBean(apiScenarioImportDetail, apiScenarioDetail);
ApiScenarioStepParseResult parseResult = this.parseStepDetails(apiScenarioStepDTOList, scenarioStepBlobMap);
apiScenarioImportDetail.setSteps(parseResult.getStepList());
apiScenarioImportDetail.setStepDetails(parseResult.getStepDetails());
return apiScenarioImportDetail;
}
private ApiScenarioStepParseResult parseStepDetails(List<ApiScenarioStepDTO> apiScenarioStepDTOList, Map<String, String> scenarioStepBlobMap) {
ApiScenarioStepParseResult apiScenarioStepParseResult = new ApiScenarioStepParseResult();
Map<String, ApiScenarioStepRequest> stepRequestIdMap = new HashMap<>();
int lastSize = 0;
while (CollectionUtils.isNotEmpty(apiScenarioStepDTOList) && apiScenarioStepDTOList.size() != lastSize) {
lastSize = apiScenarioStepDTOList.size();
List<ApiScenarioStepDTO> notMatchedList = new ArrayList<>();
for (ApiScenarioStepDTO stepDTO : apiScenarioStepDTOList) {
String oldStepId = stepDTO.getId();
if (!stepRequestIdMap.containsKey(stepDTO.getParentId()) && StringUtils.isNotBlank(stepDTO.getParentId())) {
notMatchedList.add(stepDTO);
continue;
}
ApiScenarioStepRequest stepRequest = new ApiScenarioStepRequest();
BeanUtils.copyBean(stepRequest, stepDTO);
// 赋值新ID防止和库内已有数据重复
stepRequest.setId(IDGenerator.nextStr());
// 使用旧ID用于配置Tree
stepRequestIdMap.put(oldStepId, stepRequest);
if (StringUtils.isBlank(stepDTO.getParentId())) {
apiScenarioStepParseResult.getStepList().add(stepRequest);
if (scenarioStepBlobMap.containsKey(oldStepId)) {
apiScenarioStepParseResult.getStepDetails().put(stepRequest.getId(), scenarioStepBlobMap.get(oldStepId).getBytes());
}
} else if (stepRequestIdMap.containsKey(stepDTO.getParentId())) {
if (stepRequestIdMap.get(stepDTO.getParentId()).getChildren() == null) {
stepRequestIdMap.get(stepDTO.getParentId()).setChildren(new ArrayList<>());
}
stepRequestIdMap.get(stepDTO.getParentId()).getChildren().add(stepRequest);
if (scenarioStepBlobMap.containsKey(oldStepId)) {
apiScenarioStepParseResult.getStepDetails().put(stepRequest.getId(), scenarioStepBlobMap.get(oldStepId).getBytes());
}
}
}
apiScenarioStepDTOList = notMatchedList;
}
return apiScenarioStepParseResult;
}
} }

View File

@ -5,6 +5,7 @@ import io.metersphere.api.constants.ApiScenarioStepType;
import io.metersphere.api.domain.*; import io.metersphere.api.domain.*;
import io.metersphere.api.dto.ApiFile; import io.metersphere.api.dto.ApiFile;
import io.metersphere.api.dto.converter.ApiDefinitionExportDetail; import io.metersphere.api.dto.converter.ApiDefinitionExportDetail;
import io.metersphere.api.dto.converter.ApiScenarioImportParseResult;
import io.metersphere.api.dto.converter.ApiScenarioPreImportAnalysisResult; import io.metersphere.api.dto.converter.ApiScenarioPreImportAnalysisResult;
import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.export.ApiScenarioExportResponse; import io.metersphere.api.dto.export.ApiScenarioExportResponse;
@ -140,21 +141,21 @@ public class ApiScenarioDataTransferService {
public void importScenario(MultipartFile file, ApiScenarioImportRequest request) { public void importScenario(MultipartFile file, ApiScenarioImportRequest request) {
ApiScenarioImportParser parser = ImportParserFactory.getApiScenarioImportParser(request.getType()); ApiScenarioImportParser parser = ImportParserFactory.getApiScenarioImportParser(request.getType());
List<ApiScenarioImportDetail> importScenarios; ApiScenarioImportParseResult parseResult;
try { try {
assert parser != null; assert parser != null;
importScenarios = parser.parse(file.getInputStream(), request); parseResult = parser.parse(file.getInputStream(), request);
} catch (Exception e) { } catch (Exception e) {
LogUtils.error(e.getMessage(), e); LogUtils.error(e.getMessage(), e);
throw new MSException(Translator.get("parse_data_error")); throw new MSException(Translator.get("parse_data_error"));
} }
if (CollectionUtils.isEmpty(importScenarios)) { if (CollectionUtils.isEmpty(parseResult.getImportScenarioList())) {
throw new MSException(Translator.get("parse_empty_data")); throw new MSException(Translator.get("parse_empty_data"));
} }
//解析 //解析
ApiScenarioPreImportAnalysisResult preImportAnalysisResult = this.importAnalysis( ApiScenarioPreImportAnalysisResult preImportAnalysisResult = this.importAnalysis(
importScenarios, request.getModuleId(), apiScenarioModuleService.getTree(request.getProjectId())); parseResult, request.getModuleId(), apiScenarioModuleService.getTree(request.getProjectId()));
//存储 //存储
this.save(preImportAnalysisResult, request.getProjectId(), request.getOperator(), request.isCoverData()); this.save(preImportAnalysisResult, request.getProjectId(), request.getOperator(), request.isCoverData());
@ -519,12 +520,13 @@ public class ApiScenarioDataTransferService {
return order; return order;
} }
private ApiScenarioPreImportAnalysisResult importAnalysis(List<ApiScenarioImportDetail> importScenarios, String moduleId, List<BaseTreeNode> apiScenarioModules) { private ApiScenarioPreImportAnalysisResult importAnalysis(ApiScenarioImportParseResult parseResult, String moduleId, List<BaseTreeNode> apiScenarioModules) {
ApiScenarioPreImportAnalysisResult analysisResult = new ApiScenarioPreImportAnalysisResult(); ApiScenarioPreImportAnalysisResult analysisResult = new ApiScenarioPreImportAnalysisResult();
Map<String, String> moduleIdPathMap = apiScenarioModules.stream().collect(Collectors.toMap(BaseTreeNode::getId, BaseTreeNode::getPath)); Map<String, String> moduleIdPathMap = apiScenarioModules.stream().collect(Collectors.toMap(BaseTreeNode::getId, BaseTreeNode::getPath));
Map<String, BaseTreeNode> modulePathMap = apiScenarioModules.stream().collect(Collectors.toMap(BaseTreeNode::getPath, k -> k, (k1, k2) -> k1)); Map<String, BaseTreeNode> modulePathMap = apiScenarioModules.stream().collect(Collectors.toMap(BaseTreeNode::getPath, k -> k, (k1, k2) -> k1));
List<ApiScenarioImportDetail> importScenarios = parseResult.getImportScenarioList();
for (ApiScenarioImportDetail importScenario : importScenarios) { for (ApiScenarioImportDetail importScenario : importScenarios) {
if (StringUtils.isBlank(moduleId) || StringUtils.equalsIgnoreCase(moduleId, ModuleConstants.DEFAULT_NODE_ID) || !moduleIdPathMap.containsKey(moduleId)) { if (StringUtils.isBlank(moduleId) || StringUtils.equalsIgnoreCase(moduleId, ModuleConstants.DEFAULT_NODE_ID) || !moduleIdPathMap.containsKey(moduleId)) {
importScenario.setModuleId(ModuleConstants.DEFAULT_NODE_ID); importScenario.setModuleId(ModuleConstants.DEFAULT_NODE_ID);

View File

@ -34,9 +34,7 @@ import org.springframework.util.MultiValueMap;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ -75,15 +73,11 @@ public class ApiScenarioControllerImportAndExportTests extends BaseTest {
@Test @Test
@Order(1) @Order(1)
public void testImport() throws Exception { public void testImport() throws Exception {
Map<String, String> importTypeAndSuffix = new LinkedHashMap<>();
// importTypeAndSuffix.put("metersphere", "json");
importTypeAndSuffix.put("jmeter", "jmx");
for (Map.Entry<String, String> entry : importTypeAndSuffix.entrySet()) {
ApiScenarioImportRequest request = new ApiScenarioImportRequest(); ApiScenarioImportRequest request = new ApiScenarioImportRequest();
request.setProjectId(project.getId()); request.setProjectId(project.getId());
request.setType(entry.getKey()); request.setType("jmeter");
String importType = entry.getKey(); String importType = "jmeter";
String fileSuffix = entry.getValue(); String fileSuffix = "jmx";
FileInputStream inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import-scenario/" + importType + "/simple." + fileSuffix)).getPath())); FileInputStream inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import-scenario/" + importType + "/simple." + fileSuffix)).getPath()));
MockMultipartFile file = new MockMultipartFile("file", "simple." + fileSuffix, MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream); MockMultipartFile file = new MockMultipartFile("file", "simple." + fileSuffix, MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>(); MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
@ -91,7 +85,6 @@ public class ApiScenarioControllerImportAndExportTests extends BaseTest {
paramMap.add("file", file); paramMap.add("file", file);
this.requestMultipartWithOkAndReturn(URL_POST_IMPORT, paramMap); this.requestMultipartWithOkAndReturn(URL_POST_IMPORT, paramMap);
} }
}
@Resource @Resource
private ExportTaskManager exportTaskManager; private ExportTaskManager exportTaskManager;
@ -144,6 +137,21 @@ public class ApiScenarioControllerImportAndExportTests extends BaseTest {
MsFileUtils.deleteDir("/tmp/api-scenario-export/"); MsFileUtils.deleteDir("/tmp/api-scenario-export/");
} }
@Test
@Order(3)
public void testImportMs() throws Exception {
ApiScenarioImportRequest request = new ApiScenarioImportRequest();
request.setProjectId(project.getId());
request.setType("metersphere");
String importType = "metersphere";
FileInputStream inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import-scenario/" + importType + "/all.ms")).getPath()));
MockMultipartFile file = new MockMultipartFile("file", "all.ms", MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("request", JSON.toJSONString(request));
paramMap.add("file", file);
this.requestMultipartWithOkAndReturn(URL_POST_IMPORT, paramMap);
}
private MvcResult download(String projectId, String fileId) throws Exception { private MvcResult download(String projectId, String fileId) throws Exception {
return mockMvc.perform(MockMvcRequestBuilders.get("/api/scenario/download/file/" + projectId + "/" + fileId) return mockMvc.perform(MockMvcRequestBuilders.get("/api/scenario/download/file/" + projectId + "/" + fileId)
.header(SessionConstants.HEADER_TOKEN, sessionId) .header(SessionConstants.HEADER_TOKEN, sessionId)