From bdaff53eafd527444efd21a26761ba12d9432bf1 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 19 Nov 2020 19:14:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89):=20?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E6=95=B0=E6=8D=AE=E6=A0=BC=E5=BC=8F=E6=8C=89?= =?UTF-8?q?=E7=85=A7jmeter=20=E6=95=B0=E6=8D=AE=E6=A0=BC=E5=BC=8F=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ApiDefinitionController.java | 5 +- .../api/controller/ApiTestCaseController.java | 8 +- .../dto/definition/RunDefinitionRequest.java | 23 ++ .../definition/SaveApiDefinitionRequest.java | 8 +- .../definition/SaveApiTestCaseRequest.java | 4 +- .../api/dto/definition/request/Element.java | 17 -- .../definition/request/HTTPSamplerProxy.java | 22 -- .../dto/definition/request/MsTestElement.java | 79 ++++++ .../dto/definition/request/MsTestPlan.java | 42 ++++ .../dto/definition/request/MsThreadGroup.java | 52 ++++ .../configurations/MsHeaderManager.java | 43 ++++ .../request/dns/MsDNSCacheManager.java | 78 ++++++ .../post/MsJSR223PostProcessor.java | 48 ++++ .../processors/pre/MsJSR223PreProcessor.java | 47 ++++ .../dto/definition/request/prop/BoolProp.java | 12 + .../definition/request/prop/StringProp.java | 12 + .../request/sampler/MsHTTPSamplerProxy.java | 231 ++++++++++++++++++ .../io/metersphere/api/dto/scenario/Body.java | 1 + .../api/jmeter/APIBackendListenerClient.java | 20 +- .../metersphere/api/jmeter/JMeterService.java | 21 +- .../api/jmeter/ResponseResult.java | 2 + .../api/parse/JmeterDocumentParser.java | 2 +- .../api/service/ApiDefinitionService.java | 27 +- .../api/service/ApiTestCaseService.java | 22 +- .../xml/reader/jmx/JmeterDocumentParser.java | 56 ++--- .../api/definition/components/ApiCaseList.vue | 137 +++++------ .../api/definition/components/ApiConfig.vue | 46 ++-- .../api/definition/components/Run.vue | 44 ++-- .../components/basis/AddBasisHttpApi.vue | 22 +- .../complete/AddCompleteHttpApi.vue | 9 +- .../components/debug/DebugHttpPage.vue | 42 +++- .../configurations/header-manager/index.js | 2 +- .../jsr223-post-processor/index.js | 3 +- .../jsr223-pre-processor/index.js | 3 +- .../components/sampler/http-sampler/index.js | 4 +- .../jmeter/components/test-plan/index.js | 2 +- .../jmeter/components/thread-group/index.js | 2 +- .../definition/components/jmeter/jmx/index.js | 52 ---- .../components/processor/Jsr233Processor.vue | 37 +-- .../components/request/ApiHttpRequestForm.vue | 13 +- .../components/runtest/RunTestHttpPage.vue | 30 +-- 41 files changed, 951 insertions(+), 379 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/api/dto/definition/RunDefinitionRequest.java delete mode 100644 backend/src/main/java/io/metersphere/api/dto/definition/request/Element.java delete mode 100644 backend/src/main/java/io/metersphere/api/dto/definition/request/HTTPSamplerProxy.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestPlan.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/definition/request/MsThreadGroup.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/definition/request/configurations/MsHeaderManager.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/definition/request/dns/MsDNSCacheManager.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/definition/request/processors/post/MsJSR223PostProcessor.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/definition/request/processors/pre/MsJSR223PreProcessor.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/definition/request/prop/BoolProp.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/definition/request/prop/StringProp.java create mode 100644 backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java delete mode 100644 frontend/src/business/components/api/definition/components/jmeter/jmx/index.js diff --git a/backend/src/main/java/io/metersphere/api/controller/ApiDefinitionController.java b/backend/src/main/java/io/metersphere/api/controller/ApiDefinitionController.java index 49ce3d2972..773694f55a 100644 --- a/backend/src/main/java/io/metersphere/api/controller/ApiDefinitionController.java +++ b/backend/src/main/java/io/metersphere/api/controller/ApiDefinitionController.java @@ -5,6 +5,7 @@ import com.github.pagehelper.PageHelper; import io.metersphere.api.dto.APIReportResult; import io.metersphere.api.dto.definition.ApiDefinitionRequest; 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.service.ApiDefinitionService; import io.metersphere.base.domain.ApiDefinition; @@ -61,12 +62,12 @@ public class ApiDefinitionController { } @PostMapping(value = "/run/debug", consumes = {"multipart/form-data"}) - public String runDebug(@RequestPart("request") SaveApiDefinitionRequest request, @RequestPart(value = "files") List bodyFiles) { + public String runDebug(@RequestPart("request") RunDefinitionRequest request, @RequestPart(value = "files") List bodyFiles) { return apiDefinitionService.run(request, bodyFiles); } @PostMapping(value = "/run", consumes = {"multipart/form-data"}) - public String run(@RequestPart("request") SaveApiDefinitionRequest request, @RequestPart(value = "files") List bodyFiles) { + public String run(@RequestPart("request") RunDefinitionRequest request, @RequestPart(value = "files") List bodyFiles) { return apiDefinitionService.run(request, bodyFiles); } diff --git a/backend/src/main/java/io/metersphere/api/controller/ApiTestCaseController.java b/backend/src/main/java/io/metersphere/api/controller/ApiTestCaseController.java index 91396d6a9d..7d4f44855b 100644 --- a/backend/src/main/java/io/metersphere/api/controller/ApiTestCaseController.java +++ b/backend/src/main/java/io/metersphere/api/controller/ApiTestCaseController.java @@ -29,13 +29,13 @@ public class ApiTestCaseController { } @PostMapping(value = "/create", consumes = {"multipart/form-data"}) - public void create(@RequestPart("request") SaveApiTestCaseRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List bodyFiles) { - apiTestCaseService.create(request, file, bodyFiles); + public void create(@RequestPart("request") SaveApiTestCaseRequest request, @RequestPart(value = "files") List bodyFiles) { + apiTestCaseService.create(request, bodyFiles); } @PostMapping(value = "/update", consumes = {"multipart/form-data"}) - public void update(@RequestPart("request") SaveApiTestCaseRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List bodyFiles) { - apiTestCaseService.update(request, file, bodyFiles); + public void update(@RequestPart("request") SaveApiTestCaseRequest request, @RequestPart(value = "files") List bodyFiles) { + apiTestCaseService.update(request, bodyFiles); } @GetMapping("/delete/{id}") diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/RunDefinitionRequest.java b/backend/src/main/java/io/metersphere/api/dto/definition/RunDefinitionRequest.java new file mode 100644 index 0000000000..169d0450a4 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/RunDefinitionRequest.java @@ -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 bodyUploadIds; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/SaveApiDefinitionRequest.java b/backend/src/main/java/io/metersphere/api/dto/definition/SaveApiDefinitionRequest.java index dd8eaf7ed9..24b0597a52 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/SaveApiDefinitionRequest.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/SaveApiDefinitionRequest.java @@ -1,7 +1,7 @@ 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.scenario.Scenario; import io.metersphere.base.domain.Schedule; import lombok.Getter; import lombok.Setter; @@ -31,10 +31,8 @@ public class SaveApiDefinitionRequest { private String modulePath; private String method; - - private Scenario scenario; - - private Object request; + + private MsTestElement request; private Response response; diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/SaveApiTestCaseRequest.java b/backend/src/main/java/io/metersphere/api/dto/definition/SaveApiTestCaseRequest.java index ed76ce569f..50f2588399 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/SaveApiTestCaseRequest.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/SaveApiTestCaseRequest.java @@ -1,6 +1,6 @@ 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.Setter; @@ -22,7 +22,7 @@ public class SaveApiTestCaseRequest { private String description; - private Request request; + private MsTestElement request; private String response; diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/Element.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/Element.java deleted file mode 100644 index fe1790125a..0000000000 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/Element.java +++ /dev/null @@ -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; - -} - - - - - diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/HTTPSamplerProxy.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/HTTPSamplerProxy.java deleted file mode 100644 index cce95270ee..0000000000 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/HTTPSamplerProxy.java +++ /dev/null @@ -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; - - -} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java new file mode 100644 index 0000000000..440a2263fe --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java @@ -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 hashTree; + + public void toHashTree(HashTree tree, List 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; + } + +} + + + + + diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestPlan.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestPlan.java new file mode 100644 index 0000000000..9e041cbc73 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestPlan.java @@ -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 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; + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsThreadGroup.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsThreadGroup.java new file mode 100644 index 0000000000..7443d3a3c5 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsThreadGroup.java @@ -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 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; + } +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/configurations/MsHeaderManager.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/configurations/MsHeaderManager.java new file mode 100644 index 0000000000..3c56b89316 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/configurations/MsHeaderManager.java @@ -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 headers; + + public void toHashTree(HashTree tree, List 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()); + }); + } + } +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/dns/MsDNSCacheManager.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/dns/MsDNSCacheManager.java new file mode 100644 index 0000000000..696217eeb6 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/dns/MsDNSCacheManager.java @@ -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 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 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 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 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; + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/processors/post/MsJSR223PostProcessor.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/processors/post/MsJSR223PostProcessor.java new file mode 100644 index 0000000000..83f715a96a --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/processors/post/MsJSR223PostProcessor.java @@ -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 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()); + }); + } + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/processors/pre/MsJSR223PreProcessor.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/processors/pre/MsJSR223PreProcessor.java new file mode 100644 index 0000000000..fab147f172 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/processors/pre/MsJSR223PreProcessor.java @@ -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 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()); + }); + } + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/prop/BoolProp.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/prop/BoolProp.java new file mode 100644 index 0000000000..9da886535d --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/prop/BoolProp.java @@ -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; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/prop/StringProp.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/prop/StringProp.java new file mode 100644 index 0000000000..4b565b8427 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/prop/StringProp.java @@ -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; +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java new file mode 100644 index 0000000000..b0e00b4849 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java @@ -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 arguments; + + @JSONField(ordinal = 18) + private Body body; + + @JSONField(ordinal = 19) + private List 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 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 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 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 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]); + } + + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java b/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java index 1bfa7c9f59..a9dc7ee9a0 100644 --- a/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java +++ b/backend/src/main/java/io/metersphere/api/dto/scenario/Body.java @@ -10,6 +10,7 @@ public class Body { private String type; private String raw; private String format; + private List fromUrlencoded; private List kvs; private List binary; private Object json; diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 1f14962a93..717fbe98c9 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -27,6 +27,8 @@ import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; import org.apache.jmeter.visualizers.backend.BackendListenerContext; import org.springframework.http.HttpMethod; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; import java.io.Serializable; import java.util.*; @@ -63,6 +65,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl @Override public void setupTest(BackendListenerContext context) throws Exception { + setConsole(); setParam(context); apiTestService = CommonBeanFactory.getBean(APITestService.class); if (apiTestService == null) { @@ -109,6 +112,18 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl 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 public void handleSampleResults(List sampleResults, BackendListenerContext context) { @@ -120,8 +135,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl TestResult testResult = new TestResult(); testResult.setTestId(testId); testResult.setTotal(queue.size()); - - // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id + // 一个脚本里可能包含多个场景(MsThreadGroup),所以要区分开,key: 场景Id final Map scenarios = new LinkedHashMap<>(); queue.forEach(result -> { // 线程名称: <场景名> <场景Index>-<请求Index>, 例如:Scenario 2-1 @@ -276,7 +290,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl responseResult.setResponseSize(result.getResponseData().length); responseResult.setResponseTime(result.getTime()); responseResult.setResponseMessage(result.getResponseMessage()); - + responseResult.setConsole(getConsole()); if (JMeterVars.get(result.hashCode()) != null) { List vars = new LinkedList<>(); JMeterVars.get(result.hashCode()).entrySet().parallelStream().reduce(vars, (first, second) -> { diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java index f375ba58bf..00286776fb 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -42,12 +42,12 @@ public class JMeterService { return jmxGenerator.parse(testId, testName, scenarios); } - public void run(String testId, String testName, List scenarios, String debugReportId,String runMode) { + public void run(String testId, String testName, List scenarios, String debugReportId, String runMode) { try { init(); HashTree testPlan = getHashTree(testId, testName, scenarios); JMeterVars.addJSR223PostProcessor(testPlan); - addBackendListener(testId, debugReportId,runMode, testPlan); + addBackendListener(testId, debugReportId, runMode, testPlan); LocalRunner runner = new LocalRunner(testPlan); runner.run(); } 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.setName(testId); Arguments arguments = new Arguments(); arguments.addArgument(APIBackendListenerClient.TEST_ID, testId); - if(StringUtils.isNotBlank(runMode)){ + if (StringUtils.isNotBlank(runMode)) { arguments.addArgument("runMode", runMode); } if (StringUtils.isNotBlank(debugReportId)) { @@ -97,4 +97,17 @@ public class JMeterService { backendListener.setClassname(APIBackendListenerClient.class.getCanonicalName()); 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")); + } + } + } diff --git a/backend/src/main/java/io/metersphere/api/jmeter/ResponseResult.java b/backend/src/main/java/io/metersphere/api/jmeter/ResponseResult.java index 01b37b741a..96ff6991c1 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/ResponseResult.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/ResponseResult.java @@ -25,6 +25,8 @@ public class ResponseResult { private String vars; + private String console; + private final List assertions = new ArrayList<>(); } diff --git a/backend/src/main/java/io/metersphere/api/parse/JmeterDocumentParser.java b/backend/src/main/java/io/metersphere/api/parse/JmeterDocumentParser.java index efa1771c8c..bbffeeaed5 100644 --- a/backend/src/main/java/io/metersphere/api/parse/JmeterDocumentParser.java +++ b/backend/src/main/java/io/metersphere/api/parse/JmeterDocumentParser.java @@ -26,7 +26,7 @@ public class JmeterDocumentParser { private final static String STRING_PROP = "stringProp"; private final static String ARGUMENTS = "Arguments"; 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"; public static byte[] parse(byte[] source) { diff --git a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java index 1b222cb241..bd95f41b4e 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java @@ -3,11 +3,7 @@ package io.metersphere.api.service; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import io.metersphere.api.dto.APIReportResult; -import io.metersphere.api.dto.definition.ApiComputeResult; -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.dto.definition.*; import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.TestResult; import io.metersphere.base.domain.*; @@ -24,6 +20,7 @@ import io.metersphere.commons.utils.SessionUtils; import io.metersphere.i18n.Translator; import io.metersphere.service.FileService; import io.metersphere.service.QuotaService; +import org.apache.jorphan.collections.HashTree; import org.aspectj.util.FileUtil; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -101,7 +98,6 @@ public class ApiDefinitionService { public void update(SaveApiDefinitionRequest request, List bodyFiles) { deleteFileByTestId(request.getId()); - List bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()); request.setBodyUploadIds(null); ApiDefinition test = updateTest(request); @@ -210,14 +206,6 @@ public class ApiDefinitionService { 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) { ApiTestFileExample apiTestFileExample = new ApiTestFileExample(); apiTestFileExample.createCriteria().andTestIdEqualTo(apiId); @@ -237,13 +225,13 @@ public class ApiDefinitionService { * @param bodyFiles * @return */ - public String run(SaveApiDefinitionRequest request, List bodyFiles) { + public String run(RunDefinitionRequest request, List bodyFiles) { List bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()); createBodyFiles(request.getId(), bodyUploadIds, bodyFiles); - List scenarios = new ArrayList<>(); - scenarios.add(request.getScenario()); - jMeterService.run(request.getId(), request.getName(), scenarios, request.getReportId(), ApiRunMode.DELIMIT.name()); + HashTree hashTree = request.getTestElement().get(); + // 调用执行方法 + jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), ApiRunMode.DELIMIT.name()); return request.getId(); } @@ -281,6 +269,9 @@ public class ApiDefinitionService { */ public APIReportResult getDbResult(String testId) { ApiDefinitionExecResult result = apiDefinitionExecResultMapper.selectByResourceId(testId); + if (result == null) { + return null; + } APIReportResult reportResult = new APIReportResult(); reportResult.setContent(result.getContent()); return reportResult; diff --git a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java index a39f2bfb54..a1a5bd8bab 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java @@ -53,21 +53,10 @@ public class ApiTestCaseService { return apiTestCaseMapper.selectByPrimaryKey(id); } - public void create(SaveApiTestCaseRequest request, MultipartFile file, List bodyFiles) { + public void create(SaveApiTestCaseRequest request, List bodyFiles) { List 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); - saveFile(test.getId(), file); - return test; + createBodyFiles(test, bodyUploadIds, bodyFiles); } private void checkQuota() { @@ -77,17 +66,14 @@ public class ApiTestCaseService { } } - public void update(SaveApiTestCaseRequest request, MultipartFile file, List bodyFiles) { - if (file == null) { - throw new IllegalArgumentException(Translator.get("file_cannot_be_null")); - } + public void update(SaveApiTestCaseRequest request, List bodyFiles) { + deleteFileByTestId(request.getId()); List bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()); request.setBodyUploadIds(null); ApiTestCase test = updateTest(request); createBodyFiles(test, bodyUploadIds, bodyFiles); - saveFile(test.getId(), file); } private void createBodyFiles(ApiTestCase test, List bodyUploadIds, List bodyFiles) { diff --git a/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java b/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java index 99c004e8f2..d515b6b80b 100644 --- a/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java +++ b/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java @@ -491,9 +491,9 @@ public class JmeterDocumentParser implements DocumentParser { setupElement.setAttribute("testclass", "SetupThreadGroup"); setupElement.setAttribute("testname", "setUp Thread Group"); 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"); - elementProp.setAttribute("name", "ThreadGroup.main_controller"); + elementProp.setAttribute("name", "MsThreadGroup.main_controller"); elementProp.setAttribute("elementType", "LoopController"); elementProp.setAttribute("guiclass", "LoopControlPanel"); elementProp.setAttribute("testclass", "LoopController"); @@ -502,12 +502,12 @@ public class JmeterDocumentParser implements DocumentParser { elementProp.appendChild(createBoolProp(document, "LoopController.continue_forever", false)); elementProp.appendChild(createIntProp(document, "LoopController.loops", 1)); setupElement.appendChild(elementProp); - setupElement.appendChild(createStringProp(document, "ThreadGroup.num_threads", "1")); - setupElement.appendChild(createStringProp(document, "ThreadGroup.ramp_time", "1")); - setupElement.appendChild(createStringProp(document, "ThreadGroup.duration", "")); - setupElement.appendChild(createStringProp(document, "ThreadGroup.delay", "")); - setupElement.appendChild(createBoolProp(document, "ThreadGroup.scheduler", false)); - setupElement.appendChild(createBoolProp(document, "ThreadGroup.same_user_on_next_iteration", true)); + setupElement.appendChild(createStringProp(document, "MsThreadGroup.num_threads", "1")); + setupElement.appendChild(createStringProp(document, "MsThreadGroup.ramp_time", "1")); + setupElement.appendChild(createStringProp(document, "MsThreadGroup.duration", "")); + setupElement.appendChild(createStringProp(document, "MsThreadGroup.delay", "")); + setupElement.appendChild(createBoolProp(document, "MsThreadGroup.scheduler", false)); + setupElement.appendChild(createBoolProp(document, "MsThreadGroup.same_user_on_next_iteration", true)); hashTree.appendChild(setupElement); Element setupHashTree = document.createElement(HASH_TREE_ELEMENT); @@ -568,17 +568,17 @@ public class JmeterDocumentParser implements DocumentParser { } /* - continue - + continue + false 1 - 1 - 1 - false - - - true + 1 + 1 + false + + + true */ Element tearDownElement = document.createElement("PostThreadGroup"); @@ -586,15 +586,15 @@ public class JmeterDocumentParser implements DocumentParser { tearDownElement.setAttribute("testclass", "PostThreadGroup"); tearDownElement.setAttribute("testname", "tearDown Thread Group"); tearDownElement.setAttribute("enabled", "true"); - tearDownElement.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue")); - tearDownElement.appendChild(createStringProp(document, "ThreadGroup.num_threads", "1")); - tearDownElement.appendChild(createStringProp(document, "ThreadGroup.ramp_time", "1")); - tearDownElement.appendChild(createStringProp(document, "ThreadGroup.duration", "")); - tearDownElement.appendChild(createStringProp(document, "ThreadGroup.delay", "")); - tearDownElement.appendChild(createBoolProp(document, "ThreadGroup.scheduler", false)); - tearDownElement.appendChild(createBoolProp(document, "ThreadGroup.same_user_on_next_iteration", true)); + tearDownElement.appendChild(createStringProp(document, "MsThreadGroup.on_sample_error", "continue")); + tearDownElement.appendChild(createStringProp(document, "MsThreadGroup.num_threads", "1")); + tearDownElement.appendChild(createStringProp(document, "MsThreadGroup.ramp_time", "1")); + tearDownElement.appendChild(createStringProp(document, "MsThreadGroup.duration", "")); + tearDownElement.appendChild(createStringProp(document, "MsThreadGroup.delay", "")); + tearDownElement.appendChild(createBoolProp(document, "MsThreadGroup.scheduler", false)); + tearDownElement.appendChild(createBoolProp(document, "MsThreadGroup.same_user_on_next_iteration", true)); Element elementProp = document.createElement("elementProp"); - elementProp.setAttribute("name", "ThreadGroup.main_controller"); + elementProp.setAttribute("name", "MsThreadGroup.main_controller"); elementProp.setAttribute("elementType", "LoopController"); elementProp.setAttribute("guiclass", "LoopControlPanel"); elementProp.setAttribute("testclass", "LoopController"); @@ -760,8 +760,8 @@ public class JmeterDocumentParser implements DocumentParser { threadGroup.setAttribute("guiclass", CONCURRENCY_THREAD_GROUP + "Gui"); threadGroup.setAttribute("testclass", CONCURRENCY_THREAD_GROUP); /* - - continue + + continue 2 12 2 @@ -773,11 +773,11 @@ public class JmeterDocumentParser implements DocumentParser { removeChildren(threadGroup); // 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"); 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, "RampUp", "12")); threadGroup.appendChild(createStringProp(document, "Steps", "2")); diff --git a/frontend/src/business/components/api/definition/components/ApiCaseList.vue b/frontend/src/business/components/api/definition/components/ApiCaseList.vue index 6a1f87a6dc..a7f1342272 100644 --- a/frontend/src/business/components/api/definition/components/ApiCaseList.vue +++ b/frontend/src/business/components/api/definition/components/ApiCaseList.vue @@ -10,11 +10,11 @@ @@ -61,13 +61,7 @@ - - - - - - - +{{$t('api_test.definition.request.case')}} @@ -77,7 +71,7 @@ - + @@ -124,7 +118,7 @@ - @@ -146,12 +140,11 @@

{{$t('api_test.definition.request.req_param')}}

- + -

{{$t('api_test.definition.request.assertions_rule')}}

- - + + + @@ -176,7 +169,6 @@ import MsTag from "../../../common/components/MsTag"; import MsTipButton from "../../../common/components/MsTipButton"; import MsApiRequestForm from "./request/ApiRequestForm"; - import {Test, RequestFactory} from "../model/ApiTestModel"; import {downloadFile, getUUID} from "@/common/js/utils"; import {parseEnvironment} from "../model/EnvironmentModel"; import ApiEnvironmentConfig from "../../test/components/ApiEnvironmentConfig"; @@ -241,7 +233,7 @@ }, handleCommand(e) { if (e === "run") { - this.runCase(); + this.batchRun(); } }, showInput(row) { @@ -253,35 +245,39 @@ this.apiCaseList = []; 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) { this.$warning(this.$t('api_test.environment.select_environment')); return; } this.runData = []; - if (row) { - row.test.request.url = this.api.url; - row.test.request.method = this.api.method; - row.test.request.name = row.id; - this.loading = true; - this.runData.push(row.test.request); - /*触发执行操作*/ - 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("没有可执行的用例!"); - } - } + this.loading = true; + row.request.name = row.id; + row.request.useEnvironment = this.environment.id; + this.runData.push(row.request); + /*触发执行操作*/ + this.reportId = getUUID().substring(0, 8); }, runRefresh(data) { this.loading = false; @@ -293,42 +289,33 @@ this.$get('/api/testcase/delete/' + row.id, () => { this.$success(this.$t('commons.delete_success')); this.apiCaseList.splice(index, 1); + this.$emit('refresh'); }); }, copyCase(data) { - let obj = { - name: data.name, - priority: data.priority, - type: 'create', - active: false, - test: data.test, - }; + let obj = {name: data.name, priority: data.priority, type: 'create', active: false, request: data.request}; this.apiCaseList.unshift(obj); }, - createCase(row) { - let obj = { - name: '', - priority: 'P0', - type: 'create', - active: false, - }; + addCase() { + // 初始化对象 let request = {}; - if (row) { - request = row.request; - obj.apiDefinitionId = row.apiDefinitionId; + if (this.api.request instanceof Object) { + request = this.api.request; } 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); }, + active(item) { item.active = !item.active; }, getBodyUploadFiles(row) { let bodyUploadFiles = []; row.bodyUploadIds = []; - let request = row.test.request; + let request = row.request; if (request.body) { request.body.kvs.forEach(param => { 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; }, @@ -355,7 +355,7 @@ this.$post("/api/testcase/list", condition, response => { for (let index in response.data) { 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; }); @@ -371,21 +371,16 @@ return; } 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.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"; if (row.id) { url = "/api/testcase/update"; } - this.$fileUpload(url, file, bodyFiles, row, () => { + this.$fileUpload(url, null, bodyFiles, row, () => { this.$success(this.$t('commons.save_success')); this.getApiTest(); + this.$emit('refresh'); }); }, getEnvironments() { diff --git a/frontend/src/business/components/api/definition/components/ApiConfig.vue b/frontend/src/business/components/api/definition/components/ApiConfig.vue index 41c57a60d1..f222ecf38f 100644 --- a/frontend/src/business/components/api/definition/components/ApiConfig.vue +++ b/frontend/src/business/components/api/definition/components/ApiConfig.vue @@ -10,10 +10,7 @@ diff --git a/frontend/src/business/components/api/definition/components/request/ApiHttpRequestForm.vue b/frontend/src/business/components/api/definition/components/request/ApiHttpRequestForm.vue index 271a598af1..1667f4f90b 100644 --- a/frontend/src/business/components/api/definition/components/request/ApiHttpRequestForm.vue +++ b/frontend/src/business/components/api/definition/components/request/ApiHttpRequestForm.vue @@ -12,12 +12,12 @@ - + - + @@ -32,10 +32,10 @@
- - @@ -137,6 +137,11 @@ this.request.hashTree.push(jsonPostProcessor); this.reload(); }, + remove(row) { + let index = this.request.hashTree.indexOf(row); + this.request.hashTree.splice(index, 1); + this.reload(); + }, reload() { this.isReloadData = true this.$nextTick(() => { diff --git a/frontend/src/business/components/api/definition/components/runtest/RunTestHttpPage.vue b/frontend/src/business/components/api/definition/components/runtest/RunTestHttpPage.vue index e4da0c403b..9880ea235b 100644 --- a/frontend/src/business/components/api/definition/components/runtest/RunTestHttpPage.vue +++ b/frontend/src/business/components/api/definition/components/runtest/RunTestHttpPage.vue @@ -8,14 +8,14 @@

{{$t('test_track.plan_view.base_info')}}

- + - - @@ -59,7 +59,7 @@

{{$t('api_test.definition.request.req_param')}}

- + @@ -149,11 +149,9 @@ this.$refs['apiData'].validate((valid) => { if (valid) { this.loading = true; - this.api.test.request.url = this.api.url; - this.api.test.request.method = this.api.method; - this.api.test.request.name = this.api.id; + this.api.request.name = this.api.id; this.runData = []; - this.runData.push(this.api.test.request); + this.runData.push(this.api.request); /*触发执行操作*/ this.reportId = getUUID().substring(0, 8); } @@ -197,11 +195,7 @@ saveAsCase() { this.isHide = false; this.loaded = false; - let testCase = {}; - testCase.request = this.api.request; - testCase.apiDefinitionId = this.api.id; - testCase.priority = "P0"; - this.$refs.caseList.createCase(testCase); + this.$refs.caseList.addCase(); }, saveAsApi() { let data = {}; @@ -216,17 +210,14 @@ updateApi() { let url = "/api/definition/update"; let bodyFiles = this.getBodyUploadFiles(); - let jmx = this.api.test.toJMX(); - 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.$fileUpload(url, null, bodyFiles, this.api, () => { this.$success(this.$t('commons.save_success')); this.$emit('saveApi', this.api); }); }, selectTestCase(item) { if (item != null) { - this.api.request = new RequestFactory(JSON.parse(item.request)); + this.api.request = item.request; } else { this.api.request = this.currentRequest; } @@ -266,7 +257,7 @@ environmentChange(value) { for (let i in this.environments) { if (this.environments[i].id === value) { - this.api.environment = this.environments[i]; + this.api.request.useEnvironment = this.environments[i].id; break; } } @@ -286,6 +277,7 @@ }, created() { this.api = this.apiData; + this.currentRequest = this.api.request; this.getEnvironments(); this.getResult(); }