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

File diff suppressed because it is too large Load Diff

View File

@ -1,144 +1,153 @@
package io.metersphere.api.controller; package io.metersphere.api.controller;
import com.github.pagehelper.Page; import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import io.metersphere.api.dto.*; import io.metersphere.api.dto.*;
import io.metersphere.api.dto.scenario.request.dubbo.RegistryCenter; import io.metersphere.api.dto.scenario.request.dubbo.RegistryCenter;
import io.metersphere.api.service.APITestService; import io.metersphere.api.service.APITestService;
import io.metersphere.base.domain.ApiTest; import io.metersphere.base.domain.ApiTest;
import io.metersphere.base.domain.Schedule; import io.metersphere.base.domain.Schedule;
import io.metersphere.commons.constants.RoleConstants; import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils; import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager; import io.metersphere.commons.utils.Pager;
import io.metersphere.commons.utils.SessionUtils; 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.RequiresRoles; import org.apache.shiro.authz.annotation.Logical;
import org.springframework.web.bind.annotation.*; import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.annotation.Resource;
import java.util.List;
import java.util.HashMap;
@RestController import java.util.List;
@RequestMapping(value = "/api")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR) import static io.metersphere.commons.utils.JsonPathUtils.getListJson;
public class APITestController {
@Resource
private APITestService apiTestService; @RestController
@Resource @RequestMapping(value = "/api")
private CheckOwnerService checkownerService; @RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR)
public class APITestController {
@GetMapping("recent/{count}") @Resource
public List<APITestResult> recentTest(@PathVariable int count) { private APITestService apiTestService;
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId(); @Resource
QueryAPITestRequest request = new QueryAPITestRequest(); private CheckOwnerService checkownerService;
request.setWorkspaceId(currentWorkspaceId);
request.setUserId(SessionUtils.getUserId()); @GetMapping("recent/{count}")
PageHelper.startPage(1, count, true); public List<APITestResult> recentTest(@PathVariable int count) {
return apiTestService.recentTest(request); String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
} QueryAPITestRequest request = new QueryAPITestRequest();
request.setWorkspaceId(currentWorkspaceId);
@PostMapping("/list/{goPage}/{pageSize}") request.setUserId(SessionUtils.getUserId());
public Pager<List<APITestResult>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryAPITestRequest request) { PageHelper.startPage(1, count, true);
Page<Object> page = PageHelper.startPage(goPage, pageSize, true); return apiTestService.recentTest(request);
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId()); }
return PageUtils.setPageInfo(page, apiTestService.list(request));
} @PostMapping("/list/{goPage}/{pageSize}")
public Pager<List<APITestResult>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryAPITestRequest request) {
@PostMapping("/list/ids") Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
public List<ApiTest> listByIds(@RequestBody QueryAPITestRequest request) { request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
return apiTestService.listByIds(request); return PageUtils.setPageInfo(page, apiTestService.list(request));
} }
@GetMapping("/list/{projectId}") @PostMapping("/list/ids")
public List<ApiTest> list(@PathVariable String projectId) { public List<ApiTest> listByIds(@RequestBody QueryAPITestRequest request) {
checkownerService.checkProjectOwner(projectId); return apiTestService.listByIds(request);
return apiTestService.getApiTestByProjectId(projectId); }
}
@GetMapping("/list/{projectId}")
@PostMapping(value = "/schedule/update") public List<ApiTest> list(@PathVariable String projectId) {
public void updateSchedule(@RequestBody Schedule request) { checkownerService.checkProjectOwner(projectId);
apiTestService.updateSchedule(request); return apiTestService.getApiTestByProjectId(projectId);
} }
@PostMapping(value = "/schedule/create") @PostMapping(value = "/schedule/update")
public void createSchedule(@RequestBody Schedule request) { public void updateSchedule(@RequestBody Schedule request) {
apiTestService.createSchedule(request); apiTestService.updateSchedule(request);
} }
@PostMapping(value = "/create", consumes = {"multipart/form-data"}) @PostMapping(value = "/schedule/create")
public void create(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) { public void createSchedule(@RequestBody Schedule request) {
apiTestService.create(request, file, bodyFiles); apiTestService.createSchedule(request);
} }
@PostMapping(value = "/create/merge", consumes = {"multipart/form-data"}) @PostMapping(value = "/create", consumes = {"multipart/form-data"})
public void mergeCreate(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "selectIds") List<String> selectIds) { public void create(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
apiTestService.mergeCreate(request, file, selectIds); apiTestService.create(request, file, bodyFiles);
} }
@PostMapping(value = "/update", consumes = {"multipart/form-data"}) @PostMapping(value = "/create/merge", consumes = {"multipart/form-data"})
public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) { public void mergeCreate(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "selectIds") List<String> selectIds) {
checkownerService.checkApiTestOwner(request.getId()); apiTestService.mergeCreate(request, file, selectIds);
apiTestService.update(request, file, bodyFiles); }
} @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) {
@PostMapping(value = "/copy") checkownerService.checkApiTestOwner(request.getId());
public void copy(@RequestBody SaveAPITestRequest request) { apiTestService.update(request, file, bodyFiles);
apiTestService.copy(request); }
}
@PostMapping(value = "/copy")
@GetMapping("/get/{testId}") public void copy(@RequestBody SaveAPITestRequest request) {
public APITestResult get(@PathVariable String testId) { apiTestService.copy(request);
checkownerService.checkApiTestOwner(testId); }
return apiTestService.get(testId);
} @GetMapping("/get/{testId}")
public APITestResult get(@PathVariable String testId) {
checkownerService.checkApiTestOwner(testId);
@PostMapping("/delete") return apiTestService.get(testId);
public void delete(@RequestBody DeleteAPITestRequest request) { }
String testId = request.getId();
checkownerService.checkApiTestOwner(testId);
apiTestService.delete(testId); @PostMapping("/delete")
} public void delete(@RequestBody DeleteAPITestRequest request) {
String testId = request.getId();
@PostMapping(value = "/run") checkownerService.checkApiTestOwner(testId);
public String run(@RequestBody SaveAPITestRequest request) { apiTestService.delete(testId);
return apiTestService.run(request); }
}
@PostMapping(value = "/run")
@PostMapping(value = "/run/debug", consumes = {"multipart/form-data"}) public String run(@RequestBody SaveAPITestRequest request) {
public String runDebug(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) { return apiTestService.run(request);
return apiTestService.runDebug(request, file, bodyFiles); }
}
@PostMapping(value = "/run/debug", consumes = {"multipart/form-data"})
@PostMapping(value = "/checkName") public String runDebug(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
public void checkName(@RequestBody SaveAPITestRequest request) { return apiTestService.runDebug(request, file, bodyFiles);
apiTestService.checkName(request); }
}
@PostMapping(value = "/checkName")
@PostMapping(value = "/import", consumes = {"multipart/form-data"}) public void checkName(@RequestBody SaveAPITestRequest request) {
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) apiTestService.checkName(request);
public ApiTest testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) { }
return apiTestService.apiTestImport(file, request);
} @PostMapping(value = "/import", consumes = {"multipart/form-data"})
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
@PostMapping("/dubbo/providers") public ApiTest testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) {
public List<DubboProvider> getProviders(@RequestBody RegistryCenter registry) { return apiTestService.apiTestImport(file, request);
return apiTestService.getProviders(registry); }
}
@PostMapping("/dubbo/providers")
@PostMapping("/list/schedule/{goPage}/{pageSize}") public List<DubboProvider> getProviders(@RequestBody RegistryCenter registry) {
public List<ScheduleDao> listSchedule(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryScheduleRequest request) { return apiTestService.getProviders(registry);
Page<Object> page = PageHelper.startPage(goPage, pageSize, true); }
return apiTestService.listSchedule(request);
} @PostMapping("/list/schedule/{goPage}/{pageSize}")
public List<ScheduleDao> listSchedule(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryScheduleRequest request) {
@PostMapping("/list/schedule") Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
public List<ScheduleDao> listSchedule(@RequestBody QueryScheduleRequest request) { return apiTestService.listSchedule(request);
return apiTestService.listSchedule(request); }
}
} @PostMapping("/list/schedule")
public List<ScheduleDao> listSchedule(@RequestBody QueryScheduleRequest 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

@ -1,80 +1,141 @@
<template> <template>
<div> <div>
<div class="assertion-add"> <div class="assertion-add">
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="4"> <el-col :span="4">
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type" :placeholder="$t('api_test.request.assertions.select_type')" <el-select :disabled="isReadOnly" class="assertion-item" v-model="type" :placeholder="$t('api_test.request.assertions.select_type')"
size="small"> size="small">
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/> <el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/> <el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
<el-option :label="'JSONPath'" :value="options.JSON_PATH"/> <el-option :label="'JSONPath'" :value="options.JSON_PATH"/>
<el-option :label="$t('api_test.request.assertions.response_time')" :value="options.DURATION"/> <el-option :label="$t('api_test.request.assertions.response_time')" :value="options.DURATION"/>
</el-select> </el-select>
</el-col> </el-col>
<el-col :span="20"> <el-col :span="20">
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT" :callback="after"/> <ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT" :callback="after"/>
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX" :callback="after"/> <ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX" :callback="after"/>
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath" v-if="type === options.JSON_PATH" :callback="after"/> <ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath" v-if="type === options.JSON_PATH" :callback="after"/>
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration" <ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration"
v-if="type === options.DURATION" :callback="after"/> v-if="type === options.DURATION" :callback="after"/>
<el-button v-if="!type" :disabled="true" type="primary" size="small">Add</el-button> <el-button v-if="!type" :disabled="true" type="primary" size="small">Add</el-button>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions"/> <div >
</div>
</template> <el-row :gutter="10" style="text-align: right;">
<script> <el-button
import MsApiAssertionText from "./ApiAssertionText";
import MsApiAssertionRegex from "./ApiAssertionRegex"; size="small"
import MsApiAssertionDuration from "./ApiAssertionDuration"; type="primary"
import {ASSERTION_TYPE, Assertions} from "../../model/ScenarioModel"; @click="suggestJson"
import MsApiAssertionsEdit from "./ApiAssertionsEdit"; >推荐JSONPath断言</el-button>
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath"; <el-button
size="small"
export default { type="danger"
name: "MsApiAssertions", @click="clearJson"
>清空JSONPath断言</el-button>
components: {
MsApiAssertionJsonPath, </el-row>
MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText},
</div>
props: {
assertions: Assertions,
isReadOnly: {
type: Boolean,
default: false
} <ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions"/>
}, </div>
</template>
data() {
return { <script>
options: ASSERTION_TYPE, import MsApiAssertionText from "./ApiAssertionText";
time: "", import MsApiAssertionRegex from "./ApiAssertionRegex";
type: "", import MsApiAssertionDuration from "./ApiAssertionDuration";
} import {ASSERTION_TYPE, Assertions, JSONPath} from "../../model/ScenarioModel";
}, import MsApiAssertionsEdit from "./ApiAssertionsEdit";
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
methods: {
after() { export default {
this.type = ""; name: "MsApiAssertions",
}
} components: {
MsApiAssertionJsonPath,
} MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText},
</script>
props: {
<style scoped> assertions: Assertions,
.assertion-item { jsonPathList: Array,
width: 100%; isReadOnly: {
} type: Boolean,
default: false
.assertion-add { }
padding: 10px; },
border: #DCDFE6 solid 1px;
margin: 5px 0; data() {
border-radius: 5px; return {
} options: ASSERTION_TYPE,
</style> time: "",
type: "",
}
},
methods: {
after() {
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 = [];
}
}
}
</script>
<style scoped>
.assertion-item {
width: 100%;
}
.assertion-add {
padding: 10px;
border: #DCDFE6 solid 1px;
margin: 5px 0;
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>

View File

@ -1,236 +1,237 @@
<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">
<el-input :disabled="isReadOnly" v-model="request.name" maxlength="300" show-word-limit/> <el-input :disabled="isReadOnly" v-model="request.name" maxlength="300" show-word-limit/>
</el-form-item> </el-form-item>
<el-form-item v-if="!request.useEnvironment" :label="$t('api_test.request.url')" prop="url" <el-form-item v-if="!request.useEnvironment" :label="$t('api_test.request.url')" prop="url"
class="adjust-margin-bottom"> class="adjust-margin-bottom">
<el-input :disabled="isReadOnly" v-model="request.url" maxlength="500" <el-input :disabled="isReadOnly" v-model="request.url" maxlength="500"
:placeholder="$t('api_test.request.url_description')" @change="urlChange" clearable> :placeholder="$t('api_test.request.url_description')" @change="urlChange" clearable>
<template v-slot:prepend> <template v-slot:prepend>
<ApiRequestMethodSelect :is-read-only="isReadOnly" :request="request" @change="methodChange"/> <ApiRequestMethodSelect :is-read-only="isReadOnly" :request="request" @change="methodChange"/>
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item v-if="request.useEnvironment" :label="$t('api_test.request.path')" prop="path"> <el-form-item v-if="request.useEnvironment" :label="$t('api_test.request.path')" prop="path">
<el-input :disabled="isReadOnly" v-model="request.path" maxlength="500" <el-input :disabled="isReadOnly" v-model="request.path" maxlength="500"
:placeholder="$t('api_test.request.path_description')" @change="pathChange" clearable> :placeholder="$t('api_test.request.path_description')" @change="pathChange" clearable>
<template v-slot:prepend> <template v-slot:prepend>
<ApiRequestMethodSelect :is-read-only="isReadOnly" :request="request" @change="methodChange"/> <ApiRequestMethodSelect :is-read-only="isReadOnly" :request="request" @change="methodChange"/>
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item v-if="request.useEnvironment" :label="$t('api_test.request.address')" class="adjust-margin-bottom"> <el-form-item v-if="request.useEnvironment" :label="$t('api_test.request.address')" class="adjust-margin-bottom">
<el-tag class="environment-display"> <el-tag class="environment-display">
<span class="environment-name">{{ scenario.environment ? scenario.environment.name + ': ' : '' }}</span> <span class="environment-name">{{ scenario.environment ? scenario.environment.name + ': ' : '' }}</span>
<span class="environment-url">{{ displayUrl }}</span> <span class="environment-url">{{ displayUrl }}</span>
<span v-if="!displayUrl" <span v-if="!displayUrl"
class="environment-url-tip">{{ $t('api_test.request.please_configure_socket_in_environment') }}</span> class="environment-url-tip">{{ $t('api_test.request.please_configure_socket_in_environment') }}</span>
</el-tag> </el-tag>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-switch <el-switch
v-model="request.useEnvironment" v-model="request.useEnvironment"
:active-text="$t('api_test.request.refer_to_environment')" @change="useEnvironmentChange"> :active-text="$t('api_test.request.refer_to_environment')" @change="useEnvironmentChange">
</el-switch> </el-switch>
<el-checkbox class="follow-redirects-item" v-model="request.followRedirects">{{$t('api_test.request.follow_redirects')}}</el-checkbox> <el-checkbox class="follow-redirects-item" v-model="request.followRedirects">{{$t('api_test.request.follow_redirects')}}</el-checkbox>
<el-checkbox class="do-multipart-post" v-model="request.doMultipartPost">{{$t('api_test.request.do_multipart_post')}}</el-checkbox> <el-checkbox class="do-multipart-post" v-model="request.doMultipartPost">{{$t('api_test.request.do_multipart_post')}}</el-checkbox>
</el-form-item> </el-form-item>
<el-button :disabled="!request.enable || !scenario.enable || isReadOnly" class="debug-button" size="small" <el-button :disabled="!request.enable || !scenario.enable || isReadOnly" class="debug-button" size="small"
type="primary" @click="runDebug">{{ $t('api_test.request.debug') }} type="primary" @click="runDebug">{{ $t('api_test.request.debug') }}
</el-button> </el-button>
<el-tabs v-model="activeName"> <el-tabs v-model="activeName">
<el-tab-pane :label="$t('api_test.request.parameters')" name="parameters"> <el-tab-pane :label="$t('api_test.request.parameters')" name="parameters">
<ms-api-variable :is-read-only="isReadOnly" <ms-api-variable :is-read-only="isReadOnly"
:parameters="request.parameters" :parameters="request.parameters"
:environment="scenario.environment" :environment="scenario.environment"
:scenario="scenario" :scenario="scenario"
:extract="request.extract" :extract="request.extract"
:description="$t('api_test.request.parameters_desc')"/> :description="$t('api_test.request.parameters_desc')"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.request.headers')" name="headers"> <el-tab-pane :label="$t('api_test.request.headers')" name="headers">
<ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="true" :suggestions="headerSuggestions" :items="request.headers"/> <ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="true" :suggestions="headerSuggestions" :items="request.headers"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.request.body')" name="body"> <el-tab-pane :label="$t('api_test.request.body')" name="body">
<ms-api-body :is-read-only="isReadOnly" <ms-api-body :is-read-only="isReadOnly"
:body="request.body" :body="request.body"
:scenario="scenario" :scenario="scenario"
:extract="request.extract" :extract="request.extract"
: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"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.request.processor.pre_exec_script')" name="jsr223PreProcessor"> <el-tab-pane :label="$t('api_test.request.processor.pre_exec_script')" name="jsr223PreProcessor">
<ms-jsr233-processor :is-read-only="isReadOnly" :jsr223-processor="request.jsr223PreProcessor"/> <ms-jsr233-processor :is-read-only="isReadOnly" :jsr223-processor="request.jsr223PreProcessor"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.request.processor.post_exec_script')" name="jsr223PostProcessor"> <el-tab-pane :label="$t('api_test.request.processor.post_exec_script')" name="jsr223PostProcessor">
<ms-jsr233-processor :is-read-only="isReadOnly" :jsr223-processor="request.jsr223PostProcessor"/> <ms-jsr233-processor :is-read-only="isReadOnly" :jsr223-processor="request.jsr223PostProcessor"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.request.timeout_config')" name="advancedConfig"> <el-tab-pane :label="$t('api_test.request.timeout_config')" name="advancedConfig">
<ms-api-advanced-config :is-read-only="isReadOnly" :request="request"/> <ms-api-advanced-config :is-read-only="isReadOnly" :request="request"/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-form> </el-form>
</template> </template>
<script> <script>
import MsApiKeyValue from "../ApiKeyValue"; import MsApiKeyValue from "../ApiKeyValue";
import MsApiBody from "../body/ApiBody"; import MsApiBody from "../body/ApiBody";
import MsApiAssertions from "../assertion/ApiAssertions"; import MsApiAssertions from "../assertion/ApiAssertions";
import {HttpRequest, KeyValue, Scenario} from "../../model/ScenarioModel"; import {HttpRequest, KeyValue, Scenario} from "../../model/ScenarioModel";
import MsApiExtract from "../extract/ApiExtract"; import MsApiExtract from "../extract/ApiExtract";
import ApiRequestMethodSelect from "../collapse/ApiRequestMethodSelect"; import ApiRequestMethodSelect from "../collapse/ApiRequestMethodSelect";
import {REQUEST_HEADERS} from "@/common/js/constants"; import {REQUEST_HEADERS} from "@/common/js/constants";
import MsApiVariable from "@/business/components/api/test/components/ApiVariable"; import MsApiVariable from "@/business/components/api/test/components/ApiVariable";
import MsJsr233Processor from "../processor/Jsr233Processor"; import MsJsr233Processor from "../processor/Jsr233Processor";
import MsApiAdvancedConfig from "../ApiAdvancedConfig"; import MsApiAdvancedConfig from "../ApiAdvancedConfig";
export default { export default {
name: "MsApiHttpRequestForm", name: "MsApiHttpRequestForm",
components: { components: {
MsJsr233Processor, MsJsr233Processor,
MsApiAdvancedConfig, MsApiAdvancedConfig,
MsApiVariable, ApiRequestMethodSelect, MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue}, MsApiVariable, ApiRequestMethodSelect, MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue},
props: { props: {
request: HttpRequest, request: HttpRequest,
scenario: Scenario, jsonPathList: Array,
isReadOnly: { scenario: Scenario,
type: Boolean, isReadOnly: {
default: false type: Boolean,
} default: false
}, }
},
data() {
let validateURL = (rule, value, callback) => { data() {
try { let validateURL = (rule, value, callback) => {
new URL(this.addProtocol(this.request.url)); try {
} catch (e) { new URL(this.addProtocol(this.request.url));
callback(this.$t('api_test.request.url_invalid')); } catch (e) {
} callback(this.$t('api_test.request.url_invalid'));
}; }
return { };
activeName: "parameters", return {
rules: { activeName: "parameters",
name: [ rules: {
{max: 300, message: this.$t('commons.input_limit', [1, 300]), trigger: 'blur'} name: [
], {max: 300, message: this.$t('commons.input_limit', [1, 300]), trigger: 'blur'}
url: [ ],
{max: 500, required: true, message: this.$t('commons.input_limit', [1, 500]), trigger: 'blur'}, url: [
{validator: validateURL, trigger: 'blur'} {max: 500, required: true, message: this.$t('commons.input_limit', [1, 500]), trigger: 'blur'},
], {validator: validateURL, trigger: 'blur'}
path: [ ],
{max: 500, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'}, path: [
] {max: 500, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'},
}, ]
headerSuggestions: REQUEST_HEADERS },
} headerSuggestions: REQUEST_HEADERS
}, }
},
methods: {
urlChange() { methods: {
if (!this.request.url) return; urlChange() {
let url = this.getURL(this.addProtocol(this.request.url)); if (!this.request.url) return;
if (url) { let url = this.getURL(this.addProtocol(this.request.url));
this.request.url = decodeURIComponent(url.origin + url.pathname); if (url) {
} this.request.url = decodeURIComponent(url.origin + url.pathname);
}, }
pathChange() { },
if (!this.request.path) return; pathChange() {
let url = this.getURL(this.displayUrl); if (!this.request.path) return;
let urlStr = url.origin + url.pathname; let url = this.getURL(this.displayUrl);
let envUrl = this.scenario.environment.config.httpConfig.protocol + '://' + this.scenario.environment.config.httpConfig.socket; let urlStr = url.origin + url.pathname;
this.request.path = decodeURIComponent(urlStr.substring(envUrl.length, urlStr.length)); let envUrl = this.scenario.environment.config.httpConfig.protocol + '://' + this.scenario.environment.config.httpConfig.socket;
}, this.request.path = decodeURIComponent(urlStr.substring(envUrl.length, urlStr.length));
getURL(urlStr) { },
try { getURL(urlStr) {
let url = new URL(urlStr); try {
url.searchParams.forEach((value, key) => { let url = new URL(urlStr);
if (key && value) { url.searchParams.forEach((value, key) => {
this.request.parameters.splice(0, 0, new KeyValue({name: key, value: value})); if (key && value) {
} this.request.parameters.splice(0, 0, new KeyValue({name: key, value: value}));
}); }
return url; });
} catch (e) { return url;
this.$error(this.$t('api_test.request.url_invalid'), 2000); } catch (e) {
} this.$error(this.$t('api_test.request.url_invalid'), 2000);
}, }
methodChange(value) { },
if (value === 'GET' && this.activeName === 'body') { methodChange(value) {
this.activeName = 'parameters'; if (value === 'GET' && this.activeName === 'body') {
} this.activeName = 'parameters';
}, }
useEnvironmentChange(value) { },
if (value && !this.scenario.environment) { useEnvironmentChange(value) {
this.$error(this.$t('api_test.request.please_add_environment_to_scenario'), 2000); if (value && !this.scenario.environment) {
this.request.useEnvironment = false; this.$error(this.$t('api_test.request.please_add_environment_to_scenario'), 2000);
} this.request.useEnvironment = false;
this.$refs["request"].clearValidate(); }
}, this.$refs["request"].clearValidate();
addProtocol(url) { },
if (url) { addProtocol(url) {
if (!url.toLowerCase().startsWith("https") && !url.toLowerCase().startsWith("http")) { if (url) {
return "https://" + url; if (!url.toLowerCase().startsWith("https") && !url.toLowerCase().startsWith("http")) {
} return "https://" + url;
} }
return url; }
}, return url;
runDebug() { },
this.$emit('runDebug'); runDebug() {
} this.$emit('runDebug');
}, }
},
computed: {
displayUrl() { computed: {
return (this.scenario.environment && this.scenario.environment.config.httpConfig.socket) ? displayUrl() {
this.scenario.environment.config.httpConfig.protocol + '://' + this.scenario.environment.config.httpConfig.socket + (this.request.path ? this.request.path : '') return (this.scenario.environment && this.scenario.environment.config.httpConfig.socket) ?
: ''; this.scenario.environment.config.httpConfig.protocol + '://' + this.scenario.environment.config.httpConfig.socket + (this.request.path ? this.request.path : '')
} : '';
} }
} }
</script> }
</script>
<style scoped>
<style scoped>
.el-tag {
width: 100%; .el-tag {
height: 40px; width: 100%;
line-height: 40px; height: 40px;
} line-height: 40px;
}
.environment-display {
font-size: 14px; .environment-display {
} font-size: 14px;
}
.environment-name {
font-weight: bold; .environment-name {
font-style: italic; font-weight: bold;
} font-style: italic;
}
.adjust-margin-bottom {
margin-bottom: 10px; .adjust-margin-bottom {
} margin-bottom: 10px;
}
.environment-url-tip {
color: #F56C6C; .environment-url-tip {
} color: #F56C6C;
}
.follow-redirects-item {
margin-left: 30px; .follow-redirects-item {
} margin-left: 30px;
}
.do-multipart-post {
margin-left: 10px; .do-multipart-post {
} margin-left: 10px;
}
</style>
</style>

View File

@ -1,33 +1,24 @@
<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>
<script> <script>
import {JSR223Processor, Request, RequestFactory, Scenario} from "../../model/ScenarioModel"; import {JSR223Processor, Request, RequestFactory, Scenario} from "../../model/ScenarioModel";
import MsApiHttpRequestForm from "./ApiHttpRequestForm"; import MsApiHttpRequestForm from "./ApiHttpRequestForm";
import MsApiTcpRequestForm from "./ApiTcpRequestForm"; import MsApiTcpRequestForm from "./ApiTcpRequestForm";
import MsApiDubboRequestForm from "./ApiDubboRequestForm"; import MsApiDubboRequestForm from "./ApiDubboRequestForm";
import MsScenarioResults from "../../../report/components/ScenarioResults"; import MsScenarioResults from "../../../report/components/ScenarioResults";
import MsRequestResultTail from "../../../report/components/RequestResultTail"; import MsRequestResultTail from "../../../report/components/RequestResultTail";
import MsApiSqlRequestForm from "./ApiSqlRequestForm"; 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,
@ -37,107 +28,140 @@ export default {
}, },
debugReportId: String debugReportId: String
}, },
data() { data() {
return { return {
reportId: "", reportId: "",
content: {scenarios: []}, content: {scenarios:[]},
debugReportLoading: false, debugReportLoading: false,
showDebugReport: false showDebugReport: false,
} jsonPathList:[]
}, }
computed: { },
component({request: {type}}) { computed: {
let name; component({request: {type}}) {
switch (type) { let name;
case RequestFactory.TYPES.DUBBO: switch (type) {
name = "MsApiDubboRequestForm"; case RequestFactory.TYPES.DUBBO:
break; name = "MsApiDubboRequestForm";
case RequestFactory.TYPES.SQL: break;
name = "MsApiSqlRequestForm"; case RequestFactory.TYPES.SQL:
break; name = "MsApiSqlRequestForm";
break;
case RequestFactory.TYPES.TCP: case RequestFactory.TYPES.TCP:
name = "MsApiTcpRequestForm"; name = "MsApiTcpRequestForm";
break; break;
default: default:
name = "MsApiHttpRequestForm"; name = "MsApiHttpRequestForm";
}
return name;
},
isCompleted() {
return !!this.request.debugReport;
} }
return name;
}, },
isCompleted() { watch: {
return !!this.request.debugReport; debugReportId() {
} this.getReport();
}, }
watch: { },
debugReportId() { mounted() {
this.getReport(); // beanshell
} if (!this.request.jsr223PreProcessor.script && this.request.beanShellPreProcessor) {
}, this.request.jsr223PreProcessor = new JSR223Processor(this.request.beanShellPreProcessor);
mounted() { }
// beanshell if (!this.request.jsr223PostProcessor.script && this.request.beanShellPostProcessor) {
if (!this.request.jsr223PreProcessor.script && this.request.beanShellPreProcessor) { this.request.jsr223PostProcessor = new JSR223Processor(this.request.beanShellPostProcessor);
this.request.jsr223PreProcessor = new JSR223Processor(this.request.beanShellPreProcessor); }
} },
if (!this.request.jsr223PostProcessor.script && this.request.beanShellPostProcessor) { methods: {
this.request.jsr223PostProcessor = new JSR223Processor(this.request.beanShellPostProcessor); getReport() {
} if (this.debugReportId) {
}, this.debugReportLoading = true;
methods: { this.showDebugReport = true;
getReport() { this.request.debugReport = {};
if (this.debugReportId) { let url = "/api/report/get/" + this.debugReportId;
this.debugReportLoading = true; this.$get(url, response => {
this.showDebugReport = true; let report = response.data || {};
this.request.debugReport = {}; let res = {};
let url = "/api/report/get/" + this.debugReportId;
this.$get(url, response => { if (response.data) {
let report = response.data || {}; try {
let res = {}; res = JSON.parse(report.content);
if (response.data) { } catch (e) {
try { throw e;
res = JSON.parse(report.content); }
} catch (e) { if (res) {
throw e; this.debugReportLoading = false;
} this.request.debugReport = res;
if (res) { if (res.scenarios && res.scenarios.length > 0) {
this.debugReportLoading = false; this.request.debugScenario = res.scenarios[0];
this.request.debugReport = res; this.request.debugRequestResult = this.request.debugScenario.requestResults[0];
if (res.scenarios && res.scenarios.length > 0) {
this.request.debugScenario = res.scenarios[0]; //add by Cuipeng
this.request.debugRequestResult = this.request.debugScenario.requestResults[0]; this.debugResultDetails=this.request.debugRequestResult.responseResult.body;
this.deleteReport(this.debugReportId); // console.log(this.debugResultDetails);
} else { try {
this.request.debugScenario = new Scenario();
this.request.debugRequestResult = {responseResult: {}, subRequestResults: []}; 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);
} else {
this.request.debugScenario = new Scenario();
this.request.debugRequestResult = {responseResult: {}, subRequestResults: []};
}
this.$refs.msDebugResult.reload();
} else {
setTimeout(this.getReport, 2000)
} }
this.$refs.msDebugResult.reload();
} else { } else {
setTimeout(this.getReport, 2000) this.debugReportLoading = false;
} }
} else { });
this.debugReportLoading = false; }
} },
}); deleteReport(reportId) {
this.$post('/api/report/delete', {id: reportId});
},
runDebug() {
this.$emit('runDebug', this.request);
} }
},
deleteReport(reportId) {
this.$post('/api/report/delete', {id: reportId});
},
runDebug() {
this.$emit('runDebug', this.request);
} }
} }
}
</script> </script>
<style scoped> <style scoped>
.scenario-results { .scenario-results {
margin-top: 20px; margin-top: 20px;
} }
.request-form >>> .debug-button { .request-form >>> .debug-button {
margin-left: auto; margin-left: auto;
display: block; display: block;
margin-right: 10px; margin-right: 10px;
} }
</style> </style>