fix: 解决冲突

This commit is contained in:
chenjianxing 2021-02-20 13:44:18 +08:00
commit ce7600bad4
44 changed files with 3615 additions and 433 deletions

View File

@ -48,7 +48,7 @@ curl -sSL https://github.com/metersphere/metersphere/releases/latest/download/qu
文档和演示视频:
- [完整文档](https://metersphere.io/docs/)
- [演示视频](https://www.bilibili.com/video/BV1yp4y1p72C/)
- [演示视频](http://video.fit2cloud.com/%E3%80%90%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91%E3%80%91202006%20MeterSphere%20v1.0%20%E5%8A%9F%E8%83%BD%E6%BC%94%E7%A4%BA.mp4)
## MeterSphere 企业版

View File

@ -380,7 +380,48 @@
<artifactId>json-schema-validator</artifactId>
<version>2.2.6</version>
</dependency>
<!-- 添加jmeter包支持导入的jmx能正常执行 -->
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_bolt</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_jms</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_ftp</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_junit</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_ldap</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_mail</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_components</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_native</artifactId>
<version>${jmeter.version}</version>
</dependency>
<!-- 添加jmeter包支持导入的jmx能正常执行 -->
</dependencies>
<build>

View File

@ -31,6 +31,7 @@ import io.metersphere.service.CheckPermissionService;
import io.metersphere.service.FileService;
import io.metersphere.service.ScheduleService;
import io.metersphere.track.request.testplan.SaveTestPlanRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.ContentType;
import org.apache.jorphan.collections.HashTree;
import org.apache.shiro.authz.annotation.Logical;
@ -212,19 +213,19 @@ public class APITestController {
//查询完成率进行中已完成
List<ApiDataCountResult> countResultByStatelList = apiDefinitionService.countStateByProjectID(projectId);
apiCountResult.countStatus(countResultByStatelList);
long allCount = apiCountResult.getFinishedCount()+apiCountResult.getRunningCount()+apiCountResult.getNotStartedCount();
long allCount = apiCountResult.getFinishedCount() + apiCountResult.getRunningCount() + apiCountResult.getNotStartedCount();
if(allCount!=0){
float complateRageNumber =(float)apiCountResult.getFinishedCount()*100/allCount;
if (allCount != 0) {
float complateRageNumber = (float) apiCountResult.getFinishedCount() * 100 / allCount;
DecimalFormat df = new DecimalFormat("0.0");
apiCountResult.setCompletionRage(df.format(complateRageNumber)+"%");
apiCountResult.setCompletionRage(df.format(complateRageNumber) + "%");
}
apiCountResult.setHttpCountStr("HTTP&nbsp;&nbsp;<br/><br/>"+apiCountResult.getHttpApiDataCountNumber());
apiCountResult.setRpcCountStr("RPC&nbsp;&nbsp;<br/><br/>"+apiCountResult.getRpcApiDataCountNumber());
apiCountResult.setTcpCountStr("TCP&nbsp;&nbsp;<br/><br/>"+apiCountResult.getTcpApiDataCountNumber());
apiCountResult.setSqlCountStr("SQL&nbsp;&nbsp;<br/><br/>"+apiCountResult.getSqlApiDataCountNumber());
return apiCountResult;
apiCountResult.setHttpCountStr("HTTP&nbsp;&nbsp;<br/><br/>" + apiCountResult.getHttpApiDataCountNumber());
apiCountResult.setRpcCountStr("RPC&nbsp;&nbsp;<br/><br/>" + apiCountResult.getRpcApiDataCountNumber());
apiCountResult.setTcpCountStr("TCP&nbsp;&nbsp;<br/><br/>" + apiCountResult.getTcpApiDataCountNumber());
apiCountResult.setSqlCountStr("SQL&nbsp;&nbsp;<br/><br/>" + apiCountResult.getSqlApiDataCountNumber());
return apiCountResult;
}
@GetMapping("/testCaseInfoCount/{projectId}")
@ -245,21 +246,21 @@ public class APITestController {
//未覆盖 已覆盖 统计当前接口下是否含有案例
List<ApiDataCountResult> countResultByApiCoverageList = apiDefinitionService.countApiCoverageByProjectID(projectId);
apiCountResult.countApiCoverage(countResultByApiCoverageList);
long allCount = apiCountResult.getCoverageCount()+apiCountResult.getUncoverageCount();
long allCount = apiCountResult.getCoverageCount() + apiCountResult.getUncoverageCount();
if(allCount!=0){
float coverageRageNumber =(float)apiCountResult.getCoverageCount()*100/allCount;
if (allCount != 0) {
float coverageRageNumber = (float) apiCountResult.getCoverageCount() * 100 / allCount;
DecimalFormat df = new DecimalFormat("0.0");
apiCountResult.setCoverageRage(df.format(coverageRageNumber)+"%");
apiCountResult.setCoverageRage(df.format(coverageRageNumber) + "%");
}
apiCountResult.setHttpCountStr("HTTP&nbsp;&nbsp;<br/><br/>"+apiCountResult.getHttpApiDataCountNumber());
apiCountResult.setRpcCountStr("RPC&nbsp;&nbsp;<br/><br/>"+apiCountResult.getRpcApiDataCountNumber());
apiCountResult.setTcpCountStr("TCP&nbsp;&nbsp;<br/><br/>"+apiCountResult.getTcpApiDataCountNumber());
apiCountResult.setSqlCountStr("SQL&nbsp;&nbsp;<br/><br/>"+apiCountResult.getSqlApiDataCountNumber());
apiCountResult.setHttpCountStr("HTTP&nbsp;&nbsp;<br/><br/>" + apiCountResult.getHttpApiDataCountNumber());
apiCountResult.setRpcCountStr("RPC&nbsp;&nbsp;<br/><br/>" + apiCountResult.getRpcApiDataCountNumber());
apiCountResult.setTcpCountStr("TCP&nbsp;&nbsp;<br/><br/>" + apiCountResult.getTcpApiDataCountNumber());
apiCountResult.setSqlCountStr("SQL&nbsp;&nbsp;<br/><br/>" + apiCountResult.getSqlApiDataCountNumber());
return apiCountResult;
return apiCountResult;
}
@GetMapping("/testSceneInfoCount/{projectId}")
@ -318,28 +319,28 @@ public class APITestController {
apiCountResult.countScheduleExecute(allExecuteResult);
long allCount = apiCountResult.getExecutedCount();
if(allCount!=0){
float coverageRageNumber =(float)apiCountResult.getSuccessCount()*100/allCount;
if (allCount != 0) {
float coverageRageNumber = (float) apiCountResult.getSuccessCount() * 100 / allCount;
DecimalFormat df = new DecimalFormat("0.0");
apiCountResult.setSuccessRage(df.format(coverageRageNumber)+"%");
apiCountResult.setSuccessRage(df.format(coverageRageNumber) + "%");
}
return apiCountResult;
return apiCountResult;
}
@GetMapping("/faliureCaseAboutTestPlan/{projectId}/{limitNumber}")
public List<ExecutedCaseInfoDTO> faliureCaseAboutTestPlan(@PathVariable String projectId, @PathVariable int limitNumber) {
List<ExecutedCaseInfoResult> selectDataList = apiDefinitionExecResultService.findFaliureCaseInfoByProjectIDAndLimitNumberInSevenDays(projectId,limitNumber);
List<ExecutedCaseInfoResult> selectDataList = apiDefinitionExecResultService.findFaliureCaseInfoByProjectIDAndLimitNumberInSevenDays(projectId, limitNumber);
List<ExecutedCaseInfoDTO> returnList = new ArrayList<>(limitNumber);
for(int dataIndex = 0;dataIndex < limitNumber;dataIndex ++){
for (int dataIndex = 0; dataIndex < limitNumber; dataIndex++) {
ExecutedCaseInfoDTO dataDTO = new ExecutedCaseInfoDTO();
dataDTO.setSortIndex(dataIndex+1);
dataDTO.setSortIndex(dataIndex + 1);
if(dataIndex<selectDataList.size()){
if (dataIndex < selectDataList.size()) {
ExecutedCaseInfoResult selectData = selectDataList.get(dataIndex);
dataDTO.setCaseID(selectData.getTestCaseID());
dataDTO.setCaseName(selectData.getCaseName());
@ -347,13 +348,13 @@ public class APITestController {
dataDTO.setFailureTimes(selectData.getFailureTimes());
dataDTO.setCaseType(selectData.getCaseType());
dataDTO.setTestPlanDTOList(selectData.getTestPlanDTOList());
}else {
} else {
dataDTO.setCaseName("");
dataDTO.setTestPlan("");
}
returnList.add(dataDTO);
}
return returnList;
return returnList;
}
@GetMapping("/runningTask/{projectID}")
@ -365,11 +366,11 @@ public class APITestController {
resultList) {
taskInfo.setIndex(dataIndex++);
Date nextExecutionTime = CronUtils.getNextTriggerTime(taskInfo.getRule());
if(nextExecutionTime!=null){
if (nextExecutionTime != null) {
taskInfo.setNextExecutionTime(nextExecutionTime.getTime());
}
}
return resultList;
return resultList;
}
@PostMapping(value = "/schedule/updateEnableByPrimyKey")
@ -378,6 +379,7 @@ public class APITestController {
schedule.setEnable(request.isEnable());
apiAutomationService.updateSchedule(schedule);
}
@PostMapping(value = "/historicalDataUpgrade")
public String historicalDataUpgrade(@RequestBody SaveHistoricalDataUpgrade request) {
return historicalDataUpgradeService.upgrade(request);
@ -390,32 +392,13 @@ public class APITestController {
String testName = runRequest.getName();
try{
//将ThreadGroup的testname改为接口名称
Document doc = DocumentHelper.parseText(jmxString);// 获取可续保保单列表报文模板
Element root = doc.getRootElement();
Element rootHashTreeElement = root.element("hashTree");
Element innerHashTreeElement = rootHashTreeElement.elements("hashTree").get(0);
Element theadGroupElement = innerHashTreeElement.elements("ThreadGroup").get(0);
theadGroupElement.attribute("testname").setText(testName);
List<Element> thirdHashTreeElementList =innerHashTreeElement.elements("hashTree");
for (Element element:thirdHashTreeElementList) {
List<Element> sampleProxyElementList = element.elements("HTTPSamplerProxy");
for (Element itemElement: sampleProxyElementList) {
itemElement.attribute("testname").setText(testName);
}
}
jmxString = root.asXML();
}catch (Exception e){
e.printStackTrace();
}
//将jmx处理封装为通用方法
jmxString = apiTestService.updateJmxString(jmxString,testName,true);
JmxInfoDTO dto = new JmxInfoDTO();
dto.setName(runRequest.getName()+".jmx");
dto.setName(runRequest.getName() + ".jmx");
dto.setXml(jmxString);
return dto;
return dto;
}
}

View File

@ -5,15 +5,12 @@ import com.github.pagehelper.PageHelper;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.JmxInfoDTO;
import io.metersphere.api.dto.automation.*;
import io.metersphere.api.dto.definition.ApiBatchRequest;
import io.metersphere.api.dto.definition.ApiExportResult;
import io.metersphere.api.dto.automation.parse.ScenarioImport;
import io.metersphere.api.dto.definition.RunDefinitionRequest;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.service.ApiAutomationService;
import io.metersphere.base.domain.ApiScenario;
import io.metersphere.base.domain.ApiScenarioWithBLOBs;
import io.metersphere.base.domain.Schedule;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
@ -47,6 +44,13 @@ public class ApiAutomationController {
return PageUtils.setPageInfo(page, apiAutomationService.list(request));
}
@PostMapping("/list/all")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR)
public List<ApiScenarioWithBLOBs> list(@RequestBody ApiScenarioRequest request) {
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
return apiAutomationService.get(request);
}
@PostMapping(value = "/create")
public ApiScenario create(@RequestPart("request") SaveApiScenarioRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
return apiAutomationService.create(request, bodyFiles);
@ -153,7 +157,7 @@ public class ApiAutomationController {
@PostMapping(value = "/import", consumes = {"multipart/form-data"})
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public ApiDefinitionImport testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) {
public ScenarioImport scenarioImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) {
return apiAutomationService.scenarioImport(file, request);
}

View File

@ -22,6 +22,8 @@ public class ApiDocumentInfoDTO {
private String requestBodyParamType;
private String requestBodyFormData;
private String requestBodyStrutureData;
private Object requestPreviewData;
private Object jsonSchemaBody;
private String responseHead;
private String responseBody;

View File

@ -16,4 +16,7 @@ public class ApiDocumentRequest {
private String projectId;
private List<String> moduleIds;
private String shareId;
private String name;
private String type;
private String orderCondition;
}

View File

@ -0,0 +1,23 @@
package io.metersphere.api.dto.automation;
import io.metersphere.api.dto.scenario.DatabaseConfig;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import lombok.Getter;
import lombok.Setter;
import java.util.Map;
@Getter
@Setter
public class ImportPoolsDTO {
private String envId;
private Boolean isCreate;
private EnvironmentConfig envConfig;
private ApiTestEnvironmentWithBLOBs testEnvironmentWithBLOBs;
private Map<String, DatabaseConfig> dataSources;
}

View File

@ -0,0 +1,648 @@
package io.metersphere.api.dto.automation.parse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.github.ningyu.jmeter.plugin.dubbo.sample.DubboSample;
import io.github.ningyu.jmeter.plugin.dubbo.sample.MethodArgument;
import io.github.ningyu.jmeter.plugin.util.Constants;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.automation.ImportPoolsDTO;
import io.metersphere.api.dto.definition.request.MsScenario;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.assertions.*;
import io.metersphere.api.dto.definition.request.controller.MsIfController;
import io.metersphere.api.dto.definition.request.controller.MsLoopController;
import io.metersphere.api.dto.definition.request.controller.loop.CountController;
import io.metersphere.api.dto.definition.request.controller.loop.MsForEachController;
import io.metersphere.api.dto.definition.request.controller.loop.MsWhileController;
import io.metersphere.api.dto.definition.request.extract.MsExtract;
import io.metersphere.api.dto.definition.request.extract.MsExtractJSONPath;
import io.metersphere.api.dto.definition.request.extract.MsExtractRegex;
import io.metersphere.api.dto.definition.request.extract.MsExtractXPath;
import io.metersphere.api.dto.definition.request.processors.MsJSR223Processor;
import io.metersphere.api.dto.definition.request.processors.post.MsJSR223PostProcessor;
import io.metersphere.api.dto.definition.request.processors.pre.MsJSR223PreProcessor;
import io.metersphere.api.dto.definition.request.sampler.MsDubboSampler;
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import io.metersphere.api.dto.definition.request.sampler.MsJDBCSampler;
import io.metersphere.api.dto.definition.request.sampler.MsTCPSampler;
import io.metersphere.api.dto.definition.request.sampler.dubbo.MsConfigCenter;
import io.metersphere.api.dto.definition.request.sampler.dubbo.MsConsumerAndService;
import io.metersphere.api.dto.definition.request.sampler.dubbo.MsRegistryCenter;
import io.metersphere.api.dto.definition.request.timer.MsConstantTimer;
import io.metersphere.api.dto.definition.request.unknown.MsJmeterElement;
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.DatabaseConfig;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.scenario.request.BodyFile;
import io.metersphere.api.dto.scenario.request.RequestType;
import io.metersphere.api.service.ApiTestEnvironmentService;
import io.metersphere.base.domain.ApiScenarioModule;
import io.metersphere.base.domain.ApiScenarioWithBLOBs;
import io.metersphere.base.domain.ApiTestEnvironmentExample;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.commons.constants.LoopConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.jmeter.assertions.*;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.control.ForeachController;
import org.apache.jmeter.control.IfController;
import org.apache.jmeter.control.LoopController;
import org.apache.jmeter.control.WhileController;
import org.apache.jmeter.extractor.JSR223PostProcessor;
import org.apache.jmeter.extractor.RegexExtractor;
import org.apache.jmeter.extractor.XPath2Extractor;
import org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor;
import org.apache.jmeter.modifiers.JSR223PreProcessor;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import org.apache.jmeter.protocol.http.util.HTTPFileArg;
import org.apache.jmeter.protocol.java.sampler.JSR223Sampler;
import org.apache.jmeter.protocol.jdbc.config.DataSourceElement;
import org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler;
import org.apache.jmeter.protocol.tcp.sampler.TCPSampler;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jmeter.timers.ConstantTimer;
import org.apache.jorphan.collections.HashTree;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;
public class MsJmeterParser extends ScenarioImportAbstractParser {
private final String ENV_NAME = "导入数据环境";
@Override
public ScenarioImport parse(InputStream inputSource, ApiTestImportRequest request) {
try {
Object scriptWrapper = SaveService.loadElement(inputSource);
HashTree testPlan = this.getHashTree(scriptWrapper);
// 优先初始化数据源
preInitPool(request.getProjectId(), testPlan);
MsScenario scenario = new MsScenario();
scenario.setReferenced("REF");
jmterHashTree(testPlan, scenario);
this.projectId = request.getProjectId();
ScenarioImport scenarioImport = new ScenarioImport();
scenarioImport.setData(paseObj(scenario, request));
scenarioImport.setProjectid(request.getProjectId());
return scenarioImport;
} catch (Exception e) {
e.printStackTrace();
MSException.throwException("当前JMX版本不兼容");
}
return null;
}
private List<ApiScenarioWithBLOBs> paseObj(MsScenario msScenario, ApiTestImportRequest request) {
List<ApiScenarioWithBLOBs> scenarioWithBLOBsList = new ArrayList<>();
ApiScenarioWithBLOBs scenarioWithBLOBs = new ApiScenarioWithBLOBs();
ApiScenarioModule module = buildModule(getSelectModule(request.getModuleId()), msScenario.getName());
scenarioWithBLOBs.setName(msScenario.getName());
scenarioWithBLOBs.setProjectId(request.getProjectId());
if (msScenario != null && CollectionUtils.isNotEmpty(msScenario.getHashTree())) {
scenarioWithBLOBs.setStepTotal(msScenario.getHashTree().size());
}
if (module != null) {
scenarioWithBLOBs.setApiScenarioModuleId(module.getId());
scenarioWithBLOBs.setModulePath("/" + module.getName());
}
scenarioWithBLOBs.setId(UUID.randomUUID().toString());
scenarioWithBLOBs.setScenarioDefinition(JSON.toJSONString(msScenario));
scenarioWithBLOBsList.add(scenarioWithBLOBs);
return scenarioWithBLOBsList;
}
private HashTree getHashTree(Object scriptWrapper) throws Exception {
Field field = scriptWrapper.getClass().getDeclaredField("testPlan");
field.setAccessible(true);
return (HashTree) field.get(scriptWrapper);
}
private void convertHttpSampler(MsHTTPSamplerProxy samplerProxy, HTTPSamplerProxy source) {
try {
BeanUtils.copyBean(samplerProxy, source);
if (source != null && source.getHTTPFiles().length > 0) {
samplerProxy.getBody().setBinary(new ArrayList<>());
samplerProxy.getBody().setType(Body.FORM_DATA);
List<KeyValue> keyValues = new LinkedList<>();
for (HTTPFileArg arg : source.getHTTPFiles()) {
List<BodyFile> files = new LinkedList<>();
BodyFile file = new BodyFile();
file.setId(arg.getParamName());
file.setName(arg.getPath());
files.add(file);
KeyValue keyValue = new KeyValue(arg.getParamName(), arg.getParamName());
keyValue.setContentType(arg.getProperty("HTTPArgument.content_type").toString());
keyValue.setType("file");
keyValue.setFiles(files);
keyValues.add(keyValue);
}
samplerProxy.getBody().setKvs(keyValues);
}
samplerProxy.setProtocol(RequestType.HTTP);
samplerProxy.setPort(source.getPort() + "");
if (source.getArguments() != null) {
if (source.getPostBodyRaw()) {
samplerProxy.getBody().setType(Body.RAW);
source.getArguments().getArgumentsAsMap().forEach((k, v) -> {
samplerProxy.getBody().setRaw(v);
});
} else {
List<KeyValue> keyValues = new LinkedList<>();
source.getArguments().getArgumentsAsMap().forEach((k, v) -> {
KeyValue keyValue = new KeyValue(k, v);
keyValues.add(keyValue);
});
if (CollectionUtils.isNotEmpty(keyValues)) {
samplerProxy.setArguments(keyValues);
}
}
}
samplerProxy.setPath(source.getPath());
samplerProxy.setMethod(source.getMethod());
if (source.getUrl() != null) {
// samplerProxy.setUrl(source.getUrl().toString());
}
samplerProxy.setId(UUID.randomUUID().toString());
samplerProxy.setType("HTTPSamplerProxy");
} catch (Exception e) {
e.printStackTrace();
}
}
private void convertTCPSampler(MsTCPSampler msTCPSampler, TCPSampler tcpSampler) {
msTCPSampler.setName(tcpSampler.getName());
msTCPSampler.setType("TCPSampler");
msTCPSampler.setServer(tcpSampler.getServer());
msTCPSampler.setPort(tcpSampler.getPort() + "");
msTCPSampler.setCtimeout(tcpSampler.getConnectTimeout() + "");
msTCPSampler.setReUseConnection(tcpSampler.getProperty(TCPSampler.RE_USE_CONNECTION).getBooleanValue());
msTCPSampler.setNodelay(tcpSampler.getProperty(TCPSampler.NODELAY).getBooleanValue());
msTCPSampler.setCloseConnection(tcpSampler.isCloseConnection());
msTCPSampler.setSoLinger(tcpSampler.getSoLinger() + "");
msTCPSampler.setEolByte(tcpSampler.getEolByte() + "");
msTCPSampler.setRequest(tcpSampler.getRequestData());
msTCPSampler.setUsername(tcpSampler.getProperty(ConfigTestElement.USERNAME).getStringValue());
msTCPSampler.setPassword(tcpSampler.getProperty(ConfigTestElement.PASSWORD).getStringValue());
}
private void convertDubboSample(MsDubboSampler elementNode, DubboSample sampler) {
elementNode.setName(sampler.getName());
elementNode.setType("DubboSampler");
elementNode.setProtocol("dubbo://");
elementNode.set_interface(sampler.getPropertyAsString("FIELD_DUBBO_INTERFACE"));
elementNode.setMethod(sampler.getPropertyAsString("FIELD_DUBBO_METHOD"));
MsConfigCenter configCenter = new MsConfigCenter();
configCenter.setProtocol(sampler.getPropertyAsString("FIELD_DUBBO_CONFIG_CENTER_PROTOCOL"));
configCenter.setGroup(sampler.getPropertyAsString("FIELD_DUBBO_CONFIG_CENTER_GROUP"));
configCenter.setNamespace(sampler.getPropertyAsString("FIELD_DUBBO_CONFIG_CENTER_NAMESPACE"));
configCenter.setUsername(sampler.getPropertyAsString("FIELD_DUBBO_CONFIG_CENTER_USER_NAME"));
configCenter.setPassword(sampler.getPropertyAsString("FIELD_DUBBO_CONFIG_CENTER_PASSWORD"));
configCenter.setAddress(sampler.getPropertyAsString("FIELD_DUBBO_CONFIG_CENTER_ADDRESS"));
configCenter.setTimeout(sampler.getPropertyAsString("FIELD_DUBBO_CONFIG_CENTER_TIMEOUT"));
elementNode.setConfigCenter(configCenter);
MsRegistryCenter registryCenter = new MsRegistryCenter();
registryCenter.setProtocol(sampler.getPropertyAsString("FIELD_DUBBO_REGISTRY_PROTOCOL"));
registryCenter.setAddress(sampler.getPropertyAsString("FIELD_DUBBO_ADDRESS"));
registryCenter.setGroup(sampler.getPropertyAsString("FIELD_DUBBO_REGISTRY_GROUP"));
registryCenter.setUsername(sampler.getPropertyAsString("FIELD_DUBBO_REGISTRY_USER_NAME"));
registryCenter.setPassword(sampler.getPropertyAsString("FIELD_DUBBO_REGISTRY_PASSWORD"));
registryCenter.setTimeout(sampler.getPropertyAsString("FIELD_DUBBO_REGISTRY_TIMEOUT"));
elementNode.setRegistryCenter(registryCenter);
MsConsumerAndService consumerAndService = new MsConsumerAndService();
consumerAndService.setAsync(sampler.getPropertyAsString("FIELD_DUBBO_ASYNC"));
consumerAndService.setCluster(sampler.getPropertyAsString("FIELD_DUBBO_CLUSTER"));
consumerAndService.setConnections(sampler.getPropertyAsString("FIELD_DUBBO_CONNECTIONS"));
consumerAndService.setGroup(sampler.getPropertyAsString("FIELD_DUBBO_GROUP"));
consumerAndService.setLoadBalance(sampler.getPropertyAsString("FIELD_DUBBO_LOADBALANCE"));
consumerAndService.setVersion(sampler.getPropertyAsString("FIELD_DUBBO_VERSION"));
consumerAndService.setTimeout(sampler.getPropertyAsString("FIELD_DUBBO_TIMEOUT"));
elementNode.setConsumerAndService(consumerAndService);
List<MethodArgument> methodArguments = Constants.getMethodArgs(sampler);
if (CollectionUtils.isNotEmpty(methodArguments)) {
List<KeyValue> methodArgs = new LinkedList<>();
methodArguments.forEach(item -> {
KeyValue keyValue = new KeyValue(item.getParamType(), item.getParamValue());
methodArgs.add(keyValue);
});
elementNode.setArgs(methodArgs);
}
List<MethodArgument> arguments = Constants.getAttachmentArgs(sampler);
if (CollectionUtils.isNotEmpty(arguments)) {
List<KeyValue> methodArgs = new LinkedList<>();
arguments.forEach(item -> {
KeyValue keyValue = new KeyValue(item.getParamType(), item.getParamValue());
methodArgs.add(keyValue);
});
elementNode.setAttachmentArgs(methodArgs);
}
}
/**
* 优先初始化数据池
*/
private void preInitPool(String projectId, HashTree hashTree) {
// 初始化已有数据池
initDataSource(projectId, ENV_NAME);
// 添加当前jmx 中新的数据池
preCreatePool(hashTree);
// 更新数据源
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
dataPools.getEnvConfig().setDatabaseConfigs(new ArrayList<>(dataPools.getDataSources().values()));
if (dataPools.getIsCreate()) {
dataPools.getTestEnvironmentWithBLOBs().setConfig(JSON.toJSONString(dataPools.getEnvConfig()));
String id = environmentService.add(dataPools.getTestEnvironmentWithBLOBs());
dataPools.setEnvId(id);
} else {
dataPools.getTestEnvironmentWithBLOBs().setConfig(JSON.toJSONString(dataPools.getEnvConfig()));
environmentService.update(dataPools.getTestEnvironmentWithBLOBs());
}
}
private void preCreatePool(HashTree tree) {
for (Object key : tree.keySet()) {
// JDBC 数据池
if (key instanceof DataSourceElement) {
DataSourceElement dataSourceElement = (DataSourceElement) key;
if (dataPools != null && dataPools.getDataSources() != null && dataPools.getDataSources().containsKey(dataSourceElement.getPropertyAsString("dataSource"))) {
DatabaseConfig config = dataPools.getDataSources().get(dataSourceElement.getPropertyAsString("dataSource"));
DatabaseConfig newConfig = new DatabaseConfig();
newConfig.setUsername(dataSourceElement.getPropertyAsString("username"));
newConfig.setPassword(dataSourceElement.getPropertyAsString("password"));
newConfig.setDriver(dataSourceElement.getPropertyAsString("driver"));
newConfig.setDbUrl(dataSourceElement.getPropertyAsString("dbUrl"));
newConfig.setName(dataSourceElement.getPropertyAsString("dataSource"));
newConfig.setPoolMax(dataSourceElement.getPropertyAsInt("poolMax"));
newConfig.setTimeout(dataSourceElement.getPropertyAsInt("timeout"));
newConfig.setId(config.getId());
dataPools.getDataSources().put(dataSourceElement.getPropertyAsString("dataSource"), newConfig);
} else {
DatabaseConfig newConfig = new DatabaseConfig();
newConfig.setId(UUID.randomUUID().toString());
newConfig.setUsername(dataSourceElement.getPropertyAsString("username"));
newConfig.setPassword(dataSourceElement.getPropertyAsString("password"));
newConfig.setDriver(dataSourceElement.getPropertyAsString("driver"));
newConfig.setDbUrl(dataSourceElement.getPropertyAsString("dbUrl"));
newConfig.setName(dataSourceElement.getPropertyAsString("dataSource"));
newConfig.setPoolMax(dataSourceElement.getPropertyAsInt("poolMax"));
newConfig.setTimeout(dataSourceElement.getPropertyAsInt("timeout"));
if (dataPools.getDataSources() == null) {
dataPools.setDataSources(new HashMap<>());
}
dataPools.getDataSources().put(dataSourceElement.getPropertyAsString("dataSource"), newConfig);
}
}
// 递归子项
HashTree node = tree.get(key);
if (node != null) {
preCreatePool(node);
}
}
}
private ImportPoolsDTO dataPools;
private void initDataSource(String projectId, String name) {
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
ApiTestEnvironmentExample example = new ApiTestEnvironmentExample();
example.createCriteria().andProjectIdEqualTo(projectId).andNameEqualTo(name);
// 这里的数据只有一条如果多条则有问题
List<ApiTestEnvironmentWithBLOBs> environments = environmentService.selectByExampleWithBLOBs(example);
dataPools = new ImportPoolsDTO();
if (CollectionUtils.isNotEmpty(environments)) {
dataPools.setIsCreate(false);
dataPools.setTestEnvironmentWithBLOBs(environments.get(0));
Map<String, DatabaseConfig> dataSources = new HashMap<>();
environments.forEach(environment -> {
if (environment != null && environment.getConfig() != null) {
EnvironmentConfig envConfig = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
dataPools.setEnvConfig(envConfig);
if (envConfig != null && CollectionUtils.isNotEmpty(envConfig.getDatabaseConfigs())) {
envConfig.getDatabaseConfigs().forEach(item -> {
dataSources.put(item.getName(), item);
});
}
}
dataPools.setEnvId(environment.getId());
dataPools.setDataSources(dataSources);
});
} else {
dataPools.setIsCreate(true);
ApiTestEnvironmentWithBLOBs apiTestEnvironmentWithBLOBs = new ApiTestEnvironmentWithBLOBs();
apiTestEnvironmentWithBLOBs.setId(UUID.randomUUID().toString());
dataPools.setEnvId(apiTestEnvironmentWithBLOBs.getId());
dataPools.setEnvConfig(new EnvironmentConfig());
apiTestEnvironmentWithBLOBs.setName(ENV_NAME);
apiTestEnvironmentWithBLOBs.setProjectId(projectId);
dataPools.setTestEnvironmentWithBLOBs(apiTestEnvironmentWithBLOBs);
}
}
private void convertJDBCSampler(MsJDBCSampler msJDBCSampler, JDBCSampler jdbcSampler) {
msJDBCSampler.setType("JDBCSampler");
msJDBCSampler.setName(jdbcSampler.getName());
msJDBCSampler.setProtocol("SQL");
msJDBCSampler.setQuery(jdbcSampler.getPropertyAsString("query"));
msJDBCSampler.setQueryTimeout(jdbcSampler.getPropertyAsInt("queryTimeout"));
msJDBCSampler.setResultVariable(jdbcSampler.getPropertyAsString("resultVariable"));
msJDBCSampler.setVariableNames(jdbcSampler.getPropertyAsString("variableNames"));
msJDBCSampler.setEnvironmentId(dataPools.getEnvId());
if (dataPools.getDataSources() != null && dataPools.getDataSources().get(jdbcSampler.getPropertyAsString("dataSource")) != null) {
msJDBCSampler.setDataSourceId(dataPools.getDataSources().get(jdbcSampler.getPropertyAsString("dataSource")).getId());
}
msJDBCSampler.setVariables(new LinkedList<>());
}
private void convertMsExtract(MsExtract extract, Object key) {
// 提取数据单独处理
extract.setType("Extract");
extract.setJson(new LinkedList<>());
extract.setRegex(new LinkedList<>());
extract.setXpath(new LinkedList<>());
if (key instanceof RegexExtractor) {
MsExtractRegex regex = new MsExtractRegex();
RegexExtractor regexExtractor = (RegexExtractor) key;
if (regexExtractor.useRequestHeaders()) {
regex.setUseHeaders("request_headers");
} else if (regexExtractor.useBody()) {
regex.setUseHeaders("false");
} else if (regexExtractor.useUnescapedBody()) {
regex.setUseHeaders("unescaped");
} else if (regexExtractor.useBodyAsDocument()) {
regex.setUseHeaders("as_document");
} else if (regexExtractor.useUrl()) {
regex.setUseHeaders("URL");
}
regex.setExpression(regexExtractor.getRegex());
regex.setVariable(regexExtractor.getRefName());
extract.setName(regexExtractor.getName());
extract.getRegex().add(regex);
} else if (key instanceof XPath2Extractor) {
XPath2Extractor xPath2Extractor = (XPath2Extractor) key;
MsExtractXPath xPath = new MsExtractXPath();
xPath.setVariable(xPath2Extractor.getRefName());
xPath.setExpression(xPath2Extractor.getXPathQuery());
extract.setName(xPath2Extractor.getName());
extract.getXpath().add(xPath);
} else if (key instanceof JSONPostProcessor) {
JSONPostProcessor jsonPostProcessor = (JSONPostProcessor) key;
MsExtractJSONPath jsonPath = new MsExtractJSONPath();
jsonPath.setVariable(jsonPostProcessor.getRefNames());
jsonPath.setExpression(jsonPostProcessor.getJsonPathExpressions());
extract.setName(jsonPostProcessor.getName());
extract.getJson().add(jsonPath);
}
}
private void convertMsAssertions(MsAssertions assertions, Object key) {
assertions.setJsonPath(new LinkedList<>());
assertions.setJsr223(new LinkedList<>());
assertions.setXpath2(new LinkedList<>());
assertions.setRegex(new LinkedList<>());
assertions.setDuration(new MsAssertionDuration());
assertions.setType("Assertions");
if (key instanceof ResponseAssertion) {
MsAssertionRegex assertionRegex = new MsAssertionRegex();
ResponseAssertion assertion = (ResponseAssertion) key;
assertionRegex.setDescription(assertion.getName());
assertionRegex.setAssumeSuccess(assertion.getAssumeSuccess());
if (assertion.getTestStrings() != null && !assertion.getTestStrings().isEmpty()) {
assertionRegex.setExpression(assertion.getTestStrings().get(0).getStringValue());
}
if (assertion.isTestFieldRequestData()) {
assertionRegex.setSubject("Response Data");
}
if (assertion.isTestFieldResponseCode()) {
assertionRegex.setSubject("Response Code");
}
if (assertion.isTestFieldRequestHeaders()) {
assertionRegex.setSubject("Response Headers");
}
assertions.setName(assertion.getName());
assertions.getRegex().add(assertionRegex);
} else if (key instanceof JSONPathAssertion) {
MsAssertionJsonPath assertionJsonPath = new MsAssertionJsonPath();
JSONPathAssertion jsonPathAssertion = (JSONPathAssertion) key;
assertionJsonPath.setDescription(jsonPathAssertion.getName());
assertionJsonPath.setExpression(jsonPathAssertion.getJsonPath());
assertionJsonPath.setExpect(jsonPathAssertion.getExpectedValue());
assertions.setName(jsonPathAssertion.getName());
assertions.getJsonPath().add(assertionJsonPath);
} else if (key instanceof XPath2Assertion) {
MsAssertionXPath2 assertionXPath2 = new MsAssertionXPath2();
XPath2Assertion xPath2Assertion = (XPath2Assertion) key;
assertionXPath2.setExpression(xPath2Assertion.getXPathString());
assertions.setName(xPath2Assertion.getName());
assertions.getXpath2().add(assertionXPath2);
} else if (key instanceof JSR223Assertion) {
MsAssertionJSR223 msAssertionJSR223 = new MsAssertionJSR223();
JSR223Assertion jsr223Assertion = (JSR223Assertion) key;
msAssertionJSR223.setName(jsr223Assertion.getName());
msAssertionJSR223.setDesc(jsr223Assertion.getName());
msAssertionJSR223.setScript(jsr223Assertion.getPropertyAsString("script"));
msAssertionJSR223.setScriptLanguage(jsr223Assertion.getPropertyAsString("scriptLanguage"));
assertions.setName(jsr223Assertion.getName());
assertions.getJsr223().add(msAssertionJSR223);
} else if (key instanceof DurationAssertion) {
MsAssertionDuration assertionDuration = new MsAssertionDuration();
DurationAssertion durationAssertion = (DurationAssertion) key;
assertionDuration.setValue(durationAssertion.getProperty("DurationAssertion.duration").getIntValue());
assertions.setName(durationAssertion.getName());
assertions.setDuration(assertionDuration);
}
}
/**
* 把节点对象转成XML不然执行时无法正常转换
*
* @param obj
* @return
*/
public static String objToXml(Object obj) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
SaveService.saveElement(obj, baos);
return baos.toString();
} catch (Exception e) {
e.printStackTrace();
LogUtil.warn("HashTree error, can't log jmx scenarioDefinition");
}
return null;
}
private void jmterHashTree(HashTree tree, MsTestElement scenario) {
for (Object key : tree.keySet()) {
MsTestElement elementNode;
if (CollectionUtils.isEmpty(scenario.getHashTree())) {
scenario.setHashTree(new LinkedList<>());
}
// 测试计划
if (key instanceof TestPlan) {
scenario.setName(((TestPlan) key).getName());
elementNode = new MsJmeterElement();
elementNode.setName(((TestPlan) key).getName());
((MsJmeterElement) elementNode).setJmeterElement(objToXml(key));
}
// 线程组
else if (key instanceof ThreadGroup) {
elementNode = new MsScenario(((ThreadGroup) key).getName());
}
// HTTP请求
else if (key instanceof HTTPSamplerProxy) {
elementNode = new MsHTTPSamplerProxy();
((MsHTTPSamplerProxy) elementNode).setBody(new Body());
convertHttpSampler((MsHTTPSamplerProxy) elementNode, (HTTPSamplerProxy) key);
}
// TCP请求
else if (key instanceof TCPSampler) {
elementNode = new MsTCPSampler();
TCPSampler tcpSampler = (TCPSampler) key;
convertTCPSampler((MsTCPSampler) elementNode, tcpSampler);
}
// DUBBO请求
else if (key instanceof DubboSample) {
DubboSample sampler = (DubboSample) key;
elementNode = new MsDubboSampler();
convertDubboSample((MsDubboSampler) elementNode, sampler);
}
// JDBC请求
else if (key instanceof JDBCSampler) {
elementNode = new MsJDBCSampler();
JDBCSampler jdbcSampler = (JDBCSampler) key;
convertJDBCSampler((MsJDBCSampler) elementNode, jdbcSampler);
}
// JSR自定义脚本
else if (key instanceof JSR223Sampler) {
JSR223Sampler jsr223Sampler = (JSR223Sampler) key;
elementNode = new MsJSR223Processor();
BeanUtils.copyBean(elementNode, jsr223Sampler);
((MsJSR223Processor) elementNode).setScript(jsr223Sampler.getPropertyAsString("script"));
((MsJSR223Processor) elementNode).setScriptLanguage(jsr223Sampler.getPropertyAsString("scriptLanguage"));
}
// 后置脚本
else if (key instanceof JSR223PostProcessor) {
JSR223PostProcessor jsr223Sampler = (JSR223PostProcessor) key;
elementNode = new MsJSR223PostProcessor();
BeanUtils.copyBean(elementNode, jsr223Sampler);
((MsJSR223PostProcessor) elementNode).setScript(jsr223Sampler.getPropertyAsString("script"));
((MsJSR223PostProcessor) elementNode).setScriptLanguage(jsr223Sampler.getPropertyAsString("scriptLanguage"));
}
// 前置脚本
else if (key instanceof JSR223PreProcessor) {
JSR223PreProcessor jsr223Sampler = (JSR223PreProcessor) key;
elementNode = new MsJSR223PreProcessor();
BeanUtils.copyBean(elementNode, jsr223Sampler);
((MsJSR223PreProcessor) elementNode).setScript(jsr223Sampler.getPropertyAsString("script"));
((MsJSR223PreProcessor) elementNode).setScriptLanguage(jsr223Sampler.getPropertyAsString("scriptLanguage"));
}
// 断言规则
else if (key instanceof ResponseAssertion || key instanceof JSONPathAssertion || key instanceof XPath2Assertion || key instanceof JSR223Assertion || key instanceof DurationAssertion) {
elementNode = new MsAssertions();
convertMsAssertions((MsAssertions) elementNode, key);
}
// 提取参数
else if (key instanceof RegexExtractor || key instanceof XPath2Extractor || key instanceof JSONPostProcessor) {
elementNode = new MsExtract();
convertMsExtract((MsExtract) elementNode, key);
}
// 定时器
else if (key instanceof ConstantTimer) {
elementNode = new MsConstantTimer();
BeanUtils.copyBean(elementNode, key);
elementNode.setType("ConstantTimer");
}
// IF条件控制器
else if (key instanceof IfController) {
elementNode = new MsIfController();
BeanUtils.copyBean(elementNode, key);
elementNode.setType("IfController");
}
// 次数循环控制器
else if (key instanceof LoopController) {
elementNode = new MsLoopController();
BeanUtils.copyBean(elementNode, key);
elementNode.setType("LoopController");
((MsLoopController) elementNode).setLoopType(LoopConstants.LOOP_COUNT.name());
LoopController loopController = (LoopController) key;
CountController countController = new CountController();
countController.setLoops(loopController.getLoops());
countController.setProceed(true);
((MsLoopController) elementNode).setCountController(countController);
}
// While循环控制器
else if (key instanceof WhileController) {
elementNode = new MsLoopController();
BeanUtils.copyBean(elementNode, key);
elementNode.setType("LoopController");
((MsLoopController) elementNode).setLoopType(LoopConstants.WHILE.name());
WhileController whileController = (WhileController) key;
MsWhileController countController = new MsWhileController();
countController.setValue(whileController.getCondition());
((MsLoopController) elementNode).setWhileController(countController);
}
// Foreach 循环控制器
else if (key instanceof ForeachController) {
elementNode = new MsLoopController();
BeanUtils.copyBean(elementNode, key);
elementNode.setType("LoopController");
((MsLoopController) elementNode).setLoopType(LoopConstants.FOREACH.name());
ForeachController foreachController = (ForeachController) key;
MsForEachController countController = new MsForEachController();
countController.setInputVal(foreachController.getInputValString());
countController.setReturnVal(foreachController.getReturnValString());
((MsLoopController) elementNode).setForEachController(countController);
}
// 平台不能识别的Jmeter步骤
else {
elementNode = new MsJmeterElement();
elementNode.setType("JmeterElement");
TestElement testElement = (TestElement) key;
elementNode.setName(testElement.getName());
((MsJmeterElement) elementNode).setJmeterElement(objToXml(key));
}
elementNode.setEnable(((TestElement) key).isEnabled());
elementNode.setResourceId(UUID.randomUUID().toString());
elementNode.setId(UUID.randomUUID().toString());
elementNode.setIndex(scenario.getHashTree().size() + 1 + "");
// 提取参数
if (elementNode instanceof MsExtract) {
if (CollectionUtils.isNotEmpty(((MsExtract) elementNode).getJson()) || CollectionUtils.isNotEmpty(((MsExtract) elementNode).getRegex()) || CollectionUtils.isNotEmpty(((MsExtract) elementNode).getXpath())) {
scenario.getHashTree().add(elementNode);
}
}
//断言规则
else if (elementNode instanceof MsAssertions) {
if (CollectionUtils.isNotEmpty(((MsAssertions) elementNode).getRegex()) || CollectionUtils.isNotEmpty(((MsAssertions) elementNode).getJsonPath())
|| CollectionUtils.isNotEmpty(((MsAssertions) elementNode).getJsr223()) || CollectionUtils.isNotEmpty(((MsAssertions) elementNode).getXpath2()) || ((MsAssertions) elementNode).getDuration() != null) {
scenario.getHashTree().add(elementNode);
}
}
// 争取其他请求
else {
scenario.getHashTree().add(elementNode);
}
// 递归子项
HashTree node = tree.get(key);
if (node != null) {
jmterHashTree(node, elementNode);
}
}
}
}

View File

@ -0,0 +1,205 @@
package io.metersphere.api.dto.automation.parse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.definition.request.MsScenario;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.processors.pre.MsJSR223PreProcessor;
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import io.metersphere.api.dto.definition.request.variable.ScenarioVariable;
import io.metersphere.api.dto.parse.postman.*;
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.base.domain.ApiScenarioModule;
import io.metersphere.base.domain.ApiScenarioWithBLOBs;
import io.metersphere.commons.constants.MsRequestBodyType;
import io.metersphere.commons.constants.PostmanRequestBodyMode;
import io.metersphere.commons.constants.VariableTypeConstants;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
public class MsPostmanParser extends ScenarioImportAbstractParser {
@Override
public ScenarioImport parse(InputStream source, ApiTestImportRequest request) {
String testStr = getApiTestStr(source);
PostmanCollection postmanCollection = JSON.parseObject(testStr, PostmanCollection.class);
List<PostmanKeyValue> variables = postmanCollection.getVariable();
ScenarioImport scenarioImport = new ScenarioImport();
// 场景步骤
LinkedList<MsTestElement> apiScenarioWithBLOBs = new LinkedList<>();
PostmanCollectionInfo info = postmanCollection.getInfo();
ApiScenarioWithBLOBs scenario = new ApiScenarioWithBLOBs();
scenario.setName(info.getName());
MsScenario msScenario = new MsScenario();
msScenario.setName(info.getName());
this.projectId = request.getProjectId();
parseItem(postmanCollection.getItem(), variables, msScenario, apiScenarioWithBLOBs);
// 生成场景对象
List<ApiScenarioWithBLOBs> scenarioWithBLOBs = new LinkedList<>();
paseScenario(scenarioWithBLOBs, msScenario, request);
scenarioImport.setData(scenarioWithBLOBs);
return scenarioImport;
}
private void paseScenario(List<ApiScenarioWithBLOBs> scenarioWithBLOBsList, MsScenario msScenario, ApiTestImportRequest request) {
ApiScenarioModule module = buildModule(getSelectModule(request.getModuleId()), msScenario.getName());
ApiScenarioWithBLOBs scenarioWithBLOBs = new ApiScenarioWithBLOBs();
scenarioWithBLOBs.setName(msScenario.getName());
scenarioWithBLOBs.setProjectId(request.getProjectId());
if (msScenario != null && CollectionUtils.isNotEmpty(msScenario.getHashTree())) {
scenarioWithBLOBs.setStepTotal(msScenario.getHashTree().size());
}
if (module != null) {
scenarioWithBLOBs.setApiScenarioModuleId(module.getId());
scenarioWithBLOBs.setModulePath("/" + module.getName());
}
scenarioWithBLOBs.setId(UUID.randomUUID().toString());
scenarioWithBLOBs.setScenarioDefinition(JSON.toJSONString(msScenario));
scenarioWithBLOBsList.add(scenarioWithBLOBs);
}
private void parseItem(List<PostmanItem> items, List<PostmanKeyValue> variables, MsScenario scenario, LinkedList<MsTestElement> results) {
for (PostmanItem item : items) {
List<PostmanItem> childItems = item.getItem();
if (childItems != null) {
parseItem(childItems, variables, scenario, results);
} else {
MsHTTPSamplerProxy request = parsePostman(item);
if (request != null) {
results.add(request);
}
}
}
scenario.setVariables(parseScenarioVariable(variables));
scenario.setHashTree(results);
}
private MsHTTPSamplerProxy parsePostman(PostmanItem requestItem) {
PostmanRequest requestDesc = requestItem.getRequest();
if (requestDesc == null) {
return null;
}
requestDesc.getAuth(); // todo 认证方式等待优化
PostmanUrl url = requestDesc.getUrl();
MsHTTPSamplerProxy request = buildRequest(requestItem.getName(), url.getRaw(), requestDesc.getMethod());
if (StringUtils.isNotBlank(request.getPath())) {
String path = request.getPath().split("\\?")[0];
path = path.replace("{{", "${");
path = path.replace("}}", "}");
request.setPath(path);
} else {
request.setPath("/");
}
parseBody(request.getBody(), requestDesc);
request.setArguments(parseKeyValue(url.getQuery()));
request.setHeaders(parseKeyValue(requestDesc.getHeader()));
addBodyHeader(request);
addPreScript(request, requestItem.getEvent());
return request;
}
private void addPreScript(MsHTTPSamplerProxy request, List<PostmanEvent> event) {
if (request != null && CollectionUtils.isNotEmpty(event)) {
StringBuilder scriptStr = new StringBuilder();
event = event.stream()
.filter(item -> item.getScript() != null)
.collect(Collectors.toList());
event.forEach(item -> {
PostmanScript script = item.getScript();
if (script != null) {
List<String> exec = script.getExec();
if (CollectionUtils.isNotEmpty(exec)) {
exec.forEach(col -> {
if (StringUtils.isNotEmpty(col)) {
scriptStr.append(col + "/n");
}
});
}
}
});
if (StringUtils.isNotBlank(scriptStr)) {
MsJSR223PreProcessor jsr223PreProcessor = new MsJSR223PreProcessor();
jsr223PreProcessor.setName("JSR223PreProcessor");
jsr223PreProcessor.setScriptLanguage("javascript");
jsr223PreProcessor.setScript(scriptStr.toString());
LinkedList<MsTestElement> hashTree = new LinkedList<>();
hashTree.add(jsr223PreProcessor);
request.setHashTree(hashTree);
}
}
}
private List<KeyValue> parseKeyValue(List<PostmanKeyValue> postmanKeyValues) {
if (postmanKeyValues == null) {
return null;
}
List<KeyValue> keyValues = new ArrayList<>();
postmanKeyValues.forEach(item -> keyValues.add(new KeyValue(item.getKey(), item.getValue(), item.getDescription(), item.getContentType())));
return keyValues;
}
private List<ScenarioVariable> parseScenarioVariable(List<PostmanKeyValue> postmanKeyValues) {
if (postmanKeyValues == null) {
return null;
}
List<ScenarioVariable> keyValues = new ArrayList<>();
postmanKeyValues.forEach(item -> keyValues.add(new ScenarioVariable(item.getKey(), item.getValue(), item.getDescription(), VariableTypeConstants.CONSTANT.name())));
return keyValues;
}
private void parseBody(Body body, PostmanRequest requestDesc) {
JSONObject postmanBody = requestDesc.getBody();
if (postmanBody == null) {
return;
}
String bodyMode = postmanBody.getString("mode");
if (StringUtils.isNotEmpty(bodyMode) && StringUtils.equals(bodyMode, PostmanRequestBodyMode.RAW.value())) {
parseRawBody(body, postmanBody, bodyMode);
} else if (StringUtils.isNotEmpty(bodyMode) && StringUtils.equalsAny(bodyMode, PostmanRequestBodyMode.FORM_DATA.value(), PostmanRequestBodyMode.URLENCODED.value())) {
List<PostmanKeyValue> postmanKeyValues = JSON.parseArray(postmanBody.getString(bodyMode), PostmanKeyValue.class);
body.setKvs(parseKeyValue(postmanKeyValues));
if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.FORM_DATA.value())) {
body.setType(Body.FORM_DATA);
} else if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.URLENCODED.value())) {
body.setType(Body.WWW_FROM);
}
} else if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.FILE.value())) {
body.setType(Body.BINARY);
body.setKvs(new ArrayList<>());
}
}
private void parseRawBody(Body body, JSONObject postmanBody, String bodyMode) {
body.setRaw(postmanBody.getString(bodyMode));
body.setType(MsRequestBodyType.RAW.value());
JSONObject options = postmanBody.getJSONObject("options");
if (options != null) {
JSONObject raw = options.getJSONObject(PostmanRequestBodyMode.RAW.value());
if (raw != null && raw.getString("language") != null) {
String bodyType = "";
switch (raw.getString("language")) {
case "json":
bodyType = Body.JSON;
break;
case "xml":
bodyType = Body.XML;
break;
default:
bodyType = Body.RAW;
}
body.setType(bodyType);
}
}
}
}

View File

@ -0,0 +1,65 @@
package io.metersphere.api.dto.automation.parse;
import com.alibaba.fastjson.JSON;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.base.domain.ApiScenarioModule;
import io.metersphere.base.domain.ApiScenarioWithBLOBs;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
public class MsScenarioParser extends ScenarioImportAbstractParser {
@Override
public ScenarioImport parse(InputStream source, ApiTestImportRequest request) {
String testStr = getApiTestStr(source);
this.projectId = request.getProjectId();
ScenarioImport scenarioImport = parseMsFormat(testStr, request);
return scenarioImport;
}
private ScenarioImport parseMsFormat(String testStr, ApiTestImportRequest importRequest) {
ScenarioImport apiDefinitionImport = JSON.parseObject(testStr, ScenarioImport.class);
List<ApiScenarioWithBLOBs> data = apiDefinitionImport.getData();
if (CollectionUtils.isNotEmpty(data)) {
data.forEach(item -> {
if (StringUtils.isBlank(item.getModulePath())) {
item.setApiScenarioModuleId(null);
}
parseModule(item, importRequest);
item.setId(UUID.randomUUID().toString());
item.setProjectId(this.projectId);
});
}
return apiDefinitionImport;
}
private void parseModule(ApiScenarioWithBLOBs apiDefinition, ApiTestImportRequest importRequest) {
String modulePath = apiDefinition.getModulePath();
if (StringUtils.isEmpty(modulePath)) {
return;
}
if (modulePath.startsWith("/")) {
modulePath = modulePath.substring(1, modulePath.length());
}
if (modulePath.endsWith("/")) {
modulePath = modulePath.substring(0, modulePath.length() - 1);
}
List<String> modules = Arrays.asList(modulePath.split("/"));
ApiScenarioModule parent = getSelectModule(importRequest.getModuleId());
Iterator<String> iterator = modules.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
parent = buildModule(parent, item);
if (!iterator.hasNext()) {
apiDefinition.setApiScenarioModuleId(parent.getId());
}
}
}
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.automation.parse;
import io.metersphere.base.domain.ApiScenarioWithBLOBs;
import lombok.Data;
import java.util.List;
@Data
public class ScenarioImport {
private String projectid;
private List<ApiScenarioWithBLOBs> data;
}

View File

@ -0,0 +1,169 @@
package io.metersphere.api.dto.automation.parse;
import io.metersphere.api.dto.automation.ApiScenarioModuleDTO;
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.request.RequestType;
import io.metersphere.api.service.ApiScenarioModuleService;
import io.metersphere.base.domain.ApiScenarioModule;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public abstract class ScenarioImportAbstractParser implements ScenarioImportParser {
protected ApiScenarioModuleService apiModuleService;
protected String projectId;
protected ApiScenarioModule getSelectModule(String moduleId) {
apiModuleService = CommonBeanFactory.getBean(ApiScenarioModuleService.class);
if (StringUtils.isNotBlank(moduleId) && !StringUtils.equals("root", moduleId)) {
ApiScenarioModule module = new ApiScenarioModule();
ApiScenarioModuleDTO moduleDTO = apiModuleService.getNode(moduleId);
if (moduleDTO != null) {
BeanUtils.copyBean(module, moduleDTO);
}
return module;
}
return null;
}
protected ApiScenarioModule buildModule(ApiScenarioModule parentModule, String name) {
apiModuleService = CommonBeanFactory.getBean(ApiScenarioModuleService.class);
ApiScenarioModule module;
if (parentModule != null) {
module = apiModuleService.getNewModule(name, this.projectId, parentModule.getLevel() + 1);
module.setParentId(parentModule.getId());
} else {
module = apiModuleService.getNewModule(name, this.projectId, 1);
}
createModule(module);
return module;
}
protected void createModule(ApiScenarioModule module) {
List<ApiScenarioModule> apiModules = apiModuleService.selectSameModule(module);
if (CollectionUtils.isEmpty(apiModules)) {
apiModuleService.addNode(module);
} else {
module.setId(apiModules.get(0).getId());
}
}
protected String getApiTestStr(InputStream source) {
StringBuilder testStr = null;
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(source, StandardCharsets.UTF_8))) {
testStr = new StringBuilder();
String inputStr;
while ((inputStr = bufferedReader.readLine()) != null) {
testStr.append(inputStr);
}
} catch (Exception e) {
MSException.throwException(e.getMessage());
LogUtil.error(e.getMessage(), e);
} finally {
try {
if (source != null) {
source.close();
}
} catch (IOException e) {
MSException.throwException(e.getMessage());
LogUtil.error(e.getMessage(), e);
}
}
return testStr.toString();
}
private String formatPath(String url) {
try {
URL urlObject = new URL(url);
StringBuilder pathBuffer = new StringBuilder(urlObject.getPath());
if (StringUtils.isNotEmpty(urlObject.getQuery())) {
pathBuffer.append("?").append(urlObject.getQuery());
}
return pathBuffer.toString();
} catch (Exception ex) {
return url;
}
}
protected MsHTTPSamplerProxy buildRequest(String name, String path, String method) {
MsHTTPSamplerProxy request = new MsHTTPSamplerProxy();
request.setName(name);
// 路径去掉域名/IP 地址保留方法名称及参数
request.setPath(formatPath(path));
request.setMethod(method);
request.setProtocol(RequestType.HTTP);
request.setId(UUID.randomUUID().toString());
request.setHeaders(new ArrayList<>());
request.setArguments(new ArrayList<>());
request.setRest(new ArrayList<>());
request.setAuthManager(null);
Body body = new Body();
body.initKvs();
body.initBinary();
request.setBody(body);
return request;
}
protected void addHeader(List<KeyValue> headers, String key, String value, String description, String contentType, boolean required) {
boolean hasContentType = false;
for (KeyValue header : headers) {
if (StringUtils.equalsIgnoreCase(header.getName(), key)) {
hasContentType = true;
}
}
if (!hasContentType) {
headers.add(new KeyValue(key, value, description, contentType, required));
}
}
protected void addHeader(List<KeyValue> headers, String key, String value) {
addHeader(headers, key, value, "", "", true);
}
protected void addContentType(List<KeyValue> headers, String contentType) {
addHeader(headers, "Content-Type", contentType);
}
protected void addBodyHeader(MsHTTPSamplerProxy request) {
String contentType = "";
if (request.getBody() != null && StringUtils.isNotBlank(request.getBody().getType())) {
switch (request.getBody().getType()) {
case Body.JSON:
contentType = "application/json";
break;
case Body.WWW_FROM:
contentType = "application/x-www-form-urlencoded";
break;
case Body.XML:
contentType = "application/xml";
break;
case Body.BINARY:
contentType = "application/octet-stream";
break;
}
List<KeyValue> headers = request.getHeaders();
if (headers == null) {
headers = new ArrayList<>();
request.setHeaders(headers);
}
addContentType(request.getHeaders(), contentType);
}
}
}

View File

@ -0,0 +1,9 @@
package io.metersphere.api.dto.automation.parse;
import io.metersphere.api.dto.ApiTestImportRequest;
import java.io.InputStream;
public interface ScenarioImportParser {
ScenarioImport parse(InputStream source, ApiTestImportRequest request);
}

View File

@ -0,0 +1,17 @@
package io.metersphere.api.dto.automation.parse;
import io.metersphere.commons.constants.ApiImportPlatform;
import org.apache.commons.lang3.StringUtils;
public class ScenarioImportParserFactory {
public static ScenarioImportParser getImportParser(String platform) {
if (StringUtils.equals(ApiImportPlatform.Metersphere.name(), platform)) {
return new MsScenarioParser();
} else if (StringUtils.equals(ApiImportPlatform.Postman.name(), platform)) {
return new MsPostmanParser();
} else if (StringUtils.equals(ApiImportPlatform.Jmeter.name(), platform)) {
return new MsJmeterParser();
}
return null;
}
}

View File

@ -36,8 +36,6 @@ import java.util.stream.Collectors;
public class MsScenario extends MsTestElement {
private String type = "scenario";
@JSONField(ordinal = 20)
private String name;
@JSONField(ordinal = 21)
private String referenced;
@ -56,6 +54,16 @@ public class MsScenario extends MsTestElement {
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
public MsScenario() {
}
public MsScenario(String name) {
if (StringUtils.isEmpty(name)) {
this.setName("Scenario");
}
this.setName(name);
}
@Override
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
if (!this.isEnable()) {
@ -77,7 +85,7 @@ public class MsScenario extends MsTestElement {
}
}
config.setStep(this.name);
config.setStep(this.getName());
config.setStepType("SCENARIO");
config.setEnableCookieShare(enableCookieShare);
if (StringUtils.isNotEmpty(environmentId)) {
@ -131,7 +139,7 @@ public class MsScenario extends MsTestElement {
private Arguments arguments(ParameterConfig config) {
Arguments arguments = new Arguments();
arguments.setEnabled(true);
arguments.setName(name + "Variables");
arguments.setName(this.getName() + "Variables");
arguments.setProperty(TestElement.TEST_CLASS, Arguments.class.getName());
arguments.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ArgumentsPanel"));
if (CollectionUtils.isNotEmpty(this.getVariables())) {

View File

@ -22,6 +22,7 @@ import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import io.metersphere.api.dto.definition.request.sampler.MsJDBCSampler;
import io.metersphere.api.dto.definition.request.sampler.MsTCPSampler;
import io.metersphere.api.dto.definition.request.timer.MsConstantTimer;
import io.metersphere.api.dto.definition.request.unknown.MsJmeterElement;
import io.metersphere.api.dto.definition.request.variable.ScenarioVariable;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
@ -70,11 +71,12 @@ import java.util.stream.Collectors;
@JsonSubTypes.Type(value = MsIfController.class, name = "IfController"),
@JsonSubTypes.Type(value = MsScenario.class, name = "scenario"),
@JsonSubTypes.Type(value = MsLoopController.class, name = "LoopController"),
@JsonSubTypes.Type(value = MsJmeterElement.class, name = "JmeterElement"),
})
@JSONType(seeAlso = {MsHTTPSamplerProxy.class, MsHeaderManager.class, MsJSR223Processor.class, MsJSR223PostProcessor.class,
MsJSR223PreProcessor.class, MsTestPlan.class, MsThreadGroup.class, AuthManager.class, MsAssertions.class,
MsExtract.class, MsTCPSampler.class, MsDubboSampler.class, MsJDBCSampler.class, MsConstantTimer.class, MsIfController.class, MsScenario.class, MsLoopController.class}, typeKey = "type")
MsExtract.class, MsTCPSampler.class, MsDubboSampler.class, MsJDBCSampler.class, MsConstantTimer.class, MsIfController.class, MsScenario.class, MsLoopController.class, MsJmeterElement.class}, typeKey = "type")
@Data
public abstract class MsTestElement {
private String type;
@ -105,10 +107,14 @@ public abstract class MsTestElement {
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
// 公共环境逐层传递如果自身有环境 以自身引用环境为准否则以公共环境作为请求环境
/**
* todo 公共环境逐层传递如果自身有环境 以自身引用环境为准否则以公共环境作为请求环境
*/
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
for (MsTestElement el : hashTree) {
el.toHashTree(tree, el.hashTree, config);
if (CollectionUtils.isNotEmpty(hashTree)) {
for (MsTestElement el : hashTree) {
el.toHashTree(tree, el.hashTree, config);
}
}
}

View File

@ -93,14 +93,16 @@ public class MsDubboSampler extends MsTestElement {
Constants.setInterfaceName(this.get_interface(), sampler);
Constants.setMethod(this.getMethod(), sampler);
List<MethodArgument> methodArgs = this.getArgs().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable)
.map(keyValue -> new MethodArgument(keyValue.getName(), keyValue.getValue())).collect(Collectors.toList());
Constants.setMethodArgs(methodArgs, sampler);
List<MethodArgument> attachmentArgs = this.getAttachmentArgs().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable)
.map(keyValue -> new MethodArgument(keyValue.getName(), keyValue.getValue())).collect(Collectors.toList());
Constants.setAttachmentArgs(attachmentArgs, sampler);
if (CollectionUtils.isNotEmpty(this.getArgs())) {
List<MethodArgument> methodArgs = this.getArgs().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable)
.map(keyValue -> new MethodArgument(keyValue.getName(), keyValue.getValue())).collect(Collectors.toList());
Constants.setMethodArgs(methodArgs, sampler);
}
if (CollectionUtils.isNotEmpty(this.getAttachmentArgs())) {
List<MethodArgument> attachmentArgs = this.getAttachmentArgs().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable)
.map(keyValue -> new MethodArgument(keyValue.getName(), keyValue.getValue())).collect(Collectors.toList());
Constants.setAttachmentArgs(attachmentArgs, sampler);
}
return sampler;
}

View File

@ -148,6 +148,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
String envPath = StringUtils.equals(urlObject.getPath(), "/") ? "" : urlObject.getPath();
if (StringUtils.isNotBlank(this.getPath()) && !isUrl) {
envPath += this.getPath();
sampler.setPath(envPath);
}
if (CollectionUtils.isNotEmpty(this.getRest()) && this.isRest()) {
envPath = getRestParameters(URLDecoder.decode(envPath, "UTF-8"));

View File

@ -0,0 +1,65 @@
package io.metersphere.api.dto.definition.request.unknown;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.ParameterConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jmeter.threads.ThreadGroup;
import org.apache.jorphan.collections.HashTree;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
/**
* 暂时存放所有未知的Jmeter Element对象
*/
@Data
@EqualsAndHashCode(callSuper = true)
@JSONType(typeName = "JmeterElement")
public class MsJmeterElement extends MsTestElement {
private String type = "JmeterElement";
private String jmeterElement;
@Override
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, ParameterConfig config) {
if (!this.isEnable()) {
return;
}
try {
InputStream inputSource = getStrToStream(jmeterElement);
if (inputSource != null) {
Object scriptWrapper = SaveService.loadElement(inputSource);
HashTree elementTree = tree;
if (!(scriptWrapper instanceof TestPlan) && !(scriptWrapper instanceof ThreadGroup)) {
elementTree = tree.add(scriptWrapper);
}
if (CollectionUtils.isNotEmpty(hashTree)) {
for (MsTestElement el : hashTree) {
el.toHashTree(elementTree, el.getHashTree(), config);
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static InputStream getStrToStream(String sInputString) {
if (StringUtils.isNotEmpty(sInputString)) {
try {
ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(sInputString.getBytes());
return tInputStringStream;
} catch (Exception ex) {
ex.printStackTrace();
}
}
return null;
}
}

View File

@ -40,6 +40,13 @@ public class ScenarioVariable {
private String minNumber;
private String maxNumber;
public ScenarioVariable(String key, String value, String description, String type) {
this.name = key;
this.value = value;
this.description = description;
this.type = type;
}
public boolean isConstantValid() {
if (StringUtils.equals(this.type, VariableTypeConstants.CONSTANT.name()) && StringUtils.isNotEmpty(name) && StringUtils.isNotEmpty(value)) {
return true;

View File

@ -1,15 +0,0 @@
package io.metersphere.api.parse;
import io.metersphere.commons.constants.ApiImportPlatform;
import org.apache.commons.lang3.StringUtils;
public class ApiScenarioImportParserFactory {
public static ApiImportParser getApiImportParser(String platform) {
if (StringUtils.equals(ApiImportPlatform.Metersphere.name(), platform)) {
return new ScenarioMsParser();
} else if (StringUtils.equals(ApiImportPlatform.Postman.name(), platform)) {
return new ScenarioPostmanParser();
}
return null;
}
}

View File

@ -1,40 +0,0 @@
package io.metersphere.api.parse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.dto.definition.request.MsScenario;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import io.metersphere.api.dto.parse.postman.PostmanCollection;
import io.metersphere.base.domain.ApiDefinitionWithBLOBs;
import io.metersphere.base.domain.ApiModule;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ScenarioPostmanParser extends PostmanParser {
@Override
public ApiDefinitionImport parse(InputStream source, ApiTestImportRequest request) {
this.projectId = request.getProjectId();
ApiDefinitionImport apiImport = new ApiDefinitionImport();
List<ApiDefinitionWithBLOBs> results = new ArrayList<>();
PostmanCollection postmanCollection = JSON.parseObject(getApiTestStr(source), PostmanCollection.class);
parseItem(postmanCollection.getItem(), postmanCollection.getVariable(), results, null, false);
MsScenario msScenario = new MsScenario();
LinkedList<MsTestElement> msHTTPSamplerProxies = new LinkedList<>();
results.forEach(res -> {
msHTTPSamplerProxies.add(JSONObject.parseObject(res.getRequest(), MsHTTPSamplerProxy.class));
});
msScenario.setHashTree(msHTTPSamplerProxies);
msScenario.setType("scenario");
msScenario.setName(postmanCollection.getInfo().getName());
apiImport.setScenarioDefinition(msScenario);
return apiImport;
}
}

View File

@ -32,6 +32,9 @@ import io.metersphere.track.service.TestCaseService;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.constants.CommonConstants;
import org.aspectj.util.FileUtil;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@ -457,4 +460,63 @@ public class APITestService {
copyBodyFiles(test.getId(), sourceId);
});
}
public String updateJmxString(String jmxString,String testName,boolean updateHTTPSamplerProxyName) {
try {
//将ThreadGroup的testname改为接口名称
Document doc = DocumentHelper.parseText(jmxString);// 获取可续保保单列表报文模板
Element root = doc.getRootElement();
Element rootHashTreeElement = root.element("hashTree");
Element innerHashTreeElement = rootHashTreeElement.elements("hashTree").get(0);
Element theadGroupElement = innerHashTreeElement.elements("ThreadGroup").get(0);
theadGroupElement.attribute("testname").setText(testName);
List<Element> thirdHashTreeElementList = innerHashTreeElement.elements("hashTree");
for (Element element : thirdHashTreeElementList) {
if(updateHTTPSamplerProxyName){
List<Element> sampleProxyElementList = element.elements("HTTPSamplerProxy");
for (Element itemElement : sampleProxyElementList) {
itemElement.attribute("testname").setText(testName);
}
}
//检查有没有自定义参数
List<Element> scriptHashTreeElementList = element.elements("hashTree");
for (Element scriptHashTreeElement : scriptHashTreeElementList) {
boolean isRemove = false;
List<Element> removeElement = new ArrayList<>();
List<Element> scriptElementItemList = scriptHashTreeElement.elements();
for (Element hashTreeItemElement : scriptElementItemList) {
String className = hashTreeItemElement.attributeValue("testclass");
String qname = hashTreeItemElement.getQName().getName();
if (isRemove) {
if (org.apache.commons.lang3.StringUtils.equals("hashTree", qname)) {
removeElement.add(hashTreeItemElement);
}
}
isRemove = false;
if (org.apache.commons.lang3.StringUtils.equals(className, "JSR223PostProcessor")) {
List<Element> scriptElements = hashTreeItemElement.elements("stringProp");
for (Element scriptElement : scriptElements) {
String scriptName = scriptElement.attributeValue("name");
String contentValue = scriptElement.getStringValue();
if ("script".equals(scriptName) && contentValue.startsWith("io.metersphere.api.jmeter.JMeterVars.addVars")) {
isRemove = true;
removeElement.add(hashTreeItemElement);
}
}
}
}
for (Element itemElement : removeElement) {
scriptHashTreeElement.remove(itemElement);
}
}
}
jmxString = root.asXML();
} catch (Exception e) {
e.printStackTrace();
}
return jmxString;
}
}

View File

@ -9,16 +9,15 @@ import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.DeleteAPIReportRequest;
import io.metersphere.api.dto.JmxInfoDTO;
import io.metersphere.api.dto.automation.*;
import io.metersphere.api.dto.automation.parse.ScenarioImport;
import io.metersphere.api.dto.automation.parse.ScenarioImportParser;
import io.metersphere.api.dto.automation.parse.ScenarioImportParserFactory;
import io.metersphere.api.dto.datacount.ApiDataCountResult;
import io.metersphere.api.dto.definition.RunDefinitionRequest;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.dto.definition.request.*;
import io.metersphere.api.dto.definition.request.variable.ScenarioVariable;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.parse.ApiImportParser;
import io.metersphere.api.parse.ApiScenarioImportParserFactory;
import io.metersphere.api.parse.ScenarioPostmanParser;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiScenarioMapper;
import io.metersphere.base.mapper.ApiScenarioReportMapper;
@ -46,9 +45,6 @@ import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.jorphan.collections.HashTree;
import org.apache.jorphan.collections.ListedHashTree;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -67,6 +63,8 @@ public class ApiAutomationService {
@Resource
private ApiScenarioMapper apiScenarioMapper;
@Resource
private APITestService apiTestService;
@Resource
private ExtApiScenarioMapper extApiScenarioMapper;
@Resource
private TestPlanApiScenarioMapper testPlanApiScenarioMapper;
@ -517,6 +515,7 @@ public class ApiAutomationService {
ParameterConfig config = new ParameterConfig();
config.setConfig(envConfig);
HashTree hashTree = request.getTestElement().generateHashTree(config);
System.out.println(request.getTestElement().getJmx(hashTree));
// 调用执行方法
createScenarioReport(request.getId(), request.getScenarioId(), request.getScenarioName(), ReportTriggerMode.MANUAL.name(), request.getExecuteType(), request.getProjectId(),
SessionUtils.getUserId());
@ -668,14 +667,17 @@ public class ApiAutomationService {
HashTree jmeterHashTree = generateHashTree(apiScenarios, request, null);
String jmx = testPlan.getJmx(jmeterHashTree);
jmx = apiTestService.updateJmxString(jmx,testName,false);
//将ThreadGroup的testname改为接口名称
Document doc = DocumentHelper.parseText(jmx);// 获取可续保保单列表报文模板
Element root = doc.getRootElement();
Element rootHashTreeElement = root.element("hashTree");
Element innerHashTreeElement = rootHashTreeElement.elements("hashTree").get(0);
Element theadGroupElement = innerHashTreeElement.elements("ThreadGroup").get(0);
theadGroupElement.attribute("testname").setText(testName);
jmx = root.asXML();
// Document doc = DocumentHelper.parseText(jmx);// 获取可续保保单列表报文模板
// Element root = doc.getRootElement();
// Element rootHashTreeElement = root.element("hashTree");
// Element innerHashTreeElement = rootHashTreeElement.elements("hashTree").get(0);
// Element theadGroupElement = innerHashTreeElement.elements("ThreadGroup").get(0);
// theadGroupElement.attribute("testname").setText(testName);
// jmx = root.asXML();
String name = request.getName() + ".jmx";
@ -718,53 +720,99 @@ public class ApiAutomationService {
}
}
public ApiDefinitionImport scenarioImport(MultipartFile file, ApiTestImportRequest request) {
ApiImportParser apiImportParser = ApiScenarioImportParserFactory.getApiImportParser(request.getPlatform());
ApiDefinitionImport apiImport = null;
public List<ApiScenarioWithBLOBs> get(ApiScenarioRequest request) {
ApiScenarioExample example = new ApiScenarioExample();
if (CollectionUtils.isNotEmpty(request.getIds())) {
example.createCriteria().andIdIn(request.getIds());
} else {
example.createCriteria().andProjectIdEqualTo(request.getProjectId());
}
return apiScenarioMapper.selectByExampleWithBLOBs(example);
}
public List<ApiScenarioWithBLOBs> getWithBLOBs(ApiScenarioWithBLOBs request) {
ApiScenarioExample example = new ApiScenarioExample();
example.createCriteria().andNameEqualTo(request.getName()).andProjectIdEqualTo(request.getProjectId()).andStatusNotEqualTo("Trash").andIdNotEqualTo(request.getId());
return apiScenarioMapper.selectByExampleWithBLOBs(example);
}
private void _importCreate(List<ApiScenarioWithBLOBs> sameRequest, ApiScenarioMapper batchMapper, ApiScenarioWithBLOBs scenarioWithBLOBs, ApiTestImportRequest apiTestImportRequest) {
if (CollectionUtils.isEmpty(sameRequest)) {
scenarioWithBLOBs.setId(UUID.randomUUID().toString());
batchMapper.insert(scenarioWithBLOBs);
} else {
//如果存在则修改
scenarioWithBLOBs.setId(sameRequest.get(0).getId());
batchMapper.updateByPrimaryKeyWithBLOBs(scenarioWithBLOBs);
}
}
private ApiScenarioWithBLOBs importCreate(ApiScenarioWithBLOBs request, ApiScenarioMapper batchMapper, ApiTestImportRequest apiTestImportRequest) {
final ApiScenarioWithBLOBs scenarioWithBLOBs = new ApiScenarioWithBLOBs();
BeanUtils.copyBean(scenarioWithBLOBs, request);
scenarioWithBLOBs.setCreateTime(System.currentTimeMillis());
scenarioWithBLOBs.setUpdateTime(System.currentTimeMillis());
scenarioWithBLOBs.setStatus(APITestStatus.Underway.name());
scenarioWithBLOBs.setProjectId(apiTestImportRequest.getProjectId());
if (StringUtils.isEmpty(request.getPrincipal())) {
scenarioWithBLOBs.setPrincipal(Objects.requireNonNull(SessionUtils.getUser()).getId());
}
if (request.getUserId() == null) {
scenarioWithBLOBs.setUserId(Objects.requireNonNull(SessionUtils.getUser()).getId());
} else {
scenarioWithBLOBs.setUserId(request.getUserId());
}
scenarioWithBLOBs.setDescription(request.getDescription());
List<ApiScenarioWithBLOBs> sameRequest = getWithBLOBs(scenarioWithBLOBs);
if (StringUtils.equals("fullCoverage", apiTestImportRequest.getModeId())) {
_importCreate(sameRequest, batchMapper, scenarioWithBLOBs, apiTestImportRequest);
} else if (StringUtils.equals("incrementalMerge", apiTestImportRequest.getModeId())) {
if (CollectionUtils.isEmpty(sameRequest)) {
batchMapper.insert(scenarioWithBLOBs);
}
} else {
_importCreate(sameRequest, batchMapper, scenarioWithBLOBs, apiTestImportRequest);
}
return scenarioWithBLOBs;
}
private void editScenario(ApiTestImportRequest request, ScenarioImport apiImport) {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
ApiScenarioMapper batchMapper = sqlSession.getMapper(ApiScenarioMapper.class);
List<ApiScenarioWithBLOBs> data = apiImport.getData();
int num = 0;
if (!CollectionUtils.isEmpty(data) && data.get(0) != null && data.get(0).getProjectId() != null) {
num = getNextNum(data.get(0).getProjectId());
}
for (int i = 0; i < data.size(); i++) {
ApiScenarioWithBLOBs item = data.get(i);
if (item.getName().length() > 255) {
item.setName(item.getName().substring(0, 255));
}
item.setNum(num++);
importCreate(item, batchMapper, request);
if (i % 300 == 0) {
sqlSession.flushStatements();
}
}
sqlSession.flushStatements();
}
public ScenarioImport scenarioImport(MultipartFile file, ApiTestImportRequest request) {
ScenarioImportParser apiImportParser = ScenarioImportParserFactory.getImportParser(request.getPlatform());
ScenarioImport apiImport = null;
try {
apiImport = apiImportParser.parse(file == null ? null : file.getInputStream(), request);
apiImport = Objects.requireNonNull(apiImportParser).parse(file == null ? null : file.getInputStream(), request);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("parse_data_error"));
}
createScenarioImport(file, request, apiImport);
return apiImport;
}
public void createScenarioImport(MultipartFile file, ApiTestImportRequest request, ApiDefinitionImport apiImport) {
if (CollectionUtils.isNotEmpty(apiImport.getScenarioDefinitionData())) {
List<ApiScenarioWithBLOBs> scenarioDefinitionData = apiImport.getScenarioDefinitionData();
scenarioDefinitionData.forEach(scenario -> {
SaveApiScenarioRequest saveRequest = new SaveApiScenarioRequest();
scenario.setProjectId(request.getProjectId());
BeanUtils.copyBean(saveRequest, scenario);
checkNameExist(saveRequest);
scenario.setCreateTime(System.currentTimeMillis());
scenario.setUpdateTime(System.currentTimeMillis());
scenario.setNum(getNextNum(request.getProjectId()));
if (StringUtils.isNotBlank(request.getUserId())) {
scenario.setPrincipal(request.getUserId());
} else {
scenario.setPrincipal(SessionUtils.getUserId());
}
apiScenarioMapper.insert(scenario);
});
} else {
SaveApiScenarioRequest saveReq = new SaveApiScenarioRequest();
if (StringUtils.equals(ApiImportPlatform.Postman.name(), request.getPlatform())) {
saveReq.getScenarioDefinition().setName(file.getName());
}
saveReq.setScenarioDefinition(apiImport.getScenarioDefinition());
saveReq.setName(saveReq.getScenarioDefinition().getName());
saveReq.setProjectId(request.getProjectId());
saveReq.setApiScenarioModuleId(request.getModuleId());
if (StringUtils.isNotBlank(request.getUserId())) {
saveReq.setPrincipal(request.getUserId());
} else {
saveReq.setPrincipal(SessionUtils.getUserId());
}
create(saveReq, new ArrayList<>());
if (apiImport != null) {
editScenario(request, apiImport);
}
return apiImport;
}
public ApiScenrioExportResult export(ApiScenarioBatchRequest request) {

View File

@ -35,6 +35,7 @@ public class ApiDocumentService {
public ApiDocumentInfoDTO conversionModelToDTO(ApiDefinitionWithBLOBs apiModel) {
ApiDocumentInfoDTO apiInfoDTO = new ApiDocumentInfoDTO();
JSONObject previewObj = new JSONObject();
if (apiModel != null) {
apiInfoDTO.setId(apiModel.getId());
apiInfoDTO.setName(apiModel.getName());
@ -43,7 +44,6 @@ public class ApiDocumentService {
apiInfoDTO.setStatus(apiModel.getStatus());
JSONObject requestJsonObj = JSONObject.parseObject(apiModel.getRequest());
//head赋值
if (requestJsonObj.containsKey("headers")) {
JSONArray requestHeadDataArr = new JSONArray();
@ -55,9 +55,8 @@ public class ApiDocumentService {
requestHeadDataArr.add(headObj);
}
}
apiInfoDTO.setRequestHead(requestHeadDataArr.toJSONString());
apiInfoDTO.setRequestHead(requestHeadDataArr.toJSONString());
}
//url参数赋值
JSONArray urlParamArr = new JSONArray();
if (requestJsonObj.containsKey("arguments")) {
@ -81,17 +80,43 @@ public class ApiDocumentService {
}
}
apiInfoDTO.setUrlParams(urlParamArr.toJSONString());
//请求体参数类型
if (requestJsonObj.containsKey("body")) {
JSONObject bodyObj = requestJsonObj.getJSONObject("body");
if (bodyObj.containsKey("type")) {
String type = bodyObj.getString("type");
apiInfoDTO.setRequestBodyParamType(type);
if (StringUtils.equalsAny(type, "JSON", "XML", "Raw")) {
if(StringUtils.equals(type,"WWW_FORM")){
apiInfoDTO.setRequestBodyParamType("x-www-from-urlencoded");
}else if(StringUtils.equals(type,"Form Data")) {
apiInfoDTO.setRequestBodyParamType("form-data");
}else {
apiInfoDTO.setRequestBodyParamType(type);
}
if (StringUtils.equals(type, "JSON")) {
//判断是否是JsonSchema
boolean isJsonSchema = false;
if(bodyObj.containsKey("format")){
String foramtValue = String.valueOf(bodyObj.get("format"));
if(StringUtils.equals("JSON-SCHEMA",foramtValue)){
isJsonSchema = true;
}
}
if(isJsonSchema){
apiInfoDTO.setRequestBodyParamType("JSON-SCHEMA");
apiInfoDTO.setJsonSchemaBody(bodyObj);
}else {
if (bodyObj.containsKey("raw")) {
String raw = bodyObj.getString("raw");
apiInfoDTO.setRequestBodyStrutureData(raw);
previewObj = JSONObject.parseObject(raw);
}
}
} else if (StringUtils.equalsAny(type, "XML", "Raw")) {
if (bodyObj.containsKey("raw")) {
String raw = bodyObj.getString("raw");
apiInfoDTO.setRequestBodyStrutureData(raw);
previewObj = JSONObject.parseObject(raw);
}
} else if (StringUtils.equalsAny(type, "Form Data", "WWW_FORM")) {
if (bodyObj.containsKey("kvs")) {
@ -99,8 +124,9 @@ public class ApiDocumentService {
JSONArray kvsArr = bodyObj.getJSONArray("kvs");
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("name")) {
if (kv.containsKey("name")&&kv.containsKey("value")) {
bodyParamArr.add(kv);
previewObj.put(String.valueOf(kv.get("name")),String.valueOf(kv.get("value")));
}
}
apiInfoDTO.setRequestBodyFormData(bodyParamArr.toJSONString());
@ -108,12 +134,11 @@ public class ApiDocumentService {
} else if (StringUtils.equals(type, "BINARY")) {
if (bodyObj.containsKey("binary")) {
List<Map<String, String>> bodyParamList = new ArrayList<>();
JSONArray kvsArr = bodyObj.getJSONArray("kvs");
JSONArray kvsArr = bodyObj.getJSONArray("binary");
for (int i = 0; i < kvsArr.size(); i++) {
JSONObject kv = kvsArr.getJSONObject(i);
if (kv.containsKey("description") && kv.containsKey("files")) {
Map<String, String> bodyMap = new HashMap<>();
String name = kv.getString("description");
JSONArray fileArr = kv.getJSONArray("files");
String value = "";
@ -123,11 +148,12 @@ public class ApiDocumentService {
value += fileObj.getString("name") + " ;";
}
}
bodyMap.put("name", name);
bodyMap.put("value", value);
bodyMap.put("contentType", "File");
bodyParamList.add(bodyMap);
previewObj.put(String.valueOf(name),String.valueOf(value));
}
}
apiInfoDTO.setRequestBodyFormData(JSONArray.toJSONString(bodyParamList));
@ -135,7 +161,6 @@ public class ApiDocumentService {
}
}
}
JSONObject responseJsonObj = JSONObject.parseObject(apiModel.getResponse());
//赋值响应头
if (responseJsonObj.containsKey("headers")) {
@ -149,13 +174,18 @@ public class ApiDocumentService {
}
apiInfoDTO.setResponseHead(responseHeadDataArr.toJSONString());
}
// 赋值响应体
if (responseJsonObj.containsKey("body")) {
JSONObject bodyObj = responseJsonObj.getJSONObject("body");
if (bodyObj.containsKey("type")) {
String type = bodyObj.getString("type");
apiInfoDTO.setResponseBodyParamType(type);
if(StringUtils.equals(type,"WWW_FORM")){
apiInfoDTO.setResponseBodyParamType("x-www-from-urlencoded");
}else if(StringUtils.equals(type,"Form Data")) {
apiInfoDTO.setResponseBodyParamType("form-data");
}else {
apiInfoDTO.setResponseBodyParamType(type);
}
if (StringUtils.equalsAny(type, "JSON", "XML", "Raw")) {
if (bodyObj.containsKey("raw")) {
String raw = bodyObj.getString("raw");
@ -191,10 +221,8 @@ public class ApiDocumentService {
value += fileObj.getString("name") + " ;";
}
}
bodyMap.put("name", name);
bodyMap.put("value", value);
bodyParamList.add(bodyMap);
}
}
@ -203,7 +231,6 @@ public class ApiDocumentService {
}
}
}
// 赋值响应码
if (responseJsonObj.containsKey("statusCode")) {
JSONArray responseStatusDataArr = new JSONArray();
@ -217,6 +244,7 @@ public class ApiDocumentService {
apiInfoDTO.setResponseCode(responseStatusDataArr.toJSONString());
}
}
apiInfoDTO.setRequestPreviewData(previewObj);
return apiInfoDTO;
}
}

View File

@ -211,6 +211,33 @@ public class ApiScenarioModuleService extends NodeTreeService<ApiScenarioModuleD
return dto;
}
public ApiScenarioModule getNewModule(String name, String projectId, int level) {
ApiScenarioModule node = new ApiScenarioModule();
node.setCreateTime(System.currentTimeMillis());
node.setUpdateTime(System.currentTimeMillis());
node.setId(UUID.randomUUID().toString());
node.setLevel(level);
node.setName(name);
node.setProjectId(projectId);
return node;
}
public List<ApiScenarioModule> selectSameModule(ApiScenarioModule node) {
ApiScenarioModuleExample example = new ApiScenarioModuleExample();
ApiScenarioModuleExample.Criteria criteria = example.createCriteria();
criteria.andNameEqualTo(node.getName())
.andProjectIdEqualTo(node.getProjectId());
if (StringUtils.isNotBlank(node.getParentId())) {
criteria.andParentIdEqualTo(node.getParentId());
} else {
criteria.andParentIdIsNull();
}
if (StringUtils.isNotBlank(node.getId())) {
criteria.andIdNotEqualTo(node.getId());
}
return apiScenarioModuleMapper.selectByExample(example);
}
@Override
public void updatePos(String id, Double pos) {
extApiScenarioModuleMapper.updatePos(id, pos);

View File

@ -26,6 +26,11 @@ public class ApiTestEnvironmentService {
return apiTestEnvironmentMapper.selectByExampleWithBLOBs(example);
}
public List<ApiTestEnvironmentWithBLOBs> selectByExampleWithBLOBs(ApiTestEnvironmentExample example) {
return apiTestEnvironmentMapper.selectByExampleWithBLOBs(example);
}
public ApiTestEnvironmentWithBLOBs get(String id) {
return apiTestEnvironmentMapper.selectByPrimaryKey(id);
}

View File

@ -7,6 +7,13 @@
<if test="request.projectId != null">
api.project_Id = #{request.projectId}
</if>
<if test="request.name != null">
api.project_Id like CONCAT('%', #{request.name},'%')
</if>
<if test="request.type != null">
api.method = #{request.type}
</if>
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
AND api.module_id in
<foreach collection="request.moduleIds" item="nodeId" separator="," open="(" close=")">
@ -14,6 +21,14 @@
</foreach>
</if>
</where>
ORDER BY api.create_time DESC
<if test="request.orderCondition == 'createTimeDesc'">
ORDER BY api.create_time DESC
</if>
<if test="request.orderCondition == 'editTimeAsc'">
ORDER BY api.update_time
</if>
<if test="request.orderCondition == 'editTimeDesc'">
ORDER BY api.update_time DESC
</if>
</select>
</mapper>

View File

@ -1,5 +1,5 @@
package io.metersphere.commons.constants;
public enum ApiImportPlatform {
Metersphere, Postman, Swagger2, Plugin
Metersphere, Postman, Swagger2, Plugin, Jmeter
}

@ -1 +1 @@
Subproject commit 44bcc9893033900f95c99068cd4edec740465dfe
Subproject commit 5e0b365f1080197e84055e80071165787e2e79c5

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
ALTER TABLE schedule
MODIFY COLUMN `key` VARCHAR(255);

View File

@ -1,5 +1,5 @@
<template>
<ms-container v-if="renderComponent">
<ms-container v-if="renderComponent" v-loading="loading">
<ms-aside-container>
<ms-api-scenario-module
@nodeSelectEvent="nodeChange"
@ -9,6 +9,7 @@
@setNodeTree="setNodeTree"
@enableTrash="enableTrash"
@exportAPI="exportAPI"
@refreshAll="refreshAll"
:type="'edit'"
ref="nodeTree"/>
</ms-aside-container>
@ -64,10 +65,11 @@
import MsAsideContainer from "@/business/components/common/components/MsAsideContainer";
import MsMainContainer from "@/business/components/common/components/MsMainContainer";
import MsApiScenarioList from "@/business/components/api/automation/scenario/ApiScenarioList";
import {getUUID} from "@/common/js/utils";
import {getUUID, downloadFile} from "@/common/js/utils";
import MsApiScenarioModule from "@/business/components/api/automation/scenario/ApiScenarioModule";
import MsEditApiScenario from "./scenario/EditApiScenario";
import {getCurrentProjectID} from "../../../../common/js/utils";
import {PROJECT_NAME} from "../../../../common/js/constants";
export default {
name: "ApiAutomation",
@ -102,6 +104,7 @@
currentModule: null,
moduleOptions: [],
tabs: [],
loading: false,
trashEnable: false,
selectNodeIds: [],
nodeTree: []
@ -127,6 +130,27 @@
}
},
methods: {
exportAPI() {
let obj = {projectName: localStorage.getItem(PROJECT_NAME)}
let condition = {projectId: getCurrentProjectID(), ids: this.$refs.apiScenarioList.selection};
let url = "/api/automation/list/all";
this.loading = true;
this.$post(url, condition, response => {
obj.data = response.data;
this.buildApiPath(obj.data);
this.loading = false;
downloadFile("Metersphere_Scenario_" + localStorage.getItem(PROJECT_NAME) + ".json", JSON.stringify(obj));
});
},
buildApiPath(apis) {
apis.forEach((api) => {
this.moduleOptions.forEach(item => {
if (api.moduleId === item.id) {
api.modulePath = item.path;
}
});
});
},
checkRedirectEditPage(redirectParam) {
if (redirectParam != null) {
let selectParamArr = redirectParam.split("edit:");
@ -152,13 +176,13 @@
},
changeRedirectParam(redirectIDParam) {
this.redirectID = redirectIDParam;
if(redirectIDParam!=null){
if(this.redirectFlag == "none"){
if (redirectIDParam != null) {
if (this.redirectFlag == "none") {
this.activeName = "default";
this.addListener();
this.redirectFlag = "redirected";
}
}else{
} else {
this.redirectFlag = "none";
}
},
@ -189,8 +213,8 @@
},
addListener() {
let index = this.tabs.findIndex(item => item.name === this.activeName); // tabindex
if(index != -1) { // tab
this.$nextTick(()=>{
if (index != -1) { // tab
this.$nextTick(() => {
this.$refs.autoScenarioConfig[index].addListener();
});
}
@ -241,6 +265,10 @@
this.setTabTitle(data);
this.$refs.apiScenarioList.search(data);
},
refreshAll() {
this.$refs.nodeTree.list();
this.$refs.apiScenarioList.search();
},
setTabTitle(data) {
for (let index in this.tabs) {
let tab = this.tabs[index];
@ -268,9 +296,6 @@
},
enableTrash(data) {
this.trashEnable = data;
},
exportAPI() {
this.$refs.apiScenarioList.exportApi();
}
}
}

View File

@ -16,15 +16,31 @@
ref="nodeTree">
<template v-slot:header>
<api-scenario-module-header
:condition="condition"
:current-module="currentModule"
:is-read-only="isReadOnly"
:project-id="projectId"
@exportAPI="exportAPI"
@addScenario="addScenario"
@refreshTable="$emit('refreshTable')"
@refresh="refresh"/>
<el-input :placeholder="$t('test_track.module.search')" v-model="condition.filterText" size="small">
<template v-slot:append>
<el-dropdown v-if="!isReadOnly" size="small" split-button type="primary" class="ms-api-button" @click="handleCommand('add-api')"
v-tester
@command="handleCommand">
<el-button icon="el-icon-folder-add" @click="addScenario"></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="add-scenario">{{ $t('api_test.automation.add_scenario') }}</el-dropdown-item>
<el-dropdown-item command="import">{{ $t('api_test.api_import.label') }}</el-dropdown-item>
<el-dropdown-item command="export">{{ $t('report.export') }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-input>
<module-trash-button v-if="!isReadOnly" :condition="condition" :exe="enableTrash"/>
<!-- 是否保留这个 -->
<!--<api-scenario-module-header-->
<!--:condition="condition"-->
<!--:current-module="currentModule"-->
<!--:is-read-only="isReadOnly"-->
<!--:project-id="projectId"-->
<!--@exportAPI="exportAPI"-->
<!--@addScenario="addScenario"-->
<!--@refreshTable="$emit('refreshTable')"-->
<!--@refresh="refresh"/>-->
</template>
</ms-node-tree>
@ -33,6 +49,8 @@
@saveAsEdit="saveAsEdit"
@refresh="refresh"
ref="basisScenario"/>
<api-import ref="apiImport" :moduleOptions="moduleOptions" @refreshAll="$emit('refreshAll')"/>
</div>
</template>
@ -45,11 +63,12 @@ import MsNodeTree from "../../../track/common/NodeTree";
import {buildNodePath} from "../../definition/model/NodeTree";
import ModuleTrashButton from "../../definition/components/module/ModuleTrashButton";
import ApiScenarioModuleHeader from "@/business/components/api/automation/scenario/module/ApiScenarioModuleHeader";
import ApiImport from "./common/ScenarioImport";
export default {
name: 'MsApiScenarioModule',
components: {
ApiScenarioModuleHeader,
ApiImport,
ModuleTrashButton,
MsNodeTree,
MsAddBasisScenario,
@ -83,6 +102,7 @@ export default {
projectId: "",
data: [],
currentModule: undefined,
moduleOptions: [],
}
},
mounted() {
@ -104,7 +124,29 @@ export default {
}
},
methods: {
handleCommand(e) {
switch (e) {
case "add-scenario":
this.addScenario();
break;
case "import":
this.result = this.$get("/api/automation/module/list/" + getCurrentProjectID(), response => {
if (response.data != undefined && response.data != null) {
this.data = response.data;
let moduleOptions = [];
this.data.forEach(node => {
buildNodePath(node, {path: ''}, moduleOptions);
});
this.moduleOptions = moduleOptions
}
});
this.$refs.apiImport.open(this.currentModule);
break;
case "export":
this.$emit('exportAPI');
break;
}
},
list() {
let url = undefined;
if (this.isPlanModel) {

View File

@ -400,7 +400,7 @@ export default {
},
{
title: this.$t('api_test.automation.scenario_import'),
show: this.operatingElements.indexOf('scenario') === 0,
show: this.operatingElements && this.operatingElements.indexOf('scenario') === 0,
titleColor: "#606266",
titleBgColor: "#F4F4F5",
icon: "movie",
@ -445,7 +445,7 @@ export default {
},
showButton(...names) {
for (const name of names) {
if (this.operatingElements.includes(name)) {
if (name && this.operatingElements && this.operatingElements.includes(name)) {
return true;
}
}

View File

@ -14,6 +14,7 @@ export const ELEMENTS = new Map([
['JSR223PostProcessor', []],
['Assertions', []],
['Extract', []],
['JmeterElement', []],
['CustomizeReq', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
])

View File

@ -0,0 +1,320 @@
<template>
<el-dialog :close-on-click-modal="false" :title="$t('api_test.api_import.title')" width="30%"
:visible.sync="visible" class="api-import" v-loading="result.loading" @close="close">
<div class="header-bar">
<div>{{ $t('api_test.api_import.data_format') }}</div>
<el-radio-group v-model="selectedPlatformValue">
<el-radio v-for="(item, index) in platforms" :key="index" :label="item.value">{{ item.name }}</el-radio>
</el-radio-group>
<div class="operate-button">
<el-button class="save-button" type="primary" plain @click="save">
{{ $t('commons.save') }}
</el-button>
<el-button class="cancel-button" type="warning" plain @click="visible = false">
{{ $t('commons.cancel') }}
</el-button>
</div>
</div>
<el-form :model="formData" :rules="rules" label-width="100px" v-loading="result.loading" ref="form">
<el-row>
<el-col :span="11">
<el-form-item :label="$t('commons.import_module')">
<el-select size="small" v-model="formData.moduleId" class="project-select" clearable>
<el-option v-for="item in moduleOptions" :key="item.id" :label="item.path" :value="item.id"/>
</el-select>
</el-form-item>
<el-form-item :label="$t('commons.import_mode')">
<el-select size="small" v-model="formData.modeId" class="project-select" clearable>
<el-option v-for="item in modeOptions" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="1">
<el-divider direction="vertical"/>
</el-col>
<el-col :span="12">
<el-upload
class="api-upload"
drag
action=""
:http-request="upload"
:limit="1"
:beforeUpload="uploadValidate"
:on-remove="handleRemove"
:file-list="fileList"
:on-exceed="handleExceed"
multiple>
<i class="el-icon-upload"></i>
<div class="el-upload__text" v-html="$t('load_test.upload_tips')"></div>
<div class="el-upload__tip" slot="tip">{{ $t('api_test.api_import.file_size_limit') }}</div>
</el-upload>
</el-col>
</el-row>
</el-form>
<div class="format-tip">
<div>
<span>{{ $t('api_test.api_import.tip') }}{{ selectedPlatform.tip }}</span>
</div>
<div>
<span>{{ $t('api_test.api_import.export_tip') }}{{ selectedPlatform.exportTip }}</span>
</div>
</div>
</el-dialog>
</template>
<script>
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {listenGoBack, removeGoBackListener, getCurrentProjectID} from "@/common/js/utils";
import {buildNodePath} from "@/business/components/api/definition/model/NodeTree";
export default {
name: "ScenarioImport",
components: {MsDialogFooter},
props: {
saved: {
type: Boolean,
default: true,
},
moduleOptions: {}
},
data() {
return {
visible: false,
swaggerUrlEable: false,
swaggerSynchronization: false,
showEnvironmentSelect: true,
modeOptions: [{
id: 'fullCoverage',
name: this.$t('commons.cover')
},
{
id: 'incrementalMerge',
name: this.$t('commons.not_cover')
}],
protocol: "",
platforms: [
{
name: 'Metersphere',
value: 'Metersphere',
tip: this.$t('api_test.api_import.ms_tip'),
exportTip: this.$t('api_test.api_import.ms_export_tip'),
suffixes: new Set(['json'])
},
{
name: 'Postman',
value: 'Postman',
tip: this.$t('api_test.api_import.postman_tip'),
exportTip: this.$t('api_test.api_import.post_export_tip'),
suffixes: new Set(['json'])
},
{
name: 'Jmeter',
value: 'Jmeter',
tip: this.$t('api_test.api_import.jmeter_tip'),
exportTip: this.$t('api_test.api_import.jmeter_export_tip'),
suffixes: new Set(['jmx'])
}
],
selectedPlatform: {},
selectedPlatformValue: 'Metersphere',
result: {},
projects: [],
environments: [],
useEnvironment: false,
formData: {
file: undefined,
swaggerUrl: '',
modeId: this.$t('commons.not_cover'),
moduleId: '',
},
rules: {},
currentModule: {},
fileList: []
}
},
activated() {
this.selectedPlatform = this.platforms[0];
},
watch: {
selectedPlatformValue() {
for (let i in this.platforms) {
if (this.platforms[i].value === this.selectedPlatformValue) {
this.selectedPlatform = this.platforms[i];
break;
}
}
},
},
methods: {
scheduleEdit() {
if (!this.formData.swaggerUrl) {
this.$warning(this.$t('commons.please_fill_path'));
this.swaggerSynchronization = !this.swaggerSynchronization
} else {
if (this.swaggerSynchronization) {
this.$refs.scheduleEdit.open(this.buildParam());
}
}
},
scheduleEditByText() {
this.$refs.scheduleEdit.open(this.buildParam());
},
open(module) {
this.currentModule = module;
this.visible = true;
listenGoBack(this.close);
},
upload(file) {
this.formData.file = file.file;
},
handleExceed(files, fileList) {
this.$warning(this.$t('test_track.case.import.upload_limit_count'));
},
handleRemove(file, fileList) {
this.formData.file = undefined;
},
uploadValidate(file, fileList) {
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
if (this.selectedPlatform.suffixes && !this.selectedPlatform.suffixes.has(suffix)) {
this.$warning(this.$t('api_test.api_import.suffixFormatErr'));
return false;
}
if (file.size / 1024 / 1024 > 20) {
this.$warning(this.$t('test_track.case.import.upload_limit_size'));
return false;
}
return true;
},
save() {
if (!this.formData.file) {
this.$warning("请添加一个文件");
return;
}
let suffix = this.formData.file.name.substring(this.formData.file.name.lastIndexOf('.') + 1);
if (this.selectedPlatform.suffixes && !this.selectedPlatform.suffixes.has(suffix)) {
this.$warning(this.$t('api_test.api_import.suffixFormatErr'));
return false;
}
this.$refs.form.validate(valid => {
if (valid) {
let param = this.buildParam();
this.result = this.$fileUpload('/api/automation/import', param.file, null, this.buildParam(), response => {
let res = response.data;
this.$success(this.$t('test_track.case.import.success'));
this.visible = false;
this.$emit('refreshAll', res);
});
} else {
return false;
}
});
},
buildParam() {
let param = {};
Object.assign(param, this.formData);
param.platform = this.selectedPlatformValue;
param.saved = this.saved;
if (this.currentModule) {
param.moduleId = this.formData.moduleId
this.moduleOptions.filter(item => {
if (item.id === this.formData.moduleId) {
param.modulePath = item.path
}
})
param.modeId = this.formData.modeId
}
param.projectId = getCurrentProjectID();
if (!this.swaggerUrlEable) {
param.swaggerUrl = undefined;
}
return param;
},
close() {
this.formData = {
file: undefined,
swaggerUrl: ''
};
this.fileList = [];
removeGoBackListener(this.close);
this.visible = false;
}
}
}
</script>
<style scoped>
.api-import >>> .el-dialog {
min-width: 700px;
}
.format-tip {
background: #EDEDED;
}
.api-upload {
text-align: center;
margin: auto 0;
}
.api-upload >>> .el-upload {
width: 100%;
max-width: 350px;
}
.api-upload >>> .el-upload-dragger {
width: 100%;
}
.el-radio-group {
margin: 10px 0;
}
.header-bar, .format-tip, .el-form {
border: solid #E1E1E1 1px;
margin: 10px 0;
padding: 10px;
border-radius: 3px;
}
.header-bar {
padding: 10px 30px;
}
.api-import >>> .el-dialog__body {
padding: 15px 25px;
}
.operate-button {
float: right;
}
.save-button {
margin-left: 10px;
}
.el-form {
padding: 30px 10px;
}
.dialog-footer {
float: right;
}
.swagger-url-disable {
margin-top: 10px;
margin-left: 80px;
}
.el-divider {
height: 200px;
}
</style>

View File

@ -16,10 +16,11 @@
import MsApiComponent from "./ApiComponent";
import MsLoopController from "./LoopController";
import MsApiScenarioComponent from "./ApiScenarioComponent";
import JmeterElementComponent from "./JmeterElementComponent";
export default {
name: "ComponentConfig",
components: {MsConstantTimer, MsIfController, MsJsr233Processor, MsApiAssertions, MsApiExtract, MsApiComponent, MsLoopController, MsApiScenarioComponent},
components: {MsConstantTimer, MsIfController, MsJsr233Processor, MsApiAssertions, MsApiExtract, MsApiComponent, MsLoopController, MsApiScenarioComponent, JmeterElementComponent},
props: {
type: String,
scenario: {},
@ -71,6 +72,9 @@
break;
case "AuthManager":
break;
case "JmeterElement":
name = "JmeterElementComponent";
break;
default:
name = "MsApiComponent";
break;

View File

@ -0,0 +1,57 @@
<template>
<api-base-component
@copy="copyRow"
@remove="remove"
:data="request"
:draggable="draggable"
:color="defColor"
:background-color="defBackgroundColor"
:title="defTitle">
</api-base-component>
</template>
<script>
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
import MsInstructionsIcon from "../../../../common/components/MsInstructionsIcon";
import MsDropdown from "../../../../common/components/MsDropdown";
import ApiBaseComponent from "../common/ApiBaseComponent";
import Jsr233ProcessorContent from "../common/Jsr233ProcessorContent";
export default {
name: "JmeterElementComponent",
components: {Jsr233ProcessorContent, ApiBaseComponent, MsDropdown, MsInstructionsIcon, MsCodeEdit},
props: {
draggable: {
type: Boolean,
default: false,
},
isReadOnly: {
type: Boolean,
default:
false
},
request: {
type: Object,
},
defTitle: {type: String, default: "Jmeter组件"},
defColor: {type: String, default: "#606260"},
defBackgroundColor: {type: String, default: "#F4F4FF"},
node: {},
},
methods: {
remove() {
this.$emit('remove', this.jsr223Processor, this.node);
},
copyRow() {
this.$emit('copyRow', this.jsr223Processor, this.node);
},
}
}
</script>
<style scoped>
/deep/ .el-divider {
margin-bottom: 10px;
}
</style>

View File

@ -1,17 +1,44 @@
<template>
<el-container>
<el-main>
<div>
<el-container>
<el-main style="padding-top: 0px">
<el-row>
<el-select size="small" :placeholder="$t('api_test.definition.document.order')" v-model="apiSearch.orderCondition" style="float: right;width: 180px;margin-right: 5px"
class="ms-api-header-select" @change="initApiDocSimpleList" clearable>
<el-option key="createTimeDesc" :label="$t('api_test.definition.document.create_time_sort')" value="createTimeDesc" />
<el-option key="editTimeAsc" :label="$t('api_test.definition.document.edit_time_positive_sequence')" value="editTimeAsc"/>
<el-option key="editTimeDesc" :label="$t('api_test.definition.document.edit_time_Reverse_order')" value="editTimeDesc"/>
</el-select>
<el-select size="small" :placeholder="$t('api_test.definition.document.request_method')" v-model="apiSearch.type" style="float: right;width: 180px;margin-right: 5px"
class="ms-api-header-select" @change="initApiDocSimpleList" clearable>
<el-option key="ALL" :label="$t('api_test.definition.document.data_set.all')" value="ALL"/>
<el-option key="GET" :label="'GET '+$t('api_test.definition.document.request_interface')" value="GET"/>
<el-option key="POST" :label="'POST '+$t('api_test.definition.document.request_interface')" value="POST"/>
<el-option key="PUT" :label="'PUT '+$t('api_test.definition.document.request_interface')" value="PUT"/>
<el-option key="DELETE" :label="'DELETE '+$t('api_test.definition.document.request_interface')" value="DELETE"/>
<el-option key="PATCH" :label="'PATCH '+$t('api_test.definition.document.request_interface')" value="PATCH"/>
<el-option key="OPTIONS" :label="'OPTIONS '+$t('api_test.definition.document.request_interface')" value="OPTIONS"/>
<el-option key="HEAD" :label="'HEAD '+$t('api_test.definition.document.request_interface')" value="HEAD"/>
<el-option key="CONNECT" :label="'CONNECT '+$t('api_test.definition.document.request_interface')" value="CONNECT"/>
</el-select>
<el-input :placeholder="$t('api_test.definition.document.search_by_api_name')" @blur="initApiDocSimpleList()" style="float: right;width: 180px;margin-right: 5px" size="small"
@keyup.enter.native="initApiDocSimpleList()" v-model="apiSearch.name"/>
</el-row>
<el-divider></el-divider>
<div ref="apiDocInfoDiv">
<div style="margin-bottom: 50px">
<div style="font-size: 17px">
{{apiInfo.name}}
<i class="el-icon-share"></i>{{ apiInfo.name }}
<span class="apiStatusTag">
<api-status :value="apiInfo.status"/>
</span>
</div>
<!--api请求信息-->
<el-row class="apiInfoRow">
<div class="tip">请求信息</div>
<div class="tip">
{{ $t('api_test.definition.document.request_info') }}
</div>
</el-row>
<el-row class="apiInfoRow">
<div class="simpleFontClass">
@ -19,25 +46,25 @@
:style="{'background-color': getColor(apiInfo.method), border: getColor(apiInfo.method),borderRadius:'0px', marginRight:'20px'}">
{{ apiInfo.method }}
</el-tag>
{{apiInfo.uri}}
{{ apiInfo.uri }}
</div>
</el-row>
<!--api请求头-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
请求头
{{ $t('api_test.definition.document.request_head') }}
<div v-if="getJsonArr(apiInfo.requestHead).length==0">
{{ $t('api_test.definition.document.data_set.none') }}
</div>
<div v-else>
<el-table border :show-header="false"
:data="getJsonArr(apiInfo.requestHead)" row-key="name" class="test-content adjust-table">
<el-table-column prop="name"
label="名称"
show-overflow-tooltip/>
<el-table-column prop="value"
label="值"
show-overflow-tooltip/>
:data="getJsonArr(apiInfo.requestHead)" row-key="name" class="test-content document-table">
<el-table-column prop="name"
:label="$t('api_test.definition.document.table_coloum.name')"
show-overflow-tooltip/>
<el-table-column prop="value"
:label="$t('api_test.definition.document.table_coloum.value')"
show-overflow-tooltip/>
</el-table>
</div>
</div>
@ -45,29 +72,29 @@
<!--URL参数-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
URL参数
URL{{ $t('api_test.definition.document.request_param') }}
<div v-if="getJsonArr(apiInfo.urlParams).length==0">
{{ $t('api_test.definition.document.data_set.none') }}
</div>
<div v-else>
<el-table border
:data="getJsonArr(apiInfo.urlParams)" row-key="name" class="test-content adjust-table">
<el-table-column prop="name"
label="名称"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="isEnable"
label="是否必填"
min-width="80px"
show-overflow-tooltip/>
<el-table-column prop="value"
label="值"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="description"
label="描述"
min-width="450px"
show-overflow-tooltip/>
:data="getJsonArr(apiInfo.urlParams)" row-key="name" class="test-content document-table">
<el-table-column prop="name"
:label="$t('api_test.definition.document.table_coloum.name')"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="isEnable"
:label="$t('api_test.definition.document.table_coloum.is_required')"
min-width="80px"
show-overflow-tooltip/>
<el-table-column prop="value"
:label="$t('api_test.definition.document.table_coloum.value')"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="description"
:label="$t('api_test.definition.document.table_coloum.desc')"
min-width="280px"
show-overflow-tooltip/>
</el-table>
</div>
</div>
@ -75,57 +102,67 @@
<!--api请求体 以及表格-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
请求体
{{ $t('api_test.definition.document.request_body') }}
</div>
<div class="smallFontClass">
类型:{{apiInfo.requestBodyParamType}}
{{ $t('api_test.definition.document.table_coloum.type') }}:{{ apiInfo.requestBodyParamType }}
</div>
<div>
<el-table border v-if="apiInfo.requestBodyParamType=='kv'"
:data="getJsonArr(apiInfo.requestBodyFormData)" row-key="id" class="test-content adjust-table">
<el-table-column prop="name"
label="名称"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="contentType"
label="类型"
min-width="80px"
show-overflow-tooltip/>
<el-table-column prop="description"
label="描述"
min-width="450px"
show-overflow-tooltip/>
<el-table-column label="必需"
min-width="80px"
show-overflow-tooltip>
<template v-slot:default="scope">
<div v-if="scope.enable"></div>
<div v-else-if="!scope.enable"></div>
</template>
</el-table-column>
<el-table-column prop="value"
label="默认值"
min-width="120px"
show-overflow-tooltip/>
<el-table border v-if="formParamTypes.includes(apiInfo.requestBodyParamType)"
:data="getJsonArr(apiInfo.requestBodyFormData)" row-key="name"
class="test-content document-table">
<el-table-column prop="name"
:label="$t('api_test.definition.document.table_coloum.name')"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="contentType"
:label="$t('api_test.definition.document.table_coloum.type')"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="description"
:label="$t('api_test.definition.document.table_coloum.desc')"
min-width="280px"
show-overflow-tooltip/>
<el-table-column prop="required"
:label="$t('api_test.definition.document.table_coloum.is_required')"
:formatter="formatBoolean"
min-width="80px"
show-overflow-tooltip/>
<el-table-column prop="value"
:label="$t('api_test.definition.document.table_coloum.default_value')"
min-width="120px"
show-overflow-tooltip/>
</el-table>
<div v-else-if="apiInfo.requestBodyParamType == 'JSON-SCHEMA'">
<ms-json-code-edit :body="apiInfo.jsonSchemaBody" ref="jsonCodeEdit"/>
</div>
<div v-else class="showDataDiv">
<br/>
<p style="margin: 0px 20px;"
v-html="formatRowData(apiInfo.requestBodyParamType,apiInfo.requestBodyStrutureData)">
</p>
<br/>
</div>
</div>
</el-row>
<!--范例展示-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
范例展示
{{ $t('api_test.definition.document.example_presentation') }}
</div>
<div class="showDataDiv">
<br/>
<p style="margin: 0px 20px;">
{{ apiInfo.requestBodyStrutureData }}
<p style="margin: 0px 20px;"
v-html="genPreviewData(apiInfo.requestPreviewData)">
</p>
<br/>
</div>
</el-row>
<!--响应信息-->
<el-row class="apiInfoRow">
<div class="tip">响应信息</div>
<div class="tip">
{{ $t('api_test.definition.document.response_info') }}
</div>
</el-row>
<el-row class="apiInfoRow">
@ -133,133 +170,153 @@
<!--响应头-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
响应头:
{{ $t('api_test.definition.document.response_head') }}:
<el-table border :show-header="false"
:data="getJsonArr(apiInfo.responseHead)" row-key="name" class="test-content adjust-table">
<el-table-column prop="name"
label="名称"
show-overflow-tooltip/>
<el-table-column prop="value"
label="值"
show-overflow-tooltip/>
:data="getJsonArr(apiInfo.responseHead)" row-key="name" class="test-content document-table">
<el-table-column prop="name"
:label="$t('api_test.definition.document.table_coloum.name')"
show-overflow-tooltip/>
<el-table-column prop="value"
:label="$t('api_test.definition.document.table_coloum.value')"
show-overflow-tooltip/>
</el-table>
</div>
</el-row>
<!--响应体-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
响应体
{{ $t('api_test.definition.document.response_body') }}
</div>
<div class="smallFontClass">
类型:{{apiInfo.responseBodyParamType}}
{{ $t('api_test.definition.document.table_coloum.type') }}:{{ apiInfo.responseBodyParamType }}
</div>
<div>
<el-table border v-if="apiInfo.responseBodyParamType=='kv'"
:data="getJsonArr(apiInfo.responseBodyFormData)" row-key="id" class="test-content adjust-table">
<el-table-column prop="name"
label="名称"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="contentType"
label="类型"
min-width="80px"
show-overflow-tooltip/>
<el-table-column prop="description"
label="描述"
min-width="450px"
show-overflow-tooltip/>
<el-table-column label="必需"
min-width="80px"
show-overflow-tooltip>
<template v-slot:default="scope">
<div v-if="scope.enable"></div>
<div v-else-if="!scope.enable"></div>
</template>
</el-table-column>
<el-table-column prop="value"
label="默认值"
min-width="120px"
show-overflow-tooltip/>
<el-table border v-if="formParamTypes.includes(apiInfo.responseBodyParamType)"
:data="getJsonArr(apiInfo.responseBodyFormData)" row-key="id"
class="test-content document-table">
<el-table-column prop="name"
:label="$t('api_test.definition.document.table_coloum.name')"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="contentType"
:label="$t('api_test.definition.document.table_coloum.type')"
min-width="120px"
show-overflow-tooltip/>
<el-table-column prop="description"
:label="$t('api_test.definition.document.table_coloum.desc')"
min-width="280px"
show-overflow-tooltip/>
<el-table-column prop="required"
:label="$t('api_test.definition.document.table_coloum.is_required')"
:formatter="formatBoolean"
min-width="80px"
show-overflow-tooltip/>
<el-table-column prop="value"
:label="$t('api_test.definition.document.table_coloum.default_value')"
min-width="120px"
show-overflow-tooltip/>
</el-table>
<div v-else class="showDataDiv">
<br/>
<p style="margin: 0px 20px;"
v-html="formatRowData(apiInfo.responseBodyParamType,apiInfo.responseBodyStrutureData)">
</p>
<br/>
</div>
</div>
</el-row>
<!--响应状态码-->
<el-row class="apiInfoRow">
<div class="blackFontClass">
响应状态码:
{{ $t('api_test.definition.document.response_code') }}:
<el-table border :show-header="false"
:data="getJsonArr(apiInfo.responseCode)" row-key="name" class="test-content adjust-table">
<el-table-column prop="name"
label="名称"
show-overflow-tooltip/>
<el-table-column prop="value"
label="值"
show-overflow-tooltip/>
:data="getJsonArr(apiInfo.responseCode)" row-key="name" class="test-content document-table">
<el-table-column prop="name"
:label="$t('api_test.definition.document.table_coloum.name')"
show-overflow-tooltip/>
<el-table-column prop="value"
:label="$t('api_test.definition.document.table_coloum.value')"
show-overflow-tooltip/>
</el-table>
</div>
</el-row>
</div>
</div>
</el-main>
<!-- 右侧列表 -->
<el-aside width="200px">
<div ref="apiDocList">
<el-steps style="height: 40%" direction="vertical" :active="apiStepIndex">
</el-main>
<!-- 右侧列表 -->
<el-aside width="200px" style="margin-top: 70px;">
<div ref="apiDocList" >
<el-steps style="height: 40%" direction="vertical" :active="apiStepIndex">
<el-step v-for="(apiInfo) in apiSimpleInfoArray" :key="apiInfo.id" @click.native="clickStep(apiInfo.id)">
<el-link slot="title">{{apiInfo.name}}</el-link>
<el-link slot="title">{{ apiInfo.name }}</el-link>
</el-step>
</el-steps>
</div>
</el-aside>
</el-container>
</el-steps>
</div>
</el-aside>
</el-container>
</div>
</template>
<script>
import MsAnchor from "./Anchor";
import {API_METHOD_COLOUR} from "@/business/components/api/definition/model/JsonData";
import {jsonToMap} from "@/common/js/utils";
import MsCodeEdit from "@/business/components/common/components/MsCodeEdit";
import {formatJson,} from "@/common/js/format-utils";
import ApiStatus from "@/business/components/api/definition/components/list/ApiStatus";
import {buildNodePath} from "@/business/components/api/definition/model/NodeTree";
import {calculate} from "@/business/components/api/definition/model/ApiTestModel";
import MsJsonCodeEdit from "@/business/components/common/json-schema/JsonSchemaEditor";
export default {
name: "ApiDocumentItem",
components: {
MsAnchor,ApiStatus,
MsJsonCodeEdit,
MsAnchor, ApiStatus, MsCodeEdit,
},
data() {
return {
apiStepIndex:0,
apiSimpleInfoArray:[],
apiInfo:{
method:"无",
uri:"无",
name:"无",
id:"",
requestHead:"无",
urlParams:"无",
requestBodyParamType:"无",
requestBodyFormData:'[]',
requestBodyStrutureData:"",
responseHead:"无",
responseBody:"",
responseBodyParamType:"无",
responseBodyFormData:"无",
responseBodyStrutureData:"无",
responseCode:"无",
apiStepIndex: 0,
apiSimpleInfoArray: [],
modes: ['text', 'json', 'xml', 'html'],
formParamTypes: ['form-data', 'x-www-from-urlencoded', 'BINARY'],
mockVariableFuncs: [],
apiSearch:{
name:"",
type:"ALL",
orderCondition:"createTimeDesc",
},
apiInfo: {
method: "无",
uri: "无",
name: "无",
id: "",
requestHead: "无",
urlParams: "无",
requestBodyParamType: "无",
requestBodyFormData: '[]',
requestBodyStrutureData: "",
jsonSchemaBody: {},
responseHead: "无",
responseBody: "",
responseBodyParamType: "无",
responseBodyFormData: "无",
responseBodyStrutureData: "无",
responseCode: "无",
},
methodColorMap: new Map(API_METHOD_COLOUR),
clientHeight:'',//
clientHeight: '',//
}
},
props: {
projectId:String,
moduleIds:Array,
projectId: String,
moduleIds: Array,
},
activated() {
this.initApiDocSimpleList();
this.clientHeight = `${document.documentElement.clientHeight}`;//
let that = this;
window.onresize = function(){
this.clientHeight = `${document.documentElement.clientHeight}`;
window.onresize = function () {
this.clientHeight = `${document.documentElement.clientHeight}`;
this.changeFixed(this.clientHeight);
}
},
@ -267,16 +324,16 @@ export default {
this.initApiDocSimpleList();
this.clientHeight = `${document.documentElement.clientHeight}`;//
let that = this;
window.onresize = function(){
this.clientHeight = `${document.documentElement.clientHeight}`;
window.onresize = function () {
this.clientHeight = `${document.documentElement.clientHeight}`;
this.changeFixed(this.clientHeight);
}
},
mounted() {
let that = this;
window.onresize = function(){
this.clientHeight = `${document.documentElement.clientHeight}`;
if(that.$refs.apiDocInfoDiv){
window.onresize = function () {
this.clientHeight = `${document.documentElement.clientHeight}`;
if (that.$refs.apiDocInfoDiv) {
that.$refs.apiDocInfoDiv.style.minHeight = this.clientHeight - 300 + 'px';
that.$refs.apiDocList.style.minHeight = this.clientHeight - 300 + 'px';
@ -291,109 +348,172 @@ export default {
watch: {
'$route.params.documentId'() {
},
moduleIds(){
moduleIds() {
this.initApiDocSimpleList();
},
clientHeight(){ //clientHeight
clientHeight() { //clientHeight
this.changeFixed(this.clientHeight);
}
},
methods: {
changeFixed(clientHeight){
if(this.$refs.apiDocInfoDiv){
this.$refs.apiDocInfoDiv.style.height = clientHeight -300 + 'px';
formatRowData(dataType, data) {
var returnData = data;
if (data) {
returnData = data.replace(/\n/g, '<br>');
}
return returnData;
},
changeFixed(clientHeight) {
if (this.$refs.apiDocInfoDiv) {
this.$refs.apiDocInfoDiv.style.height = clientHeight - 350 + 'px';
this.$refs.apiDocInfoDiv.style.overflow = 'auto';
this.$refs.apiDocList.style.height = clientHeight -300 + 'px';
this.$refs.apiDocList.style.height = clientHeight - 350 + 'px';
}
},
initApiDocSimpleList(){
let simpleRequest = {};
if(this.projectId!=null && this.projectId!= ""){
simpleRequest.projectId=this.projectId;
initApiDocSimpleList() {
let simpleRequest = this.apiSearch;
if (this.projectId != null && this.projectId != "") {
simpleRequest.projectId = this.projectId;
}
if(this.documentId!=null && this.documentId!= ""){
simpleRequest.documentId=this.documentId;
if (this.documentId != null && this.documentId != "") {
simpleRequest.documentId = this.documentId;
}
if(this.moduleIds.length>0){
simpleRequest.moduleIds=this.moduleIds;
if (this.moduleIds.length > 0) {
simpleRequest.moduleIds = this.moduleIds;
}
let simpleInfoUrl = "/api/document/selectApiSimpleInfo";
this.$post(simpleInfoUrl, simpleRequest, response => {
this.apiSimpleInfoArray = response.data;
this.apiStepIndex = 0;
if(this.apiSimpleInfoArray.length>0){
if (this.apiSimpleInfoArray.length > 0) {
this.selectApiInfo(this.apiSimpleInfoArray[0].id);
}
});
},
selectApiInfo(apiId){
let simpleInfoUrl = "/api/document/selectApiInfoById/"+apiId;
selectApiInfo(apiId) {
let simpleInfoUrl = "/api/document/selectApiInfoById/" + apiId;
this.$get(simpleInfoUrl, response => {
this.apiInfo = response.data;
});
},
clickStep(apiId){
clickStep(apiId) {
for (let index = 0; index < this.apiSimpleInfoArray.length; index++) {
if(apiId == this.apiSimpleInfoArray[index].id){
if (apiId == this.apiSimpleInfoArray[index].id) {
this.apiStepIndex = index;
break;
}
}
this.selectApiInfo(apiId);
},
stepClick(stepIndex){
stepClick(stepIndex) {
this.apiStepIndex = stepIndex;
},
getColor(enable, method) {
return this.methodColorMap.get(method);
},
getJsonArr(jsonString){
formatBoolean(row, column, cellValue) {
var ret = '' //
if (cellValue) {
ret = "是" //
} else {
ret = "否"
}
return ret;
},
getJsonArr(jsonString) {
let returnJsonArr = [];
if(jsonString == '无'){
if (jsonString == '无') {
return returnJsonArr;
}
let jsonArr = JSON.parse(jsonString);
//
for(var index = 0;index < jsonArr.length;index++){
for (var index = 0; index < jsonArr.length; index++) {
var item = jsonArr[index];
if(item.name!="" && item.name!=null){
if (item.name != "" && item.name != null) {
returnJsonArr.push(item);
}
}
return returnJsonArr;
}
},
//
genPreviewData(previewData) {
if (previewData != null && previewData != '') {
let showDataObj = {};
for (var key in previewData) {
// showDataObj.set(key,previewData[key]);
let value = previewData[key];
if (value.indexOf("@") >= 0) {
value = this.showPreview(value);
}
showDataObj[key] = value;
}
showDataObj = JSON.stringify(showDataObj);
previewData = formatJson(showDataObj);
}
return previewData;
},
showPreview(itemValue) {
//
if (!itemValue) {
return;
}
let index = itemValue.indexOf("|");
if (index > -1) {
itemValue = itemValue.substring(0, index).trim();
}
this.mockVariableFuncs.forEach(f => {
if (!f.name) {
return;
}
itemValue += "|" + f.name;
if (f.params) {
itemValue += ":" + f.params.map(p => p.value).join(",");
}
});
itemValue = calculate(itemValue);
return itemValue;
},
},
}
</script>
<style scoped>
.simpleFontClass{
.simpleFontClass {
font-size: 14px;
}
.blackFontClass{
.blackFontClass {
font-weight: bold;
font-size: 14px;
}
.smallFontClass{
.smallFontClass {
font-size: 13px;
margin: 20px 0px;
}
.tip {
padding: 3px 5px;
font-size: 14px;
border-radius: 4px;
border-left: 4px solid #783887;
}
.apiInfoRow{
.apiInfoRow {
margin: 20px 10px;
}
.apiStatusTag{
.apiStatusTag {
margin: 20px 5px;
}
.showDataDiv{
.showDataDiv {
background-color: #F5F7F9;
margin: 20px 0px;
}
@ -405,9 +525,11 @@ export default {
color: #C0C4CC;
border-color: #C0C4CC;
}
/deep/ .el-step__title.is-finish /deep/ .el-link.el-link--default{
/deep/ .el-step__title.is-finish /deep/ .el-link.el-link--default {
color: #C0C4CC;
}
/*
步骤条中当前节点样式和当前a标签的样式
*/
@ -415,8 +537,35 @@ export default {
color: #783887;
border-color: #783887;
}
/deep/ .el-step__title.is-process /deep/ .el-link.el-link--default {
color: #783887;
}
.document-table {
margin: 20px 10px;
width: auto;
}
.document-table /deep/ .el-table__row {
font-size: 12px;
font-weight: initial;
}
.document-table /deep/ .has-gutter {
font-size: 12px;
color: #404040;
}
.document-table /deep/ td {
border-right: 0px solid #EBEEF5
}
.document-table /deep/ th {
background-color: #FAFAFA;
border-right: 0px solid #EBEEF5
}
.el-divider--horizontal {
margin: 12px 0;
}
</style>

View File

@ -163,13 +163,14 @@ import MsTableHeaderSelectPopover from "@/business/components/common/components/
import MsTableAdvSearchBar from "@/business/components/common/components/search/MsTableAdvSearchBar";
import {API_CASE_CONFIGS} from "@/business/components/common/components/search/search-components";
import {_filter, _handleSelect, _handleSelectAll, _sort,} from "@/common/js/tableUtils";
import {API_CASE_LIST, TEST_CASE_LIST} from "@/common/js/constants";
import {Api_Case_List, Track_Test_Case} from "@/business/components/common/model/JsonData";
import {API_CASE_LIST, API_LIST, API_SCENARIO_LIST, TEST_CASE_LIST} from "@/common/js/constants";
import {Api_Case_List, Api_List, Track_Test_Case} from "@/business/components/common/model/JsonData";
import HeaderCustom from "@/business/components/common/head/HeaderCustom";
export default {
name: "ApiCaseSimpleList",
components: {
ApiListContainerWithDoc,
HeaderCustom,
MsTableHeaderSelectPopover,
MsSetEnvironment,
@ -181,28 +182,19 @@ export default {
MsTablePagination,
MsTag,
MsApiCaseList,
name: "ApiCaseSimpleList",
components: {
MsTableHeaderSelectPopover,
MsSetEnvironment,
ApiCaseList,
PriorityTableItem,
ApiListContainerWithDoc,
MsTableOperatorButton,
MsTableOperator,
MsTablePagination,
MsTag,
MsApiCaseList,
MsContainer,
MsBottomContainer,
ShowMoreBtn,
MsBatchEdit,
MsApiCaseTableExtendBtns,
MsContainer,
MsBottomContainer,
ShowMoreBtn,
MsBatchEdit,
MsApiCaseTableExtendBtns,
MsReferenceView,
MsTableAdvSearchBar
},
data() {
return {
type: API_CASE_LIST,
headerItems: Api_Case_List,
tableLabel: Api_Case_List,
condition: {
components: API_CASE_CONFIGS
},
@ -636,7 +628,6 @@ export default {
});
},
},
},
}
</script>

View File

@ -596,6 +596,36 @@ export default {
other_config: "Other Config",
message_template: "Message Template",
tcp_parameter_tip: "The request parameters can be referenced in the request template ${XXX}",
},
document: {
order: "Order",
create_time_sort: "From back to front by create time",
edit_time_positive_sequence: "From front to back by update time",
edit_time_Reverse_order: "From back to front by update time",
request_method: "Request method",
request_interface: "Request interface",
search_by_api_name : "Search by api name",
request_info: "Request info",
request_head: "Request head",
request_param: "Param",
request_body: "Request body",
example_presentation: "Example presentation",
response_info: "Response info",
response_head: "Response head",
response_body: "Response body",
response_code: "Response code",
table_coloum:{
name: "name",
value: "value",
is_required: "Is it required",
desc: "Description",
type: "Type",
default_value: "Default value",
},
data_set: {
all: "All",
none: "None"
},
}
},
automation: {
@ -849,6 +879,8 @@ export default {
postman_tip: "Only Postman Collection V2.1 json files are supported",
postman_export_tip: "Export the test collection by Postman",
swagger_export_tip: "Export jSON-formatted files via Swagger website",
jmeter_export_tip: "Generating JMX file through JMeter",
jmeter_tip: "JMX files supporting JMeter 5.2",
suffixFormatErr: "The file format does not meet the requirements",
swagger_url_import: "Import using URL",
timing_synchronization: "Timing synchronization",

View File

@ -597,6 +597,36 @@ export default {
other_config: "其他设置",
message_template: "报文模版",
tcp_parameter_tip: "请求参数可以在请求模版通过${xxx}引用",
},
document: {
order: "排序方式",
create_time_sort: "按创建时间从后到前",
edit_time_positive_sequence: "按更新时间从前到后",
edit_time_Reverse_order: "按更新时间从后到前",
request_method: "请求方式",
request_interface: "请求接口",
search_by_api_name : "名称搜索",
request_info: "请求信息",
request_head: "请求头",
request_param: "参数",
request_body: "请求体",
example_presentation: "范例展示",
response_info: "响应信息",
response_head: "响应头",
response_body: "响应体",
response_code: "响应码",
table_coloum:{
name: "名称",
value: "值",
is_required: "是否必填",
desc: "描述",
type: "类型",
default_value: "默认值",
},
data_set: {
all: "全部",
none: "无"
},
}
},
automation: {
@ -715,7 +745,7 @@ export default {
jmeter_func: "Jmeter 方法",
parameters_filter_example: "示例",
parameters_filter_tips: "只支持 MockJs 函数结果预览",
parameters_advance: "高级参数设置",
parameters_advance: "return",
parameters_preview: "预览",
parameters_mock_filter_tips: "请输入关键字进行过滤",
parameters_pre_request: "前置请求提取",
@ -852,6 +882,8 @@ export default {
swagger_tip: "支持 Swagger 2.0 与 3.0 版本的 json 文件",
post_export_tip: "通过 Postman 导出测试集合",
swagger_export_tip: "通过 Swagger 页面导出",
jmeter_export_tip: "通过 Jmeter 生成JMX文件",
jmeter_tip: "支持 Jmeter 5.2版本的JMX 文件",
suffixFormatErr: "文件格式不符合要求",
swagger_url_import: "使用URL导入",
timing_synchronization: "定时同步",

View File

@ -596,6 +596,36 @@ export default {
other_config: "其他設置",
message_template: "報文模版",
tcp_parameter_tip: "請求參數可以在請求模版通過${xxx}引用",
},
document: {
order: "排序方式",
create_time_sort: "按創建時間從後到前",
edit_time_positive_sequence: "按更新時間從前到後",
edit_time_Reverse_order: "按更新時間從後到前",
request_method: "請求方式",
request_interface: "請求接口e",
search_by_api_name : "API名稱搜索",
request_info: "請求信息",
request_head: "請求頭",
request_param: "參數",
request_body: "請求體",
example_presentation: "範例展示",
response_info: "響應信息",
response_head: "響應頭",
response_body: "響應體",
response_code: "響應碼",
table_coloum:{
name: "名稱",
value: "值",
is_required: "是否必填",
desc: "描述",
type: "類型",
default_value: "默認值",
},
data_set: {
all: "全部",
none: "無"
},
}
},
automation: {
@ -851,6 +881,8 @@ export default {
swagger_tip: "支持 Swagger 2.0 與 3.0 版本的 json 文件",
post_export_tip: "通過 Postman 導出測試集合",
swagger_export_tip: "通過 Swagger 頁面導出",
jmeter_export_tip: "通過 Jmeter 生成JMX文件",
jmeter_tip: "支持 Jmeter 5.2版本的JMX 文件",
suffixFormatErr: "文件格式不符合要求",
swagger_url_import: "使用URL導入",
timing_synchronization: "定時同步",