This commit is contained in:
chenjianxing 2020-10-26 11:34:46 +08:00
commit 42ef1bcc89
8 changed files with 2941 additions and 2657 deletions

View File

@ -363,6 +363,13 @@
<version>4.5.6</version> <version>4.5.6</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.3</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -14,6 +14,7 @@ import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.QueryScheduleRequest; import io.metersphere.controller.request.QueryScheduleRequest;
import io.metersphere.dto.ScheduleDao; import io.metersphere.dto.ScheduleDao;
import io.metersphere.service.CheckOwnerService; import io.metersphere.service.CheckOwnerService;
import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -21,8 +22,12 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List; import java.util.List;
import static io.metersphere.commons.utils.JsonPathUtils.getListJson;
@RestController @RestController
@RequestMapping(value = "/api") @RequestMapping(value = "/api")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR) @RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR)
@ -79,7 +84,6 @@ public class APITestController {
public void mergeCreate(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "selectIds") List<String> selectIds) { public void mergeCreate(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "selectIds") List<String> selectIds) {
apiTestService.mergeCreate(request, file, selectIds); apiTestService.mergeCreate(request, file, selectIds);
} }
@PostMapping(value = "/update", consumes = {"multipart/form-data"}) @PostMapping(value = "/update", consumes = {"multipart/form-data"})
public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) { public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
checkownerService.checkApiTestOwner(request.getId()); checkownerService.checkApiTestOwner(request.getId());
@ -141,4 +145,9 @@ public class APITestController {
public List<ScheduleDao> listSchedule(@RequestBody QueryScheduleRequest request) { public List<ScheduleDao> listSchedule(@RequestBody QueryScheduleRequest request) {
return apiTestService.listSchedule(request); return apiTestService.listSchedule(request);
} }
@PostMapping("/getJsonPaths")
public List<HashMap> getJsonPaths(@RequestBody QueryJsonPathRequest request) {
return getListJson(request.getJsonPath());
}
} }

View File

@ -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;
}

View File

@ -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<HashMap> getListJson(String jsonString) {
JSONObject jsonObject =JSONObject.parseObject(jsonString);
List<HashMap> allJsons =new ArrayList<>();
// 获取到所有jsonpath后获取所有的key
List<String> jsonPaths = JSONPath.paths(jsonObject).keySet()
.stream()
.collect(Collectors.toList());
//去掉根节点key
List<String> 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<String> jsonPathList = new ArrayList<>();
Iterator<String> jsonPath = jsonPaths.iterator();
///替换为点.
while (jsonPath.hasNext()) {
Map<String,String> 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;
}
}

View File

@ -22,6 +22,30 @@
</el-row> </el-row>
</div> </div>
<div >
<el-row :gutter="10" style="text-align: right;">
<el-button
size="small"
type="primary"
@click="suggestJson"
>推荐JSONPath断言</el-button>
<el-button
size="small"
type="danger"
@click="clearJson"
>清空JSONPath断言</el-button>
</el-row>
</div>
<ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions"/> <ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions"/>
</div> </div>
</template> </template>
@ -30,7 +54,7 @@
import MsApiAssertionText from "./ApiAssertionText"; import MsApiAssertionText from "./ApiAssertionText";
import MsApiAssertionRegex from "./ApiAssertionRegex"; import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionDuration from "./ApiAssertionDuration"; import MsApiAssertionDuration from "./ApiAssertionDuration";
import {ASSERTION_TYPE, Assertions} from "../../model/ScenarioModel"; import {ASSERTION_TYPE, Assertions, JSONPath} from "../../model/ScenarioModel";
import MsApiAssertionsEdit from "./ApiAssertionsEdit"; import MsApiAssertionsEdit from "./ApiAssertionsEdit";
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath"; import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
@ -43,6 +67,7 @@
props: { props: {
assertions: Assertions, assertions: Assertions,
jsonPathList: Array,
isReadOnly: { isReadOnly: {
type: Boolean, type: Boolean,
default: false default: false
@ -60,6 +85,24 @@
methods: { methods: {
after() { after() {
this.type = ""; this.type = "";
},
suggestJson() {
console.log("This is suggestJson")
// console.log(this.jsonPathList);
this.jsonPathList.forEach((item) => {
let jsonItem = new JSONPath();
jsonItem.expression=item.json_path;
jsonItem.expect=item.json_value;
jsonItem.setJSONPathDescription();
this.assertions.jsonPath.push(jsonItem);
});
},
clearJson() {
console.log("This is suggestJson")
// console.log(this.jsonPathList);
this.assertions.jsonPath = [];
} }
} }
@ -77,4 +120,22 @@
margin: 5px 0; margin: 5px 0;
border-radius: 5px; border-radius: 5px;
} }
.bg-purple-dark {
background: #99a9bf;
}
.bg-purple {
background: #d3dce6;
}
.bg-purple-light {
background: #e5e9f2;
}
.grid-content {
border-radius: 4px;
min-height: 36px;
}
.row-bg {
padding: 10px 0;
background-color: #f9fafc;
}
</style> </style>

View File

@ -1,4 +1,4 @@
<template> <template xmlns:v-slot="http://www.w3.org/1999/XSL/Transform">
<el-form :model="request" :rules="rules" ref="request" label-width="100px" :disabled="isReadOnly"> <el-form :model="request" :rules="rules" ref="request" label-width="100px" :disabled="isReadOnly">
<el-form-item :label="$t('api_test.request.name')" prop="name"> <el-form-item :label="$t('api_test.request.name')" prop="name">
@ -66,7 +66,7 @@
:environment="scenario.environment"/> :environment="scenario.environment"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.request.assertions.label')" name="assertions"> <el-tab-pane :label="$t('api_test.request.assertions.label')" name="assertions">
<ms-api-assertions :is-read-only="isReadOnly" :assertions="request.assertions"/> <ms-api-assertions :jsonPathList="jsonPathList" :is-read-only="isReadOnly" :assertions="request.assertions"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.request.extract.label')" name="extract"> <el-tab-pane :label="$t('api_test.request.extract.label')" name="extract">
<ms-api-extract :is-read-only="isReadOnly" :extract="request.extract"/> <ms-api-extract :is-read-only="isReadOnly" :extract="request.extract"/>
@ -104,6 +104,7 @@ export default {
MsApiVariable, ApiRequestMethodSelect, MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue}, MsApiVariable, ApiRequestMethodSelect, MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue},
props: { props: {
request: HttpRequest, request: HttpRequest,
jsonPathList: Array,
scenario: Scenario, scenario: Scenario,
isReadOnly: { isReadOnly: {
type: Boolean, type: Boolean,

View File

@ -1,11 +1,9 @@
<template> <template>
<div class="request-form"> <div class="request-form">
<component @runDebug="runDebug" :is="component" :is-read-only="isReadOnly" :request="request" :scenario="scenario"/> <component @runDebug="runDebug" :is="component" :jsonPathList="jsonPathList" :is-read-only="isReadOnly" :request="request" :scenario="scenario"/>
<el-divider v-if="isCompleted"></el-divider> <el-divider v-if="isCompleted"></el-divider>
<ms-request-result-tail v-loading="debugReportLoading" v-if="isCompleted" <ms-request-result-tail v-loading="debugReportLoading" v-if="isCompleted" :request="request.debugRequestResult ? request.debugRequestResult : {responseResult: {}, subRequestResults: []}"
:request="request.debugRequestResult ? request.debugRequestResult : {responseResult: {}, subRequestResults: []}" :scenario-name="request.debugScenario ? request.debugScenario.name : ''" ref="msDebugResult"/>
:scenario-name="request.debugScenario ? request.debugScenario.name : ''"
ref="msDebugResult"/>
</div> </div>
</template> </template>
@ -20,14 +18,7 @@ import MsApiSqlRequestForm from "./ApiSqlRequestForm";
export default { export default {
name: "MsApiRequestForm", name: "MsApiRequestForm",
components: { components: {MsApiSqlRequestForm, MsRequestResultTail, MsScenarioResults, MsApiDubboRequestForm, MsApiHttpRequestForm},
MsApiSqlRequestForm,
MsRequestResultTail,
MsScenarioResults,
MsApiDubboRequestForm,
MsApiHttpRequestForm,
MsApiTcpRequestForm
},
props: { props: {
scenario: Scenario, scenario: Scenario,
request: Request, request: Request,
@ -42,7 +33,8 @@ export default {
reportId: "", reportId: "",
content: {scenarios:[]}, content: {scenarios:[]},
debugReportLoading: false, debugReportLoading: false,
showDebugReport: false showDebugReport: false,
jsonPathList:[]
} }
}, },
computed: { computed: {
@ -91,6 +83,7 @@ export default {
this.$get(url, response => { this.$get(url, response => {
let report = response.data || {}; let report = response.data || {};
let res = {}; let res = {};
if (response.data) { if (response.data) {
try { try {
res = JSON.parse(report.content); res = JSON.parse(report.content);
@ -103,6 +96,37 @@ export default {
if (res.scenarios && res.scenarios.length > 0) { if (res.scenarios && res.scenarios.length > 0) {
this.request.debugScenario = res.scenarios[0]; this.request.debugScenario = res.scenarios[0];
this.request.debugRequestResult = this.request.debugScenario.requestResults[0]; this.request.debugRequestResult = this.request.debugScenario.requestResults[0];
//add by Cuipeng
this.debugResultDetails=this.request.debugRequestResult.responseResult.body;
// console.log(this.debugResultDetails);
try {
let param = {
jsonPath: this.debugResultDetails
};
this.$post("/api/getJsonPaths", param).then(response1 => {
this.jsonPathList = response1.data.data;
// console.log(this.jsonPathList);
}).catch(() => {
this.$warning("获取推荐jsonpath列表失败");
});
} catch (e) {
alert("调试结果的返回不是一个json");
throw e;
}
//add by Cuipeng
this.deleteReport(this.debugReportId); this.deleteReport(this.debugReportId);
} else { } else {
this.request.debugScenario = new Scenario(); this.request.debugScenario = new Scenario();

View File

@ -797,6 +797,10 @@ export class JSONPath extends AssertionType {
this.set(options); this.set(options);
} }
setJSONPathDescription() {
this.description = this.expression + " expect: " + (this.expect ? this.expect : '');
}
isValid() { isValid() {
return !!this.expression; return !!this.expression;
} }