From 712de81915410c9a811de83b319b0ee3c65190cc Mon Sep 17 00:00:00 2001 From: pencui Date: Mon, 26 Oct 2020 11:33:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Ehttp=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=EF=BC=8Cjsonpath=E6=96=AD=E8=A8=80=E6=89=B9=E9=87=8F=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=20(#599)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 新增jsonpath断言批量推荐功能 * 修复xpack下错误的代码结构 * 补正改版后的jsonpath解析功能 Co-authored-by: root --- backend/pom.xml | 1199 +++---- .../api/controller/APITestController.java | 297 +- .../api/dto/QueryJsonPathRequest.java | 12 + .../commons/utils/JsonPathUtils.java | 166 + .../components/assertion/ApiAssertions.vue | 221 +- .../components/request/ApiHttpRequestForm.vue | 473 +-- .../components/request/ApiRequestForm.vue | 236 +- .../api/test/model/ScenarioModel.js | 2994 +++++++++-------- 8 files changed, 2941 insertions(+), 2657 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/api/dto/QueryJsonPathRequest.java create mode 100644 backend/src/main/java/io/metersphere/commons/utils/JsonPathUtils.java diff --git a/backend/pom.xml b/backend/pom.xml index b4d37b6d32..b4d5c1a4df 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -1,597 +1,604 @@ - - - - - - metersphere-server - io.metersphere - 1.4 - - - 4.0.0 - backend - - - UTF-8 - 1.6.0 - 1.8 - 5.2.1 - 1.1.3 - 2.7.7 - 20.1.0 - - - - - org.springframework.boot - spring-boot-starter-web - - - spring-boot-starter-tomcat - org.springframework.boot - - - - - - org.springframework.boot - spring-boot-configuration-processor - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-starter-actuator - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.boot - spring-boot-starter-aop - - - org.springframework.boot - spring-boot-starter-jetty - - - org.springframework.boot - spring-boot-starter-mail - - - - org.projectlombok - lombok - - - - org.mybatis.spring.boot - mybatis-spring-boot-starter - 2.1.2 - - - - org.springframework.boot - spring-boot-starter-websocket - - - - - org.flywaydb - flyway-core - - - mysql - mysql-connector-java - runtime - - - com.github.pagehelper - pagehelper - 5.0.3 - - - - org.apache.shiro - shiro-spring-boot-starter - ${shiro.version} - - - - org.apache.commons - commons-lang3 - - - org.apache.commons - commons-collections4 - 4.1 - - - org.apache.commons - commons-text - 1.8 - - - commons-codec - commons-codec - - - - com.alibaba - fastjson - 1.2.72 - - - - - org.springdoc - springdoc-openapi-ui - 1.2.32 - - - - - - org.apache.jmeter - ApacheJMeter_http - ${jmeter.version} - - - org.apache.logging.log4j - log4j-slf4j-impl - - - - - - org.python - jython-standalone - 2.7.0 - - - - org.apache.jmeter - ApacheJMeter_functions - ${jmeter.version} - - - - org.apache.jmeter - ApacheJMeter_jdbc - ${jmeter.version} - - - - org.apache.jmeter - ApacheJMeter_tcp - ${jmeter.version} - - - - - com.microsoft.sqlserver - mssql-jdbc - 7.4.1.jre8 - - - - org.postgresql - postgresql - 42.2.14 - - - - com.oracle.database.jdbc - ojdbc8 - 19.7.0.0 - - - - - org.apache.dubbo - dubbo - ${dubbo.version} - - - org.apache.zookeeper - zookeeper - 3.4.13 - - - org.apache.curator - curator-framework - 4.0.1 - - - org.apache.curator - curator-recipes - 4.0.1 - - - - - org.apache.dubbo - dubbo-registry-nacos - ${dubbo.version} - - - com.alibaba.nacos - nacos-api - ${nacos.version} - - - com.alibaba.nacos - nacos-client - ${nacos.version} - - - - com.alibaba - easyexcel - 2.1.7 - - - - com.fit2cloud - quartz-spring-boot-starter - 0.0.7 - - - - io.metersphere - jmeter-plugins-dubbo - 2.7.11 - - - - - org.springframework.boot - spring-boot-starter-data-ldap - - - - - io.swagger - swagger-parser - 1.0.51 - - - - - org.graalvm.sdk - graal-sdk - ${graalvm.version} - compile - - - org.graalvm.js - js - ${graalvm.version} - runtime - - - org.graalvm.js - js-scriptengine - ${graalvm.version} - runtime - - - org.graalvm.tools - profiler - ${graalvm.version} - runtime - - - org.graalvm.tools - chromeinspector - ${graalvm.version} - runtime - - - - - org.pac4j - pac4j-cas - 3.0.2 - - - io.buji - buji-pac4j - 4.0.0 - - - shiro-web - org.apache.shiro - - - - - - org.jsoup - jsoup - 1.10.3 - - - - com.atlassian.commonmark - commonmark - 0.15.2 - - - - org.apache.commons - commons-compress - 1.20 - - - - org.dom4j - dom4j - 2.1.3 - - - - jaxen - jaxen - 1.2.0 - - - - org.json - json - 20171018 - - - - com.aliyun - alibaba-dingtalk-service-sdk - 1.0.1 - - - org.apache.httpcomponents - httpclient - 4.5.6 - - - - - - - - src/main/java - - **/*.properties - **/*.xml - - false - - - src/main/resources - - **/* - - false - - - - - - org.springframework.boot - spring-boot-maven-plugin - - true - - - org.projectlombok - lombok - - - org.springframework.boot - spring-boot-configuration-processor - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.12.4 - - true - - - - maven-clean-plugin - - - - src/main/resources/static - - ** - - false - - - src/main/resources/templates - - ** - - false - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - - - org.apache.maven.plugins - maven-war-plugin - 2.6 - - - - org.apache.maven.plugins - maven-jar-plugin - - - **/jmeter/lib/**/*.jar - - - - - - org.apache.maven.plugins - maven-antrun-plugin - - - main-class-placement - generate-resources - - - - - - - - - - - - - - - - run - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy - generate-resources - - copy - - - - - - - org.apache.jmeter - ApacheJMeter_functions - ${jmeter.version} - jar - true - src/main/resources/jmeter/lib/ext - ApacheJMeter_functions.jar - - - org.python - jython-standalone - 2.7.0 - jar - true - src/main/resources/jmeter/lib/ext - jython-standalone.jar - - - ${project.build.directory}/wars - false - true - - - - org.mybatis.generator - mybatis-generator-maven-plugin - 1.3.7 - - true - true - - - - mysql - mysql-connector-java - 8.0.16 - - - com.itfsw - mybatis-generator-plugin - 1.3.8 - - - - - org.apache.maven.plugins - maven-resources-plugin - - - copy-and-filter-allatori-config - package - - copy-resources - - - ${basedir}/target - - - ${basedir}/allatori - - allatori.xml - - true - - - - - - - - org.codehaus.mojo - exec-maven-plugin - - - run-allatori - package - - exec - - - - - java - - -Xms128m - -Xmx512m - -jar - ${basedir}/target/classes/allatori/allatori.jar - ${basedir}/target/classes/allatori/allatori.xml - - - - - - + + + + + + metersphere-server + io.metersphere + 1.4 + + + 4.0.0 + backend + + + UTF-8 + 1.6.0 + 1.8 + 5.2.1 + 1.1.3 + 2.7.7 + 20.1.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + spring-boot-starter-tomcat + org.springframework.boot + + + + + + org.springframework.boot + spring-boot-configuration-processor + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-jetty + + + org.springframework.boot + spring-boot-starter-mail + + + + org.projectlombok + lombok + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.1.2 + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + org.flywaydb + flyway-core + + + mysql + mysql-connector-java + runtime + + + com.github.pagehelper + pagehelper + 5.0.3 + + + + org.apache.shiro + shiro-spring-boot-starter + ${shiro.version} + + + + org.apache.commons + commons-lang3 + + + org.apache.commons + commons-collections4 + 4.1 + + + org.apache.commons + commons-text + 1.8 + + + commons-codec + commons-codec + + + + com.alibaba + fastjson + 1.2.72 + + + + + org.springdoc + springdoc-openapi-ui + 1.2.32 + + + + + + org.apache.jmeter + ApacheJMeter_http + ${jmeter.version} + + + org.apache.logging.log4j + log4j-slf4j-impl + + + + + + org.python + jython-standalone + 2.7.0 + + + + org.apache.jmeter + ApacheJMeter_functions + ${jmeter.version} + + + + org.apache.jmeter + ApacheJMeter_jdbc + ${jmeter.version} + + + + org.apache.jmeter + ApacheJMeter_tcp + ${jmeter.version} + + + + + com.microsoft.sqlserver + mssql-jdbc + 7.4.1.jre8 + + + + org.postgresql + postgresql + 42.2.14 + + + + com.oracle.database.jdbc + ojdbc8 + 19.7.0.0 + + + + + org.apache.dubbo + dubbo + ${dubbo.version} + + + org.apache.zookeeper + zookeeper + 3.4.13 + + + org.apache.curator + curator-framework + 4.0.1 + + + org.apache.curator + curator-recipes + 4.0.1 + + + + + org.apache.dubbo + dubbo-registry-nacos + ${dubbo.version} + + + com.alibaba.nacos + nacos-api + ${nacos.version} + + + com.alibaba.nacos + nacos-client + ${nacos.version} + + + + com.alibaba + easyexcel + 2.1.7 + + + + com.fit2cloud + quartz-spring-boot-starter + 0.0.7 + + + + io.metersphere + jmeter-plugins-dubbo + 2.7.11 + + + + + org.springframework.boot + spring-boot-starter-data-ldap + + + + + io.swagger + swagger-parser + 1.0.51 + + + + + org.graalvm.sdk + graal-sdk + ${graalvm.version} + compile + + + org.graalvm.js + js + ${graalvm.version} + runtime + + + org.graalvm.js + js-scriptengine + ${graalvm.version} + runtime + + + org.graalvm.tools + profiler + ${graalvm.version} + runtime + + + org.graalvm.tools + chromeinspector + ${graalvm.version} + runtime + + + + + org.pac4j + pac4j-cas + 3.0.2 + + + io.buji + buji-pac4j + 4.0.0 + + + shiro-web + org.apache.shiro + + + + + + org.jsoup + jsoup + 1.10.3 + + + + com.atlassian.commonmark + commonmark + 0.15.2 + + + + org.apache.commons + commons-compress + 1.20 + + + + org.dom4j + dom4j + 2.1.3 + + + + jaxen + jaxen + 1.2.0 + + + + org.json + json + 20171018 + + + + com.aliyun + alibaba-dingtalk-service-sdk + 1.0.1 + + + org.apache.httpcomponents + httpclient + 4.5.6 + + + + org.apache.commons + commons-text + 1.3 + + + + + + + + + src/main/java + + **/*.properties + **/*.xml + + false + + + src/main/resources + + **/* + + false + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + org.projectlombok + lombok + + + org.springframework.boot + spring-boot-configuration-processor + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12.4 + + true + + + + maven-clean-plugin + + + + src/main/resources/static + + ** + + false + + + src/main/resources/templates + + ** + + false + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + org.apache.maven.plugins + maven-war-plugin + 2.6 + + + + org.apache.maven.plugins + maven-jar-plugin + + + **/jmeter/lib/**/*.jar + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + main-class-placement + generate-resources + + + + + + + + + + + + + + + + run + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy + generate-resources + + copy + + + + + + + org.apache.jmeter + ApacheJMeter_functions + ${jmeter.version} + jar + true + src/main/resources/jmeter/lib/ext + ApacheJMeter_functions.jar + + + org.python + jython-standalone + 2.7.0 + jar + true + src/main/resources/jmeter/lib/ext + jython-standalone.jar + + + ${project.build.directory}/wars + false + true + + + + org.mybatis.generator + mybatis-generator-maven-plugin + 1.3.7 + + true + true + + + + mysql + mysql-connector-java + 8.0.16 + + + com.itfsw + mybatis-generator-plugin + 1.3.8 + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-and-filter-allatori-config + package + + copy-resources + + + ${basedir}/target + + + ${basedir}/allatori + + allatori.xml + + true + + + + + + + + org.codehaus.mojo + exec-maven-plugin + + + run-allatori + package + + exec + + + + + java + + -Xms128m + -Xmx512m + -jar + ${basedir}/target/classes/allatori/allatori.jar + ${basedir}/target/classes/allatori/allatori.xml + + + + + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/api/controller/APITestController.java b/backend/src/main/java/io/metersphere/api/controller/APITestController.java index 0951eccb13..0b73d1f183 100644 --- a/backend/src/main/java/io/metersphere/api/controller/APITestController.java +++ b/backend/src/main/java/io/metersphere/api/controller/APITestController.java @@ -1,144 +1,153 @@ -package io.metersphere.api.controller; - -import com.github.pagehelper.Page; -import com.github.pagehelper.PageHelper; -import io.metersphere.api.dto.*; -import io.metersphere.api.dto.scenario.request.dubbo.RegistryCenter; -import io.metersphere.api.service.APITestService; -import io.metersphere.base.domain.ApiTest; -import io.metersphere.base.domain.Schedule; -import io.metersphere.commons.constants.RoleConstants; -import io.metersphere.commons.utils.PageUtils; -import io.metersphere.commons.utils.Pager; -import io.metersphere.commons.utils.SessionUtils; -import io.metersphere.controller.request.QueryScheduleRequest; -import io.metersphere.dto.ScheduleDao; -import io.metersphere.service.CheckOwnerService; -import org.apache.shiro.authz.annotation.Logical; -import org.apache.shiro.authz.annotation.RequiresRoles; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import javax.annotation.Resource; - -import java.util.List; - -@RestController -@RequestMapping(value = "/api") -@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR) -public class APITestController { - @Resource - private APITestService apiTestService; - @Resource - private CheckOwnerService checkownerService; - - @GetMapping("recent/{count}") - public List recentTest(@PathVariable int count) { - String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId(); - QueryAPITestRequest request = new QueryAPITestRequest(); - request.setWorkspaceId(currentWorkspaceId); - request.setUserId(SessionUtils.getUserId()); - PageHelper.startPage(1, count, true); - return apiTestService.recentTest(request); - } - - @PostMapping("/list/{goPage}/{pageSize}") - public Pager> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryAPITestRequest request) { - Page page = PageHelper.startPage(goPage, pageSize, true); - request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId()); - return PageUtils.setPageInfo(page, apiTestService.list(request)); - } - - @PostMapping("/list/ids") - public List listByIds(@RequestBody QueryAPITestRequest request) { - return apiTestService.listByIds(request); - } - - @GetMapping("/list/{projectId}") - public List list(@PathVariable String projectId) { - checkownerService.checkProjectOwner(projectId); - return apiTestService.getApiTestByProjectId(projectId); - } - - @PostMapping(value = "/schedule/update") - public void updateSchedule(@RequestBody Schedule request) { - apiTestService.updateSchedule(request); - } - - @PostMapping(value = "/schedule/create") - public void createSchedule(@RequestBody Schedule request) { - apiTestService.createSchedule(request); - } - - @PostMapping(value = "/create", consumes = {"multipart/form-data"}) - public void create(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List bodyFiles) { - apiTestService.create(request, file, bodyFiles); - } - - @PostMapping(value = "/create/merge", consumes = {"multipart/form-data"}) - public void mergeCreate(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "selectIds") List selectIds) { - apiTestService.mergeCreate(request, file, selectIds); - } - - @PostMapping(value = "/update", consumes = {"multipart/form-data"}) - public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List bodyFiles) { - checkownerService.checkApiTestOwner(request.getId()); - apiTestService.update(request, file, bodyFiles); - } - - @PostMapping(value = "/copy") - public void copy(@RequestBody SaveAPITestRequest request) { - apiTestService.copy(request); - } - - @GetMapping("/get/{testId}") - public APITestResult get(@PathVariable String testId) { - checkownerService.checkApiTestOwner(testId); - return apiTestService.get(testId); - } - - - @PostMapping("/delete") - public void delete(@RequestBody DeleteAPITestRequest request) { - String testId = request.getId(); - checkownerService.checkApiTestOwner(testId); - apiTestService.delete(testId); - } - - @PostMapping(value = "/run") - public String run(@RequestBody SaveAPITestRequest request) { - return apiTestService.run(request); - } - - @PostMapping(value = "/run/debug", consumes = {"multipart/form-data"}) - public String runDebug(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List bodyFiles) { - return apiTestService.runDebug(request, file, bodyFiles); - } - - @PostMapping(value = "/checkName") - public void checkName(@RequestBody SaveAPITestRequest request) { - apiTestService.checkName(request); - } - - @PostMapping(value = "/import", consumes = {"multipart/form-data"}) - @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) - public ApiTest testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) { - return apiTestService.apiTestImport(file, request); - } - - @PostMapping("/dubbo/providers") - public List getProviders(@RequestBody RegistryCenter registry) { - return apiTestService.getProviders(registry); - } - - @PostMapping("/list/schedule/{goPage}/{pageSize}") - public List listSchedule(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryScheduleRequest request) { - Page page = PageHelper.startPage(goPage, pageSize, true); - return apiTestService.listSchedule(request); - } - - @PostMapping("/list/schedule") - public List listSchedule(@RequestBody QueryScheduleRequest request) { - return apiTestService.listSchedule(request); - } -} +package io.metersphere.api.controller; + +import com.github.pagehelper.Page; +import com.github.pagehelper.PageHelper; +import io.metersphere.api.dto.*; +import io.metersphere.api.dto.scenario.request.dubbo.RegistryCenter; +import io.metersphere.api.service.APITestService; +import io.metersphere.base.domain.ApiTest; +import io.metersphere.base.domain.Schedule; +import io.metersphere.commons.constants.RoleConstants; +import io.metersphere.commons.utils.PageUtils; +import io.metersphere.commons.utils.Pager; +import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.controller.request.QueryScheduleRequest; +import io.metersphere.dto.ScheduleDao; +import io.metersphere.service.CheckOwnerService; + +import org.apache.shiro.authz.annotation.Logical; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; + +import java.util.HashMap; +import java.util.List; + +import static io.metersphere.commons.utils.JsonPathUtils.getListJson; + + +@RestController +@RequestMapping(value = "/api") +@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR) +public class APITestController { + @Resource + private APITestService apiTestService; + @Resource + private CheckOwnerService checkownerService; + + @GetMapping("recent/{count}") + public List recentTest(@PathVariable int count) { + String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId(); + QueryAPITestRequest request = new QueryAPITestRequest(); + request.setWorkspaceId(currentWorkspaceId); + request.setUserId(SessionUtils.getUserId()); + PageHelper.startPage(1, count, true); + return apiTestService.recentTest(request); + } + + @PostMapping("/list/{goPage}/{pageSize}") + public Pager> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryAPITestRequest request) { + Page page = PageHelper.startPage(goPage, pageSize, true); + request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId()); + return PageUtils.setPageInfo(page, apiTestService.list(request)); + } + + @PostMapping("/list/ids") + public List listByIds(@RequestBody QueryAPITestRequest request) { + return apiTestService.listByIds(request); + } + + @GetMapping("/list/{projectId}") + public List list(@PathVariable String projectId) { + checkownerService.checkProjectOwner(projectId); + return apiTestService.getApiTestByProjectId(projectId); + } + + @PostMapping(value = "/schedule/update") + public void updateSchedule(@RequestBody Schedule request) { + apiTestService.updateSchedule(request); + } + + @PostMapping(value = "/schedule/create") + public void createSchedule(@RequestBody Schedule request) { + apiTestService.createSchedule(request); + } + + @PostMapping(value = "/create", consumes = {"multipart/form-data"}) + public void create(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List bodyFiles) { + apiTestService.create(request, file, bodyFiles); + } + + @PostMapping(value = "/create/merge", consumes = {"multipart/form-data"}) + public void mergeCreate(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "selectIds") List selectIds) { + apiTestService.mergeCreate(request, file, selectIds); + } + @PostMapping(value = "/update", consumes = {"multipart/form-data"}) + public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List bodyFiles) { + checkownerService.checkApiTestOwner(request.getId()); + apiTestService.update(request, file, bodyFiles); + } + + @PostMapping(value = "/copy") + public void copy(@RequestBody SaveAPITestRequest request) { + apiTestService.copy(request); + } + + @GetMapping("/get/{testId}") + public APITestResult get(@PathVariable String testId) { + checkownerService.checkApiTestOwner(testId); + return apiTestService.get(testId); + } + + + @PostMapping("/delete") + public void delete(@RequestBody DeleteAPITestRequest request) { + String testId = request.getId(); + checkownerService.checkApiTestOwner(testId); + apiTestService.delete(testId); + } + + @PostMapping(value = "/run") + public String run(@RequestBody SaveAPITestRequest request) { + return apiTestService.run(request); + } + + @PostMapping(value = "/run/debug", consumes = {"multipart/form-data"}) + public String runDebug(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List bodyFiles) { + return apiTestService.runDebug(request, file, bodyFiles); + } + + @PostMapping(value = "/checkName") + public void checkName(@RequestBody SaveAPITestRequest request) { + apiTestService.checkName(request); + } + + @PostMapping(value = "/import", consumes = {"multipart/form-data"}) + @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) + public ApiTest testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) { + return apiTestService.apiTestImport(file, request); + } + + @PostMapping("/dubbo/providers") + public List getProviders(@RequestBody RegistryCenter registry) { + return apiTestService.getProviders(registry); + } + + @PostMapping("/list/schedule/{goPage}/{pageSize}") + public List listSchedule(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryScheduleRequest request) { + Page page = PageHelper.startPage(goPage, pageSize, true); + return apiTestService.listSchedule(request); + } + + @PostMapping("/list/schedule") + public List listSchedule(@RequestBody QueryScheduleRequest request) { + return apiTestService.listSchedule(request); + } + + @PostMapping("/getJsonPaths") + public List getJsonPaths(@RequestBody QueryJsonPathRequest request) { + return getListJson(request.getJsonPath()); + } +} diff --git a/backend/src/main/java/io/metersphere/api/dto/QueryJsonPathRequest.java b/backend/src/main/java/io/metersphere/api/dto/QueryJsonPathRequest.java new file mode 100644 index 0000000000..b6c391abf3 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/QueryJsonPathRequest.java @@ -0,0 +1,12 @@ +package io.metersphere.api.dto; + +import java.io.Serializable; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class QueryJsonPathRequest implements Serializable { + private String jsonPath; +} diff --git a/backend/src/main/java/io/metersphere/commons/utils/JsonPathUtils.java b/backend/src/main/java/io/metersphere/commons/utils/JsonPathUtils.java new file mode 100644 index 0000000000..66a836bfc8 --- /dev/null +++ b/backend/src/main/java/io/metersphere/commons/utils/JsonPathUtils.java @@ -0,0 +1,166 @@ +package io.metersphere.commons.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.JSONPath; + +public class JsonPathUtils { + + public static List getListJson(String jsonString) { + + JSONObject jsonObject =JSONObject.parseObject(jsonString); + List allJsons =new ArrayList<>(); + + // 获取到所有jsonpath后,获取所有的key + List jsonPaths = JSONPath.paths(jsonObject).keySet() + .stream() + .collect(Collectors.toList()); + //去掉根节点key + List parentNode = new ArrayList<>(); + //根节点key + parentNode.add("/"); + //循环获取父节点key,只保留叶子节点 + for (int i = 0; i < jsonPaths.size(); i++) { + if (jsonPaths.get(i).lastIndexOf("/") > 0) { + parentNode.add(jsonPaths.get(i).substring(0, jsonPaths.get(i).lastIndexOf("/"))); + } + } + + //remove父节点key + for (String parentNodeJsonPath : parentNode) { + jsonPaths.remove(parentNodeJsonPath); + } + + List jsonPathList = new ArrayList<>(); + Iterator jsonPath = jsonPaths.iterator(); + //将/替换为点. + while (jsonPath.hasNext()) { + Map item = new HashMap<>(); + + + String o_json_path = "$" + jsonPath.next().replaceAll("/", "."); + String value = JSONPath.eval(jsonObject,o_json_path).toString(); + + if(o_json_path.toLowerCase().contains("id")) { + continue; + } + + + if(value.equals("") || value.equals("[]") || o_json_path.equals("")) { + continue; + } + + String json_path = formatJson(o_json_path); + + + + //System.out.println(json_path); + + + + item.put("json_path", json_path); + item.put("json_value", addEscapeForString(value)); + allJsons.add((HashMap)item); + + jsonPathList.add(json_path); + } + //排序 + Collections.sort(jsonPathList); + return allJsons; + } + + private static String formatJson(String json_path){ + + String ret=""; + // 正则表达式 + String reg = ".(\\d{1,3}).{0,1}"; + + Boolean change_flag = false; + Matcher m1 = Pattern.compile(reg).matcher(json_path); + + + String newStr=""; + int rest = 0; + String tail = ""; + while (m1.find()) { + + int start = m1.start(); + int end = m1.end() - 1; + if(json_path.charAt(start) != '.' || json_path.charAt(end) != '.') { + continue; + } + + + newStr += json_path.substring(rest,m1.start()) +"[*]." ; + + rest = m1.end(); + tail = json_path.substring(m1.end()); + change_flag = true; + } + + + if(change_flag) { + ret = newStr + tail; + } else { + ret = json_path; + } + + + + return ret; + + + } + + private static String addEscapeForString(String input) { + + String ret=""; + + + String reg = "[?*/]"; + + Boolean change_flag = false; + Matcher m1 = Pattern.compile(reg).matcher(input); + + + String newStr=""; + int rest = 0; + String tail = ""; + while (m1.find()) { + + int start = m1.start(); + int end = m1.end() - 1; + + + + newStr += input.substring(rest,m1.start()) + "\\" + m1.group(0) ; + + rest = m1.end(); + tail = input.substring(m1.end()); + change_flag = true; + + } + if(change_flag) { + ret = newStr + tail; + } else { + ret = input; + } + + return ret; + + + } + + + + +} diff --git a/frontend/src/business/components/api/test/components/assertion/ApiAssertions.vue b/frontend/src/business/components/api/test/components/assertion/ApiAssertions.vue index de285e519c..1e4bb6ee73 100644 --- a/frontend/src/business/components/api/test/components/assertion/ApiAssertions.vue +++ b/frontend/src/business/components/api/test/components/assertion/ApiAssertions.vue @@ -1,80 +1,141 @@ - - - - - + + + + + diff --git a/frontend/src/business/components/api/test/components/request/ApiHttpRequestForm.vue b/frontend/src/business/components/api/test/components/request/ApiHttpRequestForm.vue index 3e0e43b21a..0cc2d4f530 100644 --- a/frontend/src/business/components/api/test/components/request/ApiHttpRequestForm.vue +++ b/frontend/src/business/components/api/test/components/request/ApiHttpRequestForm.vue @@ -1,236 +1,237 @@ - - - - - + + + + + diff --git a/frontend/src/business/components/api/test/components/request/ApiRequestForm.vue b/frontend/src/business/components/api/test/components/request/ApiRequestForm.vue index 0649fd63cb..949d63b6d8 100644 --- a/frontend/src/business/components/api/test/components/request/ApiRequestForm.vue +++ b/frontend/src/business/components/api/test/components/request/ApiRequestForm.vue @@ -1,33 +1,24 @@ diff --git a/frontend/src/business/components/api/test/model/ScenarioModel.js b/frontend/src/business/components/api/test/model/ScenarioModel.js index 5865527d6c..97e72ab21e 100644 --- a/frontend/src/business/components/api/test/model/ScenarioModel.js +++ b/frontend/src/business/components/api/test/model/ScenarioModel.js @@ -1,1495 +1,1499 @@ -import { - Arguments, - CookieManager, - DNSCacheManager, - DubboSample, - DurationAssertion, - Element, - HashTree, - HeaderManager, - HTTPSamplerArguments, - HTTPsamplerFiles, - HTTPSamplerProxy, - JDBCDataSource, - JDBCSampler, - JSONPathAssertion, - JSONPostProcessor, - JSR223PostProcessor, - JSR223PreProcessor, - RegexExtractor, - ResponseCodeAssertion, - ResponseDataAssertion, - ResponseHeadersAssertion, - TestElement, - TestPlan, - ThreadGroup, - XPath2Extractor, - IfController as JMXIfController, - ConstantTimer as JMXConstantTimer, TCPSampler, -} from "./JMX"; -import Mock from "mockjs"; -import {funcFilters} from "@/common/js/func-filter"; - -export const uuid = function () { - let d = new Date().getTime() - let d2 = (performance && performance.now && (performance.now() * 1000)) || 0; - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - let r = Math.random() * 16; - if (d > 0) { - r = (d + r) % 16 | 0; - d = Math.floor(d / 16); - } else { - r = (d2 + r) % 16 | 0; - d2 = Math.floor(d2 / 16); - } - return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); - }); -} - -export const BODY_FILE_DIR = "/opt/metersphere/data/body"; //存放body文件上传目录 - -export const calculate = function (itemValue) { - if (!itemValue) { - return; - } - try { - if (itemValue.trim().startsWith("${")) { - // jmeter 内置函数不做处理 - return itemValue; - } - let funcs = itemValue.split("|"); - let value = Mock.mock(funcs[0].trim()); - if (funcs.length === 1) { - return value; - } - for (let i = 1; i < funcs.length; i++) { - let func = funcs[i].trim(); - let args = func.split(":"); - let strings = []; - if (args[1]) { - strings = args[1].split(","); - } - value = funcFilters[args[0].trim()](value, ...strings); - } - return value; - } catch (e) { - return itemValue; - } -} - -export const BODY_TYPE = { - KV: "KeyValue", - FORM_DATA: "Form Data", - RAW: "Raw" -} - -export const BODY_FORMAT = { - TEXT: "text", - JSON: "json", - XML: "xml", - HTML: "html", -} - -export const ASSERTION_TYPE = { - TEXT: "Text", - REGEX: "Regex", - JSON_PATH: "JSON", - DURATION: "Duration" -} - -export const ASSERTION_REGEX_SUBJECT = { - RESPONSE_CODE: "Response Code", - RESPONSE_HEADERS: "Response Headers", - RESPONSE_DATA: "Response Data" -} - -export const EXTRACT_TYPE = { - REGEX: "Regex", - JSON_PATH: "JSONPath", - XPATH: "XPath" -} - -export class BaseConfig { - - set(options, notUndefined) { - options = this.initOptions(options) - for (let name in options) { - if (options.hasOwnProperty(name)) { - if (!(this[name] instanceof Array)) { - if (notUndefined === true) { - this[name] = options[name] === undefined ? this[name] : options[name]; - } else { - this[name] = options[name]; - } - } - } - } - } - - sets(types, options) { - options = this.initOptions(options) - if (types) { - for (let name in types) { - if (types.hasOwnProperty(name) && options.hasOwnProperty(name)) { - options[name].forEach(o => { - this[name].push(new types[name](o)); - }) - } - } - } - } - - initOptions(options) { - return options || {}; - } - - isValid() { - return true; - } -} - -export class Test extends BaseConfig { - constructor(options) { - super(); - this.type = "MS API CONFIG"; - this.version = '1.4.0'; - this.id = uuid(); - this.name = undefined; - this.projectId = undefined; - this.scenarioDefinition = []; - this.schedule = {}; - - this.set(options); - this.sets({scenarioDefinition: Scenario}, options); - } - - export() { - let obj = { - type: this.type, - version: this.version, - scenarios: this.scenarioDefinition - }; - - return JSON.stringify(obj); - } - - initOptions(options) { - options = options || {}; - options.scenarioDefinition = options.scenarioDefinition || [new Scenario()]; - return options; - } - - isValid() { - for (let i = 0; i < this.scenarioDefinition.length; i++) { - let validator = this.scenarioDefinition[i].isValid(); - if (!validator.isValid) { - return validator; - } - } - if (!this.projectId) { - return { - isValid: false, - info: 'api_test.select_project' - } - } else if (!this.name) { - return { - isValid: false, - info: 'api_test.input_name' - } - } - return {isValid: true}; - } - - toJMX() { - return { - name: this.name + '.jmx', - xml: new JMXGenerator(this).toXML() - }; - } -} - -export class Scenario extends BaseConfig { - constructor(options = {}) { - super(); - this.id = undefined; - this.name = undefined; - this.url = undefined; - this.variables = []; - this.headers = []; - this.requests = []; - this.environmentId = undefined; - this.dubboConfig = undefined; - this.environment = undefined; - this.enableCookieShare = false; - this.enable = true; - this.databaseConfigs = []; - this.tcpConfig = undefined; - - this.set(options); - this.sets({ - variables: KeyValue, - headers: KeyValue, - requests: RequestFactory, - databaseConfigs: DatabaseConfig - }, options); - } - - initOptions(options = {}) { - options.id = options.id || uuid(); - options.requests = options.requests || [new RequestFactory()]; - options.databaseConfigs = options.databaseConfigs || []; - options.dubboConfig = new DubboConfig(options.dubboConfig); - options.tcpConfig = new TCPConfig(options.tcpConfig); - return options; - } - - clone() { - let clone = new Scenario(this); - clone.id = uuid(); - return clone; - } - - isValid() { - if (this.enable) { - for (let i = 0; i < this.requests.length; i++) { - let validator = this.requests[i].isValid(this.environmentId, this.environment); - if (!validator.isValid) { - return validator; - } - } - } - return {isValid: true}; - } - - isReference() { - return this.id.indexOf("#") !== -1 - } -} - -class DubboConfig extends BaseConfig { - constructor(options = {}) { - super(); - this.configCenter = new ConfigCenter(options.configCenter) - this.registryCenter = new RegistryCenter(options.registryCenter) - if (options.consumerAndService === undefined) { - options.consumerAndService = { - timeout: undefined, - version: undefined, - retries: undefined, - cluster: undefined, - group: undefined, - connections: undefined, - async: undefined, - loadBalance: undefined - } - } - this.consumerAndService = new ConsumerAndService(options.consumerAndService) - } -} - -export class RequestFactory { - static TYPES = { - HTTP: "HTTP", - DUBBO: "DUBBO", - SQL: "SQL", - TCP: "TCP", - } - - constructor(options = {}) { - options.type = options.type || RequestFactory.TYPES.HTTP - switch (options.type) { - case RequestFactory.TYPES.DUBBO: - return new DubboRequest(options); - case RequestFactory.TYPES.SQL: - return new SqlRequest(options); - case RequestFactory.TYPES.TCP: - return new TCPRequest(options); - default: - return new HttpRequest(options); - } - } -} - -export class Request extends BaseConfig { - constructor(type, options = {}) { - super(); - this.type = type; - this.id = options.id || uuid(); - this.name = options.name; - this.enable = options.enable === undefined ? true : options.enable; - this.assertions = new Assertions(options.assertions); - this.extract = new Extract(options.extract); - this.jsr223PreProcessor = new JSR223Processor(options.jsr223PreProcessor); - this.jsr223PostProcessor = new JSR223Processor(options.jsr223PostProcessor); - this.timer = new ConstantTimer(options.timer); - this.controller = new IfController(options.controller); - } - - showType() { - return this.type; - } - - showMethod() { - return ""; - } -} - -export class HttpRequest extends Request { - constructor(options) { - super(RequestFactory.TYPES.HTTP, options); - this.url = options.url; - this.path = options.path; - this.method = options.method || "GET"; - this.parameters = []; - this.headers = []; - this.body = new Body(options.body); - this.environment = options.environment; - this.useEnvironment = options.useEnvironment; - this.debugReport = undefined; - this.doMultipartPost = options.doMultipartPost; - this.connectTimeout = options.connectTimeout || 60 * 1000; - this.responseTimeout = options.responseTimeout; - this.followRedirects = options.followRedirects === undefined ? true : options.followRedirects; - - this.sets({parameters: KeyValue, headers: KeyValue}, options); - } - - isValid(environmentId, environment) { - if (this.enable) { - if (this.useEnvironment) { - if (!environmentId) { - return { - isValid: false, - info: 'api_test.request.please_configure_environment_in_scenario' - } - } - if (!environment.config.httpConfig.socket) { - return { - isValid: false, - info: 'api_test.request.please_configure_socket_in_environment' - } - } - } else { - if (!this.url) { - return { - isValid: false, - info: 'api_test.request.input_url' - } - } - try { - new URL(this.url) - } catch (e) { - return { - isValid: false, - info: 'api_test.request.url_invalid' - } - } - } - } - return { - isValid: true - } - } - - showType() { - return this.type; - } - - showMethod() { - return this.method.toUpperCase(); - } - -} - -export class DubboRequest extends Request { - static PROTOCOLS = { - DUBBO: "dubbo://", - RMI: "rmi://", - } - - constructor(options = {}) { - super(RequestFactory.TYPES.DUBBO, options); - this.protocol = options.protocol || DubboRequest.PROTOCOLS.DUBBO; - this.interface = options.interface; - this.method = options.method; - this.configCenter = new ConfigCenter(options.configCenter); - this.registryCenter = new RegistryCenter(options.registryCenter); - this.consumerAndService = new ConsumerAndService(options.consumerAndService); - this.args = []; - this.attachmentArgs = []; - // Scenario.dubboConfig - this.dubboConfig = undefined; - this.debugReport = undefined; - - this.sets({args: KeyValue, attachmentArgs: KeyValue}, options); - } - - isValid() { - if (this.enable) { - if (!this.interface) { - return { - isValid: false, - info: 'api_test.request.dubbo.input_interface' - } - } - if (!this.method) { - return { - isValid: false, - info: 'api_test.request.dubbo.input_method' - } - } - if (!this.registryCenter.isValid()) { - return { - isValid: false, - info: 'api_test.request.dubbo.input_registry_center' - } - } - if (!this.consumerAndService.isValid()) { - return { - isValid: false, - info: 'api_test.request.dubbo.input_consumer_service' - } - } - } - return { - isValid: true - } - } - - showType() { - return "RPC"; - } - - showMethod() { - // dubbo:// -> DUBBO - return this.protocol.substr(0, this.protocol.length - 3).toUpperCase(); - } - - clone() { - return new DubboRequest(this); - } -} - -export class SqlRequest extends Request { - - constructor(options = {}) { - super(RequestFactory.TYPES.SQL, options); - this.useEnvironment = options.useEnvironment; - this.resultVariable = options.resultVariable; - this.variableNames = options.variableNames; - this.variables = []; - this.debugReport = undefined; - this.dataSource = options.dataSource; - this.query = options.query; - // this.queryType = options.queryType; - this.queryTimeout = options.queryTimeout || 60000; - - this.sets({args: KeyValue, attachmentArgs: KeyValue, variables: KeyValue}, options); - } - - isValid() { - if (this.enable) { - if (!this.name) { - return { - isValid: false, - info: 'api_test.request.sql.name_cannot_be_empty' - } - } - if (!this.dataSource) { - return { - isValid: false, - info: 'api_test.request.sql.dataSource_cannot_be_empty' - } - } - } - return { - isValid: true - } - } - - showType() { - return "SQL"; - } - - showMethod() { - return "SQL"; - } - - clone() { - return new SqlRequest(this); - } -} - -export class TCPConfig extends BaseConfig { - static CLASSES = ["TCPClientImpl", "BinaryTCPClientImpl", "LengthPrefixedBinaryTCPClientImpl"] - - constructor(options = {}) { - super(); - this.classname = options.classname || TCPConfig.CLASSES[0]; - this.server = options.server; - this.port = options.port; - this.ctimeout = options.ctimeout; // Connect - this.timeout = options.timeout; // Response - - this.reUseConnection = options.reUseConnection === undefined ? true : options.reUseConnection; - this.nodelay = options.nodelay === undefined ? false : options.nodelay; - this.closeConnection = options.closeConnection === undefined ? false : options.closeConnection; - this.soLinger = options.soLinger; - this.eolByte = options.eolByte; - - this.username = options.username; - this.password = options.password; - } -} - -export class TCPRequest extends Request { - constructor(options = {}) { - super(RequestFactory.TYPES.TCP, options); - this.useEnvironment = options.useEnvironment; - this.debugReport = undefined; - - //设置TCPConfig的属性 - this.set(new TCPConfig(options)); - - this.request = options.request; - } - - isValid() { - return { - isValid: true - } - } - - showType() { - return "TCP"; - } - - showMethod() { - return "TCP"; - } - - clone() { - return new TCPRequest(this); - } -} - - -export class ConfigCenter extends BaseConfig { - static PROTOCOLS = ["zookeeper", "nacos", "apollo"]; - - constructor(options) { - super(); - this.protocol = undefined; - this.group = undefined; - this.namespace = undefined; - this.username = undefined; - this.address = undefined; - this.password = undefined; - this.timeout = undefined; - - this.set(options); - } - - isValid() { - return !!this.protocol || !!this.group || !!this.namespace || !!this.username || !!this.address || !!this.password || !!this.timeout; - } -} - -export class DatabaseConfig extends BaseConfig { - static DRIVER_CLASS = ["com.mysql.jdbc.Driver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "org.postgresql.Driver", "oracle.jdbc.OracleDriver"]; - - constructor(options) { - super(); - this.id = undefined; - this.name = undefined; - this.poolMax = undefined; - this.timeout = undefined; - this.driver = undefined; - this.dbUrl = undefined; - this.username = undefined; - this.password = undefined; - - this.set(options); - } - - initOptions(options = {}) { - // options.id = options.id || uuid(); - return options; - } - - isValid() { - return !!this.name || !!this.poolMax || !!this.timeout || !!this.driver || !!this.dbUrl || !!this.username || !!this.password; - } -} - -export class RegistryCenter extends BaseConfig { - static PROTOCOLS = ["none", "zookeeper", "nacos", "apollo", "multicast", "redis", "simple"]; - - constructor(options) { - super(); - this.protocol = undefined; - this.group = undefined; - this.username = undefined; - this.address = undefined; - this.password = undefined; - this.timeout = undefined; - - this.set(options); - } - - isValid() { - return !!this.protocol || !!this.group || !!this.username || !!this.address || !!this.password || !!this.timeout; - } -} - -export class ConsumerAndService extends BaseConfig { - static ASYNC_OPTIONS = ["sync", "async"]; - static LOAD_BALANCE_OPTIONS = ["random", "roundrobin", "leastactive", "consistenthash"]; - - constructor(options) { - super(); - this.timeout = "1000"; - this.version = "1.0"; - this.retries = "0"; - this.cluster = "failfast"; - this.group = undefined; - this.connections = "100"; - this.async = "sync"; - this.loadBalance = "random"; - - this.set(options); - } - - isValid() { - return !!this.timeout || !!this.version || !!this.retries || !!this.cluster || !!this.group || !!this.connections || !!this.async || !!this.loadBalance; - } -} - -export class Body extends BaseConfig { - constructor(options) { - super(); - this.type = undefined; - this.raw = undefined; - this.kvs = []; - - this.set(options); - this.sets({kvs: KeyValue}, options); - } - - isValid() { - if (this.isKV()) { - return this.kvs.some(kv => { - return kv.isValid(); - }) - } else { - return !!this.raw; - } - } - - isKV() { - return this.type === BODY_TYPE.KV; - } -} - -export class KeyValue extends BaseConfig { - constructor(options) { - options = options || {}; - options.enable = options.enable === undefined ? true : options.enable; - - super(); - this.name = undefined; - this.value = undefined; - this.type = undefined; - this.files = undefined; - this.enable = undefined; - this.uuid = undefined; - this.contentType = undefined; - this.set(options); - } - - isValid() { - return (!!this.name || !!this.value) && this.type !== 'file'; - } - - isFile() { - return (!!this.name || !!this.value) && this.type === 'file'; - } -} - -export class Assertions extends BaseConfig { - constructor(options) { - super(); - this.text = []; - this.regex = []; - this.jsonPath = []; - this.duration = undefined; - - this.set(options); - this.sets({text: Text, regex: Regex, jsonPath: JSONPath}, options); - } - - initOptions(options) { - options = options || {}; - options.duration = new Duration(options.duration); - return options; - } -} - -export class AssertionType extends BaseConfig { - constructor(type) { - super(); - this.type = type; - } -} - -export class BeanShellProcessor extends BaseConfig { - constructor(options) { - super(); - this.script = undefined; - this.set(options); - } -} - - -export class JSR223Processor extends BaseConfig { - constructor(options) { - super(); - this.script = undefined; - this.language = "beanshell"; - this.set(options); - } -} - -export class Text extends AssertionType { - constructor(options) { - super(ASSERTION_TYPE.TEXT); - this.subject = undefined; - this.condition = undefined; - this.value = undefined; - - this.set(options); - } -} - -export class Regex extends AssertionType { - constructor(options) { - super(ASSERTION_TYPE.REGEX); - this.subject = undefined; - this.expression = undefined; - this.description = undefined; - this.assumeSuccess = false; - - this.set(options); - } - - isValid() { - return !!this.subject && !!this.expression; - } -} - -export class JSONPath extends AssertionType { - constructor(options) { - super(ASSERTION_TYPE.JSON_PATH); - this.expression = undefined; - this.expect = undefined; - this.description = undefined; - - this.set(options); - } - - isValid() { - return !!this.expression; - } -} - -export class Duration extends AssertionType { - constructor(options) { - super(ASSERTION_TYPE.DURATION); - this.value = undefined; - - this.set(options); - } - - isValid() { - return !!this.value; - } -} - -export class Extract extends BaseConfig { - constructor(options) { - super(); - this.regex = []; - this.json = []; - this.xpath = []; - - this.set(options); - let types = { - json: ExtractJSONPath, - xpath: ExtractXPath, - regex: ExtractRegex - } - this.sets(types, options); - } -} - -export class ExtractType extends BaseConfig { - constructor(type) { - super(); - this.type = type; - } -} - -export class ExtractCommon extends ExtractType { - constructor(type, options) { - super(type); - this.variable = undefined; - this.useHeaders = undefined; - this.value = ""; // ${variable} - this.expression = undefined; - this.description = undefined; - this.multipleMatching = undefined; - - this.set(options); - } - - isValid() { - return !!this.variable && !!this.expression; - } -} - -export class ExtractRegex extends ExtractCommon { - constructor(options) { - super(EXTRACT_TYPE.REGEX, options); - } -} - -export class ExtractJSONPath extends ExtractCommon { - constructor(options) { - super(EXTRACT_TYPE.JSON_PATH, options); - } -} - -export class ExtractXPath extends ExtractCommon { - constructor(options) { - super(EXTRACT_TYPE.XPATH, options); - } -} - -export class Controller extends BaseConfig { - static TYPES = { - IF_CONTROLLER: "If Controller", - } - - constructor(type, options = {}) { - super(); - this.type = type - options.id = options.id || uuid(); - options.enable = options.enable === undefined ? true : options.enable; - } -} - -export class IfController extends Controller { - constructor(options = {}) { - super(Controller.TYPES.IF_CONTROLLER, options); - this.variable; - this.operator; - this.value; - - this.set(options); - } - - isValid() { - if (!!this.operator && this.operator.indexOf("empty") > 0) { - return !!this.variable && !!this.operator; - } - return !!this.variable && !!this.operator && !!this.value; - } - - label() { - if (this.isValid()) { - let label = this.variable; - if (this.operator) label += " " + this.operator; - if (this.value) label += " " + this.value; - return label; - } - return ""; - } -} - -export class Timer extends BaseConfig { - static TYPES = { - CONSTANT_TIMER: "Constant Timer", - } - - constructor(type, options = {}) { - super(); - this.type = type; - options.id = options.id || uuid(); - options.enable = options.enable === undefined ? true : options.enable; - } -} - -export class ConstantTimer extends Timer { - constructor(options = {}) { - super(Timer.TYPES.CONSTANT_TIMER, options); - this.delay; - - this.set(options); - } - - isValid() { - return this.delay > 0; - } - - label() { - if (this.isValid()) { - return this.delay + " ms"; - } - return ""; - } -} - -/** ------------------------------------------------------------------------ **/ -const JMX_ASSERTION_CONDITION = { - MATCH: 1, - CONTAINS: 1 << 1, - NOT: 1 << 2, - EQUALS: 1 << 3, - SUBSTRING: 1 << 4, - OR: 1 << 5 -} - -class JMXHttpRequest { - constructor(request, environment) { - if (request && request instanceof HttpRequest) { - this.useEnvironment = request.useEnvironment; - this.method = request.method; - if (!request.useEnvironment) { - if (!request.url.startsWith("http://") && !request.url.startsWith("https://")) { - request.url = 'http://' + request.url; - } - let url = new URL(request.url); - this.domain = decodeURIComponent(url.hostname); - this.port = url.port; - this.protocol = url.protocol.split(":")[0]; - this.path = this.getPostQueryParameters(request, decodeURIComponent(url.pathname)); - } else { - this.domain = environment.config.httpConfig.domain; - this.port = environment.config.httpConfig.port; - this.protocol = environment.config.httpConfig.protocol; - let url = new URL(environment.config.httpConfig.protocol + "://" + environment.config.httpConfig.socket); - this.path = this.getPostQueryParameters(request, decodeURIComponent(url.pathname + (request.path ? request.path : ''))); - } - this.connectTimeout = request.connectTimeout; - this.responseTimeout = request.responseTimeout; - this.followRedirects = request.followRedirects; - this.doMultipartPost = request.doMultipartPost; - } - } - - getPostQueryParameters(request, path) { - if (this.method.toUpperCase() !== "GET") { - let parameters = []; - request.parameters.forEach(parameter => { - if (parameter.name && parameter.value && parameter.enable === true) { - parameters.push(parameter); - } - }); - if (parameters.length > 0) { - path += '?'; - } - for (let i = 0; i < parameters.length; i++) { - let parameter = parameters[i]; - path += (parameter.name + '=' + parameter.value); - if (i !== parameters.length - 1) { - path += '&'; - } - } - } - return path; - } -} - -class JMXDubboRequest { - constructor(request, dubboConfig) { - // Request 复制 - let obj = request.clone(); - // 去掉无效的kv - obj.args = obj.args.filter(arg => { - return arg.isValid(); - }); - obj.attachmentArgs = obj.attachmentArgs.filter(arg => { - return arg.isValid(); - }); - - // Scenario DubboConfig复制 - this.copy(obj.configCenter, dubboConfig.configCenter); - this.copy(obj.registryCenter, dubboConfig.registryCenter); - this.copy(obj.consumerAndService, dubboConfig.consumerAndService); - - return obj; - } - - copy(target, source) { - for (let key in source) { - if (source.hasOwnProperty(key)) { - if (source[key] !== undefined && !target[key]) { - target[key] = source[key]; - } - } - } - } -} - -class JMXTCPRequest { - constructor(request, scenario) { - let obj = request.clone(); - if (request.useEnvironment) { - obj.set(scenario.environment.config.tcpConfig, true); - return obj; - } - - this.copy(this, scenario.tcpConfig); - - return obj; - } - - copy(target, source) { - for (let key in source) { - if (source.hasOwnProperty(key)) { - if (source[key] !== undefined && !target[key]) { - target[key] = source[key]; - } - } - } - } -} - -class JMeterTestPlan extends Element { - constructor() { - super('jmeterTestPlan', { - version: "1.2", properties: "5.0", jmeter: "5.2.1" - }); - - this.add(new HashTree()); - } - - put(te) { - if (te instanceof TestElement) { - this.elements[0].add(te); - } - } -} - -class JMXGenerator { - constructor(test) { - if (!test || !test.id || !(test instanceof Test)) return undefined; - - let testPlan = new TestPlan(test.name); - this.addScenarios(testPlan, test.id, test.scenarioDefinition); - - this.jmeterTestPlan = new JMeterTestPlan(); - this.jmeterTestPlan.put(testPlan); - } - - addScenarios(testPlan, testId, scenarios) { - scenarios.forEach(s => { - - if (s.enable) { - let scenario = s.clone(); - - let threadGroup = new ThreadGroup(scenario.name || ""); - - this.addScenarioVariables(threadGroup, scenario); - - this.addScenarioHeaders(threadGroup, scenario); - - this.addScenarioCookieManager(threadGroup, scenario); - - this.addJDBCDataSources(threadGroup, scenario); - scenario.requests.forEach(request => { - if (request.enable) { - if (!request.isValid()) return; - let sampler; - if (request instanceof DubboRequest) { - sampler = new DubboSample(request.name || "", new JMXDubboRequest(request, scenario.dubboConfig)); - } else if (request instanceof HttpRequest) { - sampler = new HTTPSamplerProxy(request.name || "", new JMXHttpRequest(request, scenario.environment)); - this.addRequestHeader(sampler, request); - this.addRequestArguments(sampler, request); - this.addRequestBody(sampler, request, testId); - } else if (request instanceof SqlRequest) { - request.dataSource = scenario.databaseConfigMap.get(request.dataSource); - sampler = new JDBCSampler(request.name || "", request); - this.addRequestVariables(sampler, request); - } else if (request instanceof TCPRequest) { - sampler = new TCPSampler(request.name || "", new JMXTCPRequest(request, scenario)); - } - - this.addDNSCacheManager(sampler, scenario.environment, request.useEnvironment); - - this.addRequestExtractor(sampler, request); - - this.addRequestAssertion(sampler, request); - - this.addJSR223PreProcessor(sampler, request); - - this.addConstantsTimer(sampler, request); - - if (request.controller && request.controller.isValid() && request.controller.enable) { - if (request.controller instanceof IfController) { - let controller = this.getController(sampler, request); - threadGroup.put(controller); - } - } else { - threadGroup.put(sampler); - } - } - }) - testPlan.put(threadGroup); - } - - }) - } - - addEnvironments(environments, target) { - let keys = new Set(); - target.forEach(item => { - keys.add(item.name); - }); - let envArray = environments; - if (!(envArray instanceof Array)) { - envArray = JSON.parse(environments); - } - envArray.forEach(item => { - if (item.name && !keys.has(item.name)) { - target.push(new KeyValue({name: item.name, value: item.value})); - } - }) - } - - addScenarioVariables(threadGroup, scenario) { - if (scenario.environment) { - let config = scenario.environment.config; - if (!(scenario.environment.config instanceof Object)) { - config = JSON.parse(scenario.environment.config); - } - this.addEnvironments(config.commonConfig.variables, scenario.variables) - } - let args = this.filterKV(scenario.variables); - if (args.length > 0) { - let name = scenario.name + " Variables"; - threadGroup.put(new Arguments(name, args)); - } - } - - addRequestVariables(httpSamplerProxy, request) { - let name = request.name + " Variables"; - let variables = this.filterKV(request.variables); - if (variables && variables.length > 0) { - httpSamplerProxy.put(new Arguments(name, variables)); - } - } - - addScenarioCookieManager(threadGroup, scenario) { - if (scenario.enableCookieShare) { - threadGroup.put(new CookieManager(scenario.name)); - } - } - - addDNSCacheManager(httpSamplerProxy, environment, useEnv) { - if (environment && useEnv === true) { - let commonConfig = environment.config.commonConfig; - let hosts = commonConfig.hosts; - if (commonConfig.enableHost && hosts.length > 0) { - let name = " DNSCacheManager"; - // 强化判断,如果未匹配到合适的host则不开启DNSCache - let domain = environment.config.httpConfig.domain; - let validHosts = []; - hosts.forEach(item => { - if (item.domain !== undefined && domain !== undefined) { - let d = item.domain.trim().replace("http://", "").replace("https://", ""); - if (d === domain.trim()) { - item.domain = d; // 域名去掉协议 - validHosts.push(item); - } - } - }); - if (validHosts.length > 0) { - httpSamplerProxy.put(new DNSCacheManager(name, validHosts)); - } - } - } - } - - addJDBCDataSources(threadGroup, scenario) { - let names = new Set(); - let databaseConfigMap = new Map(); - scenario.databaseConfigs.forEach(config => { - let name = config.name + "JDBCDataSource"; - threadGroup.put(new JDBCDataSource(name, config)); - names.add(name); - databaseConfigMap.set(config.id, config.name); - }); - if (scenario.environment) { - let config = scenario.environment.config; - if (!(scenario.environment.config instanceof Object)) { - config = JSON.parse(scenario.environment.config); - } - config.databaseConfigs.forEach(config => { - if (!names.has(config.name)) { - let name = config.name + "JDBCDataSource"; - threadGroup.put(new JDBCDataSource(name, config)); - databaseConfigMap.set(config.id, config.name); - } - }); - } - scenario.databaseConfigMap = databaseConfigMap; - } - - addScenarioHeaders(threadGroup, scenario) { - if (scenario.environment) { - let config = scenario.environment.config; - if (!(scenario.environment.config instanceof Object)) { - config = JSON.parse(scenario.environment.config); - } - this.addEnvironments(config.httpConfig.headers, scenario.headers) - } - let headers = this.filterKV(scenario.headers); - if (headers.length > 0) { - let name = scenario.name + " Headers"; - threadGroup.put(new HeaderManager(name, headers)); - } - } - - addRequestHeader(httpSamplerProxy, request) { - let name = request.name + " Headers"; - this.addBodyFormat(request); - let headers = this.filterKV(request.headers); - if (headers.length > 0) { - httpSamplerProxy.put(new HeaderManager(name, headers)); - } - } - - addJSR223PreProcessor(sampler, request) { - let name = request.name; - if (request.jsr223PreProcessor && request.jsr223PreProcessor.script) { - sampler.put(new JSR223PreProcessor(name, request.jsr223PreProcessor)); - } - if (request.jsr223PostProcessor && request.jsr223PostProcessor.script) { - sampler.put(new JSR223PostProcessor(name, request.jsr223PostProcessor)); - } - } - - addConstantsTimer(sampler, request) { - if (request.timer && request.timer.isValid() && request.timer.enable) { - sampler.put(new JMXConstantTimer(request.timer.label(), request.timer)); - } - } - - getController(sampler, request) { - if (request.controller.isValid() && request.controller.enable) { - if (request.controller instanceof IfController) { - let name = request.controller.label(); - let variable = "\"" + request.controller.variable + "\""; - let operator = request.controller.operator; - let value = "\"" + request.controller.value + "\""; - - if (operator === "=~" || operator === "!~") { - value = "\".*" + request.controller.value + ".*\""; - } - - if (operator === "is empty") { - variable = "empty(" + variable + ")"; - operator = ""; - value = ""; - } - - if (operator === "is not empty") { - variable = "!empty(" + variable + ")"; - operator = ""; - value = ""; - } - - let condition = "${__jexl3(" + variable + operator + value + ")}"; - let controller = new JMXIfController(name, {condition: condition}); - controller.put(sampler); - return controller; - } - } - } - - addBodyFormat(request) { - let bodyFormat = request.body.format; - if (!request.body.isKV() && bodyFormat) { - switch (bodyFormat) { - case BODY_FORMAT.JSON: - this.addContentType(request, 'application/json'); - break; - case BODY_FORMAT.HTML: - this.addContentType(request, 'text/html'); - break; - case BODY_FORMAT.XML: - this.addContentType(request, 'text/xml'); - break; - default: - break; - } - } - } - - addContentType(request, type) { - for (let index in request.headers) { - if (request.headers.hasOwnProperty(index)) { - if (request.headers[index].name === 'Content-Type') { - request.headers.splice(index, 1); - break; - } - } - } - request.headers.push(new KeyValue({name: 'Content-Type', value: type})); - } - - addRequestArguments(httpSamplerProxy, request) { - let args = this.filterKV(request.parameters); - if (args.length > 0) { - httpSamplerProxy.add(new HTTPSamplerArguments(args)); - } - } - - addRequestBody(httpSamplerProxy, request, testId) { - let body = []; - if (request.body.isKV()) { - body = this.filterKV(request.body.kvs); - this.addRequestBodyFile(httpSamplerProxy, request, testId); - } else { - httpSamplerProxy.boolProp('HTTPSampler.postBodyRaw', true); - body.push({name: '', value: request.body.raw, encode: false, enable: true}); - } - - if (request.method !== 'GET') { - httpSamplerProxy.add(new HTTPSamplerArguments(body)); - } - } - - addRequestBodyFile(httpSamplerProxy, request, testId) { - let files = []; - let kvs = this.filterKVFile(request.body.kvs); - kvs.forEach(kv => { - if ((kv.enable !== false) && kv.files) { - kv.files.forEach(file => { - let arg = {}; - arg.name = kv.name; - arg.value = BODY_FILE_DIR + '/' + testId + '/' + file.id + '_' + file.name; - files.push(arg); - }); - } - }); - httpSamplerProxy.add(new HTTPsamplerFiles(files)); - } - - addRequestAssertion(httpSamplerProxy, request) { - let assertions = request.assertions; - if (assertions.regex.length > 0) { - assertions.regex.filter(this.filter).forEach(regex => { - httpSamplerProxy.put(this.getResponseAssertion(regex)); - }) - } - - if (assertions.jsonPath.length > 0) { - assertions.jsonPath.filter(this.filter).forEach(item => { - httpSamplerProxy.put(this.getJSONPathAssertion(item)); - }) - } - - if (assertions.duration.isValid()) { - let name = "Response In Time: " + assertions.duration.value - httpSamplerProxy.put(new DurationAssertion(name, assertions.duration.value)); - } - } - - getJSONPathAssertion(jsonPath) { - let name = jsonPath.description; - return new JSONPathAssertion(name, jsonPath); - } - - getResponseAssertion(regex) { - let name = regex.description; - let type = JMX_ASSERTION_CONDITION.CONTAINS; // 固定用Match,自己写正则 - let value = regex.expression; - let assumeSuccess = regex.assumeSuccess; - switch (regex.subject) { - case ASSERTION_REGEX_SUBJECT.RESPONSE_CODE: - return new ResponseCodeAssertion(name, type, value, assumeSuccess); - case ASSERTION_REGEX_SUBJECT.RESPONSE_DATA: - return new ResponseDataAssertion(name, type, value, assumeSuccess); - case ASSERTION_REGEX_SUBJECT.RESPONSE_HEADERS: - return new ResponseHeadersAssertion(name, type, value, assumeSuccess); - } - } - - addRequestExtractor(httpSamplerProxy, request) { - let extract = request.extract; - if (extract.regex.length > 0) { - extract.regex.filter(this.filter).forEach(regex => { - httpSamplerProxy.put(this.getExtractor(regex)); - }) - } - - if (extract.json.length > 0) { - extract.json.filter(this.filter).forEach(json => { - httpSamplerProxy.put(this.getExtractor(json)); - }) - } - - if (extract.xpath.length > 0) { - extract.xpath.filter(this.filter).forEach(xpath => { - httpSamplerProxy.put(this.getExtractor(xpath)); - }) - } - } - - getExtractor(extractCommon) { - let props = { - name: extractCommon.variable, - expression: extractCommon.expression, - match: extractCommon.multipleMatching ? -1 : undefined - } - let testName = props.name - switch (extractCommon.type) { - case EXTRACT_TYPE.REGEX: - testName += " RegexExtractor"; - props.headers = extractCommon.useHeaders; // 对应jMeter body - props.template = "$1$"; - return new RegexExtractor(testName, props); - case EXTRACT_TYPE.JSON_PATH: - testName += " JSONExtractor"; - return new JSONPostProcessor(testName, props); - case EXTRACT_TYPE.XPATH: - testName += " XPath2Evaluator"; - return new XPath2Extractor(testName, props); - } - } - - filter(config) { - return config.isValid(); - } - - filterKV(kvs) { - return kvs.filter(this.filter); - } - - filterKVFile(kvs) { - return kvs.filter(kv => { - return kv.isFile(); - }); - } - - toXML() { - let xml = '\n'; - xml += this.jmeterTestPlan.toXML(); - return xml; - } -} - - +import { + Arguments, + CookieManager, + DNSCacheManager, + DubboSample, + DurationAssertion, + Element, + HashTree, + HeaderManager, + HTTPSamplerArguments, + HTTPsamplerFiles, + HTTPSamplerProxy, + JDBCDataSource, + JDBCSampler, + JSONPathAssertion, + JSONPostProcessor, + JSR223PostProcessor, + JSR223PreProcessor, + RegexExtractor, + ResponseCodeAssertion, + ResponseDataAssertion, + ResponseHeadersAssertion, + TestElement, + TestPlan, + ThreadGroup, + XPath2Extractor, + IfController as JMXIfController, + ConstantTimer as JMXConstantTimer, TCPSampler, +} from "./JMX"; +import Mock from "mockjs"; +import {funcFilters} from "@/common/js/func-filter"; + +export const uuid = function () { + let d = new Date().getTime() + let d2 = (performance && performance.now && (performance.now() * 1000)) || 0; + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + let r = Math.random() * 16; + if (d > 0) { + r = (d + r) % 16 | 0; + d = Math.floor(d / 16); + } else { + r = (d2 + r) % 16 | 0; + d2 = Math.floor(d2 / 16); + } + return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }); +} + +export const BODY_FILE_DIR = "/opt/metersphere/data/body"; //存放body文件上传目录 + +export const calculate = function (itemValue) { + if (!itemValue) { + return; + } + try { + if (itemValue.trim().startsWith("${")) { + // jmeter 内置函数不做处理 + return itemValue; + } + let funcs = itemValue.split("|"); + let value = Mock.mock(funcs[0].trim()); + if (funcs.length === 1) { + return value; + } + for (let i = 1; i < funcs.length; i++) { + let func = funcs[i].trim(); + let args = func.split(":"); + let strings = []; + if (args[1]) { + strings = args[1].split(","); + } + value = funcFilters[args[0].trim()](value, ...strings); + } + return value; + } catch (e) { + return itemValue; + } +} + +export const BODY_TYPE = { + KV: "KeyValue", + FORM_DATA: "Form Data", + RAW: "Raw" +} + +export const BODY_FORMAT = { + TEXT: "text", + JSON: "json", + XML: "xml", + HTML: "html", +} + +export const ASSERTION_TYPE = { + TEXT: "Text", + REGEX: "Regex", + JSON_PATH: "JSON", + DURATION: "Duration" +} + +export const ASSERTION_REGEX_SUBJECT = { + RESPONSE_CODE: "Response Code", + RESPONSE_HEADERS: "Response Headers", + RESPONSE_DATA: "Response Data" +} + +export const EXTRACT_TYPE = { + REGEX: "Regex", + JSON_PATH: "JSONPath", + XPATH: "XPath" +} + +export class BaseConfig { + + set(options, notUndefined) { + options = this.initOptions(options) + for (let name in options) { + if (options.hasOwnProperty(name)) { + if (!(this[name] instanceof Array)) { + if (notUndefined === true) { + this[name] = options[name] === undefined ? this[name] : options[name]; + } else { + this[name] = options[name]; + } + } + } + } + } + + sets(types, options) { + options = this.initOptions(options) + if (types) { + for (let name in types) { + if (types.hasOwnProperty(name) && options.hasOwnProperty(name)) { + options[name].forEach(o => { + this[name].push(new types[name](o)); + }) + } + } + } + } + + initOptions(options) { + return options || {}; + } + + isValid() { + return true; + } +} + +export class Test extends BaseConfig { + constructor(options) { + super(); + this.type = "MS API CONFIG"; + this.version = '1.4.0'; + this.id = uuid(); + this.name = undefined; + this.projectId = undefined; + this.scenarioDefinition = []; + this.schedule = {}; + + this.set(options); + this.sets({scenarioDefinition: Scenario}, options); + } + + export() { + let obj = { + type: this.type, + version: this.version, + scenarios: this.scenarioDefinition + }; + + return JSON.stringify(obj); + } + + initOptions(options) { + options = options || {}; + options.scenarioDefinition = options.scenarioDefinition || [new Scenario()]; + return options; + } + + isValid() { + for (let i = 0; i < this.scenarioDefinition.length; i++) { + let validator = this.scenarioDefinition[i].isValid(); + if (!validator.isValid) { + return validator; + } + } + if (!this.projectId) { + return { + isValid: false, + info: 'api_test.select_project' + } + } else if (!this.name) { + return { + isValid: false, + info: 'api_test.input_name' + } + } + return {isValid: true}; + } + + toJMX() { + return { + name: this.name + '.jmx', + xml: new JMXGenerator(this).toXML() + }; + } +} + +export class Scenario extends BaseConfig { + constructor(options = {}) { + super(); + this.id = undefined; + this.name = undefined; + this.url = undefined; + this.variables = []; + this.headers = []; + this.requests = []; + this.environmentId = undefined; + this.dubboConfig = undefined; + this.environment = undefined; + this.enableCookieShare = false; + this.enable = true; + this.databaseConfigs = []; + this.tcpConfig = undefined; + + this.set(options); + this.sets({ + variables: KeyValue, + headers: KeyValue, + requests: RequestFactory, + databaseConfigs: DatabaseConfig + }, options); + } + + initOptions(options = {}) { + options.id = options.id || uuid(); + options.requests = options.requests || [new RequestFactory()]; + options.databaseConfigs = options.databaseConfigs || []; + options.dubboConfig = new DubboConfig(options.dubboConfig); + options.tcpConfig = new TCPConfig(options.tcpConfig); + return options; + } + + clone() { + let clone = new Scenario(this); + clone.id = uuid(); + return clone; + } + + isValid() { + if (this.enable) { + for (let i = 0; i < this.requests.length; i++) { + let validator = this.requests[i].isValid(this.environmentId, this.environment); + if (!validator.isValid) { + return validator; + } + } + } + return {isValid: true}; + } + + isReference() { + return this.id.indexOf("#") !== -1 + } +} + +class DubboConfig extends BaseConfig { + constructor(options = {}) { + super(); + this.configCenter = new ConfigCenter(options.configCenter) + this.registryCenter = new RegistryCenter(options.registryCenter) + if (options.consumerAndService === undefined) { + options.consumerAndService = { + timeout: undefined, + version: undefined, + retries: undefined, + cluster: undefined, + group: undefined, + connections: undefined, + async: undefined, + loadBalance: undefined + } + } + this.consumerAndService = new ConsumerAndService(options.consumerAndService) + } +} + +export class RequestFactory { + static TYPES = { + HTTP: "HTTP", + DUBBO: "DUBBO", + SQL: "SQL", + TCP: "TCP", + } + + constructor(options = {}) { + options.type = options.type || RequestFactory.TYPES.HTTP + switch (options.type) { + case RequestFactory.TYPES.DUBBO: + return new DubboRequest(options); + case RequestFactory.TYPES.SQL: + return new SqlRequest(options); + case RequestFactory.TYPES.TCP: + return new TCPRequest(options); + default: + return new HttpRequest(options); + } + } +} + +export class Request extends BaseConfig { + constructor(type, options = {}) { + super(); + this.type = type; + this.id = options.id || uuid(); + this.name = options.name; + this.enable = options.enable === undefined ? true : options.enable; + this.assertions = new Assertions(options.assertions); + this.extract = new Extract(options.extract); + this.jsr223PreProcessor = new JSR223Processor(options.jsr223PreProcessor); + this.jsr223PostProcessor = new JSR223Processor(options.jsr223PostProcessor); + this.timer = new ConstantTimer(options.timer); + this.controller = new IfController(options.controller); + } + + showType() { + return this.type; + } + + showMethod() { + return ""; + } +} + +export class HttpRequest extends Request { + constructor(options) { + super(RequestFactory.TYPES.HTTP, options); + this.url = options.url; + this.path = options.path; + this.method = options.method || "GET"; + this.parameters = []; + this.headers = []; + this.body = new Body(options.body); + this.environment = options.environment; + this.useEnvironment = options.useEnvironment; + this.debugReport = undefined; + this.doMultipartPost = options.doMultipartPost; + this.connectTimeout = options.connectTimeout || 60 * 1000; + this.responseTimeout = options.responseTimeout; + this.followRedirects = options.followRedirects === undefined ? true : options.followRedirects; + + this.sets({parameters: KeyValue, headers: KeyValue}, options); + } + + isValid(environmentId, environment) { + if (this.enable) { + if (this.useEnvironment) { + if (!environmentId) { + return { + isValid: false, + info: 'api_test.request.please_configure_environment_in_scenario' + } + } + if (!environment.config.httpConfig.socket) { + return { + isValid: false, + info: 'api_test.request.please_configure_socket_in_environment' + } + } + } else { + if (!this.url) { + return { + isValid: false, + info: 'api_test.request.input_url' + } + } + try { + new URL(this.url) + } catch (e) { + return { + isValid: false, + info: 'api_test.request.url_invalid' + } + } + } + } + return { + isValid: true + } + } + + showType() { + return this.type; + } + + showMethod() { + return this.method.toUpperCase(); + } + +} + +export class DubboRequest extends Request { + static PROTOCOLS = { + DUBBO: "dubbo://", + RMI: "rmi://", + } + + constructor(options = {}) { + super(RequestFactory.TYPES.DUBBO, options); + this.protocol = options.protocol || DubboRequest.PROTOCOLS.DUBBO; + this.interface = options.interface; + this.method = options.method; + this.configCenter = new ConfigCenter(options.configCenter); + this.registryCenter = new RegistryCenter(options.registryCenter); + this.consumerAndService = new ConsumerAndService(options.consumerAndService); + this.args = []; + this.attachmentArgs = []; + // Scenario.dubboConfig + this.dubboConfig = undefined; + this.debugReport = undefined; + + this.sets({args: KeyValue, attachmentArgs: KeyValue}, options); + } + + isValid() { + if (this.enable) { + if (!this.interface) { + return { + isValid: false, + info: 'api_test.request.dubbo.input_interface' + } + } + if (!this.method) { + return { + isValid: false, + info: 'api_test.request.dubbo.input_method' + } + } + if (!this.registryCenter.isValid()) { + return { + isValid: false, + info: 'api_test.request.dubbo.input_registry_center' + } + } + if (!this.consumerAndService.isValid()) { + return { + isValid: false, + info: 'api_test.request.dubbo.input_consumer_service' + } + } + } + return { + isValid: true + } + } + + showType() { + return "RPC"; + } + + showMethod() { + // dubbo:// -> DUBBO + return this.protocol.substr(0, this.protocol.length - 3).toUpperCase(); + } + + clone() { + return new DubboRequest(this); + } +} + +export class SqlRequest extends Request { + + constructor(options = {}) { + super(RequestFactory.TYPES.SQL, options); + this.useEnvironment = options.useEnvironment; + this.resultVariable = options.resultVariable; + this.variableNames = options.variableNames; + this.variables = []; + this.debugReport = undefined; + this.dataSource = options.dataSource; + this.query = options.query; + // this.queryType = options.queryType; + this.queryTimeout = options.queryTimeout || 60000; + + this.sets({args: KeyValue, attachmentArgs: KeyValue, variables: KeyValue}, options); + } + + isValid() { + if (this.enable) { + if (!this.name) { + return { + isValid: false, + info: 'api_test.request.sql.name_cannot_be_empty' + } + } + if (!this.dataSource) { + return { + isValid: false, + info: 'api_test.request.sql.dataSource_cannot_be_empty' + } + } + } + return { + isValid: true + } + } + + showType() { + return "SQL"; + } + + showMethod() { + return "SQL"; + } + + clone() { + return new SqlRequest(this); + } +} + +export class TCPConfig extends BaseConfig { + static CLASSES = ["TCPClientImpl", "BinaryTCPClientImpl", "LengthPrefixedBinaryTCPClientImpl"] + + constructor(options = {}) { + super(); + this.classname = options.classname || TCPConfig.CLASSES[0]; + this.server = options.server; + this.port = options.port; + this.ctimeout = options.ctimeout; // Connect + this.timeout = options.timeout; // Response + + this.reUseConnection = options.reUseConnection === undefined ? true : options.reUseConnection; + this.nodelay = options.nodelay === undefined ? false : options.nodelay; + this.closeConnection = options.closeConnection === undefined ? false : options.closeConnection; + this.soLinger = options.soLinger; + this.eolByte = options.eolByte; + + this.username = options.username; + this.password = options.password; + } +} + +export class TCPRequest extends Request { + constructor(options = {}) { + super(RequestFactory.TYPES.TCP, options); + this.useEnvironment = options.useEnvironment; + this.debugReport = undefined; + + //设置TCPConfig的属性 + this.set(new TCPConfig(options)); + + this.request = options.request; + } + + isValid() { + return { + isValid: true + } + } + + showType() { + return "TCP"; + } + + showMethod() { + return "TCP"; + } + + clone() { + return new TCPRequest(this); + } +} + + +export class ConfigCenter extends BaseConfig { + static PROTOCOLS = ["zookeeper", "nacos", "apollo"]; + + constructor(options) { + super(); + this.protocol = undefined; + this.group = undefined; + this.namespace = undefined; + this.username = undefined; + this.address = undefined; + this.password = undefined; + this.timeout = undefined; + + this.set(options); + } + + isValid() { + return !!this.protocol || !!this.group || !!this.namespace || !!this.username || !!this.address || !!this.password || !!this.timeout; + } +} + +export class DatabaseConfig extends BaseConfig { + static DRIVER_CLASS = ["com.mysql.jdbc.Driver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "org.postgresql.Driver", "oracle.jdbc.OracleDriver"]; + + constructor(options) { + super(); + this.id = undefined; + this.name = undefined; + this.poolMax = undefined; + this.timeout = undefined; + this.driver = undefined; + this.dbUrl = undefined; + this.username = undefined; + this.password = undefined; + + this.set(options); + } + + initOptions(options = {}) { + // options.id = options.id || uuid(); + return options; + } + + isValid() { + return !!this.name || !!this.poolMax || !!this.timeout || !!this.driver || !!this.dbUrl || !!this.username || !!this.password; + } +} + +export class RegistryCenter extends BaseConfig { + static PROTOCOLS = ["none", "zookeeper", "nacos", "apollo", "multicast", "redis", "simple"]; + + constructor(options) { + super(); + this.protocol = undefined; + this.group = undefined; + this.username = undefined; + this.address = undefined; + this.password = undefined; + this.timeout = undefined; + + this.set(options); + } + + isValid() { + return !!this.protocol || !!this.group || !!this.username || !!this.address || !!this.password || !!this.timeout; + } +} + +export class ConsumerAndService extends BaseConfig { + static ASYNC_OPTIONS = ["sync", "async"]; + static LOAD_BALANCE_OPTIONS = ["random", "roundrobin", "leastactive", "consistenthash"]; + + constructor(options) { + super(); + this.timeout = "1000"; + this.version = "1.0"; + this.retries = "0"; + this.cluster = "failfast"; + this.group = undefined; + this.connections = "100"; + this.async = "sync"; + this.loadBalance = "random"; + + this.set(options); + } + + isValid() { + return !!this.timeout || !!this.version || !!this.retries || !!this.cluster || !!this.group || !!this.connections || !!this.async || !!this.loadBalance; + } +} + +export class Body extends BaseConfig { + constructor(options) { + super(); + this.type = undefined; + this.raw = undefined; + this.kvs = []; + + this.set(options); + this.sets({kvs: KeyValue}, options); + } + + isValid() { + if (this.isKV()) { + return this.kvs.some(kv => { + return kv.isValid(); + }) + } else { + return !!this.raw; + } + } + + isKV() { + return this.type === BODY_TYPE.KV; + } +} + +export class KeyValue extends BaseConfig { + constructor(options) { + options = options || {}; + options.enable = options.enable === undefined ? true : options.enable; + + super(); + this.name = undefined; + this.value = undefined; + this.type = undefined; + this.files = undefined; + this.enable = undefined; + this.uuid = undefined; + this.contentType = undefined; + this.set(options); + } + + isValid() { + return (!!this.name || !!this.value) && this.type !== 'file'; + } + + isFile() { + return (!!this.name || !!this.value) && this.type === 'file'; + } +} + +export class Assertions extends BaseConfig { + constructor(options) { + super(); + this.text = []; + this.regex = []; + this.jsonPath = []; + this.duration = undefined; + + this.set(options); + this.sets({text: Text, regex: Regex, jsonPath: JSONPath}, options); + } + + initOptions(options) { + options = options || {}; + options.duration = new Duration(options.duration); + return options; + } +} + +export class AssertionType extends BaseConfig { + constructor(type) { + super(); + this.type = type; + } +} + +export class BeanShellProcessor extends BaseConfig { + constructor(options) { + super(); + this.script = undefined; + this.set(options); + } +} + + +export class JSR223Processor extends BaseConfig { + constructor(options) { + super(); + this.script = undefined; + this.language = "beanshell"; + this.set(options); + } +} + +export class Text extends AssertionType { + constructor(options) { + super(ASSERTION_TYPE.TEXT); + this.subject = undefined; + this.condition = undefined; + this.value = undefined; + + this.set(options); + } +} + +export class Regex extends AssertionType { + constructor(options) { + super(ASSERTION_TYPE.REGEX); + this.subject = undefined; + this.expression = undefined; + this.description = undefined; + this.assumeSuccess = false; + + this.set(options); + } + + isValid() { + return !!this.subject && !!this.expression; + } +} + +export class JSONPath extends AssertionType { + constructor(options) { + super(ASSERTION_TYPE.JSON_PATH); + this.expression = undefined; + this.expect = undefined; + this.description = undefined; + + this.set(options); + } + + setJSONPathDescription() { + this.description = this.expression + " expect: " + (this.expect ? this.expect : ''); + } + + isValid() { + return !!this.expression; + } +} + +export class Duration extends AssertionType { + constructor(options) { + super(ASSERTION_TYPE.DURATION); + this.value = undefined; + + this.set(options); + } + + isValid() { + return !!this.value; + } +} + +export class Extract extends BaseConfig { + constructor(options) { + super(); + this.regex = []; + this.json = []; + this.xpath = []; + + this.set(options); + let types = { + json: ExtractJSONPath, + xpath: ExtractXPath, + regex: ExtractRegex + } + this.sets(types, options); + } +} + +export class ExtractType extends BaseConfig { + constructor(type) { + super(); + this.type = type; + } +} + +export class ExtractCommon extends ExtractType { + constructor(type, options) { + super(type); + this.variable = undefined; + this.useHeaders = undefined; + this.value = ""; // ${variable} + this.expression = undefined; + this.description = undefined; + this.multipleMatching = undefined; + + this.set(options); + } + + isValid() { + return !!this.variable && !!this.expression; + } +} + +export class ExtractRegex extends ExtractCommon { + constructor(options) { + super(EXTRACT_TYPE.REGEX, options); + } +} + +export class ExtractJSONPath extends ExtractCommon { + constructor(options) { + super(EXTRACT_TYPE.JSON_PATH, options); + } +} + +export class ExtractXPath extends ExtractCommon { + constructor(options) { + super(EXTRACT_TYPE.XPATH, options); + } +} + +export class Controller extends BaseConfig { + static TYPES = { + IF_CONTROLLER: "If Controller", + } + + constructor(type, options = {}) { + super(); + this.type = type + options.id = options.id || uuid(); + options.enable = options.enable === undefined ? true : options.enable; + } +} + +export class IfController extends Controller { + constructor(options = {}) { + super(Controller.TYPES.IF_CONTROLLER, options); + this.variable; + this.operator; + this.value; + + this.set(options); + } + + isValid() { + if (!!this.operator && this.operator.indexOf("empty") > 0) { + return !!this.variable && !!this.operator; + } + return !!this.variable && !!this.operator && !!this.value; + } + + label() { + if (this.isValid()) { + let label = this.variable; + if (this.operator) label += " " + this.operator; + if (this.value) label += " " + this.value; + return label; + } + return ""; + } +} + +export class Timer extends BaseConfig { + static TYPES = { + CONSTANT_TIMER: "Constant Timer", + } + + constructor(type, options = {}) { + super(); + this.type = type; + options.id = options.id || uuid(); + options.enable = options.enable === undefined ? true : options.enable; + } +} + +export class ConstantTimer extends Timer { + constructor(options = {}) { + super(Timer.TYPES.CONSTANT_TIMER, options); + this.delay; + + this.set(options); + } + + isValid() { + return this.delay > 0; + } + + label() { + if (this.isValid()) { + return this.delay + " ms"; + } + return ""; + } +} + +/** ------------------------------------------------------------------------ **/ +const JMX_ASSERTION_CONDITION = { + MATCH: 1, + CONTAINS: 1 << 1, + NOT: 1 << 2, + EQUALS: 1 << 3, + SUBSTRING: 1 << 4, + OR: 1 << 5 +} + +class JMXHttpRequest { + constructor(request, environment) { + if (request && request instanceof HttpRequest) { + this.useEnvironment = request.useEnvironment; + this.method = request.method; + if (!request.useEnvironment) { + if (!request.url.startsWith("http://") && !request.url.startsWith("https://")) { + request.url = 'http://' + request.url; + } + let url = new URL(request.url); + this.domain = decodeURIComponent(url.hostname); + this.port = url.port; + this.protocol = url.protocol.split(":")[0]; + this.path = this.getPostQueryParameters(request, decodeURIComponent(url.pathname)); + } else { + this.domain = environment.config.httpConfig.domain; + this.port = environment.config.httpConfig.port; + this.protocol = environment.config.httpConfig.protocol; + let url = new URL(environment.config.httpConfig.protocol + "://" + environment.config.httpConfig.socket); + this.path = this.getPostQueryParameters(request, decodeURIComponent(url.pathname + (request.path ? request.path : ''))); + } + this.connectTimeout = request.connectTimeout; + this.responseTimeout = request.responseTimeout; + this.followRedirects = request.followRedirects; + this.doMultipartPost = request.doMultipartPost; + } + } + + getPostQueryParameters(request, path) { + if (this.method.toUpperCase() !== "GET") { + let parameters = []; + request.parameters.forEach(parameter => { + if (parameter.name && parameter.value && parameter.enable === true) { + parameters.push(parameter); + } + }); + if (parameters.length > 0) { + path += '?'; + } + for (let i = 0; i < parameters.length; i++) { + let parameter = parameters[i]; + path += (parameter.name + '=' + parameter.value); + if (i !== parameters.length - 1) { + path += '&'; + } + } + } + return path; + } +} + +class JMXDubboRequest { + constructor(request, dubboConfig) { + // Request 复制 + let obj = request.clone(); + // 去掉无效的kv + obj.args = obj.args.filter(arg => { + return arg.isValid(); + }); + obj.attachmentArgs = obj.attachmentArgs.filter(arg => { + return arg.isValid(); + }); + + // Scenario DubboConfig复制 + this.copy(obj.configCenter, dubboConfig.configCenter); + this.copy(obj.registryCenter, dubboConfig.registryCenter); + this.copy(obj.consumerAndService, dubboConfig.consumerAndService); + + return obj; + } + + copy(target, source) { + for (let key in source) { + if (source.hasOwnProperty(key)) { + if (source[key] !== undefined && !target[key]) { + target[key] = source[key]; + } + } + } + } +} + +class JMXTCPRequest { + constructor(request, scenario) { + let obj = request.clone(); + if (request.useEnvironment) { + obj.set(scenario.environment.config.tcpConfig, true); + return obj; + } + + this.copy(this, scenario.tcpConfig); + + return obj; + } + + copy(target, source) { + for (let key in source) { + if (source.hasOwnProperty(key)) { + if (source[key] !== undefined && !target[key]) { + target[key] = source[key]; + } + } + } + } +} + +class JMeterTestPlan extends Element { + constructor() { + super('jmeterTestPlan', { + version: "1.2", properties: "5.0", jmeter: "5.2.1" + }); + + this.add(new HashTree()); + } + + put(te) { + if (te instanceof TestElement) { + this.elements[0].add(te); + } + } +} + +class JMXGenerator { + constructor(test) { + if (!test || !test.id || !(test instanceof Test)) return undefined; + + let testPlan = new TestPlan(test.name); + this.addScenarios(testPlan, test.id, test.scenarioDefinition); + + this.jmeterTestPlan = new JMeterTestPlan(); + this.jmeterTestPlan.put(testPlan); + } + + addScenarios(testPlan, testId, scenarios) { + scenarios.forEach(s => { + + if (s.enable) { + let scenario = s.clone(); + + let threadGroup = new ThreadGroup(scenario.name || ""); + + this.addScenarioVariables(threadGroup, scenario); + + this.addScenarioHeaders(threadGroup, scenario); + + this.addScenarioCookieManager(threadGroup, scenario); + + this.addJDBCDataSources(threadGroup, scenario); + scenario.requests.forEach(request => { + if (request.enable) { + if (!request.isValid()) return; + let sampler; + if (request instanceof DubboRequest) { + sampler = new DubboSample(request.name || "", new JMXDubboRequest(request, scenario.dubboConfig)); + } else if (request instanceof HttpRequest) { + sampler = new HTTPSamplerProxy(request.name || "", new JMXHttpRequest(request, scenario.environment)); + this.addRequestHeader(sampler, request); + this.addRequestArguments(sampler, request); + this.addRequestBody(sampler, request, testId); + } else if (request instanceof SqlRequest) { + request.dataSource = scenario.databaseConfigMap.get(request.dataSource); + sampler = new JDBCSampler(request.name || "", request); + this.addRequestVariables(sampler, request); + } else if (request instanceof TCPRequest) { + sampler = new TCPSampler(request.name || "", new JMXTCPRequest(request, scenario)); + } + + this.addDNSCacheManager(sampler, scenario.environment, request.useEnvironment); + + this.addRequestExtractor(sampler, request); + + this.addRequestAssertion(sampler, request); + + this.addJSR223PreProcessor(sampler, request); + + this.addConstantsTimer(sampler, request); + + if (request.controller && request.controller.isValid() && request.controller.enable) { + if (request.controller instanceof IfController) { + let controller = this.getController(sampler, request); + threadGroup.put(controller); + } + } else { + threadGroup.put(sampler); + } + } + }) + testPlan.put(threadGroup); + } + + }) + } + + addEnvironments(environments, target) { + let keys = new Set(); + target.forEach(item => { + keys.add(item.name); + }); + let envArray = environments; + if (!(envArray instanceof Array)) { + envArray = JSON.parse(environments); + } + envArray.forEach(item => { + if (item.name && !keys.has(item.name)) { + target.push(new KeyValue({name: item.name, value: item.value})); + } + }) + } + + addScenarioVariables(threadGroup, scenario) { + if (scenario.environment) { + let config = scenario.environment.config; + if (!(scenario.environment.config instanceof Object)) { + config = JSON.parse(scenario.environment.config); + } + this.addEnvironments(config.commonConfig.variables, scenario.variables) + } + let args = this.filterKV(scenario.variables); + if (args.length > 0) { + let name = scenario.name + " Variables"; + threadGroup.put(new Arguments(name, args)); + } + } + + addRequestVariables(httpSamplerProxy, request) { + let name = request.name + " Variables"; + let variables = this.filterKV(request.variables); + if (variables && variables.length > 0) { + httpSamplerProxy.put(new Arguments(name, variables)); + } + } + + addScenarioCookieManager(threadGroup, scenario) { + if (scenario.enableCookieShare) { + threadGroup.put(new CookieManager(scenario.name)); + } + } + + addDNSCacheManager(httpSamplerProxy, environment, useEnv) { + if (environment && useEnv === true) { + let commonConfig = environment.config.commonConfig; + let hosts = commonConfig.hosts; + if (commonConfig.enableHost && hosts.length > 0) { + let name = " DNSCacheManager"; + // 强化判断,如果未匹配到合适的host则不开启DNSCache + let domain = environment.config.httpConfig.domain; + let validHosts = []; + hosts.forEach(item => { + if (item.domain !== undefined && domain !== undefined) { + let d = item.domain.trim().replace("http://", "").replace("https://", ""); + if (d === domain.trim()) { + item.domain = d; // 域名去掉协议 + validHosts.push(item); + } + } + }); + if (validHosts.length > 0) { + httpSamplerProxy.put(new DNSCacheManager(name, validHosts)); + } + } + } + } + + addJDBCDataSources(threadGroup, scenario) { + let names = new Set(); + let databaseConfigMap = new Map(); + scenario.databaseConfigs.forEach(config => { + let name = config.name + "JDBCDataSource"; + threadGroup.put(new JDBCDataSource(name, config)); + names.add(name); + databaseConfigMap.set(config.id, config.name); + }); + if (scenario.environment) { + let config = scenario.environment.config; + if (!(scenario.environment.config instanceof Object)) { + config = JSON.parse(scenario.environment.config); + } + config.databaseConfigs.forEach(config => { + if (!names.has(config.name)) { + let name = config.name + "JDBCDataSource"; + threadGroup.put(new JDBCDataSource(name, config)); + databaseConfigMap.set(config.id, config.name); + } + }); + } + scenario.databaseConfigMap = databaseConfigMap; + } + + addScenarioHeaders(threadGroup, scenario) { + if (scenario.environment) { + let config = scenario.environment.config; + if (!(scenario.environment.config instanceof Object)) { + config = JSON.parse(scenario.environment.config); + } + this.addEnvironments(config.httpConfig.headers, scenario.headers) + } + let headers = this.filterKV(scenario.headers); + if (headers.length > 0) { + let name = scenario.name + " Headers"; + threadGroup.put(new HeaderManager(name, headers)); + } + } + + addRequestHeader(httpSamplerProxy, request) { + let name = request.name + " Headers"; + this.addBodyFormat(request); + let headers = this.filterKV(request.headers); + if (headers.length > 0) { + httpSamplerProxy.put(new HeaderManager(name, headers)); + } + } + + addJSR223PreProcessor(sampler, request) { + let name = request.name; + if (request.jsr223PreProcessor && request.jsr223PreProcessor.script) { + sampler.put(new JSR223PreProcessor(name, request.jsr223PreProcessor)); + } + if (request.jsr223PostProcessor && request.jsr223PostProcessor.script) { + sampler.put(new JSR223PostProcessor(name, request.jsr223PostProcessor)); + } + } + + addConstantsTimer(sampler, request) { + if (request.timer && request.timer.isValid() && request.timer.enable) { + sampler.put(new JMXConstantTimer(request.timer.label(), request.timer)); + } + } + + getController(sampler, request) { + if (request.controller.isValid() && request.controller.enable) { + if (request.controller instanceof IfController) { + let name = request.controller.label(); + let variable = "\"" + request.controller.variable + "\""; + let operator = request.controller.operator; + let value = "\"" + request.controller.value + "\""; + + if (operator === "=~" || operator === "!~") { + value = "\".*" + request.controller.value + ".*\""; + } + + if (operator === "is empty") { + variable = "empty(" + variable + ")"; + operator = ""; + value = ""; + } + + if (operator === "is not empty") { + variable = "!empty(" + variable + ")"; + operator = ""; + value = ""; + } + + let condition = "${__jexl3(" + variable + operator + value + ")}"; + let controller = new JMXIfController(name, {condition: condition}); + controller.put(sampler); + return controller; + } + } + } + + addBodyFormat(request) { + let bodyFormat = request.body.format; + if (!request.body.isKV() && bodyFormat) { + switch (bodyFormat) { + case BODY_FORMAT.JSON: + this.addContentType(request, 'application/json'); + break; + case BODY_FORMAT.HTML: + this.addContentType(request, 'text/html'); + break; + case BODY_FORMAT.XML: + this.addContentType(request, 'text/xml'); + break; + default: + break; + } + } + } + + addContentType(request, type) { + for (let index in request.headers) { + if (request.headers.hasOwnProperty(index)) { + if (request.headers[index].name === 'Content-Type') { + request.headers.splice(index, 1); + break; + } + } + } + request.headers.push(new KeyValue({name: 'Content-Type', value: type})); + } + + addRequestArguments(httpSamplerProxy, request) { + let args = this.filterKV(request.parameters); + if (args.length > 0) { + httpSamplerProxy.add(new HTTPSamplerArguments(args)); + } + } + + addRequestBody(httpSamplerProxy, request, testId) { + let body = []; + if (request.body.isKV()) { + body = this.filterKV(request.body.kvs); + this.addRequestBodyFile(httpSamplerProxy, request, testId); + } else { + httpSamplerProxy.boolProp('HTTPSampler.postBodyRaw', true); + body.push({name: '', value: request.body.raw, encode: false, enable: true}); + } + + if (request.method !== 'GET') { + httpSamplerProxy.add(new HTTPSamplerArguments(body)); + } + } + + addRequestBodyFile(httpSamplerProxy, request, testId) { + let files = []; + let kvs = this.filterKVFile(request.body.kvs); + kvs.forEach(kv => { + if ((kv.enable !== false) && kv.files) { + kv.files.forEach(file => { + let arg = {}; + arg.name = kv.name; + arg.value = BODY_FILE_DIR + '/' + testId + '/' + file.id + '_' + file.name; + files.push(arg); + }); + } + }); + httpSamplerProxy.add(new HTTPsamplerFiles(files)); + } + + addRequestAssertion(httpSamplerProxy, request) { + let assertions = request.assertions; + if (assertions.regex.length > 0) { + assertions.regex.filter(this.filter).forEach(regex => { + httpSamplerProxy.put(this.getResponseAssertion(regex)); + }) + } + + if (assertions.jsonPath.length > 0) { + assertions.jsonPath.filter(this.filter).forEach(item => { + httpSamplerProxy.put(this.getJSONPathAssertion(item)); + }) + } + + if (assertions.duration.isValid()) { + let name = "Response In Time: " + assertions.duration.value + httpSamplerProxy.put(new DurationAssertion(name, assertions.duration.value)); + } + } + + getJSONPathAssertion(jsonPath) { + let name = jsonPath.description; + return new JSONPathAssertion(name, jsonPath); + } + + getResponseAssertion(regex) { + let name = regex.description; + let type = JMX_ASSERTION_CONDITION.CONTAINS; // 固定用Match,自己写正则 + let value = regex.expression; + let assumeSuccess = regex.assumeSuccess; + switch (regex.subject) { + case ASSERTION_REGEX_SUBJECT.RESPONSE_CODE: + return new ResponseCodeAssertion(name, type, value, assumeSuccess); + case ASSERTION_REGEX_SUBJECT.RESPONSE_DATA: + return new ResponseDataAssertion(name, type, value, assumeSuccess); + case ASSERTION_REGEX_SUBJECT.RESPONSE_HEADERS: + return new ResponseHeadersAssertion(name, type, value, assumeSuccess); + } + } + + addRequestExtractor(httpSamplerProxy, request) { + let extract = request.extract; + if (extract.regex.length > 0) { + extract.regex.filter(this.filter).forEach(regex => { + httpSamplerProxy.put(this.getExtractor(regex)); + }) + } + + if (extract.json.length > 0) { + extract.json.filter(this.filter).forEach(json => { + httpSamplerProxy.put(this.getExtractor(json)); + }) + } + + if (extract.xpath.length > 0) { + extract.xpath.filter(this.filter).forEach(xpath => { + httpSamplerProxy.put(this.getExtractor(xpath)); + }) + } + } + + getExtractor(extractCommon) { + let props = { + name: extractCommon.variable, + expression: extractCommon.expression, + match: extractCommon.multipleMatching ? -1 : undefined + } + let testName = props.name + switch (extractCommon.type) { + case EXTRACT_TYPE.REGEX: + testName += " RegexExtractor"; + props.headers = extractCommon.useHeaders; // 对应jMeter body + props.template = "$1$"; + return new RegexExtractor(testName, props); + case EXTRACT_TYPE.JSON_PATH: + testName += " JSONExtractor"; + return new JSONPostProcessor(testName, props); + case EXTRACT_TYPE.XPATH: + testName += " XPath2Evaluator"; + return new XPath2Extractor(testName, props); + } + } + + filter(config) { + return config.isValid(); + } + + filterKV(kvs) { + return kvs.filter(this.filter); + } + + filterKVFile(kvs) { + return kvs.filter(kv => { + return kv.isFile(); + }); + } + + toXML() { + let xml = '\n'; + xml += this.jmeterTestPlan.toXML(); + return xml; + } +} + +