feat(接口定义): 断言-文档结构校验基本功能完成
This commit is contained in:
parent
3286ba3b67
commit
316394076b
|
@ -457,6 +457,12 @@
|
||||||
<artifactId>generex</artifactId>
|
<artifactId>generex</artifactId>
|
||||||
<version>1.0.2</version>
|
<version>1.0.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.sf.json-lib</groupId>
|
||||||
|
<artifactId>json-lib</artifactId>
|
||||||
|
<version>2.4</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -8,6 +8,8 @@ import io.metersphere.api.dto.automation.ApiScenarioRequest;
|
||||||
import io.metersphere.api.dto.automation.ReferenceDTO;
|
import io.metersphere.api.dto.automation.ReferenceDTO;
|
||||||
import io.metersphere.api.dto.definition.*;
|
import io.metersphere.api.dto.definition.*;
|
||||||
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
|
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
|
||||||
|
import io.metersphere.api.dto.definition.request.assertions.document.DocumentElement;
|
||||||
|
import io.metersphere.api.dto.scenario.Body;
|
||||||
import io.metersphere.api.dto.swaggerurl.SwaggerTaskResult;
|
import io.metersphere.api.dto.swaggerurl.SwaggerTaskResult;
|
||||||
import io.metersphere.api.dto.swaggerurl.SwaggerUrlRequest;
|
import io.metersphere.api.dto.swaggerurl.SwaggerUrlRequest;
|
||||||
import io.metersphere.api.service.ApiDefinitionService;
|
import io.metersphere.api.service.ApiDefinitionService;
|
||||||
|
@ -21,6 +23,7 @@ import io.metersphere.commons.constants.NoticeConstants;
|
||||||
import io.metersphere.commons.constants.OperLogConstants;
|
import io.metersphere.commons.constants.OperLogConstants;
|
||||||
import io.metersphere.commons.constants.PermissionConstants;
|
import io.metersphere.commons.constants.PermissionConstants;
|
||||||
import io.metersphere.commons.json.JSONSchemaGenerator;
|
import io.metersphere.commons.json.JSONSchemaGenerator;
|
||||||
|
import io.metersphere.commons.json.JSONToDocumentUtils;
|
||||||
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.controller.request.ResetOrderRequest;
|
import io.metersphere.controller.request.ResetOrderRequest;
|
||||||
|
@ -61,6 +64,7 @@ public class ApiDefinitionController {
|
||||||
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
|
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
|
||||||
return PageUtils.setPageInfo(page, apiDefinitionService.list(request));
|
return PageUtils.setPageInfo(page, apiDefinitionService.list(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/week/list/{goPage}/{pageSize}")
|
@PostMapping("/week/list/{goPage}/{pageSize}")
|
||||||
@RequiresPermissions("PROJECT_API_DEFINITION:READ")
|
@RequiresPermissions("PROJECT_API_DEFINITION:READ")
|
||||||
public Pager<List<ApiDefinitionResult>> weekList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody ApiDefinitionRequest request) {
|
public Pager<List<ApiDefinitionResult>> weekList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody ApiDefinitionRequest request) {
|
||||||
|
@ -327,4 +331,15 @@ public class ApiDefinitionController {
|
||||||
public List<String> getFollows(@PathVariable String definitionId) {
|
public List<String> getFollows(@PathVariable String definitionId) {
|
||||||
return apiDefinitionService.getFollows(definitionId);
|
return apiDefinitionService.getFollows(definitionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/getDocument/{id}/{type}")
|
||||||
|
public List<DocumentElement> getDocument(@PathVariable String id,@PathVariable String type) {
|
||||||
|
return apiDefinitionService.getDocument(id,type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/jsonGenerator")
|
||||||
|
public List<DocumentElement> jsonGenerator(@RequestBody Body body) {
|
||||||
|
return JSONToDocumentUtils.getDocument(body.getRaw(),body.getType());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@ package io.metersphere.api.dto.definition.request.assertions;
|
||||||
|
|
||||||
import com.alibaba.fastjson.annotation.JSONType;
|
import com.alibaba.fastjson.annotation.JSONType;
|
||||||
import io.metersphere.api.dto.definition.request.ParameterConfig;
|
import io.metersphere.api.dto.definition.request.ParameterConfig;
|
||||||
|
import io.metersphere.api.dto.definition.request.assertions.document.MsAssertionDocument;
|
||||||
|
import io.metersphere.api.service.ApiDefinitionService;
|
||||||
|
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||||
import io.metersphere.plugin.core.MsParameter;
|
import io.metersphere.plugin.core.MsParameter;
|
||||||
import io.metersphere.plugin.core.MsTestElement;
|
import io.metersphere.plugin.core.MsTestElement;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -27,6 +30,7 @@ public class MsAssertions extends MsTestElement {
|
||||||
private List<MsAssertionXPath2> xpath2;
|
private List<MsAssertionXPath2> xpath2;
|
||||||
private MsAssertionDuration duration;
|
private MsAssertionDuration duration;
|
||||||
private String type = "Assertions";
|
private String type = "Assertions";
|
||||||
|
private MsAssertionDocument document;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, MsParameter msParameter) {
|
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, MsParameter msParameter) {
|
||||||
|
@ -39,6 +43,27 @@ public class MsAssertions extends MsTestElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAssertions(HashTree hashTree) {
|
private void addAssertions(HashTree hashTree) {
|
||||||
|
// 增加JSON文档结构校验
|
||||||
|
if (this.getDocument() != null && this.getDocument().getType().equals("JSON")) {
|
||||||
|
if (StringUtils.isNotEmpty(this.getDocument().getData().getJsonFollowAPI())) {
|
||||||
|
ApiDefinitionService apiDefinitionService = CommonBeanFactory.getBean(ApiDefinitionService.class);
|
||||||
|
this.getDocument().getData().setJson(apiDefinitionService.getDocument(this.getDocument().getData().getJsonFollowAPI(), "JSON"));
|
||||||
|
}
|
||||||
|
if (CollectionUtils.isNotEmpty(this.getDocument().getData().getJson())) {
|
||||||
|
this.getDocument().getData().parseJson(hashTree, this.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 增加XML文档结构校验
|
||||||
|
if (this.getDocument() != null && this.getDocument().getType().equals("XML") && CollectionUtils.isNotEmpty(this.getDocument().getData().getXml())) {
|
||||||
|
if (StringUtils.isNotEmpty(this.getDocument().getData().getXmlFollowAPI())) {
|
||||||
|
ApiDefinitionService apiDefinitionService = CommonBeanFactory.getBean(ApiDefinitionService.class);
|
||||||
|
this.getDocument().getData().setXml(apiDefinitionService.getDocument(this.getDocument().getData().getXmlFollowAPI(), "XML"));
|
||||||
|
}
|
||||||
|
if (CollectionUtils.isNotEmpty(this.getDocument().getData().getXml())) {
|
||||||
|
this.getDocument().getData().parseXml(hashTree, this.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (CollectionUtils.isNotEmpty(this.getRegex())) {
|
if (CollectionUtils.isNotEmpty(this.getRegex())) {
|
||||||
this.getRegex().stream().filter(MsAssertionRegex::isValid).forEach(assertion ->
|
this.getRegex().stream().filter(MsAssertionRegex::isValid).forEach(assertion ->
|
||||||
hashTree.add(responseAssertion(assertion))
|
hashTree.add(responseAssertion(assertion))
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package io.metersphere.api.dto.definition.request.assertions.document;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Condition {
|
||||||
|
private String key;
|
||||||
|
private Object value;
|
||||||
|
|
||||||
|
public Condition() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Condition(String key, Object value) {
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,170 @@
|
||||||
|
package io.metersphere.api.dto.definition.request.assertions.document;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.jmeter.assertions.JSONPathAssertion;
|
||||||
|
import org.apache.jmeter.assertions.XMLAssertion;
|
||||||
|
import org.apache.jmeter.save.SaveService;
|
||||||
|
import org.apache.jmeter.testelement.TestElement;
|
||||||
|
import org.apache.jorphan.collections.HashTree;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Document {
|
||||||
|
private String jsonFollowAPI;
|
||||||
|
private String xmlFollowAPI;
|
||||||
|
private List<DocumentElement> json;
|
||||||
|
private List<DocumentElement> xml;
|
||||||
|
private String assertionName;
|
||||||
|
|
||||||
|
public void parseJson(HashTree hashTree, String name) {
|
||||||
|
this.assertionName = name;
|
||||||
|
// 提取出合并的权限
|
||||||
|
Map<String, ElementCondition> conditionMap = new HashMap<>();
|
||||||
|
conditions(this.getJson(), conditionMap);
|
||||||
|
// 数据处理生成断言条件
|
||||||
|
List<JSONPathAssertion> list = new LinkedList<>();
|
||||||
|
formatting(this.getJson(), list, null, conditionMap);
|
||||||
|
|
||||||
|
if (CollectionUtils.isNotEmpty(list)) {
|
||||||
|
hashTree.add(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parseXml(HashTree hashTree, String name) {
|
||||||
|
this.assertionName = name;
|
||||||
|
// 提取出合并的权限
|
||||||
|
Map<String, ElementCondition> conditionMap = new HashMap<>();
|
||||||
|
conditions(this.getXml(), conditionMap);
|
||||||
|
// 数据处理生成断言条件
|
||||||
|
List<XMLAssertion> list = new LinkedList<>();
|
||||||
|
xmlFormatting(this.getXml(), list, null, conditionMap);
|
||||||
|
|
||||||
|
if (CollectionUtils.isNotEmpty(list)) {
|
||||||
|
hashTree.add(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void conditions(List<DocumentElement> dataList, Map<String, ElementCondition> conditionMap) {
|
||||||
|
dataList.forEach(item -> {
|
||||||
|
if (StringUtils.isEmpty(item.getGroupId())) {
|
||||||
|
conditionMap.put(item.getId(),
|
||||||
|
new ElementCondition(item.isInclude(), item.isTypeVerification(), item.isArrayVerification(),
|
||||||
|
new LinkedList<Condition>() {{
|
||||||
|
this.add(new Condition(item.getContentVerification(), item.getExpectedOutcome()));
|
||||||
|
}}));
|
||||||
|
} else {
|
||||||
|
if (conditionMap.containsKey(item.getGroupId())) {
|
||||||
|
conditionMap.get(item.getGroupId()).getConditions().add(new Condition(item.getContentVerification(), item.getExpectedOutcome()));
|
||||||
|
} else {
|
||||||
|
conditionMap.put(item.getGroupId(),
|
||||||
|
new ElementCondition(item.isInclude(), item.isTypeVerification(), item.isArrayVerification(),
|
||||||
|
new LinkedList<Condition>() {{
|
||||||
|
this.add(new Condition(item.getContentVerification(), item.getExpectedOutcome()));
|
||||||
|
}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (CollectionUtils.isNotEmpty(item.getChildren())) {
|
||||||
|
conditions(item.getChildren(), conditionMap);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void formatting(List<DocumentElement> dataList, List<JSONPathAssertion> list, DocumentElement parentNode, Map<String, ElementCondition> conditionMap) {
|
||||||
|
for (DocumentElement item : dataList) {
|
||||||
|
if (StringUtils.isEmpty(item.getGroupId())) {
|
||||||
|
if (!item.getId().equals("root")) {
|
||||||
|
if (parentNode != null) {
|
||||||
|
item.setJsonPath(parentNode.getJsonPath() + "." + item.getName());
|
||||||
|
} else {
|
||||||
|
item.setJsonPath("$." + item.getName());
|
||||||
|
}
|
||||||
|
if (!StringUtils.equalsAny(item.getContentVerification(), "none", null) || item.isInclude()) {
|
||||||
|
list.add(newJSONPathAssertion(item, conditionMap.get(item.getId())));
|
||||||
|
}
|
||||||
|
if (CollectionUtils.isNotEmpty(item.getChildren())) {
|
||||||
|
formatting(item.getChildren(), list, item, conditionMap);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (CollectionUtils.isNotEmpty(item.getChildren())) {
|
||||||
|
formatting(item.getChildren(), list, null, conditionMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void xmlFormatting(List<DocumentElement> dataList, List<XMLAssertion> list, DocumentElement parentNode, Map<String, ElementCondition> conditionMap) {
|
||||||
|
for (DocumentElement item : dataList) {
|
||||||
|
if (StringUtils.isEmpty(item.getGroupId())) {
|
||||||
|
if (parentNode != null) {
|
||||||
|
item.setJsonPath(parentNode.getJsonPath() + "." + item.getName());
|
||||||
|
} else {
|
||||||
|
item.setJsonPath("$." + item.getName());
|
||||||
|
}
|
||||||
|
if (!StringUtils.equalsAny(item.getContentVerification(), "none", null) || item.isInclude()) {
|
||||||
|
list.add(newXMLAssertion(item, conditionMap.get(item.getId())));
|
||||||
|
}
|
||||||
|
if (CollectionUtils.isNotEmpty(item.getChildren())) {
|
||||||
|
xmlFormatting(item.getChildren(), list, item, conditionMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getConditionStr(DocumentElement item, ElementCondition elementCondition) {
|
||||||
|
StringBuilder conditionStr = new StringBuilder();
|
||||||
|
if (elementCondition != null && CollectionUtils.isNotEmpty(elementCondition.getConditions())) {
|
||||||
|
elementCondition.getConditions().forEach(condition -> {
|
||||||
|
conditionStr.append(item.getLabel(item.getContentVerification()).replace("'%'", (item.getExpectedOutcome() != null ? item.getExpectedOutcome().toString() : "")));
|
||||||
|
conditionStr.append(" and ");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
String label = "";
|
||||||
|
if (StringUtils.isNotEmpty(conditionStr.toString())) {
|
||||||
|
label = conditionStr.toString().substring(0, conditionStr.toString().length() - 4);
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONPathAssertion newJSONPathAssertion(DocumentElement item, ElementCondition elementCondition) {
|
||||||
|
JSONPathAssertion assertion = new JSONPathAssertion();
|
||||||
|
assertion.setEnabled(true);
|
||||||
|
assertion.setJsonValidationBool(true);
|
||||||
|
assertion.setExpectNull(false);
|
||||||
|
assertion.setInvert(false);
|
||||||
|
|
||||||
|
assertion.setName((StringUtils.isNotEmpty(assertionName) ? assertionName : "DocumentAssertion") + ("==" + item.getJsonPath() + " " + getConditionStr(item, elementCondition)));
|
||||||
|
assertion.setProperty(TestElement.TEST_CLASS, JSONPathAssertion.class.getName());
|
||||||
|
assertion.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("JSONPathAssertionGui"));
|
||||||
|
assertion.setJsonPath(item.getJsonPath());
|
||||||
|
assertion.setExpectedValue(item.getExpectedOutcome() != null ? item.getExpectedOutcome().toString() : "");
|
||||||
|
assertion.setProperty("ASS_OPTION", "DOCUMENT");
|
||||||
|
assertion.setProperty("ElementCondition", JSON.toJSONString(elementCondition));
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(item.getContentVerification()) || "regular".equals(item.getContentVerification())) {
|
||||||
|
assertion.setIsRegex(true);
|
||||||
|
} else {
|
||||||
|
assertion.setIsRegex(false);
|
||||||
|
}
|
||||||
|
return assertion;
|
||||||
|
}
|
||||||
|
|
||||||
|
private XMLAssertion newXMLAssertion(DocumentElement item, ElementCondition elementCondition) {
|
||||||
|
XMLAssertion assertion = new XMLAssertion();
|
||||||
|
assertion.setEnabled(true);
|
||||||
|
assertion.setName((StringUtils.isNotEmpty(assertionName) ? assertionName : "XMLAssertion") + "==" + (item.getJsonPath() + " " + getConditionStr(item, elementCondition)));
|
||||||
|
assertion.setProperty(TestElement.TEST_CLASS, XMLAssertion.class.getName());
|
||||||
|
assertion.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("XMLAssertionGui"));
|
||||||
|
assertion.setProperty("XML_PATH", item.getJsonPath());
|
||||||
|
assertion.setProperty("EXPECTED_VALUE", item.getExpectedOutcome() != null ? item.getExpectedOutcome().toString() : "");
|
||||||
|
assertion.setProperty("ElementCondition", JSON.toJSONString(elementCondition));
|
||||||
|
return assertion;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package io.metersphere.api.dto.definition.request.assertions.document;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class DocumentElement {
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
private boolean include;
|
||||||
|
private String status;
|
||||||
|
private boolean typeVerification;
|
||||||
|
private String type;
|
||||||
|
private String groupId;
|
||||||
|
private int rowspan;
|
||||||
|
private boolean arrayVerification;
|
||||||
|
private String contentVerification;
|
||||||
|
private Object expectedOutcome;
|
||||||
|
|
||||||
|
private List<DocumentElement> children;
|
||||||
|
|
||||||
|
// 候补两个属性,在执行时组装数据用
|
||||||
|
private String jsonPath;
|
||||||
|
List<String> conditions;
|
||||||
|
|
||||||
|
public DocumentElement() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public DocumentElement(String name, String type, Object expectedOutcome, List<DocumentElement> children) {
|
||||||
|
this.id = UUID.randomUUID().toString();
|
||||||
|
this.name = name;
|
||||||
|
this.expectedOutcome = expectedOutcome;
|
||||||
|
this.type = type;
|
||||||
|
this.children = children == null ? this.children = new LinkedList<>() : children;
|
||||||
|
this.rowspan = 1;
|
||||||
|
this.contentVerification = "value_eq";
|
||||||
|
if (StringUtils.equalsAny(type, "object", "array")) {
|
||||||
|
this.contentVerification = "none";
|
||||||
|
} else if (expectedOutcome == null || StringUtils.isEmpty(expectedOutcome.toString())) {
|
||||||
|
this.contentVerification = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DocumentElement(String id, String name, String type, Object expectedOutcome, List<DocumentElement> children) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.expectedOutcome = expectedOutcome;
|
||||||
|
this.type = type;
|
||||||
|
this.children = children == null ? this.children = new LinkedList<>() : children;
|
||||||
|
this.rowspan = 1;
|
||||||
|
this.contentVerification = "value_eq";
|
||||||
|
if (StringUtils.equalsAny(type, "object", "array")) {
|
||||||
|
this.contentVerification = "none";
|
||||||
|
} else if (expectedOutcome == null || StringUtils.isEmpty(expectedOutcome.toString())) {
|
||||||
|
this.contentVerification = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DocumentElement newRoot(String type, List<DocumentElement> children) {
|
||||||
|
return new DocumentElement("root", "root", type, "", children);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel(String value) {
|
||||||
|
String label = "";
|
||||||
|
switch (value) {
|
||||||
|
case "value_eq":
|
||||||
|
label = "值-等于[value='%']";
|
||||||
|
break;
|
||||||
|
case "value_not_eq":
|
||||||
|
label = "值-不等于[value!='%']";
|
||||||
|
break;
|
||||||
|
case "value_in":
|
||||||
|
label = "值-包含[include='%']";
|
||||||
|
break;
|
||||||
|
case "length_eq":
|
||||||
|
label = "长度-等于[length='%']";
|
||||||
|
break;
|
||||||
|
case "length_not_eq":
|
||||||
|
label = "长度-不等于[length!='%']";
|
||||||
|
break;
|
||||||
|
case "length_gt":
|
||||||
|
label = "长度-大于[length>'%']";
|
||||||
|
break;
|
||||||
|
case "length_lt":
|
||||||
|
label = "长度-小于[length<'%']";
|
||||||
|
break;
|
||||||
|
case "regular":
|
||||||
|
label = "正则匹配";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
label = "不校验[]";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package io.metersphere.api.dto.definition.request.assertions.document;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ElementCondition {
|
||||||
|
private boolean include;
|
||||||
|
private boolean typeVerification;
|
||||||
|
private boolean arrayVerification;
|
||||||
|
List<Condition> conditions;
|
||||||
|
|
||||||
|
public ElementCondition() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ElementCondition(boolean include, boolean typeVerification, boolean arrayVerification, List<Condition> conditions) {
|
||||||
|
this.include = include;
|
||||||
|
this.typeVerification = typeVerification;
|
||||||
|
this.arrayVerification = arrayVerification;
|
||||||
|
this.conditions = conditions;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package io.metersphere.api.dto.definition.request.assertions.document;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class MsAssertionDocument {
|
||||||
|
private String type;
|
||||||
|
private Document data;
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
|
||||||
import io.metersphere.api.dto.definition.parse.ApiDefinitionImportParserFactory;
|
import io.metersphere.api.dto.definition.parse.ApiDefinitionImportParserFactory;
|
||||||
import io.metersphere.api.dto.definition.parse.Swagger3Parser;
|
import io.metersphere.api.dto.definition.parse.Swagger3Parser;
|
||||||
import io.metersphere.api.dto.definition.request.ParameterConfig;
|
import io.metersphere.api.dto.definition.request.ParameterConfig;
|
||||||
|
import io.metersphere.api.dto.definition.request.assertions.document.DocumentElement;
|
||||||
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
|
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
|
||||||
import io.metersphere.api.dto.definition.request.sampler.MsTCPSampler;
|
import io.metersphere.api.dto.definition.request.sampler.MsTCPSampler;
|
||||||
import io.metersphere.api.dto.mockconfig.MockConfigImportDTO;
|
import io.metersphere.api.dto.mockconfig.MockConfigImportDTO;
|
||||||
|
@ -32,6 +33,8 @@ import io.metersphere.base.mapper.*;
|
||||||
import io.metersphere.base.mapper.ext.*;
|
import io.metersphere.base.mapper.ext.*;
|
||||||
import io.metersphere.commons.constants.*;
|
import io.metersphere.commons.constants.*;
|
||||||
import io.metersphere.commons.exception.MSException;
|
import io.metersphere.commons.exception.MSException;
|
||||||
|
import io.metersphere.commons.json.JSONSchemaToDocumentUtils;
|
||||||
|
import io.metersphere.commons.json.JSONToDocumentUtils;
|
||||||
import io.metersphere.commons.utils.*;
|
import io.metersphere.commons.utils.*;
|
||||||
import io.metersphere.controller.request.ResetOrderRequest;
|
import io.metersphere.controller.request.ResetOrderRequest;
|
||||||
import io.metersphere.controller.request.ScheduleRequest;
|
import io.metersphere.controller.request.ScheduleRequest;
|
||||||
|
@ -1729,4 +1732,38 @@ public class ApiDefinitionService {
|
||||||
List<ApiDefinitionFollow> follows = apiDefinitionFollowMapper.selectByExample(example);
|
List<ApiDefinitionFollow> follows = apiDefinitionFollowMapper.selectByExample(example);
|
||||||
return follows.stream().map(ApiDefinitionFollow::getFollowId).distinct().collect(Collectors.toList());
|
return follows.stream().map(ApiDefinitionFollow::getFollowId).distinct().collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DocumentElement> getDocument(String id, String type) {
|
||||||
|
ApiDefinitionWithBLOBs bloBs = apiDefinitionMapper.selectByPrimaryKey(id);
|
||||||
|
List<DocumentElement> list = new LinkedList<>();
|
||||||
|
if (bloBs != null && StringUtils.isNotEmpty(bloBs.getResponse())) {
|
||||||
|
JSONObject object = JSON.parseObject(bloBs.getResponse());
|
||||||
|
JSONObject body = (JSONObject) object.get("body");
|
||||||
|
if (body != null) {
|
||||||
|
if (StringUtils.equals(type, "JSON")) {
|
||||||
|
String jsonSchema = body.getString("jsonSchema");
|
||||||
|
String dataType = body.getString("type");
|
||||||
|
if (StringUtils.equalsAny(dataType, "JSON", "JSON-SCHEMA") && StringUtils.isNotEmpty(jsonSchema)) {
|
||||||
|
JSONObject obj = (JSONObject) body.get("jsonSchema");
|
||||||
|
if (StringUtils.equals(obj.getString("type"), "array")) {
|
||||||
|
list.add(new DocumentElement().newRoot("array", JSONSchemaToDocumentUtils.getDocument(jsonSchema)));
|
||||||
|
} else {
|
||||||
|
list.add(new DocumentElement().newRoot("object", JSONSchemaToDocumentUtils.getDocument(jsonSchema)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
list.add(new DocumentElement().newRoot("object", null));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String xml = body.getString("raw");
|
||||||
|
String dataType = body.getString("type");
|
||||||
|
if (StringUtils.equals(dataType, "XML") && StringUtils.isNotEmpty(xml)) {
|
||||||
|
list = JSONToDocumentUtils.getDocument(xml, type);
|
||||||
|
} else {
|
||||||
|
list.add(new DocumentElement().newRoot("root", null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,213 @@
|
||||||
|
package io.metersphere.commons.json;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
import io.metersphere.api.dto.definition.request.assertions.document.DocumentElement;
|
||||||
|
import io.metersphere.jmeter.utils.ScriptEngineUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
public class JSONSchemaToDocumentUtils {
|
||||||
|
|
||||||
|
private static void analyzeRootSchemaElement(JsonObject rootElement, List<DocumentElement> roots) {
|
||||||
|
if (rootElement.has("type") || rootElement.has("allOf")) {
|
||||||
|
analyzeObject(rootElement, roots);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void analyzeObject(JsonObject object, List<DocumentElement> roots) {
|
||||||
|
if (object.has("allOf")) {
|
||||||
|
JsonArray allOfArray = object.get("allOf").getAsJsonArray();
|
||||||
|
for (JsonElement allOfElement : allOfArray) {
|
||||||
|
JsonObject allOfElementObj = allOfElement.getAsJsonObject();
|
||||||
|
if (allOfElementObj.has("properties")) {
|
||||||
|
// Properties elements will become the attributes/references of the element
|
||||||
|
JsonObject propertiesObj = allOfElementObj.get("properties").getAsJsonObject();
|
||||||
|
for (Entry<String, JsonElement> entry : propertiesObj.entrySet()) {
|
||||||
|
String propertyKey = entry.getKey();
|
||||||
|
JsonObject propertyObj = propertiesObj.get(propertyKey).getAsJsonObject();
|
||||||
|
analyzeProperty(roots, propertyKey, propertyObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (object.has("properties")) {
|
||||||
|
JsonObject propertiesObj = object.get("properties").getAsJsonObject();
|
||||||
|
for (Entry<String, JsonElement> entry : propertiesObj.entrySet()) {
|
||||||
|
String propertyKey = entry.getKey();
|
||||||
|
JsonObject propertyObj = propertiesObj.get(propertyKey).getAsJsonObject();
|
||||||
|
analyzeProperty(roots, propertyKey, propertyObj);
|
||||||
|
}
|
||||||
|
} else if (object.has("type") && object.get("type").getAsString().equals("array")) {
|
||||||
|
analyzeProperty(roots, "MS-OBJECT", object);
|
||||||
|
} else if (object.has("type") && !object.get("type").getAsString().equals("object")) {
|
||||||
|
analyzeProperty(roots, object.getAsString(), object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void analyzeProperty(List<DocumentElement> concept,
|
||||||
|
String propertyName, JsonObject object) {
|
||||||
|
if (object.has("type")) {
|
||||||
|
String propertyObjType = null;
|
||||||
|
if (object.get("type") instanceof JsonPrimitive) {
|
||||||
|
propertyObjType = object.get("type").getAsString();
|
||||||
|
} else if (object.get("type") instanceof JsonArray) {
|
||||||
|
JsonArray typeArray = (JsonArray) object.get("type").getAsJsonArray();
|
||||||
|
propertyObjType = typeArray.get(0).getAsString();
|
||||||
|
}
|
||||||
|
if (object.has("default")) {
|
||||||
|
concept.add(new DocumentElement(propertyName, propertyObjType, object.get("default") != null ? object.get("default").toString() : "", null));
|
||||||
|
} else if (object.has("enum")) {
|
||||||
|
try {
|
||||||
|
if (object.has("mock") && object.get("mock").getAsJsonObject() != null && StringUtils.isNotEmpty(object.get("mock").getAsJsonObject().get("mock").getAsString())) {
|
||||||
|
Object value = object.get("mock").getAsJsonObject().get("mock");
|
||||||
|
concept.add(new DocumentElement(propertyName, propertyObjType, value.toString(), null));
|
||||||
|
} else {
|
||||||
|
List<Object> list = analyzeEnumProperty(object);
|
||||||
|
if (list.size() > 0) {
|
||||||
|
int index = (int) (Math.random() * list.size());
|
||||||
|
concept.add(new DocumentElement(propertyName, propertyObjType, list.get(index).toString(), null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
concept.add(new DocumentElement(propertyName, propertyObjType, "", null));
|
||||||
|
}
|
||||||
|
} else if (propertyObjType.equals("string")) {
|
||||||
|
// 先设置空值
|
||||||
|
String value = "";
|
||||||
|
if (object.has("default")) {
|
||||||
|
value = object.get("default") != null ? object.get("default").toString() : "";
|
||||||
|
}
|
||||||
|
if (object.has("mock") && object.get("mock").getAsJsonObject() != null && StringUtils.isNotEmpty(object.get("mock").getAsJsonObject().get("mock").getAsString()) && StringUtils.isNotEmpty(object.get("mock").getAsJsonObject().get("mock").getAsString())) {
|
||||||
|
value = ScriptEngineUtils.buildFunctionCallString(object.get("mock").getAsJsonObject().get("mock").getAsString());
|
||||||
|
}
|
||||||
|
concept.add(new DocumentElement(propertyName, propertyObjType, value, null));
|
||||||
|
} else if (propertyObjType.equals("integer")) {
|
||||||
|
Object value = null;
|
||||||
|
if (object.has("default")) {
|
||||||
|
value = object.get("default");
|
||||||
|
}
|
||||||
|
if (object.has("mock") && object.get("mock").getAsJsonObject() != null && StringUtils.isNotEmpty(object.get("mock").getAsJsonObject().get("mock").getAsString())) {
|
||||||
|
try {
|
||||||
|
value = object.get("mock").getAsJsonObject().get("mock").getAsInt();
|
||||||
|
} catch (Exception e) {
|
||||||
|
value = ScriptEngineUtils.buildFunctionCallString(object.get("mock").getAsJsonObject().get("mock").getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
concept.add(new DocumentElement(propertyName, propertyObjType, value, null));
|
||||||
|
} else if (propertyObjType.equals("number")) {
|
||||||
|
Object value = null;
|
||||||
|
if (object.has("default")) {
|
||||||
|
value = object.get("default");
|
||||||
|
}
|
||||||
|
if (object.has("mock") && object.get("mock").getAsJsonObject() != null && StringUtils.isNotEmpty(object.get("mock").getAsJsonObject().get("mock").getAsString())) {
|
||||||
|
try {
|
||||||
|
value = object.get("mock").getAsJsonObject().get("mock").getAsNumber();
|
||||||
|
} catch (Exception e) {
|
||||||
|
value = ScriptEngineUtils.buildFunctionCallString(object.get("mock").getAsJsonObject().get("mock").getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
concept.add(new DocumentElement(propertyName, propertyObjType, value, null));
|
||||||
|
} else if (propertyObjType.equals("boolean")) {
|
||||||
|
Object value = null;
|
||||||
|
if (object.has("default")) {
|
||||||
|
value = object.get("default");
|
||||||
|
}
|
||||||
|
if (object.has("mock") && object.get("mock").getAsJsonObject() != null && StringUtils.isNotEmpty(object.get("mock").getAsJsonObject().get("mock").getAsString())) {
|
||||||
|
try {
|
||||||
|
value = ScriptEngineUtils.buildFunctionCallString(object.get("mock").getAsJsonObject().get("mock").toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
concept.add(new DocumentElement(propertyName, propertyObjType, value, null));
|
||||||
|
} else if (propertyObjType.equals("array")) {
|
||||||
|
analyzeArray(propertyObjType, propertyName, object);
|
||||||
|
} else if (propertyObjType.equals("object")) {
|
||||||
|
List<DocumentElement> list = new LinkedList<>();
|
||||||
|
concept.add(new DocumentElement(propertyName, propertyObjType, "", list));
|
||||||
|
analyzeObject(object, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void analyzeArray(String propertyObjType, String propertyName, JsonObject object) {
|
||||||
|
// 先设置空值
|
||||||
|
List<DocumentElement> array = new LinkedList<>();
|
||||||
|
JsonArray jsonArray = new JsonArray();
|
||||||
|
if (object.has("items") && object.get("items").isJsonArray()) {
|
||||||
|
jsonArray = object.get("items").getAsJsonArray();
|
||||||
|
} else {
|
||||||
|
JsonObject itemsObject = object.get("items").getAsJsonObject();
|
||||||
|
array.add(new DocumentElement(propertyName, propertyObjType, itemsObject, null));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < jsonArray.size(); i++) {
|
||||||
|
JsonObject itemsObject = jsonArray.get(i).getAsJsonObject();
|
||||||
|
if (object.has("items")) {
|
||||||
|
Object value = null;
|
||||||
|
if (itemsObject.has("mock") && itemsObject.get("mock").getAsJsonObject() != null && StringUtils.isNotEmpty(itemsObject.get("mock").getAsJsonObject().get("mock").getAsString())) {
|
||||||
|
try {
|
||||||
|
value = itemsObject.get("mock").getAsJsonObject().get("mock").getAsInt();
|
||||||
|
} catch (Exception e) {
|
||||||
|
value = ScriptEngineUtils.buildFunctionCallString(itemsObject.get("mock").getAsJsonObject().get("mock").getAsString());
|
||||||
|
}
|
||||||
|
} else if (itemsObject.has("type") && itemsObject.get("type").getAsString().equals("string")) {
|
||||||
|
if (itemsObject.has("default")) {
|
||||||
|
value = itemsObject.get("default");
|
||||||
|
} else if (itemsObject.has("mock") && itemsObject.get("mock").getAsJsonObject() != null && StringUtils.isNotEmpty(itemsObject.get("mock").getAsJsonObject().get("mock").getAsString())) {
|
||||||
|
value = ScriptEngineUtils.buildFunctionCallString(itemsObject.get("mock").getAsJsonObject().get("mock").getAsString());
|
||||||
|
}
|
||||||
|
} else if (itemsObject.has("type") && itemsObject.get("type").getAsString().equals("number")) {
|
||||||
|
if (itemsObject.has("default")) {
|
||||||
|
value = itemsObject.get("default");
|
||||||
|
} else if (itemsObject.has("mock") && itemsObject.get("mock").getAsJsonObject() != null && StringUtils.isNotEmpty(itemsObject.get("mock").getAsJsonObject().get("mock").getAsString())) {
|
||||||
|
value = ScriptEngineUtils.buildFunctionCallString(itemsObject.get("mock").getAsJsonObject().get("mock").getAsString());
|
||||||
|
}
|
||||||
|
} else if (itemsObject.has("properties")) {
|
||||||
|
List<DocumentElement> propertyConcept = new LinkedList<>();
|
||||||
|
JsonObject propertiesObj = itemsObject.get("properties").getAsJsonObject();
|
||||||
|
for (Entry<String, JsonElement> entry : propertiesObj.entrySet()) {
|
||||||
|
String propertyKey = entry.getKey();
|
||||||
|
JsonObject propertyObj = propertiesObj.get(propertyKey).getAsJsonObject();
|
||||||
|
analyzeProperty(propertyConcept, propertyKey, propertyObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
array.add(new DocumentElement(propertyName, itemsObject.get("type").getAsString(), value, null));
|
||||||
|
} else if (object.has("items") && object.get("items").isJsonArray()) {
|
||||||
|
JsonArray itemsObjectArray = object.get("items").getAsJsonArray();
|
||||||
|
array.add(new DocumentElement(propertyName, itemsObject.get("type").getAsString(), itemsObjectArray, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Object> analyzeEnumProperty(JsonObject object) {
|
||||||
|
List<Object> list = new LinkedList<>();
|
||||||
|
String jsonStr = null;
|
||||||
|
try {
|
||||||
|
JsonArray enumValues = object.get("enum").getAsJsonArray();
|
||||||
|
for (JsonElement enumValueElem : enumValues) {
|
||||||
|
String enumValue = enumValueElem.getAsString();
|
||||||
|
list.add(enumValue);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
jsonStr = object.get("enum").getAsString();
|
||||||
|
}
|
||||||
|
if (jsonStr != null && list.isEmpty()) {
|
||||||
|
String[] arrays = jsonStr.split("\n");
|
||||||
|
for (String str : arrays) {
|
||||||
|
list.add(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<DocumentElement> getDocument(String jsonSchema) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
JsonElement element = gson.fromJson(jsonSchema, JsonElement.class);
|
||||||
|
JsonObject rootElement = element.getAsJsonObject();
|
||||||
|
List<DocumentElement> roots = new LinkedList<>();
|
||||||
|
analyzeRootSchemaElement(rootElement, roots);
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package io.metersphere.commons.json;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONArray;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import io.metersphere.api.dto.definition.request.assertions.document.DocumentElement;
|
||||||
|
import io.metersphere.commons.utils.LogUtil;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.dom4j.Document;
|
||||||
|
import org.dom4j.Element;
|
||||||
|
import org.dom4j.io.SAXReader;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class JSONToDocumentUtils {
|
||||||
|
|
||||||
|
public static void jsonDataFormatting(JSONArray array, List<DocumentElement> children) {
|
||||||
|
for (int i = 0; i < array.size(); i++) {
|
||||||
|
JSONObject element = array.getJSONObject(i);
|
||||||
|
jsonDataFormatting(element, children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void jsonDataFormatting(JSONObject object, List<DocumentElement> children) {
|
||||||
|
for (String key : object.keySet()) {
|
||||||
|
Object value = object.get(key);
|
||||||
|
if (value instanceof JSONObject) {
|
||||||
|
List<DocumentElement> childrenElements = new LinkedList<>();
|
||||||
|
children.add(new DocumentElement(key, "object", "", childrenElements));
|
||||||
|
jsonDataFormatting((JSONObject) value, childrenElements);
|
||||||
|
} else if (value instanceof JSONArray) {
|
||||||
|
List<DocumentElement> childrenElements = new LinkedList<>();
|
||||||
|
children.add(new DocumentElement(key, "array", "", childrenElements));
|
||||||
|
jsonDataFormatting((JSONArray) value, childrenElements);
|
||||||
|
} else {
|
||||||
|
children.add(new DocumentElement(key, "string", value, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static List<DocumentElement> getDocument(String json, String type) {
|
||||||
|
try {
|
||||||
|
if (StringUtils.equals(type, "JSON")) {
|
||||||
|
List<DocumentElement> roots = new LinkedList<>();
|
||||||
|
List<DocumentElement> children = new LinkedList<>();
|
||||||
|
roots.add(new DocumentElement("root", "root", "object", "", children));
|
||||||
|
JSONObject object = JSON.parseObject(json);
|
||||||
|
jsonDataFormatting(object, children);
|
||||||
|
return roots;
|
||||||
|
} else {
|
||||||
|
return getXmlDocument(json);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtil.error(e);
|
||||||
|
return new LinkedList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从指定节点开始,递归遍历所有子节点
|
||||||
|
*/
|
||||||
|
public static void getNodes(Element node, List<DocumentElement> children) {
|
||||||
|
//递归遍历当前节点所有的子节点
|
||||||
|
List<Element> listElement = node.elements();
|
||||||
|
if (listElement.isEmpty()) {
|
||||||
|
children.add(new DocumentElement(node.getName(), "string", node.getTextTrim(), null));
|
||||||
|
}
|
||||||
|
for (Element element : listElement) {//遍历所有一级子节点
|
||||||
|
List<Element> elementNodes = element.elements();
|
||||||
|
if (elementNodes.size() > 0) {
|
||||||
|
List<DocumentElement> elements = new LinkedList<>();
|
||||||
|
children.add(new DocumentElement(element.getName(), "object", element.getTextTrim(), elements));
|
||||||
|
getNodes(element, elements);//递归
|
||||||
|
} else {
|
||||||
|
getNodes(element, children);//递归
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<DocumentElement> getXmlDocument(String xml) {
|
||||||
|
List<DocumentElement> roots = new LinkedList<>();
|
||||||
|
try {
|
||||||
|
InputStream is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
|
||||||
|
// 创建saxReader对象
|
||||||
|
SAXReader reader = new SAXReader();
|
||||||
|
// 通过read方法读取一个文件 转换成Document对象
|
||||||
|
Document document = reader.read(is);
|
||||||
|
//获取根节点元素对象
|
||||||
|
getNodes(document.getRootElement(), roots);
|
||||||
|
// 未能处理root补偿root 节点
|
||||||
|
if (roots.size() > 1) {
|
||||||
|
Element node = document.getRootElement();
|
||||||
|
List<DocumentElement> newRoots = new LinkedList<>();
|
||||||
|
newRoots.add(new DocumentElement("root", node.getName(), "object", node.getTextTrim(), roots));
|
||||||
|
return newRoots;
|
||||||
|
} else if (roots.size() == 1) {
|
||||||
|
roots.get(0).setId("root");
|
||||||
|
}
|
||||||
|
return roots;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogUtil.error(ex);
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,10 +5,14 @@
|
||||||
|
|
||||||
package org.apache.jmeter.assertions;
|
package org.apache.jmeter.assertions;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.jayway.jsonpath.JsonPath;
|
import com.jayway.jsonpath.JsonPath;
|
||||||
import com.jayway.jsonpath.Predicate;
|
import com.jayway.jsonpath.Predicate;
|
||||||
|
import io.metersphere.api.dto.definition.request.assertions.document.Condition;
|
||||||
|
import io.metersphere.api.dto.definition.request.assertions.document.ElementCondition;
|
||||||
import net.minidev.json.JSONArray;
|
import net.minidev.json.JSONArray;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.jmeter.samplers.SampleResult;
|
import org.apache.jmeter.samplers.SampleResult;
|
||||||
import org.apache.jmeter.testelement.AbstractTestElement;
|
import org.apache.jmeter.testelement.AbstractTestElement;
|
||||||
|
@ -47,6 +51,10 @@ public class JSONPathAssertion extends AbstractTestElement implements Serializab
|
||||||
return getPropertyAsString("ASS_OPTION");
|
return getPropertyAsString("ASS_OPTION");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getElementCondition() {
|
||||||
|
return getPropertyAsString("ElementCondition");
|
||||||
|
}
|
||||||
|
|
||||||
public String getJsonPath() {
|
public String getJsonPath() {
|
||||||
return this.getPropertyAsString("JSON_PATH");
|
return this.getPropertyAsString("JSON_PATH");
|
||||||
}
|
}
|
||||||
|
@ -132,6 +140,9 @@ public class JSONPathAssertion extends AbstractTestElement implements Serializab
|
||||||
case "LT":
|
case "LT":
|
||||||
msg = "Value < '%s', but found '%s'";
|
msg = "Value < '%s', but found '%s'";
|
||||||
break;
|
break;
|
||||||
|
case "DOCUMENT":
|
||||||
|
msg = (StringUtils.isNotEmpty(this.getName()) ? this.getName().split("==")[1] : "") + "校验失败,返回数据:" + (value != null ? value.toString() : "");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
msg = "Value expected to be '%s', but found '%s'";
|
msg = "Value expected to be '%s', but found '%s'";
|
||||||
|
@ -202,13 +213,70 @@ public class JSONPathAssertion extends AbstractTestElement implements Serializab
|
||||||
case "LT":
|
case "LT":
|
||||||
refFlag = isLt(str, getExpectedValue());
|
refFlag = isLt(str, getExpectedValue());
|
||||||
break;
|
break;
|
||||||
|
case "DOCUMENT":
|
||||||
|
refFlag = isDocument(str);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return refFlag;
|
return refFlag;
|
||||||
}
|
}
|
||||||
return str.equals(this.getExpectedValue());
|
return str.equals(this.getExpectedValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private String ifValue(Object value) {
|
||||||
|
if (value != null) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDocument(String resValue) {
|
||||||
|
String condition = this.getElementCondition();
|
||||||
|
if (StringUtils.isNotEmpty(condition)) {
|
||||||
|
ElementCondition elementCondition = JSON.parseObject(condition, ElementCondition.class);
|
||||||
|
boolean isTrue = true;
|
||||||
|
if (CollectionUtils.isNotEmpty(elementCondition.getConditions())) {
|
||||||
|
for (Condition item : elementCondition.getConditions()) {
|
||||||
|
String expectedValue = ifValue(item.getValue());
|
||||||
|
switch (item.getKey()) {
|
||||||
|
case "value_eq":
|
||||||
|
isTrue = StringUtils.equals(resValue, expectedValue);
|
||||||
|
break;
|
||||||
|
case "value_not_eq":
|
||||||
|
isTrue = !StringUtils.equals(resValue, expectedValue);
|
||||||
|
break;
|
||||||
|
case "value_in":
|
||||||
|
isTrue = StringUtils.contains(resValue, expectedValue);
|
||||||
|
break;
|
||||||
|
case "length_eq":
|
||||||
|
isTrue = (StringUtils.isNotEmpty(resValue) && StringUtils.isNotEmpty(expectedValue) && resValue.length() == expectedValue.length())
|
||||||
|
|| (StringUtils.isEmpty(resValue) && StringUtils.isEmpty(expectedValue));
|
||||||
|
break;
|
||||||
|
case "length_not_eq":
|
||||||
|
isTrue = (StringUtils.isNotEmpty(resValue) && StringUtils.isNotEmpty(expectedValue) && resValue.length() != expectedValue.length())
|
||||||
|
|| (StringUtils.isEmpty(resValue) || StringUtils.isEmpty(expectedValue));
|
||||||
|
break;
|
||||||
|
case "length_gt":
|
||||||
|
isTrue = (StringUtils.isNotEmpty(resValue) && StringUtils.isNotEmpty(expectedValue) && resValue.length() > expectedValue.length())
|
||||||
|
|| (StringUtils.isNotEmpty(resValue) && StringUtils.isEmpty(expectedValue));
|
||||||
|
break;
|
||||||
|
case "length_lt":
|
||||||
|
isTrue = (StringUtils.isNotEmpty(resValue) && StringUtils.isNotEmpty(expectedValue) && resValue.length() < expectedValue.length())
|
||||||
|
|| (StringUtils.isEmpty(resValue) || StringUtils.isEmpty(expectedValue));
|
||||||
|
break;
|
||||||
|
case "regular":
|
||||||
|
Pattern pattern = JMeterUtils.getPatternCache().getPattern(this.getExpectedValue());
|
||||||
|
isTrue = JMeterUtils.getMatcher().matches(resValue, pattern);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!isTrue) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isTrue;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public AssertionResult getResult(SampleResult samplerResult) {
|
public AssertionResult getResult(SampleResult samplerResult) {
|
||||||
AssertionResult result = new AssertionResult(this.getName());
|
AssertionResult result = new AssertionResult(this.getName());
|
||||||
|
|
|
@ -0,0 +1,254 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to you under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.jmeter.assertions;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.jayway.jsonpath.JsonPath;
|
||||||
|
import com.jayway.jsonpath.Predicate;
|
||||||
|
import io.metersphere.api.dto.definition.request.assertions.document.Condition;
|
||||||
|
import io.metersphere.api.dto.definition.request.assertions.document.ElementCondition;
|
||||||
|
import net.minidev.json.JSONArray;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.jmeter.samplers.SampleResult;
|
||||||
|
import org.apache.jmeter.testelement.AbstractTestElement;
|
||||||
|
import org.apache.jmeter.testelement.ThreadListener;
|
||||||
|
import org.apache.jmeter.util.JMeterUtils;
|
||||||
|
import org.apache.oro.text.regex.Pattern;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.XML;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.XMLReader;
|
||||||
|
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import javax.xml.parsers.SAXParserFactory;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the result is a well-formed XML content using {@link XMLReader}
|
||||||
|
*/
|
||||||
|
public class XMLAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(XMLAssertion.class);
|
||||||
|
private static ThreadLocal<DecimalFormat> decimalFormatter = ThreadLocal.withInitial(XMLAssertion::createDecimalFormat);
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 242L;
|
||||||
|
|
||||||
|
public String getXmlPath() {
|
||||||
|
return this.getPropertyAsString("XML_PATH");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExpectedValue() {
|
||||||
|
return this.getPropertyAsString("EXPECTED_VALUE");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCondition() {
|
||||||
|
return getPropertyAsString("ElementCondition");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DecimalFormat createDecimalFormat() {
|
||||||
|
DecimalFormat decimalFormatter = new DecimalFormat("#.#");
|
||||||
|
decimalFormatter.setMaximumFractionDigits(340);
|
||||||
|
decimalFormatter.setMinimumFractionDigits(1);
|
||||||
|
return decimalFormatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// one builder for all requests in a thread
|
||||||
|
private static final ThreadLocal<XMLReader> XML_READER = new ThreadLocal<XMLReader>() {
|
||||||
|
@Override
|
||||||
|
protected XMLReader initialValue() {
|
||||||
|
try {
|
||||||
|
XMLReader reader = SAXParserFactory.newInstance()
|
||||||
|
.newSAXParser()
|
||||||
|
.getXMLReader();
|
||||||
|
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||||
|
return reader;
|
||||||
|
} catch (SAXException | ParserConfigurationException e) {
|
||||||
|
log.error("Error initializing XMLReader in XMLAssertion", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the result of the Assertion.
|
||||||
|
* Here it checks whether the Sample data is XML.
|
||||||
|
* If so an AssertionResult containing a FailureMessage will be returned.
|
||||||
|
* Otherwise the returned AssertionResult will reflect the success of the Sample.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AssertionResult getResult(SampleResult response) {
|
||||||
|
// no error as default
|
||||||
|
AssertionResult result = new AssertionResult(getName());
|
||||||
|
String resultData = response.getResponseDataAsString();
|
||||||
|
if (resultData.length() == 0) {
|
||||||
|
return result.setResultForNull();
|
||||||
|
}
|
||||||
|
result.setFailure(false);
|
||||||
|
XMLReader builder = XML_READER.get();
|
||||||
|
if (builder != null) {
|
||||||
|
try {
|
||||||
|
builder.setErrorHandler(new LogErrorHandler());
|
||||||
|
builder.parse(new InputSource(new StringReader(resultData)));
|
||||||
|
try {
|
||||||
|
JSONObject xmlJSONObj = XML.toJSONObject(resultData);
|
||||||
|
String jsonPrettyPrintString = xmlJSONObj.toString(4);
|
||||||
|
doAssert(jsonPrettyPrintString);
|
||||||
|
} catch (Exception e) {
|
||||||
|
result.setError(true);
|
||||||
|
result.setFailure(true);
|
||||||
|
result.setFailureMessage(e.getMessage());
|
||||||
|
}
|
||||||
|
} catch (SAXException | IOException e) {
|
||||||
|
result.setError(true);
|
||||||
|
result.setFailure(true);
|
||||||
|
result.setFailureMessage(e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.setError(true);
|
||||||
|
result.setFailureMessage("Cannot initialize XMLReader in element:" + getName() + ", check jmeter.log file");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void doAssert(String jsonString) {
|
||||||
|
Object value = JsonPath.read(jsonString, this.getXmlPath(), new Predicate[0]);
|
||||||
|
if (value instanceof JSONArray) {
|
||||||
|
if (this.arrayMatched((JSONArray) value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.isEquals(value)) {
|
||||||
|
String msg = (StringUtils.isNotEmpty(this.getName()) ? this.getName().split("==")[1] : "") + "校验失败,返回数据:" + (value != null ? value.toString() : "");
|
||||||
|
throw new IllegalStateException(String.format(msg, this.getExpectedValue(), objectToString(value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isEquals(Object subj) {
|
||||||
|
String str = objectToString(subj);
|
||||||
|
return isDocument(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean arrayMatched(JSONArray value) {
|
||||||
|
if (value.isEmpty() && "[]".equals(this.getExpectedValue())) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Object[] var2 = value.toArray();
|
||||||
|
int var3 = var2.length;
|
||||||
|
|
||||||
|
for (int var4 = 0; var4 < var3; ++var4) {
|
||||||
|
Object subj = var2[var4];
|
||||||
|
if (subj == null || this.isEquals(subj)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isEquals(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String ifValue(Object value) {
|
||||||
|
if (value != null) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDocument(String resValue) {
|
||||||
|
String condition = this.getCondition();
|
||||||
|
if (StringUtils.isNotEmpty(condition)) {
|
||||||
|
ElementCondition elementCondition = JSON.parseObject(condition, ElementCondition.class);
|
||||||
|
boolean isTrue = true;
|
||||||
|
if (CollectionUtils.isNotEmpty(elementCondition.getConditions())) {
|
||||||
|
for (Condition item : elementCondition.getConditions()) {
|
||||||
|
String expectedValue = ifValue(item.getValue());
|
||||||
|
switch (item.getKey()) {
|
||||||
|
case "value_eq":
|
||||||
|
isTrue = StringUtils.equals(resValue, expectedValue);
|
||||||
|
break;
|
||||||
|
case "value_not_eq":
|
||||||
|
isTrue = !StringUtils.equals(resValue, expectedValue);
|
||||||
|
break;
|
||||||
|
case "value_in":
|
||||||
|
isTrue = StringUtils.contains(resValue, expectedValue);
|
||||||
|
break;
|
||||||
|
case "length_eq":
|
||||||
|
isTrue = (StringUtils.isNotEmpty(resValue) && StringUtils.isNotEmpty(expectedValue) && resValue.length() == expectedValue.length())
|
||||||
|
|| (StringUtils.isEmpty(resValue) && StringUtils.isEmpty(expectedValue));
|
||||||
|
break;
|
||||||
|
case "length_not_eq":
|
||||||
|
isTrue = (StringUtils.isNotEmpty(resValue) && StringUtils.isNotEmpty(expectedValue) && resValue.length() != expectedValue.length())
|
||||||
|
|| (StringUtils.isEmpty(resValue) || StringUtils.isEmpty(expectedValue));
|
||||||
|
break;
|
||||||
|
case "length_gt":
|
||||||
|
isTrue = (StringUtils.isNotEmpty(resValue) && StringUtils.isNotEmpty(expectedValue) && resValue.length() > expectedValue.length())
|
||||||
|
|| (StringUtils.isNotEmpty(resValue) && StringUtils.isEmpty(expectedValue));
|
||||||
|
break;
|
||||||
|
case "length_lt":
|
||||||
|
isTrue = (StringUtils.isNotEmpty(resValue) && StringUtils.isNotEmpty(expectedValue) && resValue.length() < expectedValue.length())
|
||||||
|
|| (StringUtils.isEmpty(resValue) || StringUtils.isEmpty(expectedValue));
|
||||||
|
break;
|
||||||
|
case "regular":
|
||||||
|
Pattern pattern = JMeterUtils.getPatternCache().getPattern(this.getExpectedValue());
|
||||||
|
isTrue = JMeterUtils.getMatcher().matches(resValue, pattern);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!isTrue) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isTrue;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String objectToString(Object subj) {
|
||||||
|
String str;
|
||||||
|
if (subj == null) {
|
||||||
|
str = "null";
|
||||||
|
} else if (subj instanceof Map) {
|
||||||
|
str = new Gson().toJson(subj);
|
||||||
|
} else if (!(subj instanceof Double) && !(subj instanceof Float)) {
|
||||||
|
str = subj.toString();
|
||||||
|
} else {
|
||||||
|
str = ((DecimalFormat) decimalFormatter.get()).format(subj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void threadStarted() {
|
||||||
|
// nothing to do on thread start
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void threadFinished() {
|
||||||
|
XML_READER.remove();
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,31 +15,61 @@
|
||||||
<div class="assertion-add" :draggable="draggable">
|
<div class="assertion-add" :draggable="draggable">
|
||||||
<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"
|
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type" :placeholder="$t('api_test.request.assertions.select_type')" size="small">
|
||||||
:placeholder="$t('api_test.request.assertions.select_type')"
|
|
||||||
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="'XPath'" :value="options.XPATH2"/>
|
<el-option :label="'XPath'" :value="options.XPATH2"/>
|
||||||
<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-option :label="$t('api_test.request.assertions.jsr223')" :value="options.JSR223"/>
|
<el-option :label="$t('api_test.request.assertions.jsr223')" :value="options.JSR223"/>
|
||||||
|
<el-option :label="$t('api_test.definition.request.document_structure')" :value="options.DOCUMENT"/>
|
||||||
</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"
|
<ms-api-assertion-text
|
||||||
:callback="after"/>
|
:is-read-only="isReadOnly"
|
||||||
|
:list="assertions.regex"
|
||||||
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX"
|
:callback="after"
|
||||||
:callback="after"/>
|
v-if="type === options.TEXT"
|
||||||
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath"
|
/>
|
||||||
v-if="type === options.JSON_PATH" :callback="after"/>
|
<ms-api-assertion-regex
|
||||||
<ms-api-assertion-x-path2 :is-read-only="isReadOnly" :list="assertions.xpath2" v-if="type === options.XPATH2"
|
:is-read-only="isReadOnly"
|
||||||
:callback="after"/>
|
:list="assertions.regex"
|
||||||
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration"
|
:callback="after"
|
||||||
v-if="type === options.DURATION" :callback="after"/>
|
v-if="type === options.REGEX"
|
||||||
<ms-api-assertion-jsr223 :is-read-only="isReadOnly" :list="assertions.jsr223" v-if="type === options.JSR223"
|
/>
|
||||||
:callback="after"/>
|
<ms-api-assertion-json-path
|
||||||
|
:is-read-only="isReadOnly"
|
||||||
|
:list="assertions.jsonPath"
|
||||||
|
:callback="after"
|
||||||
|
v-if="type === options.JSON_PATH"
|
||||||
|
/>
|
||||||
|
<ms-api-assertion-x-path2
|
||||||
|
:is-read-only="isReadOnly"
|
||||||
|
:list="assertions.xpath2"
|
||||||
|
:callback="after"
|
||||||
|
v-if="type === options.XPATH2"
|
||||||
|
/>
|
||||||
|
<ms-api-assertion-duration
|
||||||
|
v-model="time"
|
||||||
|
:is-read-only="isReadOnly"
|
||||||
|
:duration="assertions.duration"
|
||||||
|
:callback="after"
|
||||||
|
v-if="type === options.DURATION"
|
||||||
|
/>
|
||||||
|
<ms-api-assertion-jsr223
|
||||||
|
:is-read-only="isReadOnly"
|
||||||
|
:list="assertions.jsr223"
|
||||||
|
:callback="after"
|
||||||
|
v-if="type === options.JSR223"
|
||||||
|
/>
|
||||||
|
<ms-api-assertion-document
|
||||||
|
:is-read-only="isReadOnly"
|
||||||
|
v-model="time"
|
||||||
|
:document="assertions.document"
|
||||||
|
:callback="after"
|
||||||
|
v-if="type === options.DOCUMENT"
|
||||||
|
/>
|
||||||
<el-button v-if="!type" :disabled="true" type="primary" size="small">
|
<el-button v-if="!type" :disabled="true" type="primary" size="small">
|
||||||
{{ $t('api_test.request.assertions.add') }}
|
{{ $t('api_test.request.assertions.add') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
@ -47,12 +77,23 @@
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<api-json-path-suggest-button :open-tip="$t('api_test.request.assertions.json_path_suggest')"
|
<api-json-path-suggest-button
|
||||||
:clear-tip="$t('api_test.request.assertions.json_path_clear')" @open="suggestJsonOpen" @clear="clearJson"/>
|
:open-tip="$t('api_test.request.assertions.json_path_suggest')"
|
||||||
|
:clear-tip="$t('api_test.request.assertions.json_path_clear')"
|
||||||
|
@open="suggestJsonOpen"
|
||||||
|
@clear="clearJson"/>
|
||||||
|
|
||||||
<ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions" :reloadData="reloadData" style="margin-bottom: 20px"/>
|
<ms-api-assertions-edit
|
||||||
|
:is-read-only="isReadOnly"
|
||||||
|
:assertions="assertions"
|
||||||
|
:apiId="apiId"
|
||||||
|
:reloadData="reloadData"
|
||||||
|
style="margin-bottom: 20px"/>
|
||||||
|
|
||||||
<ms-api-jsonpath-suggest :tip="$t('api_test.request.extract.suggest_tip')" @addSuggest="addJsonPathSuggest" ref="jsonpathSuggest"/>
|
<ms-api-jsonpath-suggest
|
||||||
|
:tip="$t('api_test.request.extract.suggest_tip')"
|
||||||
|
@addSuggest="addJsonPathSuggest"
|
||||||
|
ref="jsonpathSuggest"/>
|
||||||
|
|
||||||
</api-base-component>
|
</api-base-component>
|
||||||
</template>
|
</template>
|
||||||
|
@ -71,6 +112,7 @@ import {getUUID} from "@/common/js/utils";
|
||||||
import ApiJsonPathSuggestButton from "./ApiJsonPathSuggestButton";
|
import ApiJsonPathSuggestButton from "./ApiJsonPathSuggestButton";
|
||||||
import MsApiJsonpathSuggest from "./ApiJsonpathSuggest";
|
import MsApiJsonpathSuggest from "./ApiJsonpathSuggest";
|
||||||
import ApiBaseComponent from "../../../automation/scenario/common/ApiBaseComponent";
|
import ApiBaseComponent from "../../../automation/scenario/common/ApiBaseComponent";
|
||||||
|
import MsApiAssertionDocument from "./document/DocumentHeader";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsApiAssertions",
|
name: "MsApiAssertions",
|
||||||
|
@ -82,7 +124,11 @@ export default {
|
||||||
MsApiAssertionJsr223,
|
MsApiAssertionJsr223,
|
||||||
MsApiJsonpathSuggestList,
|
MsApiJsonpathSuggestList,
|
||||||
MsApiAssertionJsonPath,
|
MsApiAssertionJsonPath,
|
||||||
MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText
|
MsApiAssertionsEdit,
|
||||||
|
MsApiAssertionDuration,
|
||||||
|
MsApiAssertionRegex,
|
||||||
|
MsApiAssertionText,
|
||||||
|
MsApiAssertionDocument,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
draggable: {
|
draggable: {
|
||||||
|
@ -100,6 +146,7 @@ export default {
|
||||||
assertions: {},
|
assertions: {},
|
||||||
node: {},
|
node: {},
|
||||||
request: {},
|
request: {},
|
||||||
|
apiId: String,
|
||||||
response: {},
|
response: {},
|
||||||
customizeStyle: {
|
customizeStyle: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
|
@ -1,51 +1,64 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-loading="loading">
|
<div v-loading="loading">
|
||||||
<div class="assertion-item-editing regex" v-if="assertions.regex.length > 0">
|
<div class="assertion-item-editing regex" v-if="assertions.regex.length > 0">
|
||||||
<div>
|
<div> {{ $t("api_test.request.assertions.regex") }}</div>
|
||||||
{{ $t("api_test.request.assertions.regex") }}
|
|
||||||
</div>
|
|
||||||
<div class="regex-item" v-for="(regex, index) in assertions.regex" :key="index">
|
<div class="regex-item" v-for="(regex, index) in assertions.regex" :key="index">
|
||||||
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex"
|
<ms-api-assertion-regex
|
||||||
:regex="regex" :edit="true" :index="index"/>
|
:is-read-only="isReadOnly"
|
||||||
|
:list="assertions.regex"
|
||||||
|
:regex="regex"
|
||||||
|
:edit="true"
|
||||||
|
:index="index"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="assertion-item-editing json_path" v-if="assertions.jsonPath.length > 0">
|
<div class="assertion-item-editing json_path" v-if="assertions.jsonPath.length > 0">
|
||||||
<div>
|
<div> {{ 'JSONPath' }}</div>
|
||||||
{{ 'JSONPath' }}
|
|
||||||
</div>
|
|
||||||
<div class="regex-item" v-for="(jsonPath, index) in assertions.jsonPath" :key="index">
|
<div class="regex-item" v-for="(jsonPath, index) in assertions.jsonPath" :key="index">
|
||||||
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath"
|
<ms-api-assertion-json-path
|
||||||
:json-path="jsonPath" :edit="true" :index="index"/>
|
:is-read-only="isReadOnly"
|
||||||
|
:list="assertions.jsonPath"
|
||||||
|
:json-path="jsonPath"
|
||||||
|
:edit="true"
|
||||||
|
:index="index"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="assertion-item-editing x_path" v-if="assertions.xpath2.length > 0">
|
<div class="assertion-item-editing x_path" v-if="assertions.xpath2.length > 0">
|
||||||
<div>
|
<div> {{ 'XPath' }}</div>
|
||||||
{{ 'XPath' }}
|
|
||||||
</div>
|
|
||||||
<div class="regex-item" v-for="(xPath, index) in assertions.xpath2" :key="index">
|
<div class="regex-item" v-for="(xPath, index) in assertions.xpath2" :key="index">
|
||||||
<ms-api-assertion-x-path2 :is-read-only="isReadOnly" :list="assertions.xpath2"
|
<ms-api-assertion-x-path2
|
||||||
:x-path2="xPath" :edit="true" :index="index"/>
|
:is-read-only="isReadOnly"
|
||||||
|
:list="assertions.xpath2"
|
||||||
|
:x-path2="xPath"
|
||||||
|
:edit="true"
|
||||||
|
:index="index"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="assertion-item-editing jsr223" v-if="assertions.jsr223.length > 0">
|
<div class="assertion-item-editing jsr223" v-if="assertions.jsr223.length > 0">
|
||||||
<div>
|
<div> {{ $t("api_test.request.assertions.script") }}</div>
|
||||||
{{ $t("api_test.request.assertions.script") }}
|
|
||||||
</div>
|
|
||||||
<div class="regex-item" v-for="(assertion, index) in assertions.jsr223" :key="index">
|
<div class="regex-item" v-for="(assertion, index) in assertions.jsr223" :key="index">
|
||||||
<ms-api-assertion-jsr223 :is-read-only="isReadOnly" :list="assertions.jsr223"
|
<ms-api-assertion-jsr223
|
||||||
:assertion="assertion" :edit="true" :index="index"/>
|
:is-read-only="isReadOnly"
|
||||||
|
:list="assertions.jsr223"
|
||||||
|
:assertion="assertion"
|
||||||
|
:edit="true"
|
||||||
|
:index="index"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="assertion-item-editing response-time" v-if="isShow">
|
<div class="assertion-item-editing response-time" v-if="isShow">
|
||||||
<div>
|
<div> {{ $t("api_test.request.assertions.response_time") }}</div>
|
||||||
{{ $t("api_test.request.assertions.response_time") }}
|
<ms-api-assertion-duration
|
||||||
|
:is-read-only="isReadOnly"
|
||||||
|
v-model="assertions.duration.value"
|
||||||
|
:duration="assertions.duration"
|
||||||
|
:edit="true"/>
|
||||||
</div>
|
</div>
|
||||||
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="assertions.duration.value"
|
<div class="assertion-item-editing response-time" v-if="isDocument">
|
||||||
:duration="assertions.duration" :edit="true"/>
|
<div> {{ assertions.document.type }}-{{ $t("api_test.definition.request.document_structure") }}</div>
|
||||||
|
<ms-document-body :document="assertions.document" :apiId="apiId"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -63,12 +76,17 @@
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
MsApiAssertionXPath2,
|
MsApiAssertionXPath2,
|
||||||
MsApiAssertionJsr223, MsApiAssertionJsonPath, MsApiAssertionDuration, MsApiAssertionRegex
|
MsApiAssertionJsr223,
|
||||||
|
MsApiAssertionJsonPath,
|
||||||
|
MsApiAssertionDuration,
|
||||||
|
MsApiAssertionRegex,
|
||||||
|
MsDocumentBody: () => import("./document/DocumentBody"),
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
assertions: {},
|
assertions: {},
|
||||||
reloadData: String,
|
reloadData: String,
|
||||||
|
apiId: String,
|
||||||
isReadOnly: {
|
isReadOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
@ -83,6 +101,9 @@
|
||||||
isShow() {
|
isShow() {
|
||||||
let rt = this.assertions.duration;
|
let rt = this.assertions.duration;
|
||||||
return rt.value !== undefined && rt.value !== 0;
|
return rt.value !== undefined && rt.value !== 0;
|
||||||
|
},
|
||||||
|
isDocument() {
|
||||||
|
return this.assertions.document.data && (this.assertions.document.data.json.length > 0 || this.assertions.document.data.xml.length > 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
|
@ -0,0 +1,461 @@
|
||||||
|
<template>
|
||||||
|
<div class="ms-border">
|
||||||
|
<div style="margin-bottom: 10px">
|
||||||
|
<span class="ms-import" @click="importData">
|
||||||
|
<i class="el-icon-edit-outline" style="font-size: 16px"/>
|
||||||
|
{{ $t('commons.import') }}
|
||||||
|
</span>
|
||||||
|
<el-checkbox v-model="checked" @change="checkedAPI">跟随API定义</el-checkbox>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
:data="tableData"
|
||||||
|
:span-method="objectSpanMethod"
|
||||||
|
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
|
||||||
|
@cell-mouse-enter="editRow"
|
||||||
|
@cell-mouse-leave="editLeave"
|
||||||
|
row-key="id"
|
||||||
|
default-expand-all
|
||||||
|
v-loading="loading">
|
||||||
|
|
||||||
|
<el-table-column prop="name" :label="$t('api_test.definition.request.esb_table.name')" width="230">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-input v-if="scope.row.status && scope.column.fixed && scope.row.id!=='root'" v-model="scope.row.name" style="width: 140px" size="mini"/>
|
||||||
|
<span v-else>{{ scope.row.name }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="include" width="78" label="必含" :render-header="renderHeader">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-checkbox v-model="scope.row.include" @change="handleCheckOneChange" :disabled="checked"/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="typeVerification" width="100" label="类型校验" :render-header="renderHeaderType">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-checkbox v-model="scope.row.typeVerification" @change="handleCheckOneChange" :disabled="checked"/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="type" :label="$t('api_test.definition.request.esb_table.type')" width="120">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-select v-model="scope.row.type" :placeholder="$t('commons.please_select')" size="mini" @change="changeType(scope.row)" :disabled="checked">
|
||||||
|
<el-option v-for="item in typeSelectOptions " :key="item.value" :label="item.label" :value="item.value"/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="arrayVerification" width="140" label="校验数组内元素" :render-header="renderHeaderArray">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-checkbox v-model="scope.row.arrayVerification" @change="handleCheckOneChange" v-if="scope.row.type==='array'" :disabled="checked"/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="contentVerification" label="内容校验" width="200">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-select v-model="scope.row.contentVerification" :placeholder="$t('commons.please_select')" size="mini" :disabled="checked">
|
||||||
|
<el-option v-for="item in verificationOptions " :key="item.value" :label="item.label" :value="item.value"/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="expectedOutcome" label="预期结果" min-width="200">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-input v-if="scope.row.status && scope.column.fixed" v-model="scope.row.expectedOutcome" size="mini"/>
|
||||||
|
<span v-else>{{ scope.row.expectedOutcome }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column :label="$t('commons.operating')" width="200" fixed="right">
|
||||||
|
<template v-slot:default="scope">
|
||||||
|
<span>
|
||||||
|
<el-button size="mini" type="text" circle @click="addVerification(scope.row)" :disabled="scope.row.type ==='object' || checked || scope.row.id==='root'">添加校验</el-button>
|
||||||
|
<el-button size="mini" type="text" @click="addRow(scope.row)" :disabled="(scope.row.type !=='object' && scope.row.type !=='array') || checked">添加子字段</el-button>
|
||||||
|
<el-button size="mini" type="text" @click="remove(scope.row)" :disabled="checked || scope.row.id==='root'">{{ $t('commons.remove') }}</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<ms-document-import :document="document" @setJSONData="setJSONData" ref="import"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import {getUUID} from "@/common/js/utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "MsDocumentBody",
|
||||||
|
components: {
|
||||||
|
MsDocumentImport: () => import("./DocumentImport"),
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
document: {},
|
||||||
|
apiId: String,
|
||||||
|
showOptionsButton: Boolean,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
verificationOptions: [
|
||||||
|
{value: 'none', label: '不校验[]'},
|
||||||
|
{value: 'value_eq', label: '值-等于[value=]'},
|
||||||
|
{value: 'value_not_eq', label: '值-不等于[value!=]'},
|
||||||
|
{value: 'value_in', label: '值-包含[include=]'},
|
||||||
|
{value: 'length_eq', label: '长度-等于[length=]'},
|
||||||
|
{value: 'length_not_eq', label: '长度-不等于[length!=]'},
|
||||||
|
{value: 'length_gt', label: '长度-大于[length>]'},
|
||||||
|
{value: 'length_lt', label: '长度-小于[length<]'},
|
||||||
|
{value: 'regular', label: '正则匹配'}
|
||||||
|
],
|
||||||
|
typeSelectOptions: [
|
||||||
|
{value: 'object', label: 'object'},
|
||||||
|
{value: 'array', label: 'array'},
|
||||||
|
{value: 'string', label: 'string'},
|
||||||
|
{value: 'int', label: 'int'},
|
||||||
|
{value: 'number', label: 'number'},
|
||||||
|
|
||||||
|
],
|
||||||
|
requiredSelectOptions: [
|
||||||
|
{value: true, label: '必填'},
|
||||||
|
{value: false, label: '非必填'},
|
||||||
|
],
|
||||||
|
checked: false,
|
||||||
|
tableData: Array,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.document.type === "JSON") {
|
||||||
|
this.checked = this.document.data.jsonFollowAPI ? true : false;
|
||||||
|
} else if (this.document.type === "XML") {
|
||||||
|
this.checked = this.document.data.xmlFollowAPI ? true : false;
|
||||||
|
}
|
||||||
|
this.changeData();
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'document.type'() {
|
||||||
|
if (this.document.type === "JSON") {
|
||||||
|
this.checked = this.document.data.jsonFollowAPI ? true : false;
|
||||||
|
} else if (this.document.type === "XML") {
|
||||||
|
this.checked = this.document.data.xmlFollowAPI ? true : false;
|
||||||
|
}
|
||||||
|
this.changeData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setJSONData(data) {
|
||||||
|
this.checked = false;
|
||||||
|
this.tableData = data;
|
||||||
|
if (this.document.type === "JSON") {
|
||||||
|
this.document.data.json = this.tableData;
|
||||||
|
this.document.data.jsonFollowAPI = this.apiId;
|
||||||
|
} else if (this.document.type === "XML") {
|
||||||
|
this.document.data.xml = this.tableData;
|
||||||
|
this.document.data.xmlFollowAPI = this.apiId;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
checkedAPI() {
|
||||||
|
this.changeData();
|
||||||
|
},
|
||||||
|
reload() {
|
||||||
|
this.loading = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getAPI() {
|
||||||
|
let url = "/api/definition/getDocument/" + this.apiId + "/" + this.document.type;
|
||||||
|
this.$get(url, response => {
|
||||||
|
if (response.data) {
|
||||||
|
this.tableData = response.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
changeData() {
|
||||||
|
if (this.document.data) {
|
||||||
|
this.tableData = [];
|
||||||
|
if (this.document.type === "JSON") {
|
||||||
|
this.document.data.jsonFollowAPI = this.checked ? this.apiId : "";
|
||||||
|
if (this.document.data.jsonFollowAPI) {
|
||||||
|
this.getAPI();
|
||||||
|
} else {
|
||||||
|
this.tableData = this.document.data.json;
|
||||||
|
}
|
||||||
|
} else if (this.document.type === "XML") {
|
||||||
|
this.document.data.xmlFollowAPI = this.checked ? this.apiId : "";
|
||||||
|
if (this.document.data.xmlFollowAPI) {
|
||||||
|
this.getAPI();
|
||||||
|
} else {
|
||||||
|
this.tableData = this.document.data.xml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.reload();
|
||||||
|
}
|
||||||
|
if (this.tableData && this.tableData.length > 0) {
|
||||||
|
this.tableData.forEach(row => {
|
||||||
|
if (row.name == null || row.name === "") {
|
||||||
|
this.remove(row);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
objectSpanMethod({row, column, rowIndex, columnIndex}) {
|
||||||
|
if (columnIndex === 0 || columnIndex === 1 || columnIndex === 2 || columnIndex === 3 || columnIndex === 4) {
|
||||||
|
return {
|
||||||
|
rowspan: row.rowspan,
|
||||||
|
colspan: row.rowspan > 0 ? 1 : 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changeType(row) {
|
||||||
|
row.children = [];
|
||||||
|
},
|
||||||
|
getValue(key) {
|
||||||
|
let value = "";
|
||||||
|
this.verificationOptions.forEach(item => {
|
||||||
|
if (key === item.value) {
|
||||||
|
value = item.label;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
renderHeader(h, {column}) {
|
||||||
|
return h(
|
||||||
|
'span', [
|
||||||
|
h('el-checkbox', {
|
||||||
|
style: 'margin-right:5px;',
|
||||||
|
on: {
|
||||||
|
change: this.handleCheckAllChange
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
h('span', column.label)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
renderHeaderType(h, {column}) {
|
||||||
|
return h(
|
||||||
|
'span', [
|
||||||
|
h('el-checkbox', {
|
||||||
|
style: 'margin-right:5px;',
|
||||||
|
on: {
|
||||||
|
change: this.handleType
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
h('span', column.label)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
renderHeaderArray(h, {column}) {
|
||||||
|
return h(
|
||||||
|
'span', [
|
||||||
|
h('el-checkbox', {
|
||||||
|
style: 'margin-right:5px;',
|
||||||
|
on: {
|
||||||
|
change: this.handleArray
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
h('span', column.label)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
childrenChecked(arr, type, val) {
|
||||||
|
if (arr && arr.length > 0) {
|
||||||
|
arr.forEach(item => {
|
||||||
|
if (type === 1) {
|
||||||
|
item.include = val
|
||||||
|
}
|
||||||
|
if (type === 2) {
|
||||||
|
item.typeVerification = val
|
||||||
|
}
|
||||||
|
if (type === 3) {
|
||||||
|
item.arrayVerification = val
|
||||||
|
}
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
this.childrenChecked(item.children, type, val);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCheckAllChange(val) {
|
||||||
|
this.tableData.forEach(item => {
|
||||||
|
item.include = val;
|
||||||
|
this.childrenChecked(item.children, 1, val);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleType(val) {
|
||||||
|
this.tableData.forEach(item => {
|
||||||
|
item.typeVerification = val;
|
||||||
|
this.childrenChecked(item.children, 2, val);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleArray(val) {
|
||||||
|
this.tableData.forEach(item => {
|
||||||
|
item.arrayVerification = val;
|
||||||
|
this.childrenChecked(item.children, 3, val);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleCheckOneChange(val) {
|
||||||
|
},
|
||||||
|
importData() {
|
||||||
|
this.$refs.import.openOneClickOperation();
|
||||||
|
},
|
||||||
|
remove(row) {
|
||||||
|
this.removeTableRow(row);
|
||||||
|
},
|
||||||
|
addRow(row) {
|
||||||
|
//首先保存当前行内容
|
||||||
|
row.type = "object";
|
||||||
|
let newRow = this.getNewRow();
|
||||||
|
row.children.push(newRow);
|
||||||
|
},
|
||||||
|
verSet(arr, row) {
|
||||||
|
// 第三条
|
||||||
|
if (row.groupId) {
|
||||||
|
const index = arr.findIndex(d => d.id === row.groupId);
|
||||||
|
if (index !== -1) {
|
||||||
|
arr[index].rowspan = arr[index].rowspan + 1;
|
||||||
|
}
|
||||||
|
} else if (row.rowspan > 0) {
|
||||||
|
const index = arr.findIndex(d => d.id === row.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
arr[index].rowspan = arr[index].rowspan + 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
row.rowspan = 2;
|
||||||
|
}
|
||||||
|
arr.forEach(item => {
|
||||||
|
// 找到当前行的位置
|
||||||
|
if (item.id === row.id) {
|
||||||
|
let newRow = JSON.parse(JSON.stringify(row));
|
||||||
|
newRow.id = getUUID();
|
||||||
|
newRow.groupId = !row.groupId ? row.id : row.groupId;
|
||||||
|
newRow.rowspan = 0;
|
||||||
|
if (row.type !== "object" || row.type !== "array") {
|
||||||
|
newRow.children = [];
|
||||||
|
}
|
||||||
|
const index = arr.findIndex(d => d.id === item.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
arr.splice(index + 1, 0, newRow);
|
||||||
|
} else {
|
||||||
|
arr.push(newRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
this.verSet(item.children, row);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addVerification(row) {
|
||||||
|
if (!row.groupId && row.rowspan == 0) {
|
||||||
|
row.rowspan = 2;
|
||||||
|
}
|
||||||
|
this.verSet(this.tableData, row);
|
||||||
|
},
|
||||||
|
confirm(row) {
|
||||||
|
this.validateRowData(row) ? row.status = '' : row.status;
|
||||||
|
if (row.status === "") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
getNewRow() {
|
||||||
|
let row = {
|
||||||
|
id: getUUID(),
|
||||||
|
name: "",
|
||||||
|
include: false,
|
||||||
|
status: true,
|
||||||
|
typeVerification: false,
|
||||||
|
type: "string",
|
||||||
|
groupId: "",
|
||||||
|
rowspan: 1,
|
||||||
|
arrayVerification: false,
|
||||||
|
contentVerification: "none",
|
||||||
|
expectedOutcome: "",
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
return row;
|
||||||
|
},
|
||||||
|
validateRowData(row) {
|
||||||
|
if (row.name == null || row.name === '') {
|
||||||
|
this.$warning("参数名" + "不能为空,且不能包含英文小数点[.]");
|
||||||
|
return false;
|
||||||
|
} else if (row.name.indexOf(".") > 0) {
|
||||||
|
this.$warning("参数名[" + row.name + "]不合法,不能包含英文小数点\".\"!");
|
||||||
|
return false;
|
||||||
|
} else if (row.type == null || row.type === '') {
|
||||||
|
this.$warning("类型" + "不能为空!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
editRow(row, column, cell) {
|
||||||
|
if (this.checked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
column.fixed = true;
|
||||||
|
row.status = true;
|
||||||
|
},
|
||||||
|
editLeave(row, column, cell, event) {
|
||||||
|
column.fixed = false;
|
||||||
|
row.status = false;
|
||||||
|
},
|
||||||
|
removeVerSet(arr, row) {
|
||||||
|
// 第三条
|
||||||
|
if (!row.groupId) {
|
||||||
|
const index = arr.findIndex(d => d.groupId === row.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
// 把合并权限让出去
|
||||||
|
arr[index].rowspan = row.rowspan - 1;
|
||||||
|
arr[index].id = row.id;
|
||||||
|
arr[index].groupId = "";
|
||||||
|
}
|
||||||
|
} else if (row.groupId) {
|
||||||
|
const index = arr.findIndex(d => d.id === row.groupId);
|
||||||
|
if (index !== -1) {
|
||||||
|
arr[index].rowspan = arr[index].rowspan - 1;
|
||||||
|
}
|
||||||
|
} else if (row.rowspan > 1) {
|
||||||
|
const index = arr.findIndex(d => d.id === row.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
arr[index].rowspan = arr[index].rowspan - 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
row.rowspan = 1;
|
||||||
|
}
|
||||||
|
arr.forEach(item => {
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
this.removeVerSet(item.children, row);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeTableRow(row) {
|
||||||
|
this.removeVerSet(this.tableData, row);
|
||||||
|
this.removeFromDataStruct(this.tableData, row);
|
||||||
|
},
|
||||||
|
removeFromDataStruct(dataStruct, row) {
|
||||||
|
if (dataStruct == null || dataStruct.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let rowIndex = dataStruct.indexOf(row);
|
||||||
|
if (rowIndex >= 0) {
|
||||||
|
dataStruct.splice(rowIndex, 1);
|
||||||
|
} else {
|
||||||
|
dataStruct.forEach(itemData => {
|
||||||
|
if (itemData.children != null && itemData.children.length > 0) {
|
||||||
|
this.removeFromDataStruct(itemData.children, row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.ms-import {
|
||||||
|
margin: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.ms-import:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
border-color: #783887;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,96 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-row :gutter="10" type="flex" justify="space-between" align="middle">
|
||||||
|
<el-col>
|
||||||
|
<el-select v-model="document.type" size="small">
|
||||||
|
<el-option label="JSON" value="JSON"/>
|
||||||
|
<el-option label="XML" value="XML"/>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
<el-col class="assertion-btn">
|
||||||
|
<el-button :disabled="isReadOnly" type="danger" size="mini" icon="el-icon-delete" circle @click="remove" v-if="edit"/>
|
||||||
|
<el-button :disabled="isReadOnly" type="primary" size="small" @click="add" v-else>
|
||||||
|
{{ $t('api_test.request.assertions.add') }}
|
||||||
|
</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import {getUUID} from "@/common/js/utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "DocumentHeader",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
value: [Number, String],
|
||||||
|
document: {},
|
||||||
|
edit: Boolean,
|
||||||
|
callback: Function,
|
||||||
|
isReadOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
add() {
|
||||||
|
if (this.document.type === "JSON" && this.document.data.json.length === 0) {
|
||||||
|
let obj = {
|
||||||
|
id: "root",
|
||||||
|
name: "root",
|
||||||
|
status: true,
|
||||||
|
groupId: "",
|
||||||
|
rowspan: 1,
|
||||||
|
include: false,
|
||||||
|
typeVerification: false,
|
||||||
|
type: "object",
|
||||||
|
arrayVerification: false,
|
||||||
|
contentVerifications: "none",
|
||||||
|
expectedOutcome: "",
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
this.document.data.json.push(obj);
|
||||||
|
}
|
||||||
|
if (this.document.type === "XML" && this.document.data.xml.length === 0) {
|
||||||
|
let obj = {
|
||||||
|
id: getUUID(),
|
||||||
|
name: "root",
|
||||||
|
status: true,
|
||||||
|
groupId: "",
|
||||||
|
rowspan: 1,
|
||||||
|
include: false,
|
||||||
|
typeVerification: false,
|
||||||
|
type: "object",
|
||||||
|
arrayVerification: false,
|
||||||
|
contentVerifications: "none",
|
||||||
|
expectedOutcome: "",
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
this.document.data.xml.push(obj);
|
||||||
|
}
|
||||||
|
this.callback();
|
||||||
|
},
|
||||||
|
remove() {
|
||||||
|
},
|
||||||
|
change(value) {
|
||||||
|
|
||||||
|
},
|
||||||
|
input(value) {
|
||||||
|
|
||||||
|
},
|
||||||
|
validate() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.assertion-btn {
|
||||||
|
text-align: center;
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,117 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('commons.import') + document.type"
|
||||||
|
:visible.sync="importVisible"
|
||||||
|
width="50%"
|
||||||
|
append-to-body
|
||||||
|
show-close
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@closed="handleClose">
|
||||||
|
<div style="height: 400px">
|
||||||
|
<ms-code-edit
|
||||||
|
:mode="mode"
|
||||||
|
:data.sync="json" theme="eclipse" :modes="[]"
|
||||||
|
ref="codeEdit"/>
|
||||||
|
</div>
|
||||||
|
<span slot="footer" class="dialog-footer">
|
||||||
|
<ms-dialog-footer
|
||||||
|
@cancel="importVisible = false"
|
||||||
|
@confirm="saveConfirm"/>
|
||||||
|
</span>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MsDialogFooter from '../../../../../common/components/MsDialogFooter'
|
||||||
|
import MsCodeEdit from "../../../../../common/components/MsCodeEdit";
|
||||||
|
import json5 from 'json5';
|
||||||
|
import MsConvert from "@/business/components/common/json-schema/convert/convert";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "MsDocumentImport",
|
||||||
|
components: {MsDialogFooter, MsCodeEdit},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
importVisible: false,
|
||||||
|
activeName: "JSON",
|
||||||
|
mode: "json",
|
||||||
|
json: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {},
|
||||||
|
props: {
|
||||||
|
document: {}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openOneClickOperation() {
|
||||||
|
this.mode = this.document.type.toLowerCase();
|
||||||
|
this.importVisible = true;
|
||||||
|
},
|
||||||
|
checkIsJson(json) {
|
||||||
|
try {
|
||||||
|
json5.parse(json);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
* 验证xml格式的正确性
|
||||||
|
*/
|
||||||
|
validateXML(xmlContent) {
|
||||||
|
//errorCode 0是xml正确,1是xml错误,2是无法验证
|
||||||
|
let xmlDoc, errorMessage, errorCode = 0;
|
||||||
|
if (document.implementation.createDocument) {
|
||||||
|
let parser = new DOMParser();
|
||||||
|
xmlDoc = parser.parseFromString(xmlContent, "text/xml");
|
||||||
|
let error = xmlDoc.getElementsByTagName("parsererror");
|
||||||
|
if (error.length > 0) {
|
||||||
|
if (xmlDoc.documentElement.nodeName == "parsererror") {
|
||||||
|
errorCode = 1;
|
||||||
|
errorMessage = xmlDoc.documentElement.childNodes[0].nodeValue;
|
||||||
|
} else {
|
||||||
|
errorCode = 1;
|
||||||
|
errorMessage = xmlDoc.getElementsByTagName("parsererror")[0].innerHTML;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorMessage = "格式正确";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorCode = 2;
|
||||||
|
errorMessage = "浏览器不支持验证,无法验证xml正确性";
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"msg": errorMessage,
|
||||||
|
"error_code": errorCode
|
||||||
|
};
|
||||||
|
},
|
||||||
|
saveConfirm() {
|
||||||
|
if (this.document.type === "JSON" && !this.checkIsJson(this.json)) {
|
||||||
|
this.$error("导入的数据非JSON格式");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.document.type === "XML" && this.validateXML(this.json).error_code > 0) {
|
||||||
|
this.$error("导入的数据非XML格式");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let url = "/api/definition/jsonGenerator";
|
||||||
|
this.$post(url, {raw: this.json, type: this.document.type}, response => {
|
||||||
|
if (response.data) {
|
||||||
|
console.log(response.data)
|
||||||
|
this.$emit('setJSONData', response.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.importVisible = false;
|
||||||
|
},
|
||||||
|
handleClose() {
|
||||||
|
this.importVisible = false;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -137,6 +137,8 @@ export default {
|
||||||
let data = MsConvert.format(JSON.parse(this.body.raw));
|
let data = MsConvert.format(JSON.parse(this.body.raw));
|
||||||
if (this.body.jsonSchema) {
|
if (this.body.jsonSchema) {
|
||||||
this.body.jsonSchema = this.deepAssign(this.body.jsonSchema, data);
|
this.body.jsonSchema = this.deepAssign(this.body.jsonSchema, data);
|
||||||
|
}else{
|
||||||
|
this.body.jsonSchema = data;
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
this.body.jsonSchema = "";
|
this.body.jsonSchema = "";
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<ms-jmx-step :request="api.request" :response="responseData"/>
|
<ms-jmx-step :request="api.request" :apiId="api.id" :response="responseData"/>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 加载用例 -->
|
<!-- 加载用例 -->
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
<ms-request-result-tail :response="responseData" ref="runResult"/>
|
<ms-request-result-tail :response="responseData" ref="runResult"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ms-jmx-step :request="api.request" :response="responseData"/>
|
<ms-jmx-step :request="api.request" :apiId="api.id" :response="responseData"/>
|
||||||
|
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<ms-request-result-tail :response="responseData" :currentProtocol="currentProtocol" ref="runResult"/>
|
<ms-request-result-tail :response="responseData" :currentProtocol="currentProtocol" ref="runResult"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ms-jmx-step :request="api.request" :response="responseData"/>
|
<ms-jmx-step :request="api.request" :apiId="api.id" :response="responseData"/>
|
||||||
|
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<ms-jmx-step :request="api.request" :response="responseData"/>
|
<ms-jmx-step :request="api.request" :apiId="api.id" :response="responseData"/>
|
||||||
|
|
||||||
<div v-if="api.method=='ESB'">
|
<div v-if="api.method=='ESB'">
|
||||||
<p class="tip">{{$t('api_test.definition.request.res_param')}}</p>
|
<p class="tip">{{$t('api_test.definition.request.res_param')}}</p>
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
@copyRow="copyRow"
|
@copyRow="copyRow"
|
||||||
@remove="remove"
|
@remove="remove"
|
||||||
:response="response"
|
:response="response"
|
||||||
|
:apiId="apiId"
|
||||||
:is-read-only="isReadOnly"
|
:is-read-only="isReadOnly"
|
||||||
:assertions="row"/>
|
:assertions="row"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -97,6 +98,7 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
request: {},
|
request: {},
|
||||||
response: {},
|
response: {},
|
||||||
|
apiId: String,
|
||||||
showScript: {
|
showScript: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
|
|
|
@ -103,6 +103,7 @@ export const ASSERTION_TYPE = {
|
||||||
DURATION: "Duration",
|
DURATION: "Duration",
|
||||||
JSR223: "JSR223",
|
JSR223: "JSR223",
|
||||||
XPATH2: "XPath2",
|
XPATH2: "XPath2",
|
||||||
|
DOCUMENT: "Document",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ASSERTION_REGEX_SUBJECT = {
|
export const ASSERTION_REGEX_SUBJECT = {
|
||||||
|
@ -126,8 +127,7 @@ export class BaseConfig {
|
||||||
if (!(this[name] instanceof Array)) {
|
if (!(this[name] instanceof Array)) {
|
||||||
if (notUndefined === true) {
|
if (notUndefined === true) {
|
||||||
this[name] = options[name] === undefined ? this[name] : options[name];
|
this[name] = options[name] === undefined ? this[name] : options[name];
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this[name] = options[name];
|
this[name] = options[name];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -782,6 +782,7 @@ export class Assertions extends BaseConfig {
|
||||||
this.xpath2 = [];
|
this.xpath2 = [];
|
||||||
this.duration = undefined;
|
this.duration = undefined;
|
||||||
this.enable = true;
|
this.enable = true;
|
||||||
|
this.document = {type: "JSON", data: {xmlFollowAPI: false, jsonFollowAPI: false, json: [], xml: []}};
|
||||||
this.set(options);
|
this.set(options);
|
||||||
this.sets({text: Text, regex: Regex, jsonPath: JSONPath, jsr223: AssertionJSR223, xpath2: XPath2}, options);
|
this.sets({text: Text, regex: Regex, jsonPath: JSONPath, jsr223: AssertionJSR223, xpath2: XPath2}, options);
|
||||||
}
|
}
|
||||||
|
|
|
@ -955,6 +955,7 @@ export default {
|
||||||
add_data: "Add Data"
|
add_data: "Add Data"
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
|
document_structure: "Document structure verification",
|
||||||
grade_info: "Filter by rank",
|
grade_info: "Filter by rank",
|
||||||
grade_order_asc: "from low to high by use case level",
|
grade_order_asc: "from low to high by use case level",
|
||||||
grade_order_desc: "from high to low by use case level",
|
grade_order_desc: "from high to low by use case level",
|
||||||
|
|
|
@ -963,6 +963,7 @@ export default {
|
||||||
add_data: "去添加"
|
add_data: "去添加"
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
|
document_structure: "文档结构校验",
|
||||||
grade_info: "按等级筛选",
|
grade_info: "按等级筛选",
|
||||||
grade_order_asc: "按用例等级从低到高",
|
grade_order_asc: "按用例等级从低到高",
|
||||||
grade_order_desc: "按用例等级从高到低",
|
grade_order_desc: "按用例等级从高到低",
|
||||||
|
|
|
@ -961,6 +961,7 @@ export default {
|
||||||
add_data: "去添加"
|
add_data: "去添加"
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
|
document_structure: "文檔結構校驗",
|
||||||
grade_info: "按等級篩選",
|
grade_info: "按等級篩選",
|
||||||
grade_order_asc: "按用例等級從低到高",
|
grade_order_asc: "按用例等級從低到高",
|
||||||
grade_order_desc: "按用例等級從高到低",
|
grade_order_desc: "按用例等級從高到低",
|
||||||
|
|
Loading…
Reference in New Issue