feat(接口定义): 请求数据格式按照jmeter 数据格式重构完成

This commit is contained in:
fit2-zhao 2020-11-19 19:14:06 +08:00
parent 8fd084db51
commit bdaff53eaf
41 changed files with 951 additions and 379 deletions

View File

@ -5,6 +5,7 @@ import com.github.pagehelper.PageHelper;
import io.metersphere.api.dto.APIReportResult; import io.metersphere.api.dto.APIReportResult;
import io.metersphere.api.dto.definition.ApiDefinitionRequest; import io.metersphere.api.dto.definition.ApiDefinitionRequest;
import io.metersphere.api.dto.definition.ApiDefinitionResult; import io.metersphere.api.dto.definition.ApiDefinitionResult;
import io.metersphere.api.dto.definition.RunDefinitionRequest;
import io.metersphere.api.dto.definition.SaveApiDefinitionRequest; import io.metersphere.api.dto.definition.SaveApiDefinitionRequest;
import io.metersphere.api.service.ApiDefinitionService; import io.metersphere.api.service.ApiDefinitionService;
import io.metersphere.base.domain.ApiDefinition; import io.metersphere.base.domain.ApiDefinition;
@ -61,12 +62,12 @@ public class ApiDefinitionController {
} }
@PostMapping(value = "/run/debug", consumes = {"multipart/form-data"}) @PostMapping(value = "/run/debug", consumes = {"multipart/form-data"})
public String runDebug(@RequestPart("request") SaveApiDefinitionRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) { public String runDebug(@RequestPart("request") RunDefinitionRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
return apiDefinitionService.run(request, bodyFiles); return apiDefinitionService.run(request, bodyFiles);
} }
@PostMapping(value = "/run", consumes = {"multipart/form-data"}) @PostMapping(value = "/run", consumes = {"multipart/form-data"})
public String run(@RequestPart("request") SaveApiDefinitionRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) { public String run(@RequestPart("request") RunDefinitionRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
return apiDefinitionService.run(request, bodyFiles); return apiDefinitionService.run(request, bodyFiles);
} }

View File

@ -29,13 +29,13 @@ public class ApiTestCaseController {
} }
@PostMapping(value = "/create", consumes = {"multipart/form-data"}) @PostMapping(value = "/create", consumes = {"multipart/form-data"})
public void create(@RequestPart("request") SaveApiTestCaseRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) { public void create(@RequestPart("request") SaveApiTestCaseRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
apiTestCaseService.create(request, file, bodyFiles); apiTestCaseService.create(request, bodyFiles);
} }
@PostMapping(value = "/update", consumes = {"multipart/form-data"}) @PostMapping(value = "/update", consumes = {"multipart/form-data"})
public void update(@RequestPart("request") SaveApiTestCaseRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) { public void update(@RequestPart("request") SaveApiTestCaseRequest request, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
apiTestCaseService.update(request, file, bodyFiles); apiTestCaseService.update(request, bodyFiles);
} }
@GetMapping("/delete/{id}") @GetMapping("/delete/{id}")

View File

@ -0,0 +1,23 @@
package io.metersphere.api.dto.definition;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.response.Response;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Setter
@Getter
public class RunDefinitionRequest {
private String id;
private String reportId;
private MsTestElement testElement;
private Response response;
private List<String> bodyUploadIds;
}

View File

@ -1,7 +1,7 @@
package io.metersphere.api.dto.definition; package io.metersphere.api.dto.definition;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.response.Response; import io.metersphere.api.dto.definition.response.Response;
import io.metersphere.api.dto.scenario.Scenario;
import io.metersphere.base.domain.Schedule; import io.metersphere.base.domain.Schedule;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -32,9 +32,7 @@ public class SaveApiDefinitionRequest {
private String method; private String method;
private Scenario scenario; private MsTestElement request;
private Object request;
private Response response; private Response response;

View File

@ -1,6 +1,6 @@
package io.metersphere.api.dto.definition; package io.metersphere.api.dto.definition;
import io.metersphere.api.dto.scenario.request.Request; import io.metersphere.api.dto.definition.request.MsTestElement;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -22,7 +22,7 @@ public class SaveApiTestCaseRequest {
private String description; private String description;
private Request request; private MsTestElement request;
private String response; private String response;

View File

@ -1,17 +0,0 @@
package io.metersphere.api.dto.definition.request;
import org.apache.jorphan.collections.HashTree;
public class Element {
public String id;
public String type;
public String name;
public HashTree elementHashTree;
}

View File

@ -1,22 +0,0 @@
package io.metersphere.api.dto.definition.request;
public class HTTPSamplerProxy extends Element {
public String protocol;
public String domain;
public String port;
public String method;
public String path;
public String contentEncoding;
public boolean autoRedirects;
public boolean followRedirects;
public boolean useKeepalive;
public boolean postBodyRaw;
public boolean doMultipartPost;
public boolean browserCompatibleMultipart;
public String embeddedUrlRe;
public String connectTimeout;
public String responseTimeout;
public String arguments;
}

View File

@ -0,0 +1,79 @@
package io.metersphere.api.dto.definition.request;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.metersphere.api.dto.definition.request.configurations.MsHeaderManager;
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.MsHTTPSamplerProxy;
import io.metersphere.commons.utils.LogUtil;
import lombok.Data;
import org.apache.jmeter.save.SaveService;
import org.apache.jorphan.collections.HashTree;
import org.apache.jorphan.collections.ListedHashTree;
import java.io.ByteArrayOutputStream;
import java.util.LinkedList;
import java.util.List;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = MsHTTPSamplerProxy.class, name = "HTTPSamplerProxy"),
@JsonSubTypes.Type(value = MsHeaderManager.class, name = "HeaderManager"),
@JsonSubTypes.Type(value = MsJSR223PostProcessor.class, name = "JSR223PostProcessor"),
@JsonSubTypes.Type(value = MsJSR223PreProcessor.class, name = "JSR223PreProcessor"),
@JsonSubTypes.Type(value = MsTestPlan.class, name = "TestPlan"),
@JsonSubTypes.Type(value = MsThreadGroup.class, name = "ThreadGroup"),
})
@JSONType(seeAlso = {MsHTTPSamplerProxy.class, MsHeaderManager.class, MsJSR223PostProcessor.class, MsJSR223PreProcessor.class}, typeKey = "type")
@Data
public class MsTestElement {
private String type;
@JSONField(ordinal = 1)
private String id;
@JSONField(ordinal = 2)
private String name;
@JSONField(ordinal = 3)
private String label;
@JSONField(ordinal = 4)
private LinkedList<MsTestElement> hashTree;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
for (MsTestElement el : hashTree) {
el.toHashTree(tree, el.hashTree);
}
}
/**
* 转换JMX
*
* @param hashTree
* @return
*/
public String getJmx(HashTree hashTree) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
SaveService.saveTree(hashTree, baos);
System.out.print(baos.toString());
return baos.toString();
} catch (Exception e) {
e.printStackTrace();
LogUtil.warn("HashTree error, can't log jmx content");
}
return null;
}
public HashTree get() {
HashTree jmeterTestPlanHashTree = new ListedHashTree();
this.toHashTree(jmeterTestPlanHashTree, this.hashTree);
return jmeterTestPlanHashTree;
}
}

View File

@ -0,0 +1,42 @@
package io.metersphere.api.dto.definition.request;
import com.alibaba.fastjson.annotation.JSONType;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jorphan.collections.HashTree;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@JSONType(typeName = "TestPlan")
public class MsTestPlan extends MsTestElement {
private String type = "TestPlan";
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
final HashTree testPlanTree = tree.add(getPlan());
if (CollectionUtils.isNotEmpty(hashTree)) {
hashTree.forEach(el -> {
el.toHashTree(testPlanTree, el.getHashTree());
});
}
}
public TestPlan getPlan() {
TestPlan testPlan = new TestPlan(this.getName() + "TestPlan");
testPlan.setProperty(TestElement.TEST_CLASS, TestPlan.class.getName());
testPlan.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestPlanGui"));
testPlan.setEnabled(true);
testPlan.setFunctionalMode(false);
testPlan.setSerialized(true);
testPlan.setTearDownOnShutdown(true);
testPlan.setUserDefinedVariables(new Arguments());
return testPlan;
}
}

View File

@ -0,0 +1,52 @@
package io.metersphere.api.dto.definition.request;
import com.alibaba.fastjson.annotation.JSONType;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.jmeter.control.LoopController;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.threads.ThreadGroup;
import org.apache.jorphan.collections.HashTree;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@JSONType(typeName = "ThreadGroup")
public class MsThreadGroup extends MsTestElement {
private String type = "ThreadGroup";
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
final HashTree groupTree = tree.add(getThreadGroup());
if (CollectionUtils.isNotEmpty(hashTree)) {
hashTree.forEach(el -> {
el.toHashTree(groupTree, el.getHashTree());
});
}
}
public ThreadGroup getThreadGroup() {
LoopController loopController = new LoopController();
loopController.setName("LoopController");
loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName());
loopController.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("LoopControlPanel"));
loopController.setEnabled(true);
loopController.setLoops(1);
ThreadGroup threadGroup = new ThreadGroup();
threadGroup.setEnabled(true);
threadGroup.setName(this.getName() + "ThreadGroup");
threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName());
threadGroup.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ThreadGroupGui"));
threadGroup.setNumThreads(1);
threadGroup.setRampUp(1);
threadGroup.setDelay(0);
threadGroup.setDuration(0);
threadGroup.setProperty(ThreadGroup.ON_SAMPLE_ERROR, ThreadGroup.ON_SAMPLE_ERROR_CONTINUE);
threadGroup.setScheduler(false);
threadGroup.setSamplerController(loopController);
return threadGroup;
}
}

View File

@ -0,0 +1,43 @@
package io.metersphere.api.dto.definition.request.configurations;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.scenario.KeyValue;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.jmeter.protocol.http.control.Header;
import org.apache.jmeter.protocol.http.control.HeaderManager;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@JSONType(typeName = "HeaderManager")
public class MsHeaderManager extends MsTestElement {
private String type = "HeaderManager";
@JSONField(ordinal = 10)
private List<KeyValue> headers;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
HeaderManager headerManager = new HeaderManager();
headerManager.setEnabled(true);
headerManager.setName(this.getName() + "Headers");
headerManager.setProperty(TestElement.TEST_CLASS, HeaderManager.class.getName());
headerManager.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("HeaderPanel"));
headers.stream().filter(KeyValue::isValid).filter(KeyValue::isEnable).forEach(keyValue ->
headerManager.add(new Header(keyValue.getName(), keyValue.getValue()))
);
final HashTree headersTree = tree.add(headerManager);
if (CollectionUtils.isNotEmpty(hashTree)) {
hashTree.forEach(el -> {
el.toHashTree(headersTree, el.getHashTree());
});
}
}
}

View File

@ -0,0 +1,78 @@
package io.metersphere.api.dto.definition.request.dns;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.scenario.environment.Host;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.http.control.DNSCacheManager;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import java.util.ArrayList;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@JSONType(typeName = "DNSCacheManager")
public class MsDNSCacheManager extends MsTestElement {
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
for (MsTestElement el : hashTree) {
el.toHashTree(tree, el.getHashTree());
}
}
public static void addEnvironmentVariables(HashTree samplerHashTree, String name, EnvironmentConfig config) {
name += "Environment Variables";
samplerHashTree.add(arguments(name, config.getCommonConfig().getVariables()));
}
public static void addEnvironmentDNS(HashTree samplerHashTree, String name, EnvironmentConfig config) {
if (config.getCommonConfig().isEnableHost() && CollectionUtils.isNotEmpty(config.getCommonConfig().getHosts())) {
String domain = config.getHttpConfig().getDomain().trim();
List<Host> hosts = new ArrayList<>();
config.getCommonConfig().getHosts().forEach(host -> {
if (StringUtils.isNotBlank(host.getDomain())) {
String hostDomain = host.getDomain().trim().replace("http://", "").replace("https://", "");
if (StringUtils.equals(hostDomain, domain)) {
host.setDomain(hostDomain); // 域名去掉协议
hosts.add(host);
}
}
});
samplerHashTree.add(dnsCacheManager(name + " DNSCacheManager", hosts));
}
}
private static Arguments arguments(String name, List<KeyValue> variables) {
Arguments arguments = new Arguments();
arguments.setEnabled(true);
arguments.setName(name);
arguments.setProperty(TestElement.TEST_CLASS, Arguments.class.getName());
arguments.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ArgumentsPanel"));
variables.stream().filter(KeyValue::isValid).filter(KeyValue::isEnable).forEach(keyValue ->
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=")
);
return arguments;
}
private static DNSCacheManager dnsCacheManager(String name, List<Host> hosts) {
DNSCacheManager dnsCacheManager = new DNSCacheManager();
dnsCacheManager.setEnabled(true);
dnsCacheManager.setName(name);
dnsCacheManager.setProperty(TestElement.TEST_CLASS, DNSCacheManager.class.getName());
dnsCacheManager.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("DNSCachePanel"));
dnsCacheManager.setCustomResolver(true);
hosts.forEach(host -> dnsCacheManager.addHost(host.getDomain(), host.getIp()));
return dnsCacheManager;
}
}

View File

@ -0,0 +1,48 @@
package io.metersphere.api.dto.definition.request.processors.post;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.prop.StringProp;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.jmeter.extractor.JSR223PostProcessor;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@JSONType(typeName = "JSR223PostProcessor")
public class MsJSR223PostProcessor extends MsTestElement {
private String type = "JSR223PostProcessor";
@JSONField(ordinal = 10)
private StringProp script;
@JSONField(ordinal = 11)
private StringProp scriptLanguage;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
JSR223PostProcessor processor = new JSR223PostProcessor();
processor.setEnabled(true);
processor.setName(this.getName() + "JSR223PostProcessor");
processor.setProperty(TestElement.TEST_CLASS, JSR223PostProcessor.class.getName());
processor.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI"));
processor.setProperty("cacheKey", "true");
processor.setProperty("scriptLanguage", this.getScriptLanguage().getValue());
processor.setProperty("script", this.getScript().getValue());
final HashTree jsr223PostTree = tree.add(processor);
if (CollectionUtils.isNotEmpty(hashTree)) {
hashTree.forEach(el -> {
el.toHashTree(jsr223PostTree, el.getHashTree());
});
}
}
}

View File

@ -0,0 +1,47 @@
package io.metersphere.api.dto.definition.request.processors.pre;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.prop.StringProp;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.jmeter.modifiers.JSR223PreProcessor;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@JSONType(typeName = "JSR223PreProcessor")
public class MsJSR223PreProcessor extends MsTestElement {
private String type = "JSR223PreProcessor";
@JSONField(ordinal = 10)
private StringProp script;
@JSONField(ordinal = 11)
private StringProp scriptLanguage;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
JSR223PreProcessor processor = new JSR223PreProcessor();
processor.setEnabled(true);
processor.setName(this.getName() + "JSR223PreProcessor");
processor.setProperty(TestElement.TEST_CLASS, JSR223PreProcessor.class.getName());
processor.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI"));
processor.setProperty("cacheKey", "true");
processor.setProperty("scriptLanguage", this.getScriptLanguage().getValue());
processor.setProperty("script", this.getScript().getValue());
final HashTree jsr223PreTree = tree.add(processor);
if (CollectionUtils.isNotEmpty(hashTree)) {
hashTree.forEach(el -> {
el.toHashTree(jsr223PreTree, el.getHashTree());
});
}
}
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.definition.request.prop;
import lombok.Data;
@Data
public class BoolProp {
private String id;
private String key;
private String name;
private String type;
private boolean value;
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.definition.request.prop;
import lombok.Data;
@Data
public class StringProp {
private String id;
private String key;
private String name;
private String type;
private String value;
}

View File

@ -0,0 +1,231 @@
package io.metersphere.api.dto.definition.request.sampler;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.dns.MsDNSCacheManager;
import io.metersphere.api.dto.definition.request.prop.BoolProp;
import io.metersphere.api.dto.definition.request.prop.StringProp;
import io.metersphere.api.dto.scenario.AuthConfig;
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.service.ApiTestEnvironmentService;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import org.apache.jmeter.protocol.http.util.HTTPArgument;
import org.apache.jmeter.protocol.http.util.HTTPFileArg;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Data
@EqualsAndHashCode(callSuper = true)
@JSONType(typeName = "HTTPSamplerProxy")
public class MsHTTPSamplerProxy extends MsTestElement {
private String type = "HTTPSamplerProxy";
@JSONField(ordinal = 10)
private StringProp protocol;
@JSONField(ordinal = 11)
private StringProp domain;
@JSONField(ordinal = 12)
private StringProp port;
@JSONField(ordinal = 13)
private StringProp method;
@JSONField(ordinal = 14)
private StringProp path;
@JSONField(ordinal = 15)
private StringProp connectTimeout;
@JSONField(ordinal = 16)
private StringProp responseTimeout;
@JSONField(ordinal = 17)
private List<KeyValue> arguments;
@JSONField(ordinal = 18)
private Body body;
@JSONField(ordinal = 19)
private List<KeyValue> rest;
@JSONField(ordinal = 20)
private AuthConfig authConfig;
@JSONField(ordinal = 21)
private BoolProp followRedirects;
@JSONField(ordinal = 22)
private BoolProp doMultipartPost;
@JSONField(ordinal = 23)
private String useEnvironment;
@JSONField(ordinal = 24)
private String url;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
HTTPSamplerProxy sampler = new HTTPSamplerProxy();
sampler.setEnabled(true);
sampler.setName(this.getName());
sampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName());
sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("HttpTestSampleGui"));
sampler.setMethod(this.getMethod().getValue());
sampler.setContentEncoding("UTF-8");
sampler.setConnectTimeout(this.getConnectTimeout().getValue() == null ? "6000" : this.getConnectTimeout().getValue());
sampler.setResponseTimeout(this.getResponseTimeout().getValue() == null ? "6000" : this.getResponseTimeout().getValue());
sampler.setFollowRedirects(this.getFollowRedirects() != null ? this.getFollowRedirects().isValue() : true);
sampler.setUseKeepAlive(true);
sampler.setDoMultipart(this.getDoMultipartPost() != null ? this.getDoMultipartPost().isValue() : true);
EnvironmentConfig config = null;
if (useEnvironment != null) {
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
ApiTestEnvironmentWithBLOBs environment = environmentService.get(useEnvironment);
config = JSONObject.parseObject(environment.getConfig(), EnvironmentConfig.class);
}
try {
if (config != null) {
String url = "";
sampler.setDomain(config.getHttpConfig().getDomain());
sampler.setPort(config.getHttpConfig().getPort());
sampler.setProtocol(config.getHttpConfig().getProtocol());
url = config.getHttpConfig().getProtocol() + "://" + config.getHttpConfig().getSocket();
URL urlObject = new URL(url);
String envPath = StringUtils.equals(urlObject.getPath(), "/") ? "" : urlObject.getPath();
if (StringUtils.isNotBlank(this.getPath().getValue())) {
envPath += this.getPath().getValue();
}
sampler.setPath(getPostQueryParameters(URLDecoder.decode(envPath, "UTF-8")));
} else {
String url = this.getUrl();
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url;
}
URL urlObject = new URL(url);
sampler.setDomain(URLDecoder.decode(urlObject.getHost(), "UTF-8"));
sampler.setPort(urlObject.getPort());
sampler.setProtocol(urlObject.getProtocol());
sampler.setPath(getPostQueryParameters(URLDecoder.decode(urlObject.getPath(), "UTF-8")));
}
} catch (Exception e) {
LogUtil.error(e);
}
// 请求参数
if (CollectionUtils.isNotEmpty(this.getArguments())) {
sampler.setArguments(httpArguments(this.getArguments()));
}
// rest参数处理
if (CollectionUtils.isNotEmpty(this.getRest())) {
sampler.setArguments(httpArguments(this.getRest()));
}
// 请求体
if (!StringUtils.equals(this.getMethod().getValue(), "GET")) {
List<KeyValue> body = new ArrayList<>();
if (this.getBody().isKV()) {
body = this.getBody().getKvs().stream().filter(KeyValue::isValid).collect(Collectors.toList());
sampler.setHTTPFiles(httpFileArgs());
} else if (this.getBody().isBinary()) {
// 上传二进制数据处理
} else if (this.getBody().isJson()) {
} else {
if (StringUtils.isNotBlank(this.getBody().getRaw())) {
sampler.setPostBodyRaw(true);
KeyValue keyValue = new KeyValue("", this.getBody().getRaw());
keyValue.setEnable(true);
keyValue.setEncode(false);
body.add(keyValue);
}
if (StringUtils.isNotBlank(this.getBody().getXml())) {
sampler.setPostBodyRaw(true);
KeyValue keyValue = new KeyValue("", this.getBody().getXml());
keyValue.setEnable(true);
keyValue.setEncode(false);
body.add(keyValue);
}
}
sampler.setArguments(httpArguments(body));
}
final HashTree httpSamplerTree = tree.add(sampler);
//判断是否要开启DNS
if (config != null && config.getCommonConfig() != null && config.getCommonConfig().isEnableHost()) {
MsDNSCacheManager.addEnvironmentVariables(httpSamplerTree, this.getName(), config);
MsDNSCacheManager.addEnvironmentDNS(httpSamplerTree, this.getName(), config);
}
if (CollectionUtils.isNotEmpty(hashTree)) {
hashTree.forEach(el -> {
el.toHashTree(httpSamplerTree, el.getHashTree());
});
}
}
private String getPostQueryParameters(String path) {
if (!StringUtils.equals(this.getMethod().getValue(), "GET")) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(path);
stringBuffer.append("?");
this.getArguments().stream().filter(KeyValue::isEnable).filter(KeyValue::isValid).forEach(keyValue ->
stringBuffer.append(keyValue.getName()).append("=").append(keyValue.getValue()).append("&")
);
return stringBuffer.substring(0, stringBuffer.length() - 1);
}
return path;
}
private Arguments httpArguments(List<KeyValue> list) {
Arguments arguments = new Arguments();
list.stream().filter(KeyValue::isValid).filter(KeyValue::isEnable).forEach(keyValue -> {
HTTPArgument httpArgument = new HTTPArgument(keyValue.getName(), keyValue.getValue());
httpArgument.setAlwaysEncoded(keyValue.isEncode());
if (StringUtils.isNotBlank(keyValue.getContentType())) {
httpArgument.setContentType(keyValue.getContentType());
}
arguments.addArgument(httpArgument);
}
);
return arguments;
}
private HTTPFileArg[] httpFileArgs() {
final String BODY_FILE_DIR = "/opt/metersphere/data/body";
List<HTTPFileArg> list = new ArrayList<>();
this.getBody().getKvs().stream().filter(KeyValue::isFile).filter(KeyValue::isEnable).forEach(keyValue -> {
if (keyValue.getFiles() != null) {
keyValue.getFiles().forEach(file -> {
String paramName = keyValue.getName();
String path = BODY_FILE_DIR + '/' + this.getId() + '/' + file.getId() + '_' + file.getName();
String mimetype = keyValue.getContentType();
list.add(new HTTPFileArg(path, paramName, mimetype));
});
}
});
return list.toArray(new HTTPFileArg[0]);
}
}

View File

@ -10,6 +10,7 @@ public class Body {
private String type; private String type;
private String raw; private String raw;
private String format; private String format;
private List<KeyValue> fromUrlencoded;
private List<KeyValue> kvs; private List<KeyValue> kvs;
private List<KeyValue> binary; private List<KeyValue> binary;
private Object json; private Object json;

View File

@ -27,6 +27,8 @@ import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient;
import org.apache.jmeter.visualizers.backend.BackendListenerContext; import org.apache.jmeter.visualizers.backend.BackendListenerContext;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.*; import java.util.*;
@ -63,6 +65,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
@Override @Override
public void setupTest(BackendListenerContext context) throws Exception { public void setupTest(BackendListenerContext context) throws Exception {
setConsole();
setParam(context); setParam(context);
apiTestService = CommonBeanFactory.getBean(APITestService.class); apiTestService = CommonBeanFactory.getBean(APITestService.class);
if (apiTestService == null) { if (apiTestService == null) {
@ -109,6 +112,18 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
super.setupTest(context); super.setupTest(context);
} }
//获得控制台内容
private PrintStream oldPrintStream = System.out;
private ByteArrayOutputStream bos = new ByteArrayOutputStream();
private void setConsole() {
System.setOut(new PrintStream(bos)); //设置新的out
}
private String getConsole() {
System.setOut(oldPrintStream);
return bos.toString();
}
@Override @Override
public void handleSampleResults(List<SampleResult> sampleResults, BackendListenerContext context) { public void handleSampleResults(List<SampleResult> sampleResults, BackendListenerContext context) {
@ -120,8 +135,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
TestResult testResult = new TestResult(); TestResult testResult = new TestResult();
testResult.setTestId(testId); testResult.setTestId(testId);
testResult.setTotal(queue.size()); testResult.setTotal(queue.size());
// 一个脚本里可能包含多个场景(MsThreadGroup)所以要区分开key: 场景Id
// 一个脚本里可能包含多个场景(ThreadGroup)所以要区分开key: 场景Id
final Map<String, ScenarioResult> scenarios = new LinkedHashMap<>(); final Map<String, ScenarioResult> scenarios = new LinkedHashMap<>();
queue.forEach(result -> { queue.forEach(result -> {
// 线程名称: <场景名> <场景Index>-<请求Index>, 例如Scenario 2-1 // 线程名称: <场景名> <场景Index>-<请求Index>, 例如Scenario 2-1
@ -276,7 +290,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
responseResult.setResponseSize(result.getResponseData().length); responseResult.setResponseSize(result.getResponseData().length);
responseResult.setResponseTime(result.getTime()); responseResult.setResponseTime(result.getTime());
responseResult.setResponseMessage(result.getResponseMessage()); responseResult.setResponseMessage(result.getResponseMessage());
responseResult.setConsole(getConsole());
if (JMeterVars.get(result.hashCode()) != null) { if (JMeterVars.get(result.hashCode()) != null) {
List<String> vars = new LinkedList<>(); List<String> vars = new LinkedList<>();
JMeterVars.get(result.hashCode()).entrySet().parallelStream().reduce(vars, (first, second) -> { JMeterVars.get(result.hashCode()).entrySet().parallelStream().reduce(vars, (first, second) -> {

View File

@ -42,12 +42,12 @@ public class JMeterService {
return jmxGenerator.parse(testId, testName, scenarios); return jmxGenerator.parse(testId, testName, scenarios);
} }
public void run(String testId, String testName, List<Scenario> scenarios, String debugReportId,String runMode) { public void run(String testId, String testName, List<Scenario> scenarios, String debugReportId, String runMode) {
try { try {
init(); init();
HashTree testPlan = getHashTree(testId, testName, scenarios); HashTree testPlan = getHashTree(testId, testName, scenarios);
JMeterVars.addJSR223PostProcessor(testPlan); JMeterVars.addJSR223PostProcessor(testPlan);
addBackendListener(testId, debugReportId,runMode, testPlan); addBackendListener(testId, debugReportId, runMode, testPlan);
LocalRunner runner = new LocalRunner(testPlan); LocalRunner runner = new LocalRunner(testPlan);
runner.run(); runner.run();
} catch (Exception e) { } catch (Exception e) {
@ -82,12 +82,12 @@ public class JMeterService {
} }
} }
private void addBackendListener(String testId, String debugReportId,String runMode, HashTree testPlan) { private void addBackendListener(String testId, String debugReportId, String runMode, HashTree testPlan) {
BackendListener backendListener = new BackendListener(); BackendListener backendListener = new BackendListener();
backendListener.setName(testId); backendListener.setName(testId);
Arguments arguments = new Arguments(); Arguments arguments = new Arguments();
arguments.addArgument(APIBackendListenerClient.TEST_ID, testId); arguments.addArgument(APIBackendListenerClient.TEST_ID, testId);
if(StringUtils.isNotBlank(runMode)){ if (StringUtils.isNotBlank(runMode)) {
arguments.addArgument("runMode", runMode); arguments.addArgument("runMode", runMode);
} }
if (StringUtils.isNotBlank(debugReportId)) { if (StringUtils.isNotBlank(debugReportId)) {
@ -97,4 +97,17 @@ public class JMeterService {
backendListener.setClassname(APIBackendListenerClient.class.getCanonicalName()); backendListener.setClassname(APIBackendListenerClient.class.getCanonicalName());
testPlan.add(testPlan.getArray()[0], backendListener); testPlan.add(testPlan.getArray()[0], backendListener);
} }
public void runDefinition(String testId, HashTree testPlan, String debugReportId, String runMode) {
try {
JMeterVars.addJSR223PostProcessor(testPlan);
addBackendListener(testId, debugReportId, runMode, testPlan);
LocalRunner runner = new LocalRunner(testPlan);
runner.run();
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("api_load_script_error"));
}
}
} }

View File

@ -25,6 +25,8 @@ public class ResponseResult {
private String vars; private String vars;
private String console;
private final List<ResponseAssertionResult> assertions = new ArrayList<>(); private final List<ResponseAssertionResult> assertions = new ArrayList<>();
} }

View File

@ -26,7 +26,7 @@ public class JmeterDocumentParser {
private final static String STRING_PROP = "stringProp"; private final static String STRING_PROP = "stringProp";
private final static String ARGUMENTS = "Arguments"; private final static String ARGUMENTS = "Arguments";
private final static String COLLECTION_PROP = "collectionProp"; private final static String COLLECTION_PROP = "collectionProp";
private final static String HTTP_SAMPLER_PROXY = "HTTPSamplerProxy"; private final static String HTTP_SAMPLER_PROXY = "MsHTTPSamplerProxy";
private final static String ELEMENT_PROP = "elementProp"; private final static String ELEMENT_PROP = "elementProp";
public static byte[] parse(byte[] source) { public static byte[] parse(byte[] source) {

View File

@ -3,11 +3,7 @@ package io.metersphere.api.service;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.APIReportResult; import io.metersphere.api.dto.APIReportResult;
import io.metersphere.api.dto.definition.ApiComputeResult; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.definition.ApiDefinitionRequest;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
import io.metersphere.api.dto.definition.SaveApiDefinitionRequest;
import io.metersphere.api.dto.scenario.Scenario;
import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.jmeter.TestResult; import io.metersphere.api.jmeter.TestResult;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
@ -24,6 +20,7 @@ import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.i18n.Translator; import io.metersphere.i18n.Translator;
import io.metersphere.service.FileService; import io.metersphere.service.FileService;
import io.metersphere.service.QuotaService; import io.metersphere.service.QuotaService;
import org.apache.jorphan.collections.HashTree;
import org.aspectj.util.FileUtil; import org.aspectj.util.FileUtil;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -101,7 +98,6 @@ public class ApiDefinitionService {
public void update(SaveApiDefinitionRequest request, List<MultipartFile> bodyFiles) { public void update(SaveApiDefinitionRequest request, List<MultipartFile> bodyFiles) {
deleteFileByTestId(request.getId()); deleteFileByTestId(request.getId());
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()); List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
request.setBodyUploadIds(null); request.setBodyUploadIds(null);
ApiDefinition test = updateTest(request); ApiDefinition test = updateTest(request);
@ -210,14 +206,6 @@ public class ApiDefinitionService {
return test; return test;
} }
private void saveFile(String apiId, MultipartFile file) {
final FileMetadata metadata = fileService.saveFile(file);
ApiTestFile apiTestFile = new ApiTestFile();
apiTestFile.setTestId(apiId);
apiTestFile.setFileId(metadata.getId());
apiTestFileMapper.insert(apiTestFile);
}
private void deleteFileByTestId(String apiId) { private void deleteFileByTestId(String apiId) {
ApiTestFileExample apiTestFileExample = new ApiTestFileExample(); ApiTestFileExample apiTestFileExample = new ApiTestFileExample();
apiTestFileExample.createCriteria().andTestIdEqualTo(apiId); apiTestFileExample.createCriteria().andTestIdEqualTo(apiId);
@ -237,13 +225,13 @@ public class ApiDefinitionService {
* @param bodyFiles * @param bodyFiles
* @return * @return
*/ */
public String run(SaveApiDefinitionRequest request, List<MultipartFile> bodyFiles) { public String run(RunDefinitionRequest request, List<MultipartFile> bodyFiles) {
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()); List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
createBodyFiles(request.getId(), bodyUploadIds, bodyFiles); createBodyFiles(request.getId(), bodyUploadIds, bodyFiles);
List<Scenario> scenarios = new ArrayList<>(); HashTree hashTree = request.getTestElement().get();
scenarios.add(request.getScenario()); // 调用执行方法
jMeterService.run(request.getId(), request.getName(), scenarios, request.getReportId(), ApiRunMode.DELIMIT.name()); jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), ApiRunMode.DELIMIT.name());
return request.getId(); return request.getId();
} }
@ -281,6 +269,9 @@ public class ApiDefinitionService {
*/ */
public APIReportResult getDbResult(String testId) { public APIReportResult getDbResult(String testId) {
ApiDefinitionExecResult result = apiDefinitionExecResultMapper.selectByResourceId(testId); ApiDefinitionExecResult result = apiDefinitionExecResultMapper.selectByResourceId(testId);
if (result == null) {
return null;
}
APIReportResult reportResult = new APIReportResult(); APIReportResult reportResult = new APIReportResult();
reportResult.setContent(result.getContent()); reportResult.setContent(result.getContent());
return reportResult; return reportResult;

View File

@ -53,21 +53,10 @@ public class ApiTestCaseService {
return apiTestCaseMapper.selectByPrimaryKey(id); return apiTestCaseMapper.selectByPrimaryKey(id);
} }
public void create(SaveApiTestCaseRequest request, MultipartFile file, List<MultipartFile> bodyFiles) { public void create(SaveApiTestCaseRequest request, List<MultipartFile> bodyFiles) {
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()); List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
ApiTestCase test = createTest(request, file);
createBodyFiles(test, bodyUploadIds, bodyFiles);
}
private ApiTestCase createTest(SaveApiTestCaseRequest request, MultipartFile file) {
if (file == null) {
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
}
checkQuota();
request.setBodyUploadIds(null);
ApiTestCase test = createTest(request); ApiTestCase test = createTest(request);
saveFile(test.getId(), file); createBodyFiles(test, bodyUploadIds, bodyFiles);
return test;
} }
private void checkQuota() { private void checkQuota() {
@ -77,17 +66,14 @@ public class ApiTestCaseService {
} }
} }
public void update(SaveApiTestCaseRequest request, MultipartFile file, List<MultipartFile> bodyFiles) { public void update(SaveApiTestCaseRequest request, List<MultipartFile> bodyFiles) {
if (file == null) {
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
}
deleteFileByTestId(request.getId()); deleteFileByTestId(request.getId());
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()); List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
request.setBodyUploadIds(null); request.setBodyUploadIds(null);
ApiTestCase test = updateTest(request); ApiTestCase test = updateTest(request);
createBodyFiles(test, bodyUploadIds, bodyFiles); createBodyFiles(test, bodyUploadIds, bodyFiles);
saveFile(test.getId(), file);
} }
private void createBodyFiles(ApiTestCase test, List<String> bodyUploadIds, List<MultipartFile> bodyFiles) { private void createBodyFiles(ApiTestCase test, List<String> bodyUploadIds, List<MultipartFile> bodyFiles) {

View File

@ -491,9 +491,9 @@ public class JmeterDocumentParser implements DocumentParser {
setupElement.setAttribute("testclass", "SetupThreadGroup"); setupElement.setAttribute("testclass", "SetupThreadGroup");
setupElement.setAttribute("testname", "setUp Thread Group"); setupElement.setAttribute("testname", "setUp Thread Group");
setupElement.setAttribute("enabled", "true"); setupElement.setAttribute("enabled", "true");
setupElement.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "stoptestnow")); setupElement.appendChild(createStringProp(document, "MsThreadGroup.on_sample_error", "stoptestnow"));
Element elementProp = document.createElement("elementProp"); Element elementProp = document.createElement("elementProp");
elementProp.setAttribute("name", "ThreadGroup.main_controller"); elementProp.setAttribute("name", "MsThreadGroup.main_controller");
elementProp.setAttribute("elementType", "LoopController"); elementProp.setAttribute("elementType", "LoopController");
elementProp.setAttribute("guiclass", "LoopControlPanel"); elementProp.setAttribute("guiclass", "LoopControlPanel");
elementProp.setAttribute("testclass", "LoopController"); elementProp.setAttribute("testclass", "LoopController");
@ -502,12 +502,12 @@ public class JmeterDocumentParser implements DocumentParser {
elementProp.appendChild(createBoolProp(document, "LoopController.continue_forever", false)); elementProp.appendChild(createBoolProp(document, "LoopController.continue_forever", false));
elementProp.appendChild(createIntProp(document, "LoopController.loops", 1)); elementProp.appendChild(createIntProp(document, "LoopController.loops", 1));
setupElement.appendChild(elementProp); setupElement.appendChild(elementProp);
setupElement.appendChild(createStringProp(document, "ThreadGroup.num_threads", "1")); setupElement.appendChild(createStringProp(document, "MsThreadGroup.num_threads", "1"));
setupElement.appendChild(createStringProp(document, "ThreadGroup.ramp_time", "1")); setupElement.appendChild(createStringProp(document, "MsThreadGroup.ramp_time", "1"));
setupElement.appendChild(createStringProp(document, "ThreadGroup.duration", "")); setupElement.appendChild(createStringProp(document, "MsThreadGroup.duration", ""));
setupElement.appendChild(createStringProp(document, "ThreadGroup.delay", "")); setupElement.appendChild(createStringProp(document, "MsThreadGroup.delay", ""));
setupElement.appendChild(createBoolProp(document, "ThreadGroup.scheduler", false)); setupElement.appendChild(createBoolProp(document, "MsThreadGroup.scheduler", false));
setupElement.appendChild(createBoolProp(document, "ThreadGroup.same_user_on_next_iteration", true)); setupElement.appendChild(createBoolProp(document, "MsThreadGroup.same_user_on_next_iteration", true));
hashTree.appendChild(setupElement); hashTree.appendChild(setupElement);
Element setupHashTree = document.createElement(HASH_TREE_ELEMENT); Element setupHashTree = document.createElement(HASH_TREE_ELEMENT);
@ -568,17 +568,17 @@ public class JmeterDocumentParser implements DocumentParser {
} }
/* /*
<PostThreadGroup guiclass="PostThreadGroupGui" testclass="PostThreadGroup" testname="tearDown Thread Group" enabled="true"> <PostThreadGroup guiclass="PostThreadGroupGui" testclass="PostThreadGroup" testname="tearDown Thread Group" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp> <stringProp name="MsThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> <elementProp name="MsThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp> <boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">1</stringProp> <stringProp name="LoopController.loops">1</stringProp>
</elementProp> </elementProp>
<stringProp name="ThreadGroup.num_threads">1</stringProp> <stringProp name="MsThreadGroup.num_threads">1</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp> <stringProp name="MsThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp> <boolProp name="MsThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp> <stringProp name="MsThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp> <stringProp name="MsThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp> <boolProp name="MsThreadGroup.same_user_on_next_iteration">true</boolProp>
</PostThreadGroup> </PostThreadGroup>
*/ */
Element tearDownElement = document.createElement("PostThreadGroup"); Element tearDownElement = document.createElement("PostThreadGroup");
@ -586,15 +586,15 @@ public class JmeterDocumentParser implements DocumentParser {
tearDownElement.setAttribute("testclass", "PostThreadGroup"); tearDownElement.setAttribute("testclass", "PostThreadGroup");
tearDownElement.setAttribute("testname", "tearDown Thread Group"); tearDownElement.setAttribute("testname", "tearDown Thread Group");
tearDownElement.setAttribute("enabled", "true"); tearDownElement.setAttribute("enabled", "true");
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue")); tearDownElement.appendChild(createStringProp(document, "MsThreadGroup.on_sample_error", "continue"));
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.num_threads", "1")); tearDownElement.appendChild(createStringProp(document, "MsThreadGroup.num_threads", "1"));
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.ramp_time", "1")); tearDownElement.appendChild(createStringProp(document, "MsThreadGroup.ramp_time", "1"));
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.duration", "")); tearDownElement.appendChild(createStringProp(document, "MsThreadGroup.duration", ""));
tearDownElement.appendChild(createStringProp(document, "ThreadGroup.delay", "")); tearDownElement.appendChild(createStringProp(document, "MsThreadGroup.delay", ""));
tearDownElement.appendChild(createBoolProp(document, "ThreadGroup.scheduler", false)); tearDownElement.appendChild(createBoolProp(document, "MsThreadGroup.scheduler", false));
tearDownElement.appendChild(createBoolProp(document, "ThreadGroup.same_user_on_next_iteration", true)); tearDownElement.appendChild(createBoolProp(document, "MsThreadGroup.same_user_on_next_iteration", true));
Element elementProp = document.createElement("elementProp"); Element elementProp = document.createElement("elementProp");
elementProp.setAttribute("name", "ThreadGroup.main_controller"); elementProp.setAttribute("name", "MsThreadGroup.main_controller");
elementProp.setAttribute("elementType", "LoopController"); elementProp.setAttribute("elementType", "LoopController");
elementProp.setAttribute("guiclass", "LoopControlPanel"); elementProp.setAttribute("guiclass", "LoopControlPanel");
elementProp.setAttribute("testclass", "LoopController"); elementProp.setAttribute("testclass", "LoopController");
@ -760,8 +760,8 @@ public class JmeterDocumentParser implements DocumentParser {
threadGroup.setAttribute("guiclass", CONCURRENCY_THREAD_GROUP + "Gui"); threadGroup.setAttribute("guiclass", CONCURRENCY_THREAD_GROUP + "Gui");
threadGroup.setAttribute("testclass", CONCURRENCY_THREAD_GROUP); threadGroup.setAttribute("testclass", CONCURRENCY_THREAD_GROUP);
/* /*
<elementProp name="ThreadGroup.main_controller" elementType="com.blazemeter.jmeter.control.VirtualUserController"/> <elementProp name="MsThreadGroup.main_controller" elementType="com.blazemeter.jmeter.control.VirtualUserController"/>
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp> <stringProp name="MsThreadGroup.on_sample_error">continue</stringProp>
<stringProp name="TargetLevel">2</stringProp> <stringProp name="TargetLevel">2</stringProp>
<stringProp name="RampUp">12</stringProp> <stringProp name="RampUp">12</stringProp>
<stringProp name="Steps">2</stringProp> <stringProp name="Steps">2</stringProp>
@ -773,11 +773,11 @@ public class JmeterDocumentParser implements DocumentParser {
removeChildren(threadGroup); removeChildren(threadGroup);
// elementProp // elementProp
Element elementProp = document.createElement("elementProp"); Element elementProp = document.createElement("elementProp");
elementProp.setAttribute("name", "ThreadGroup.main_controller"); elementProp.setAttribute("name", "MsThreadGroup.main_controller");
elementProp.setAttribute("elementType", "com.blazemeter.jmeter.control.VirtualUserController"); elementProp.setAttribute("elementType", "com.blazemeter.jmeter.control.VirtualUserController");
threadGroup.appendChild(elementProp); threadGroup.appendChild(elementProp);
threadGroup.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue")); threadGroup.appendChild(createStringProp(document, "MsThreadGroup.on_sample_error", "continue"));
threadGroup.appendChild(createStringProp(document, "TargetLevel", "2")); threadGroup.appendChild(createStringProp(document, "TargetLevel", "2"));
threadGroup.appendChild(createStringProp(document, "RampUp", "12")); threadGroup.appendChild(createStringProp(document, "RampUp", "12"));
threadGroup.appendChild(createStringProp(document, "Steps", "2")); threadGroup.appendChild(createStringProp(document, "Steps", "2"));

View File

@ -10,11 +10,11 @@
<el-col :span="1"> <el-col :span="1">
<template> <template>
<div> <div>
<ms-tag v-if="api.api_status == 'Prepare'" type="info" <ms-tag v-if="api.status == 'Prepare'" type="info"
:content="$t('test_track.plan.plan_status_prepare')"/> :content="$t('test_track.plan.plan_status_prepare')"/>
<ms-tag v-if="api.api_status == 'Underway'" type="primary" <ms-tag v-if="api.status == 'Underway'" type="primary"
:content="$t('test_track.plan.plan_status_running')"/> :content="$t('test_track.plan.plan_status_running')"/>
<ms-tag v-if="api.api_status == 'Completed'" type="success" <ms-tag v-if="api.status == 'Completed'" type="success"
:content="$t('test_track.plan.plan_status_completed')"/> :content="$t('test_track.plan.plan_status_completed')"/>
</div> </div>
</template> </template>
@ -61,13 +61,7 @@
</div> </div>
</el-col> </el-col>
<el-col :span="2"> <el-col :span="2">
<!--<div class="ms-api-header-select">--> <el-dropdown size="small" split-button type="primary" class="ms-api-header-select" @click="addCase"
<!--<el-button size="small" style="background-color: #783887;color: white" @click="createCase">-->
<!--+{{$t('api_test.definition.request.case')}}-->
<!--</el-button>-->
<!--</div>-->
<el-dropdown size="small" split-button type="primary" class="ms-api-header-select" @click="createCase"
@command="handleCommand"> @command="handleCommand">
+{{$t('api_test.definition.request.case')}} +{{$t('api_test.definition.request.case')}}
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
@ -77,7 +71,7 @@
</el-col> </el-col>
<el-col :span="2"> <el-col :span="1">
<button type="button" aria-label="Close" class="el-card-btn" @click="apiCaseClose()"><i <button type="button" aria-label="Close" class="el-card-btn" @click="apiCaseClose()"><i
class="el-dialog__close el-icon el-icon-close"></i></button> class="el-dialog__close el-icon el-icon-close"></i></button>
</el-col> </el-col>
@ -124,7 +118,7 @@
</div> </div>
</el-col> </el-col>
<el-col :span="4"> <el-col :span="4">
<ms-tip-button @click="runCase(item)" :tip="$t('api_test.run')" icon="el-icon-video-play" <ms-tip-button @click="singleRun(item)" :tip="$t('api_test.run')" icon="el-icon-video-play"
style="background-color: #409EFF;color: white" size="mini" circle/> style="background-color: #409EFF;color: white" size="mini" circle/>
<ms-tip-button @click="copyCase(item)" :tip="$t('commons.copy')" icon="el-icon-document-copy" <ms-tip-button @click="copyCase(item)" :tip="$t('commons.copy')" icon="el-icon-document-copy"
size="mini" circle/> size="mini" circle/>
@ -146,12 +140,11 @@
<div v-if="item.active"> <div v-if="item.active">
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p> <p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
<ms-api-request-form :is-read-only="isReadOnly" :request="item.test.request"/> <ms-api-request-form :is-read-only="isReadOnly" :headers="item.request.hashTree[0].headers " :request="item.request"/>
<p class="tip">{{$t('api_test.definition.request.assertions_rule')}} </p> <!-- <p class="tip">{{$t('api_test.definition.request.assertions_rule')}} </p>-->
<!--<ms-api-assertions :request="item.request" :is-read-only="isReadOnly"-->
<ms-api-assertions :request="item.test.request" :is-read-only="isReadOnly" <!--:assertions="item.request.assertions"/>-->
:assertions="item.test.request.assertions"/>
<!-- 保存操作 --> <!-- 保存操作 -->
<el-button type="primary" size="small" style="margin: 20px; float: right" @click="saveTestCase(item)"> <el-button type="primary" size="small" style="margin: 20px; float: right" @click="saveTestCase(item)">
@ -176,7 +169,6 @@
import MsTag from "../../../common/components/MsTag"; import MsTag from "../../../common/components/MsTag";
import MsTipButton from "../../../common/components/MsTipButton"; import MsTipButton from "../../../common/components/MsTipButton";
import MsApiRequestForm from "./request/ApiRequestForm"; import MsApiRequestForm from "./request/ApiRequestForm";
import {Test, RequestFactory} from "../model/ApiTestModel";
import {downloadFile, getUUID} from "@/common/js/utils"; import {downloadFile, getUUID} from "@/common/js/utils";
import {parseEnvironment} from "../model/EnvironmentModel"; import {parseEnvironment} from "../model/EnvironmentModel";
import ApiEnvironmentConfig from "../../test/components/ApiEnvironmentConfig"; import ApiEnvironmentConfig from "../../test/components/ApiEnvironmentConfig";
@ -241,7 +233,7 @@
}, },
handleCommand(e) { handleCommand(e) {
if (e === "run") { if (e === "run") {
this.runCase(); this.batchRun();
} }
}, },
showInput(row) { showInput(row) {
@ -253,35 +245,39 @@
this.apiCaseList = []; this.apiCaseList = [];
this.$emit('apiCaseClose'); this.$emit('apiCaseClose');
}, },
runCase(row) { batchRun() {
if (!this.environment) {
this.$warning(this.$t('api_test.environment.select_environment'));
return;
}
this.loading = true;
if (this.apiCaseList.length > 0) {
this.apiCaseList.forEach(item => {
if (item.type != "create") {
item.request.name = item.id;
item.request.useEnvironment = this.environment.id;
this.runData.push(item.request);
}
})
this.loading = true;
/*触发执行操作*/
this.reportId = getUUID().substring(0, 8);
} else {
this.$warning("没有可执行的用例!");
}
},
singleRun(row) {
if (!this.environment) { if (!this.environment) {
this.$warning(this.$t('api_test.environment.select_environment')); this.$warning(this.$t('api_test.environment.select_environment'));
return; return;
} }
this.runData = []; this.runData = [];
if (row) { this.loading = true;
row.test.request.url = this.api.url; row.request.name = row.id;
row.test.request.method = this.api.method; row.request.useEnvironment = this.environment.id;
row.test.request.name = row.id; this.runData.push(row.request);
this.loading = true; /*触发执行操作*/
this.runData.push(row.test.request); this.reportId = getUUID().substring(0, 8);
/*触发执行操作*/
this.reportId = getUUID().substring(0, 8);
} else {
if (this.apiCaseList.length > 0) {
this.apiCaseList.forEach(item => {
if (item.type != "create") {
item.test.request.name = item.id;
this.runData.push(item.test.request);
}
})
this.loading = true;
/*触发执行操作*/
this.reportId = getUUID().substring(0, 8);
} else {
this.$warning("没有可执行的用例!");
}
}
}, },
runRefresh(data) { runRefresh(data) {
this.loading = false; this.loading = false;
@ -293,42 +289,33 @@
this.$get('/api/testcase/delete/' + row.id, () => { this.$get('/api/testcase/delete/' + row.id, () => {
this.$success(this.$t('commons.delete_success')); this.$success(this.$t('commons.delete_success'));
this.apiCaseList.splice(index, 1); this.apiCaseList.splice(index, 1);
this.$emit('refresh');
}); });
}, },
copyCase(data) { copyCase(data) {
let obj = { let obj = {name: data.name, priority: data.priority, type: 'create', active: false, request: data.request};
name: data.name,
priority: data.priority,
type: 'create',
active: false,
test: data.test,
};
this.apiCaseList.unshift(obj); this.apiCaseList.unshift(obj);
}, },
createCase(row) { addCase() {
let obj = { //
name: '',
priority: 'P0',
type: 'create',
active: false,
};
let request = {}; let request = {};
if (row) { if (this.api.request instanceof Object) {
request = row.request; request = this.api.request;
obj.apiDefinitionId = row.apiDefinitionId;
} else { } else {
request: new RequestFactory(JSON.parse(this.api.request)) request = JSON.parse(this.api.request);
} }
obj.test = new Test({request: request}); let obj = {apiDefinitionId: this.api.id, name: '', priority: 'P0', type: 'create', active: false};
obj.request = request;
this.apiCaseList.unshift(obj); this.apiCaseList.unshift(obj);
}, },
active(item) { active(item) {
item.active = !item.active; item.active = !item.active;
}, },
getBodyUploadFiles(row) { getBodyUploadFiles(row) {
let bodyUploadFiles = []; let bodyUploadFiles = [];
row.bodyUploadIds = []; row.bodyUploadIds = [];
let request = row.test.request; let request = row.request;
if (request.body) { if (request.body) {
request.body.kvs.forEach(param => { request.body.kvs.forEach(param => {
if (param.files) { if (param.files) {
@ -343,6 +330,19 @@
}); });
} }
}); });
request.body.binary.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
row.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
} }
return bodyUploadFiles; return bodyUploadFiles;
}, },
@ -355,7 +355,7 @@
this.$post("/api/testcase/list", condition, response => { this.$post("/api/testcase/list", condition, response => {
for (let index in response.data) { for (let index in response.data) {
let test = response.data[index]; let test = response.data[index];
test.test = new Test({request: new RequestFactory(JSON.parse(test.request))}); test.request = JSON.parse(test.request);
} }
this.apiCaseList = response.data; this.apiCaseList = response.data;
}); });
@ -371,21 +371,16 @@
return; return;
} }
let bodyFiles = this.getBodyUploadFiles(row); let bodyFiles = this.getBodyUploadFiles(row);
row.test.request.url = this.api.url;
row.test.request.method = this.api.method;
row.projectId = this.api.projectId; row.projectId = this.api.projectId;
row.apiDefinitionId = row.apiDefinitionId || this.api.id; row.apiDefinitionId = row.apiDefinitionId || this.api.id;
row.request = row.test.request;
let jmx = row.test.toJMX();
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
let file = new File([blob], jmx.name);
let url = "/api/testcase/create"; let url = "/api/testcase/create";
if (row.id) { if (row.id) {
url = "/api/testcase/update"; url = "/api/testcase/update";
} }
this.$fileUpload(url, file, bodyFiles, row, () => { this.$fileUpload(url, null, bodyFiles, row, () => {
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));
this.getApiTest(); this.getApiTest();
this.$emit('refresh');
}); });
}, },
getEnvironments() { getEnvironments() {

View File

@ -10,10 +10,7 @@
<script> <script>
import MsAddCompleteHttpApi from "./complete/AddCompleteHttpApi"; import MsAddCompleteHttpApi from "./complete/AddCompleteHttpApi";
import {RequestFactory, ResponseFactory, Test, Body} from "../model/ApiTestModel"; import {ResponseFactory, Body} from "../model/ApiTestModel";
import JMX from "./jmeter/jmx";
import JmeterTestPlan from "./jmeter/components/jmeter-test-plan";
import TestPlan from "./jmeter/components/test-plan";
import {getUUID} from "@/common/js/utils"; import {getUUID} from "@/common/js/utils";
import {createComponent, Request} from "./jmeter/components"; import {createComponent, Request} from "./jmeter/components";
import Sampler from "./jmeter/components/sampler/sampler"; import Sampler from "./jmeter/components/sampler/sampler";
@ -24,10 +21,8 @@
components: {MsAddCompleteHttpApi}, components: {MsAddCompleteHttpApi},
data() { data() {
return { return {
reqType: RequestFactory.TYPES.HTTP, reqType: Request.TYPES.HTTP,
reqUrl: "", reqUrl: "",
test: new Test(),
jmx: new JMX(),
request: Sampler, request: Sampler,
response: {}, response: {},
headers: [], headers: [],
@ -40,20 +35,9 @@
protocol: String, protocol: String,
}, },
created() { created() {
let jmx = new JMX();
let rootTestPlan = new JmeterTestPlan();
//
let testPlan = new TestPlan();
rootTestPlan.hashTree = [testPlan];
jmx.elements = [rootTestPlan];
// 线
let threadGroup = createComponent("ThreadGroup");
testPlan.hashTree = [threadGroup];
this.jmx = jmx;
switch (this.protocol) { switch (this.protocol) {
case Request.TYPES.SQL: case Request.TYPES.SQL:
this.request = createComponent("OT"); this.request = createComponent("SQL");
break; break;
case Request.TYPES.DUBBO: case Request.TYPES.DUBBO:
this.request = createComponent("JDBCSampler"); this.request = createComponent("JDBCSampler");
@ -65,15 +49,11 @@
this.createHttp(); this.createHttp();
break; break;
} }
if (this.currentApi.response != null && this.currentApi.response != 'null' && this.currentApi.response != undefined) {
threadGroup.hashTree = [this.request]; this.response = new ResponseFactory(JSON.parse(this.currentApi.response));
this.response = this.currentApi.response != null ? new ResponseFactory(JSON.parse(this.currentApi.response)) : { } else {
headers: [], this.response = {headers: [], body: new Body(), statusCode: [], type: "HTTP"};
body: new Body(), }
statusCode: [],
type: "HTTP"
};
if (this.currentApi != null && this.currentApi.id != null) { if (this.currentApi != null && this.currentApi.id != null) {
this.reqUrl = "/api/definition/update"; this.reqUrl = "/api/definition/update";
} else { } else {
@ -84,8 +64,9 @@
methods: { methods: {
runTest(data) { runTest(data) {
data.projectId = this.currentProject.id; data.projectId = this.currentProject.id;
data.request = data.test.request; this.request.hashTree[0].headers = this.headers;
data.response = data.test.response; data.request = this.request;
data.response = this.response;
let bodyFiles = this.getBodyUploadFiles(data); let bodyFiles = this.getBodyUploadFiles(data);
this.$fileUpload(this.reqUrl, null, bodyFiles, data, () => { this.$fileUpload(this.reqUrl, null, bodyFiles, data, () => {
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));
@ -94,13 +75,15 @@
}); });
}, },
createHttp() { createHttp() {
if (this.currentApi.request != null) { if (this.currentApi.request != undefined && this.currentApi.request != null) {
this.request = JSON.parse(this.currentApi.request); this.request = JSON.parse(this.currentApi.request);
this.currentApi.request = this.request;
this.headers = this.request.hashTree[0].headers; this.headers = this.request.hashTree[0].headers;
} else { } else {
let header = createComponent("HeaderManager"); let header = createComponent("HeaderManager");
this.request = createComponent("HTTPSamplerProxy"); this.request = createComponent("HTTPSamplerProxy");
this.request.hashTree = [header]; this.request.hashTree = [header];
this.currentApi.request = this.request;
} }
}, },
saveApi(data) { saveApi(data) {
@ -108,7 +91,6 @@
this.request.hashTree[0].headers = this.headers; this.request.hashTree[0].headers = this.headers;
data.request = this.request; data.request = this.request;
data.response = this.response; data.response = this.response;
console.log(data)
let bodyFiles = this.getBodyUploadFiles(data); let bodyFiles = this.getBodyUploadFiles(data);
this.$fileUpload(this.reqUrl, null, bodyFiles, data, () => { this.$fileUpload(this.reqUrl, null, bodyFiles, data, () => {
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));

View File

@ -2,8 +2,10 @@
<div></div> <div></div>
</template> </template>
<script> <script>
import {Scenario} from "../model/ApiTestModel";
import {getUUID} from "@/common/js/utils"; import {getUUID} from "@/common/js/utils";
import HeaderManager from "./jmeter/components/configurations/header-manager";
import ThreadGroup from "./jmeter/components/thread-group";
import TestPlan from "./jmeter/components/test-plan";
export default { export default {
name: 'MsRun', name: 'MsRun',
@ -65,29 +67,35 @@
}); });
} }
}); });
request.body.binary.forEach(param => {
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
obj.bodyUploadIds.push(fileId);
bodyUploadFiles.push(item.file);
}
});
}
});
} }
}); });
return bodyUploadFiles; return bodyUploadFiles;
}, },
run() { run() {
let reqObj = {}; let testPlan = new TestPlan();
let scenario = new Scenario({requests: this.runData}); let threadGroup = new ThreadGroup();
let url = ""; threadGroup.hashTree = [];
let reportId = getUUID().substring(0, 8); testPlan.hashTree = [threadGroup];
this.runData.forEach(item => {
threadGroup.hashTree.push(item);
})
let reqObj = {id: this.reportId, testElement: testPlan};
let bodyFiles = this.getBodyUploadFiles(reqObj); let bodyFiles = this.getBodyUploadFiles(reqObj);
scenario.requests.forEach(item => { let url = "";
if (this.environment != null) {
item.useEnvironment = true;
scenario.environmentId = this.environment.id;
}
item.definition = true;
});
scenario.name = reportId;
scenario.dubboConfig = null;
scenario.tcpConfig = null;
reqObj.name = reportId;
reqObj.scenario = scenario;
reqObj.id = reportId;
if (this.debug) { if (this.debug) {
url = "/api/definition/run/debug"; url = "/api/definition/run/debug";
} else { } else {

View File

@ -34,7 +34,6 @@
:autosize="{ minRows: 2, maxRows: 10}" :autosize="{ minRows: 2, maxRows: 10}"
:rows="2" size="small"/> :rows="2" size="small"/>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template v-slot:footer> <template v-slot:footer>
@ -48,9 +47,10 @@
<script> <script>
import MsDialogFooter from "../../../../common/components/MsDialogFooter"; import MsDialogFooter from "../../../../common/components/MsDialogFooter";
import {WORKSPACE_ID} from '../../../../../../common/js/constants'; import {WORKSPACE_ID} from '../../../../../../common/js/constants';
import {Test} from "../../model/ApiTestModel"
import {REQ_METHOD} from "../../model/JsonData"; import {REQ_METHOD} from "../../model/JsonData";
import {getCurrentUser} from "../../../../../../common/js/utils"; import {getCurrentUser, getUUID} from "../../../../../../common/js/utils";
import {createComponent} from "../jmeter/components";
import HeaderManager from "../jmeter/components/configurations/header-manager";
export default { export default {
name: "MsAddBasisHttpApi", name: "MsAddBasisHttpApi",
@ -81,22 +81,18 @@
if (valid) { if (valid) {
let bodyFiles = []; let bodyFiles = [];
let url = "/api/definition/create"; let url = "/api/definition/create";
let test = new Test();
this.httpForm.bodyUploadIds = []; this.httpForm.bodyUploadIds = [];
this.httpForm.request = test.request;
this.httpForm.request.url = this.httpForm.url;
this.httpForm.request.method = this.httpForm.method;
this.httpForm.projectId = this.projectId; this.httpForm.projectId = this.projectId;
this.httpForm.id = test.id; this.httpForm.id = getUUID().substring(0, 8);
let header = createComponent("HeaderManager");
let request = createComponent("HTTPSamplerProxy");
request.hashTree = [header];
this.httpForm.request = request;
if (this.currentModule != null) { if (this.currentModule != null) {
this.httpForm.modulePath = this.currentModule.method != undefined ? this.currentModule.method : null; this.httpForm.modulePath = this.currentModule.method != undefined ? this.currentModule.method : null;
this.httpForm.moduleId = this.currentModule.id; this.httpForm.moduleId = this.currentModule.id;
} }
let jmx = test.toJMX(); this.result = this.$fileUpload(url, null, bodyFiles, this.httpForm, () => {
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
let file = new File([blob], jmx.name);
this.result = this.$fileUpload(url, file, bodyFiles, this.httpForm, () => {
this.httpVisible = false; this.httpVisible = false;
this.$parent.refresh(this.currentModule); this.$parent.refresh(this.currentModule);
}); });

View File

@ -22,9 +22,9 @@
</el-form-item> </el-form-item>
<el-form-item :label="$t('api_report.request')" prop="url"> <el-form-item :label="$t('api_report.request')" prop="url">
<el-input :placeholder="$t('api_test.definition.request.path_info')" v-model="httpForm.url" <el-input :placeholder="$t('api_test.definition.request.path_info')" v-model="httpForm.request.path.value"
class="ms-http-input" size="small" style="margin-top: 5px"> class="ms-http-input" size="small" style="margin-top: 5px">
<el-select v-model="httpForm.method" slot="prepend" style="width: 100px" size="small"> <el-select v-model="httpForm.request.method.value" slot="prepend" style="width: 100px" size="small">
<el-option v-for="item in reqOptions" :key="item.id" :label="item.label" :value="item.id"/> <el-option v-for="item in reqOptions" :key="item.id" :label="item.label" :value="item.id"/>
</el-select> </el-select>
</el-input> </el-input>
@ -127,6 +127,7 @@
}, },
setParameter() { setParameter() {
this.httpForm.modulePath = this.getPath(this.httpForm.moduleId); this.httpForm.modulePath = this.getPath(this.httpForm.moduleId);
this.httpForm.url = this.httpForm.request.path.value;
}, },
saveApi() { saveApi() {
if (this.currentProject === null) { if (this.currentProject === null) {
@ -163,7 +164,7 @@
<style scoped> <style scoped>
.ms-http-input { .ms-http-input {
width: 500px; width: 400px;
} }
.tip { .tip {
@ -175,7 +176,7 @@
} }
.ms-http-textarea { .ms-http-textarea {
width: 500px; width: 400px;
} }
.ms-left-cell { .ms-left-cell {

View File

@ -26,7 +26,7 @@
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p> <p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
<!-- HTTP 请求参数 --> <!-- HTTP 请求参数 -->
<ms-api-request-form :request="test.request"/> <ms-api-request-form :headers="headers" :request="request"/>
</el-form> </el-form>
<!-- HTTP 请求返回数据 --> <!-- HTTP 请求返回数据 -->
@ -41,14 +41,13 @@
<script> <script>
import MsApiRequestForm from "../request/ApiRequestForm"; import MsApiRequestForm from "../request/ApiRequestForm";
import {Test} from "../../model/ApiTestModel";
import MsResponseResult from "../response/ResponseResult"; import MsResponseResult from "../response/ResponseResult";
import MsRequestMetric from "../response/RequestMetric"; import MsRequestMetric from "../response/RequestMetric";
import {getUUID, getCurrentUser} from "@/common/js/utils"; import {getUUID, getCurrentUser} from "@/common/js/utils";
import MsResponseText from "../response/ResponseText"; import MsResponseText from "../response/ResponseText";
import MsRun from "../Run"; import MsRun from "../Run";
import {createComponent, Request} from "../jmeter/components";
import HeaderManager from "../jmeter/components/configurations/header-manager";
import {REQ_METHOD} from "../../model/JsonData"; import {REQ_METHOD} from "../../model/JsonData";
import MsRequestResultTail from "../response/RequestResultTail"; import MsRequestResultTail from "../response/RequestResultTail";
@ -66,10 +65,27 @@
responseData: {type: 'HTTP', responseResult: {}, subRequestResults: []}, responseData: {type: 'HTTP', responseResult: {}, subRequestResults: []},
loading: false, loading: false,
debugResultId: "", debugResultId: "",
test: new Test(),
runData: [], runData: [],
headers: [],
reportId: "", reportId: "",
reqOptions: REQ_METHOD, reqOptions: REQ_METHOD,
request: {},
}
},
created() {
switch (this.protocol) {
case Request.TYPES.SQL:
this.request = createComponent("SQL");
break;
case Request.TYPES.DUBBO:
this.request = createComponent("JDBCSampler");
break;
case Request.TYPES.TCP:
this.request = createComponent("TCPSampler");
break;
default:
this.createHttp();
break;
} }
}, },
watch: { watch: {
@ -85,15 +101,21 @@
this.runDebug(); this.runDebug();
} }
}, },
createHttp() {
let header = createComponent("HeaderManager");
this.request = createComponent("HTTPSamplerProxy");
this.request.hashTree = [header];
},
runDebug() { runDebug() {
this.$refs['debugForm'].validate((valid) => { this.$refs['debugForm'].validate((valid) => {
if (valid) { if (valid) {
this.loading = true; this.loading = true;
this.test.request.url = this.debugForm.url; this.request.url = this.debugForm.url;
this.test.request.method = this.debugForm.method; this.request.method.value = this.debugForm.method;
this.test.request.name = getUUID().substring(0, 8); this.request.hashTree[0].headers = this.headers;
this.request.name = getUUID().substring(0, 8);
this.runData = []; this.runData = [];
this.runData.push(this.test.request); this.runData.push(this.request);
/*触发执行操作*/ /*触发执行操作*/
this.reportId = getUUID().substring(0, 8); this.reportId = getUUID().substring(0, 8);
} }
@ -107,7 +129,7 @@
saveAs() { saveAs() {
this.$refs['debugForm'].validate((valid) => { this.$refs['debugForm'].validate((valid) => {
if (valid) { if (valid) {
this.debugForm.request = JSON.stringify(this.test.request); this.debugForm.request = JSON.stringify(this.request);
this.debugForm.userId = getCurrentUser().id; this.debugForm.userId = getCurrentUser().id;
this.debugForm.status = "Underway"; this.debugForm.status = "Underway";
this.$emit('saveAs', this.debugForm); this.$emit('saveAs', this.debugForm);

View File

@ -15,7 +15,7 @@ const DEFAULT_OPTIONS = {
export default class HeaderManager extends Configuration { export default class HeaderManager extends Configuration {
constructor(options = DEFAULT_OPTIONS) { constructor(options = DEFAULT_OPTIONS) {
super(options); super(options);
this.type = "HeaderManager";
this.headers = []; this.headers = [];
let collectionProp = this.initCollectionProp('HeaderManager.headers'); let collectionProp = this.initCollectionProp('HeaderManager.headers');
collectionProp.forEach(elementProp => { collectionProp.forEach(elementProp => {

View File

@ -14,7 +14,8 @@ const DEFAULT_OPTIONS = {
export default class JSR223PostProcessor extends PostProcessor { export default class JSR223PostProcessor extends PostProcessor {
constructor(options = DEFAULT_OPTIONS) { constructor(options = DEFAULT_OPTIONS) {
super(options); super(options);
this.scriptLanguage = this.initStringProp("scriptLanguage", "groovy") this.type = "JSR223PostProcessor";
this.scriptLanguage = this.initStringProp("scriptLanguage", "java")
this.parameters = this.initStringProp("parameters") this.parameters = this.initStringProp("parameters")
this.filename = this.initStringProp("filename") this.filename = this.initStringProp("filename")
this.cacheKey = this.initStringProp("cacheKey", true) this.cacheKey = this.initStringProp("cacheKey", true)

View File

@ -14,7 +14,8 @@ const DEFAULT_OPTIONS = {
export default class JSR223PreProcessor extends PostProcessor { export default class JSR223PreProcessor extends PostProcessor {
constructor(options = DEFAULT_OPTIONS) { constructor(options = DEFAULT_OPTIONS) {
super(options); super(options);
this.scriptLanguage = this.initStringProp("scriptLanguage", "groovy") this.type = "JSR223PreProcessor";
this.scriptLanguage = this.initStringProp("scriptLanguage", "java")
this.parameters = this.initStringProp("parameters") this.parameters = this.initStringProp("parameters")
this.filename = this.initStringProp("filename") this.filename = this.initStringProp("filename")
this.cacheKey = this.initStringProp("cacheKey", true) this.cacheKey = this.initStringProp("cacheKey", true)

View File

@ -1,6 +1,6 @@
import {boolProp, elementProp, stringProp} from "../../../props"; import {boolProp, elementProp, stringProp} from "../../../props";
import Sampler from "../sampler"; import Sampler from "../sampler";
import {BaseConfig, BODY_TYPE, KeyValue,Body} from "../../../../../model/ApiTestModel"; import {BaseConfig, BODY_TYPE, KeyValue, Body} from "../../../../../model/ApiTestModel";
const DEFAULT_OPTIONS = { const DEFAULT_OPTIONS = {
options: { options: {
@ -18,7 +18,7 @@ export default class HTTPSamplerProxy extends Sampler {
this.protocol = this.initStringProp('HTTPSampler.protocol', "https"); this.protocol = this.initStringProp('HTTPSampler.protocol', "https");
this.domain = this.initStringProp('HTTPSampler.domain'); this.domain = this.initStringProp('HTTPSampler.domain');
this.port = this.initStringProp('HTTPSampler.port'); this.port = this.initStringProp('HTTPSampler.port');
this.type = "HTTPSamplerProxy";
this.method = this.initStringProp('HTTPSampler.method', "GET"); this.method = this.initStringProp('HTTPSampler.method', "GET");
this.path = this.initStringProp('HTTPSampler.path'); this.path = this.initStringProp('HTTPSampler.path');
this.contentEncoding = this.initStringProp('HTTPSampler.contentEncoding', "UTF-8"); this.contentEncoding = this.initStringProp('HTTPSampler.contentEncoding', "UTF-8");

View File

@ -13,7 +13,7 @@ export default class TestPlan extends HashTreeElement {
constructor(options = DEFAULT_OPTIONS) { constructor(options = DEFAULT_OPTIONS) {
super(options); super(options);
this.$type = TYPE; this.$type = TYPE;
this.type = TYPE;
this.functionalMode = this.initBoolProp('TestPlan.functional_mode', false); this.functionalMode = this.initBoolProp('TestPlan.functional_mode', false);
this.serializeThreadGroups = this.initBoolProp('TestPlan.serialize_threadgroups', false); this.serializeThreadGroups = this.initBoolProp('TestPlan.serialize_threadgroups', false);
this.tearDownOnShutdown = this.initBoolProp('TestPlan.tearDown_on_shutdown', true); this.tearDownOnShutdown = this.initBoolProp('TestPlan.tearDown_on_shutdown', true);

View File

@ -12,7 +12,7 @@ export default class ThreadGroup extends HashTreeElement {
constructor(options = DEFAULT_OPTIONS) { constructor(options = DEFAULT_OPTIONS) {
super(options); super(options);
this.$type = TYPE; this.$type = TYPE;
this.type = TYPE;
this.onSampleError = this.initStringProp('ThreadGroup.on_sample_error', 'continue'); this.onSampleError = this.initStringProp('ThreadGroup.on_sample_error', 'continue');
this.numThreads = this.initStringProp('ThreadGroup.num_threads', 1); this.numThreads = this.initStringProp('ThreadGroup.num_threads', 1);
this.rampTime = this.initStringProp('ThreadGroup.ramp_time', 1); this.rampTime = this.initStringProp('ThreadGroup.ramp_time', 1);

View File

@ -1,52 +0,0 @@
import Element from "../element";
import {loadComponent} from "../components";
import JmeterTestPlan from "../components/jmeter-test-plan";
import TestPlan from "../components/test-plan";
import {js2xml, xml2js} from "xml-js";
const DEFAULT_OPTIONS = {
declaration: {attributes: {version: "1.0", encoding: "UTF-8"}}
}
export default class JMX extends Element {
constructor(options = DEFAULT_OPTIONS) {
super(options);
this.declaration = options.declaration;
if (options.elements) {
this.elements = [];
options.elements.forEach(e => {
this.elements.push(loadComponent(e));
})
}
}
toJson() {
let json = {
declaration: this.declaration
};
if (this.elements) {
json.elements = [];
this.elements.forEach(e => {
json.elements.push(e.toJson());
})
}
return json;
}
toXML() {
return js2xml(this.toJson(), {spaces: 2});
}
static create() {
let jmx = new JMX();
let jmeterTestPlan = new JmeterTestPlan();
let testPlan = new TestPlan();
jmeterTestPlan.hashTree = [testPlan];
jmx.elements = [jmeterTestPlan];
return jmx;
}
static fromJMX(xml) {
return new JMX(xml2js(xml));
}
}

View File

@ -3,11 +3,9 @@
<el-row> <el-row>
<div> <div>
<el-button class="ms-left-buttion" size="small" :type="styleType" plain>{{title}}</el-button> <el-button class="ms-left-buttion" size="small" :type="styleType" plain>{{title}}</el-button>
<i class="icon el-icon-arrow-right" :class="{'is-active': active}" @click="changeActive" <i class="icon el-icon-arrow-right" :class="{'is-active': active}" @click="changeActive" style="margin-left: 20px"/>
style="margin-left: 20px"/> <el-input size="small" v-model="jsr223ProcessorData.name" class="ms-api-header-select" style="width: 380px"/>
<el-button size="small" style="float: right" @click="remove">移除</el-button>
<el-input size="small" v-model="jsr223ProcessorData.name"
class="ms-api-header-select" style="width: 380px"/>
</div> </div>
</el-row> </el-row>
<el-collapse-transition> <el-collapse-transition>
@ -24,7 +22,7 @@
</el-row> </el-row>
<el-row> <el-row>
<el-col :span="20" class="script-content"> <el-col :span="20" class="script-content">
<ms-code-edit v-if="isCodeEditAlive" :mode="codeEditModeMap[jsr223ProcessorData.language]=== null ?'java':'python'" <ms-code-edit v-if="isCodeEditAlive" :mode="jsr223ProcessorData.scriptLanguage.value"
:read-only="isReadOnly" :read-only="isReadOnly"
:data.sync="jsr223ProcessorData.script.value" theme="eclipse" :modes="['java','python']" :data.sync="jsr223ProcessorData.script.value" theme="eclipse" :modes="['java','python']"
ref="codeEdit"/> ref="codeEdit"/>
@ -90,7 +88,7 @@
], ],
isCodeEditAlive: true, isCodeEditAlive: true,
languages: [ languages: [
'beanshell', "python" 'java', "python"
], ],
codeEditModeMap: { codeEditModeMap: {
beanshell: 'java', beanshell: 'java',
@ -102,16 +100,6 @@
this.jsr223ProcessorData = this.jsr223Processor; this.jsr223ProcessorData = this.jsr223Processor;
}, },
props: { props: {
type: {
type: String,
default:
"create",
}
,
name: {
type: String,
}
,
isReadOnly: { isReadOnly: {
type: Boolean, type: Boolean,
default: default:
@ -149,21 +137,20 @@
this.jsr223ProcessorData.script += ';'; this.jsr223ProcessorData.script += ';';
} }
this.reload(); this.reload();
} },
, remove(){
this.$emit('remove', this.jsr223ProcessorData);
},
reload() { reload() {
this.isCodeEditAlive = false; this.isCodeEditAlive = false;
this.$nextTick(() => (this.isCodeEditAlive = true)); this.$nextTick(() => (this.isCodeEditAlive = true));
} },
,
languageChange(language) { languageChange(language) {
this.jsr223ProcessorData.language = language; this.jsr223ProcessorData.language = language;
} },
,
changeActive() { changeActive() {
this.active = !this.active; this.active = !this.active;
} },
,
} }
} }
</script> </script>

View File

@ -12,12 +12,12 @@
<!--query 参数--> <!--query 参数-->
<el-tab-pane :label="$t('api_test.definition.request.query_param')" name="parameters"> <el-tab-pane :label="$t('api_test.definition.request.query_param')" name="parameters">
<ms-api-variable :is-read-only="isReadOnly" :parameters="request.arguments"/> <ms-api-variable :is-read-only="isReadOnly" :parameters="request.arguments"/>
</el-tab-pane> </el-tab-pane>
<!--REST 参数--> <!--REST 参数-->
<el-tab-pane :label="$t('api_test.definition.request.rest_param')" name="rest"> <el-tab-pane :label="$t('api_test.definition.request.rest_param')" name="rest">
<ms-api-variable :is-read-only="isReadOnly" :parameters="request.rest"/> <ms-api-variable :is-read-only="isReadOnly" :parameters="request.rest"/>
</el-tab-pane> </el-tab-pane>
<!--请求体--> <!--请求体-->
@ -32,10 +32,10 @@
</el-tabs> </el-tabs>
</div> </div>
<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData"> <div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData">
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" :is-read-only="false" title="前置脚本" style-type="warning" <ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @remove="remove" :is-read-only="false" title="前置脚本" style-type="warning"
:jsr223-processor="row"/> :jsr223-processor="row"/>
<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" :is-read-only="false" title="后置脚本" style-type="success" <ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @remove="remove" :is-read-only="false" title="后置脚本" style-type="success"
:jsr223-processor="row"/> :jsr223-processor="row"/>
<!--<ms-api-assertions v-if="row.label.indexOf('Assertion')>0" :is-read-only="isReadOnly" :request="request"/>--> <!--<ms-api-assertions v-if="row.label.indexOf('Assertion')>0" :is-read-only="isReadOnly" :request="request"/>-->
@ -137,6 +137,11 @@
this.request.hashTree.push(jsonPostProcessor); this.request.hashTree.push(jsonPostProcessor);
this.reload(); this.reload();
}, },
remove(row) {
let index = this.request.hashTree.indexOf(row);
this.request.hashTree.splice(index, 1);
this.reload();
},
reload() { reload() {
this.isReloadData = true this.isReloadData = true
this.$nextTick(() => { this.$nextTick(() => {

View File

@ -8,14 +8,14 @@
<p class="tip">{{$t('test_track.plan_view.base_info')}} </p> <p class="tip">{{$t('test_track.plan_view.base_info')}} </p>
<!-- 请求方法 --> <!-- 请求方法 -->
<el-form-item :label="$t('api_report.request')" prop="method"> <el-form-item :label="$t('api_report.request')" prop="method">
<el-select v-model="api.method" style="width: 100px" size="small"> <el-select v-model="api.request.method.value" style="width: 100px" size="small">
<el-option v-for="item in reqOptions" :key="item.id" :label="item.label" :value="item.id"/> <el-option v-for="item in reqOptions" :key="item.id" :label="item.label" :value="item.id"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- 执行环境 --> <!-- 执行环境 -->
<el-form-item prop="environmentId"> <el-form-item prop="environmentId">
<el-select v-model="api.environmentId" size="small" class="ms-htt-width" <el-select v-model="api.request.useEnvironment" size="small" class="ms-htt-width"
:placeholder="$t('api_test.definition.request.run_env')" :placeholder="$t('api_test.definition.request.run_env')"
@change="environmentChange" clearable> @change="environmentChange" clearable>
<el-option v-for="(environment, index) in environments" :key="index" <el-option v-for="(environment, index) in environments" :key="index"
@ -36,7 +36,7 @@
<!-- 请求地址 --> <!-- 请求地址 -->
<el-form-item prop="url"> <el-form-item prop="url">
<el-input :placeholder="$t('api_test.definition.request.path_info')" v-model="api.url" class="ms-htt-width" <el-input :placeholder="$t('api_test.definition.request.path_info')" v-model="api.request.path.value" class="ms-htt-width"
size="small" :disabled="false"/> size="small" :disabled="false"/>
</el-form-item> </el-form-item>
@ -59,7 +59,7 @@
<p class="tip">{{$t('api_test.definition.request.req_param')}} </p> <p class="tip">{{$t('api_test.definition.request.req_param')}} </p>
<!-- HTTP 请求参数 --> <!-- HTTP 请求参数 -->
<ms-api-request-form :request="api.request"/> <ms-api-request-form :headers="api.request.hashTree[0].headers" :request="api.request"/>
</el-form> </el-form>
<!--返回结果--> <!--返回结果-->
@ -149,11 +149,9 @@
this.$refs['apiData'].validate((valid) => { this.$refs['apiData'].validate((valid) => {
if (valid) { if (valid) {
this.loading = true; this.loading = true;
this.api.test.request.url = this.api.url; this.api.request.name = this.api.id;
this.api.test.request.method = this.api.method;
this.api.test.request.name = this.api.id;
this.runData = []; this.runData = [];
this.runData.push(this.api.test.request); this.runData.push(this.api.request);
/*触发执行操作*/ /*触发执行操作*/
this.reportId = getUUID().substring(0, 8); this.reportId = getUUID().substring(0, 8);
} }
@ -197,11 +195,7 @@
saveAsCase() { saveAsCase() {
this.isHide = false; this.isHide = false;
this.loaded = false; this.loaded = false;
let testCase = {}; this.$refs.caseList.addCase();
testCase.request = this.api.request;
testCase.apiDefinitionId = this.api.id;
testCase.priority = "P0";
this.$refs.caseList.createCase(testCase);
}, },
saveAsApi() { saveAsApi() {
let data = {}; let data = {};
@ -216,17 +210,14 @@
updateApi() { updateApi() {
let url = "/api/definition/update"; let url = "/api/definition/update";
let bodyFiles = this.getBodyUploadFiles(); let bodyFiles = this.getBodyUploadFiles();
let jmx = this.api.test.toJMX(); this.$fileUpload(url, null, bodyFiles, this.api, () => {
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
let file = new File([blob], jmx.name);
this.$fileUpload(url, file, bodyFiles, this.api, () => {
this.$success(this.$t('commons.save_success')); this.$success(this.$t('commons.save_success'));
this.$emit('saveApi', this.api); this.$emit('saveApi', this.api);
}); });
}, },
selectTestCase(item) { selectTestCase(item) {
if (item != null) { if (item != null) {
this.api.request = new RequestFactory(JSON.parse(item.request)); this.api.request = item.request;
} else { } else {
this.api.request = this.currentRequest; this.api.request = this.currentRequest;
} }
@ -266,7 +257,7 @@
environmentChange(value) { environmentChange(value) {
for (let i in this.environments) { for (let i in this.environments) {
if (this.environments[i].id === value) { if (this.environments[i].id === value) {
this.api.environment = this.environments[i]; this.api.request.useEnvironment = this.environments[i].id;
break; break;
} }
} }
@ -286,6 +277,7 @@
}, },
created() { created() {
this.api = this.apiData; this.api = this.apiData;
this.currentRequest = this.api.request;
this.getEnvironments(); this.getEnvironments();
this.getResult(); this.getResult();
} }