feat(接口定义): 断言-文档结构校验基本功能完成

This commit is contained in:
fit2-zhao 2021-11-04 12:54:59 +08:00 committed by fit2-zhao
parent 3286ba3b67
commit 316394076b
28 changed files with 1929 additions and 129 deletions

View File

@ -457,6 +457,12 @@
<artifactId>generex</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
<build>

View File

@ -8,6 +8,8 @@ import io.metersphere.api.dto.automation.ApiScenarioRequest;
import io.metersphere.api.dto.automation.ReferenceDTO;
import io.metersphere.api.dto.definition.*;
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.SwaggerUrlRequest;
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.PermissionConstants;
import io.metersphere.commons.json.JSONSchemaGenerator;
import io.metersphere.commons.json.JSONToDocumentUtils;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.controller.request.ResetOrderRequest;
@ -61,6 +64,7 @@ public class ApiDefinitionController {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, apiDefinitionService.list(request));
}
@PostMapping("/week/list/{goPage}/{pageSize}")
@RequiresPermissions("PROJECT_API_DEFINITION:READ")
public Pager<List<ApiDefinitionResult>> weekList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody ApiDefinitionRequest request) {
@ -319,7 +323,7 @@ public class ApiDefinitionController {
}
@PostMapping("/relationship/relate/{goPage}/{pageSize}")
public Pager< List<ApiDefinitionResult>> getRelationshipRelateList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody ApiDefinitionRequest request) {
public Pager<List<ApiDefinitionResult>> getRelationshipRelateList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody ApiDefinitionRequest request) {
return apiDefinitionService.getRelationshipRelateList(request, goPage, pageSize);
}
@ -327,4 +331,15 @@ public class ApiDefinitionController {
public List<String> getFollows(@PathVariable String 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());
}
}

View File

@ -2,6 +2,9 @@ package io.metersphere.api.dto.definition.request.assertions;
import com.alibaba.fastjson.annotation.JSONType;
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.MsTestElement;
import lombok.Data;
@ -27,6 +30,7 @@ public class MsAssertions extends MsTestElement {
private List<MsAssertionXPath2> xpath2;
private MsAssertionDuration duration;
private String type = "Assertions";
private MsAssertionDocument document;
@Override
public void toHashTree(HashTree tree, List<MsTestElement> hashTree, MsParameter msParameter) {
@ -39,6 +43,27 @@ public class MsAssertions extends MsTestElement {
}
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())) {
this.getRegex().stream().filter(MsAssertionRegex::isValid).forEach(assertion ->
hashTree.add(responseAssertion(assertion))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.Swagger3Parser;
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.MsTCPSampler;
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.commons.constants.*;
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.controller.request.ResetOrderRequest;
import io.metersphere.controller.request.ScheduleRequest;
@ -725,11 +728,11 @@ public class ApiDefinitionService {
private void reSetImportMocksApiId(List<MockConfigImportDTO> mocks, String originId, String newId, int apiNum) {
if (CollectionUtils.isNotEmpty(mocks)) {
int index = 1;
for(MockConfigImportDTO item : mocks){
for (MockConfigImportDTO item : mocks) {
if (StringUtils.equals(item.getApiId(), originId)) {
item.setApiId(newId);
}
item.setExpectNum(apiNum+"_"+index);
item.setExpectNum(apiNum + "_" + index);
index++;
}
}
@ -901,7 +904,7 @@ public class ApiDefinitionService {
if (request.getConfig() != null && StringUtils.isNotBlank(request.getConfig().getResourcePoolId())) {
jMeterService.runTest(request.getId(), request.getId(), runMode, null, request.getConfig());
} else {
jMeterService.runLocal(request.getId(),request.getConfig(), hashTree, request.getReportId(), runMode);
jMeterService.runLocal(request.getId(), request.getConfig(), hashTree, request.getReportId(), runMode);
}
return request.getId();
}
@ -1015,10 +1018,10 @@ public class ApiDefinitionService {
apiImport = (ApiDefinitionImport) Objects.requireNonNull(apiImportParser).parse(file == null ? null : file.getInputStream(), request);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
String returnThrowException = e.getMessage();
if(StringUtils.contains(returnThrowException,"模块树最大深度为")){
String returnThrowException = e.getMessage();
if (StringUtils.contains(returnThrowException, "模块树最大深度为")) {
MSException.throwException(returnThrowException);
}else {
} else {
MSException.throwException(Translator.get("parse_data_error"));
}
// 发送通知
@ -1207,8 +1210,8 @@ public class ApiDefinitionService {
}
public List<ApiDefinition> selectApiDefinitionBydIds(List<String> ids) {
if(CollectionUtils.isEmpty(ids)){
return new ArrayList<>();
if (CollectionUtils.isEmpty(ids)) {
return new ArrayList<>();
}
ApiDefinitionExample example = new ApiDefinitionExample();
example.createCriteria().andIdIn(ids);
@ -1493,7 +1496,7 @@ public class ApiDefinitionService {
}
for (ApiDefinition api : apiList) {
String path = api.getPath();
if(StringUtils.isEmpty(path)){
if (StringUtils.isEmpty(path)) {
continue;
}
if (path.startsWith("/")) {
@ -1729,4 +1732,38 @@ public class ApiDefinitionService {
List<ApiDefinitionFollow> follows = apiDefinitionFollowMapper.selectByExample(example);
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;
}
}

View File

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

View File

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

View File

@ -5,10 +5,14 @@
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;
@ -47,6 +51,10 @@ public class JSONPathAssertion extends AbstractTestElement implements Serializab
return getPropertyAsString("ASS_OPTION");
}
public String getElementCondition() {
return getPropertyAsString("ElementCondition");
}
public String getJsonPath() {
return this.getPropertyAsString("JSON_PATH");
}
@ -132,6 +140,9 @@ public class JSONPathAssertion extends AbstractTestElement implements Serializab
case "LT":
msg = "Value < '%s', but found '%s'";
break;
case "DOCUMENT":
msg = (StringUtils.isNotEmpty(this.getName()) ? this.getName().split("==")[1] : "") + "校验失败,返回数据:" + (value != null ? value.toString() : "");
break;
}
} else {
msg = "Value expected to be '%s', but found '%s'";
@ -202,13 +213,70 @@ public class JSONPathAssertion extends AbstractTestElement implements Serializab
case "LT":
refFlag = isLt(str, getExpectedValue());
break;
case "DOCUMENT":
refFlag = isDocument(str);
break;
}
return refFlag;
}
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) {
AssertionResult result = new AssertionResult(this.getName());

View File

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

View File

@ -15,31 +15,61 @@
<div class="assertion-add" :draggable="draggable">
<el-row :gutter="10">
<el-col :span="4">
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type"
:placeholder="$t('api_test.request.assertions.select_type')"
size="small">
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type" :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.regex')" :value="options.REGEX"/>
<el-option :label="'JSONPath'" :value="options.JSON_PATH"/>
<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.jsr223')" :value="options.JSR223"/>
<el-option :label="$t('api_test.definition.request.document_structure')" :value="options.DOCUMENT"/>
</el-select>
</el-col>
<el-col :span="20">
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT"
:callback="after"/>
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX"
:callback="after"/>
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath"
v-if="type === options.JSON_PATH" :callback="after"/>
<ms-api-assertion-x-path2 :is-read-only="isReadOnly" :list="assertions.xpath2" v-if="type === options.XPATH2"
:callback="after"/>
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration"
v-if="type === options.DURATION" :callback="after"/>
<ms-api-assertion-jsr223 :is-read-only="isReadOnly" :list="assertions.jsr223" v-if="type === options.JSR223"
:callback="after"/>
<ms-api-assertion-text
:is-read-only="isReadOnly"
:list="assertions.regex"
:callback="after"
v-if="type === options.TEXT"
/>
<ms-api-assertion-regex
:is-read-only="isReadOnly"
:list="assertions.regex"
:callback="after"
v-if="type === options.REGEX"
/>
<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">
{{ $t('api_test.request.assertions.add') }}
</el-button>
@ -47,12 +77,23 @@
</el-row>
</div>
<api-json-path-suggest-button :open-tip="$t('api_test.request.assertions.json_path_suggest')"
:clear-tip="$t('api_test.request.assertions.json_path_clear')" @open="suggestJsonOpen" @clear="clearJson"/>
<api-json-path-suggest-button
: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>
</template>
@ -71,6 +112,7 @@ import {getUUID} from "@/common/js/utils";
import ApiJsonPathSuggestButton from "./ApiJsonPathSuggestButton";
import MsApiJsonpathSuggest from "./ApiJsonpathSuggest";
import ApiBaseComponent from "../../../automation/scenario/common/ApiBaseComponent";
import MsApiAssertionDocument from "./document/DocumentHeader";
export default {
name: "MsApiAssertions",
@ -82,7 +124,11 @@ export default {
MsApiAssertionJsr223,
MsApiJsonpathSuggestList,
MsApiAssertionJsonPath,
MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText
MsApiAssertionsEdit,
MsApiAssertionDuration,
MsApiAssertionRegex,
MsApiAssertionText,
MsApiAssertionDocument,
},
props: {
draggable: {
@ -100,6 +146,7 @@ export default {
assertions: {},
node: {},
request: {},
apiId: String,
response: {},
customizeStyle: {
type: String,

View File

@ -1,135 +1,156 @@
<template>
<div v-loading="loading">
<div class="assertion-item-editing regex" v-if="assertions.regex.length > 0">
<div>
{{ $t("api_test.request.assertions.regex") }}
</div>
<div> {{ $t("api_test.request.assertions.regex") }}</div>
<div class="regex-item" v-for="(regex, index) in assertions.regex" :key="index">
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex"
:regex="regex" :edit="true" :index="index"/>
<ms-api-assertion-regex
:is-read-only="isReadOnly"
:list="assertions.regex"
:regex="regex"
:edit="true"
:index="index"/>
</div>
</div>
<div class="assertion-item-editing json_path" v-if="assertions.jsonPath.length > 0">
<div>
{{ 'JSONPath' }}
</div>
<div> {{ 'JSONPath' }}</div>
<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"
:json-path="jsonPath" :edit="true" :index="index"/>
<ms-api-assertion-json-path
:is-read-only="isReadOnly"
:list="assertions.jsonPath"
:json-path="jsonPath"
:edit="true"
:index="index"/>
</div>
</div>
<div class="assertion-item-editing x_path" v-if="assertions.xpath2.length > 0">
<div>
{{ 'XPath' }}
</div>
<div> {{ 'XPath' }}</div>
<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"
:x-path2="xPath" :edit="true" :index="index"/>
<ms-api-assertion-x-path2
:is-read-only="isReadOnly"
:list="assertions.xpath2"
:x-path2="xPath"
:edit="true"
:index="index"/>
</div>
</div>
<div class="assertion-item-editing jsr223" v-if="assertions.jsr223.length > 0">
<div>
{{ $t("api_test.request.assertions.script") }}
</div>
<div> {{ $t("api_test.request.assertions.script") }}</div>
<div class="regex-item" v-for="(assertion, index) in assertions.jsr223" :key="index">
<ms-api-assertion-jsr223 :is-read-only="isReadOnly" :list="assertions.jsr223"
:assertion="assertion" :edit="true" :index="index"/>
<ms-api-assertion-jsr223
:is-read-only="isReadOnly"
:list="assertions.jsr223"
:assertion="assertion"
:edit="true"
:index="index"/>
</div>
</div>
<div class="assertion-item-editing response-time" v-if="isShow">
<div>
{{ $t("api_test.request.assertions.response_time") }}
</div>
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="assertions.duration.value"
:duration="assertions.duration" :edit="true"/>
<div> {{ $t("api_test.request.assertions.response_time") }}</div>
<ms-api-assertion-duration
:is-read-only="isReadOnly"
v-model="assertions.duration.value"
:duration="assertions.duration"
:edit="true"/>
</div>
<div class="assertion-item-editing response-time" v-if="isDocument">
<div> {{ assertions.document.type }}-{{ $t("api_test.definition.request.document_structure") }}</div>
<ms-document-body :document="assertions.document" :apiId="apiId"/>
</div>
</div>
</template>
<script>
import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionDuration from "./ApiAssertionDuration";
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
import MsApiAssertionJsr223 from "./ApiAssertionJsr223";
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionDuration from "./ApiAssertionDuration";
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
import MsApiAssertionJsr223 from "./ApiAssertionJsr223";
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
export default {
name: "MsApiAssertionsEdit",
export default {
name: "MsApiAssertionsEdit",
components: {
MsApiAssertionXPath2,
MsApiAssertionJsr223, MsApiAssertionJsonPath, MsApiAssertionDuration, MsApiAssertionRegex
},
components: {
MsApiAssertionXPath2,
MsApiAssertionJsr223,
MsApiAssertionJsonPath,
MsApiAssertionDuration,
MsApiAssertionRegex,
MsDocumentBody: () => import("./document/DocumentBody"),
},
props: {
assertions: {},
reloadData: String,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
loading: false,
}
},
computed: {
isShow() {
let rt = this.assertions.duration;
return rt.value !== undefined && rt.value !== 0;
}
},
watch: {
reloadData() {
this.reload();
}
},
methods: {
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
props: {
assertions: {},
reloadData: String,
apiId: String,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
loading: false,
}
},
computed: {
isShow() {
let rt = this.assertions.duration;
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: {
reloadData() {
this.reload();
}
},
methods: {
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
}
}
</script>
<style scoped>
.assertion-item-editing {
padding-left: 10px;
margin-top: 10px;
}
.assertion-item-editing {
padding-left: 10px;
margin-top: 10px;
}
.assertion-item-editing.regex {
border-left: 2px solid #7B0274;
}
.assertion-item-editing.regex {
border-left: 2px solid #7B0274;
}
.assertion-item-editing.json_path {
border-left: 2px solid #44B3D2;
}
.assertion-item-editing.json_path {
border-left: 2px solid #44B3D2;
}
.assertion-item-editing.response-time {
border-left: 2px solid #DD0240;
}
.assertion-item-editing.response-time {
border-left: 2px solid #DD0240;
}
.assertion-item-editing.jsr223 {
border-left: 2px solid #1FDD02;
}
.assertion-item-editing.jsr223 {
border-left: 2px solid #1FDD02;
}
.assertion-item-editing.x_path {
border-left: 2px solid #fca130;
}
.assertion-item-editing.x_path {
border-left: 2px solid #fca130;
}
.regex-item {
margin-top: 10px;
}
.regex-item {
margin-top: 10px;
}
</style>

View File

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

View File

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

View File

@ -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 0xml1xml2
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>

View File

@ -137,6 +137,8 @@ export default {
let data = MsConvert.format(JSON.parse(this.body.raw));
if (this.body.jsonSchema) {
this.body.jsonSchema = this.deepAssign(this.body.jsonSchema, data);
}else{
this.body.jsonSchema = data;
}
} catch (ex) {
this.body.jsonSchema = "";

View File

@ -29,7 +29,7 @@
</div>
<ms-jmx-step :request="api.request" :response="responseData"/>
<ms-jmx-step :request="api.request" :apiId="api.id" :response="responseData"/>
</el-card>
<!-- 加载用例 -->

View File

@ -55,7 +55,7 @@
<ms-request-result-tail :response="responseData" ref="runResult"/>
</div>
<ms-jmx-step :request="api.request" :response="responseData"/>
<ms-jmx-step :request="api.request" :apiId="api.id" :response="responseData"/>
</el-card>

View File

@ -27,7 +27,7 @@
<ms-request-result-tail :response="responseData" :currentProtocol="currentProtocol" ref="runResult"/>
</div>
<ms-jmx-step :request="api.request" :response="responseData"/>
<ms-jmx-step :request="api.request" :apiId="api.id" :response="responseData"/>
</el-card>

View File

@ -47,7 +47,7 @@
</div>
</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'">
<p class="tip">{{$t('api_test.definition.request.res_param')}}</p>

View File

@ -55,6 +55,7 @@
@copyRow="copyRow"
@remove="remove"
:response="response"
:apiId="apiId"
:is-read-only="isReadOnly"
:assertions="row"/>
</div>
@ -97,6 +98,7 @@ export default {
props: {
request: {},
response: {},
apiId: String,
showScript: {
type: Boolean,
default: true,

View File

@ -103,6 +103,7 @@ export const ASSERTION_TYPE = {
DURATION: "Duration",
JSR223: "JSR223",
XPATH2: "XPath2",
DOCUMENT: "Document",
}
export const ASSERTION_REGEX_SUBJECT = {
@ -126,8 +127,7 @@ export class BaseConfig {
if (!(this[name] instanceof Array)) {
if (notUndefined === true) {
this[name] = options[name] === undefined ? this[name] : options[name];
}
else {
} else {
this[name] = options[name];
}
}
@ -782,6 +782,7 @@ export class Assertions extends BaseConfig {
this.xpath2 = [];
this.duration = undefined;
this.enable = true;
this.document = {type: "JSON", data: {xmlFollowAPI: false, jsonFollowAPI: false, json: [], xml: []}};
this.set(options);
this.sets({text: Text, regex: Regex, jsonPath: JSONPath, jsr223: AssertionJSR223, xpath2: XPath2}, options);
}
@ -846,7 +847,7 @@ export class JSR223Processor extends BaseConfig {
this.resourceId = uuid();
this.active = false;
this.type = "JSR223Processor";
this.label="";
this.label = "";
this.script = undefined;
this.scriptLanguage = "beanshell";
this.enable = true;
@ -1115,7 +1116,7 @@ export class TransactionController extends Controller {
label() {
if (this.isValid()) {
let label = this.$t('api_test.automation.transcation_controller');
if(this.name != null && this.name !== ""){
if (this.name != null && this.name !== "") {
label = this.name;
}
return label;

View File

@ -955,6 +955,7 @@ export default {
add_data: "Add Data"
},
request: {
document_structure: "Document structure verification",
grade_info: "Filter by rank",
grade_order_asc: "from low to high by use case level",
grade_order_desc: "from high to low by use case level",

View File

@ -963,6 +963,7 @@ export default {
add_data: "去添加"
},
request: {
document_structure: "文档结构校验",
grade_info: "按等级筛选",
grade_order_asc: "按用例等级从低到高",
grade_order_desc: "按用例等级从高到低",

View File

@ -961,6 +961,7 @@ export default {
add_data: "去添加"
},
request: {
document_structure: "文檔結構校驗",
grade_info: "按等級篩選",
grade_order_asc: "按用例等級從低到高",
grade_order_desc: "按用例等級從高到低",