feat(接口定义): JSONpath 断言增加操作符
This commit is contained in:
parent
b80da9b9ae
commit
c5d58d40c3
|
@ -422,6 +422,12 @@
|
|||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
<!-- 添加jmeter包支持导入的jmx能正常执行 -->
|
||||
<dependency>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
<version>2.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -489,6 +489,7 @@ public class MsJmeterParser extends ApiImportAbstractParser<ScenarioImport> {
|
|||
assertionJsonPath.setDescription(jsonPathAssertion.getName());
|
||||
assertionJsonPath.setExpression(jsonPathAssertion.getJsonPath());
|
||||
assertionJsonPath.setExpect(jsonPathAssertion.getExpectedValue());
|
||||
assertionJsonPath.setOption(jsonPathAssertion.getPropertyAsString("ASS_OPTION"));
|
||||
assertions.setName(jsonPathAssertion.getName());
|
||||
assertions.getJsonPath().add(assertionJsonPath);
|
||||
} else if (key instanceof XPath2Assertion) {
|
||||
|
|
|
@ -10,6 +10,7 @@ public class MsAssertionJsonPath extends MsAssertionType {
|
|||
private String expect;
|
||||
private String expression;
|
||||
private String description;
|
||||
private String option = "REGEX";
|
||||
|
||||
public MsAssertionJsonPath() {
|
||||
setType(MsAssertionType.JSON_PATH);
|
||||
|
|
|
@ -96,7 +96,12 @@ public class MsAssertions extends MsTestElement {
|
|||
assertion.setJsonValidationBool(true);
|
||||
assertion.setExpectNull(false);
|
||||
assertion.setInvert(false);
|
||||
assertion.setProperty("ASS_OPTION",assertionJsonPath.getOption());
|
||||
if (StringUtils.isEmpty(assertionJsonPath.getOption()) || "REGEX".equals(assertionJsonPath.getOption())) {
|
||||
assertion.setIsRegex(true);
|
||||
} else {
|
||||
assertion.setIsRegex(false);
|
||||
}
|
||||
return assertion;
|
||||
}
|
||||
|
||||
|
|
|
@ -136,8 +136,8 @@ public class XmindCaseParser {
|
|||
data.setNodePath(nodePath);
|
||||
|
||||
|
||||
if (data.getName().length() > 50) {
|
||||
process.add(Translator.get("test_case") + Translator.get("test_track.length_less_than") + "50", nodePath + data.getName());
|
||||
if (data.getName().length() > 200) {
|
||||
process.add(Translator.get("test_case") + Translator.get("test_track.length_less_than") + "200", nodePath + data.getName());
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(nodePath)) {
|
||||
|
|
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* 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 java.io.Serializable;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is main class for JSONPath Assertion which verifies assertion on
|
||||
* previous sample result using JSON path expression
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public class JSONPathAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener {
|
||||
private static final Logger log = LoggerFactory.getLogger(JSONPathAssertion.class);
|
||||
private static final long serialVersionUID = 2L;
|
||||
public static final String JSONPATH = "JSON_PATH";
|
||||
public static final String EXPECTEDVALUE = "EXPECTED_VALUE";
|
||||
public static final String JSONVALIDATION = "JSONVALIDATION";
|
||||
public static final String EXPECT_NULL = "EXPECT_NULL";
|
||||
public static final String INVERT = "INVERT";
|
||||
public static final String ISREGEX = "ISREGEX";
|
||||
|
||||
private static ThreadLocal<DecimalFormat> decimalFormatter =
|
||||
ThreadLocal.withInitial(JSONPathAssertion::createDecimalFormat);
|
||||
|
||||
public String getOption() {
|
||||
return getPropertyAsString("ASS_OPTION");
|
||||
}
|
||||
|
||||
private static DecimalFormat createDecimalFormat() {
|
||||
DecimalFormat decimalFormatter = new DecimalFormat("#.#");
|
||||
decimalFormatter.setMaximumFractionDigits(340); // java.text.DecimalFormat.DOUBLE_FRACTION_DIGITS == 340
|
||||
decimalFormatter.setMinimumFractionDigits(1);
|
||||
return decimalFormatter;
|
||||
}
|
||||
|
||||
public String getJsonPath() {
|
||||
return getPropertyAsString(JSONPATH);
|
||||
}
|
||||
|
||||
public void setJsonPath(String jsonPath) {
|
||||
setProperty(JSONPATH, jsonPath);
|
||||
}
|
||||
|
||||
public String getExpectedValue() {
|
||||
return getPropertyAsString(EXPECTEDVALUE);
|
||||
}
|
||||
|
||||
public void setExpectedValue(String expectedValue) {
|
||||
setProperty(EXPECTEDVALUE, expectedValue);
|
||||
}
|
||||
|
||||
public void setJsonValidationBool(boolean jsonValidation) {
|
||||
setProperty(JSONVALIDATION, jsonValidation);
|
||||
}
|
||||
|
||||
public void setExpectNull(boolean val) {
|
||||
setProperty(EXPECT_NULL, val);
|
||||
}
|
||||
|
||||
public boolean isExpectNull() {
|
||||
return getPropertyAsBoolean(EXPECT_NULL);
|
||||
}
|
||||
|
||||
public boolean isJsonValidationBool() {
|
||||
return getPropertyAsBoolean(JSONVALIDATION);
|
||||
}
|
||||
|
||||
public void setInvert(boolean invert) {
|
||||
setProperty(INVERT, invert);
|
||||
}
|
||||
|
||||
public boolean isInvert() {
|
||||
return getPropertyAsBoolean(INVERT);
|
||||
}
|
||||
|
||||
public void setIsRegex(boolean flag) {
|
||||
setProperty(ISREGEX, flag);
|
||||
}
|
||||
|
||||
public boolean isUseRegex() {
|
||||
return getPropertyAsBoolean(ISREGEX, true);
|
||||
}
|
||||
|
||||
private void doAssert(String jsonString) {
|
||||
Object value = JsonPath.read(jsonString, getJsonPath());
|
||||
|
||||
if (!isJsonValidationBool()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value instanceof JSONArray) {
|
||||
if (arrayMatched((JSONArray) value)) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if ((isExpectNull() && value == null)
|
||||
|| isEquals(value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isExpectNull()) {
|
||||
throw new IllegalStateException(String.format("Value expected to be null, but found '%s'", value));
|
||||
} else {
|
||||
String msg;
|
||||
if (isUseRegex()) {
|
||||
msg = "Value expected to match regexp '%s', but it did not match: '%s'";
|
||||
} else {
|
||||
msg = "Value expected to be '%s', but found '%s'";
|
||||
}
|
||||
throw new IllegalStateException(String.format(msg, getExpectedValue(), objectToString(value)));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean arrayMatched(JSONArray value) {
|
||||
if (value.isEmpty() && "[]".equals(getExpectedValue())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Object subj : value.toArray()) {
|
||||
if ((subj == null && isExpectNull())
|
||||
|| isEquals(subj)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return isEquals(value);
|
||||
}
|
||||
|
||||
private boolean isEquals(Object subj) {
|
||||
String str = objectToString(subj);
|
||||
if (isUseRegex()) {
|
||||
Pattern pattern = JMeterUtils.getPatternCache().getPattern(getExpectedValue());
|
||||
return JMeterUtils.getMatcher().matches(str, pattern);
|
||||
} else {
|
||||
if (StringUtils.isNotEmpty(getOption())) {
|
||||
boolean refFlag = false;
|
||||
switch (getOption()) {
|
||||
case "CONTAINS":
|
||||
refFlag = str.contains(getExpectedValue());
|
||||
break;
|
||||
case "NOT_CONTAINS":
|
||||
refFlag = !str.contains(getExpectedValue());
|
||||
break;
|
||||
case "EQUALS":
|
||||
refFlag = str.equals(getExpectedValue());
|
||||
break;
|
||||
case "NOT_EQUALS":
|
||||
refFlag = !str.contains(getExpectedValue());
|
||||
break;
|
||||
}
|
||||
return refFlag;
|
||||
}
|
||||
return str.equals(getExpectedValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssertionResult getResult(SampleResult samplerResult) {
|
||||
AssertionResult result = new AssertionResult(getName());
|
||||
String responseData = samplerResult.getResponseDataAsString();
|
||||
if (responseData.isEmpty()) {
|
||||
return result.setResultForNull();
|
||||
}
|
||||
|
||||
result.setFailure(false);
|
||||
result.setFailureMessage("");
|
||||
|
||||
if (!isInvert()) {
|
||||
try {
|
||||
doAssert(responseData);
|
||||
} catch (Exception e) {
|
||||
log.debug("Assertion failed", e);
|
||||
result.setFailure(true);
|
||||
result.setFailureMessage(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
doAssert(responseData);
|
||||
result.setFailure(true);
|
||||
if (isJsonValidationBool()) {
|
||||
if (isExpectNull()) {
|
||||
result.setFailureMessage("Failed that JSONPath " + getJsonPath() + " not matches null");
|
||||
} else {
|
||||
result.setFailureMessage("Failed that JSONPath " + getJsonPath() + " not matches " + getExpectedValue());
|
||||
}
|
||||
} else {
|
||||
result.setFailureMessage("Failed that JSONPath not exists: " + getJsonPath());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Assertion failed, as expected", e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String objectToString(Object subj) {
|
||||
String str;
|
||||
if (subj == null) {
|
||||
str = "null";
|
||||
} else if (subj instanceof Map) {
|
||||
//noinspection unchecked
|
||||
str = new JSONObject((Map<String, Object>) subj).toJSONString();
|
||||
} else if (subj instanceof Double || subj instanceof Float) {
|
||||
str = decimalFormatter.get().format(subj);
|
||||
} else {
|
||||
str = subj.toString();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void threadStarted() {
|
||||
// nothing to do on thread start
|
||||
}
|
||||
|
||||
@Override
|
||||
public void threadFinished() {
|
||||
decimalFormatter.remove();
|
||||
}
|
||||
}
|
|
@ -1,13 +1,24 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-loading="loading">
|
||||
<el-row :gutter="10" type="flex" justify="space-between" align="middle">
|
||||
<el-col>
|
||||
<el-input :disabled="isReadOnly" v-model="jsonPath.expression" maxlength="200" size="small" show-word-limit
|
||||
:placeholder="$t('api_test.request.extract.json_path_expression')"/>
|
||||
</el-col>
|
||||
<el-col>
|
||||
<el-select v-model="jsonPath.option" class="ms-col-type" size="small" style="width:40%;margin-right: 10px" @change="reload">
|
||||
<el-option :label="$t('api_test.request.assertions.contains')" value="CONTAINS"/>
|
||||
<el-option :label="$t('api_test.request.assertions.not_contains')" value="NOT_CONTAINS"/>
|
||||
<el-option :label="$t('api_test.request.assertions.equals')" value="EQUALS"/>
|
||||
<el-option :label="$t('commons.adv_search.operators.not_equals')" value="NOT_EQUALS"/>
|
||||
<el-option label="正则匹配" value="REGEX"/>
|
||||
</el-select>
|
||||
<el-input :disabled="isReadOnly" v-model="jsonPath.expect" size="small" show-word-limit
|
||||
:placeholder="$t('api_test.request.assertions.expect')"/>
|
||||
:placeholder="$t('api_test.request.assertions.expect')" style="width: 50%"/>
|
||||
<el-tooltip placement="top" v-if="jsonPath.option === 'REGEX'">
|
||||
<div slot="content">特殊字符"$ ( ) * + . [ ] \ ^ { } |"需转义为"\ "+"特殊字符",如"\$"</div>
|
||||
<i class="el-icon-question" style="cursor: pointer"/>
|
||||
</el-tooltip>
|
||||
</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"/>
|
||||
|
@ -44,8 +55,15 @@
|
|||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
if (!this.jsonPath.option) {
|
||||
this.jsonPath.option = "REGEX";
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -71,6 +89,12 @@
|
|||
jsonPath.description = jsonPath.expression + " expect: " + (jsonPath.expect ? jsonPath.expect : '');
|
||||
return jsonPath;
|
||||
},
|
||||
reload() {
|
||||
this.loading = true
|
||||
this.$nextTick(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
setJSONPathDescription() {
|
||||
this.jsonPath.description = this.jsonPath.expression + " expect: " + (this.jsonPath.expect ? this.jsonPath.expect : '');
|
||||
}
|
||||
|
|
|
@ -56,21 +56,21 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiAssertionText from "./ApiAssertionText";
|
||||
import MsApiAssertionRegex from "./ApiAssertionRegex";
|
||||
import MsApiAssertionDuration from "./ApiAssertionDuration";
|
||||
import {ASSERTION_TYPE, JSONPath} from "../../model/ApiTestModel";
|
||||
import MsApiAssertionsEdit from "./ApiAssertionsEdit";
|
||||
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
|
||||
import MsApiAssertionJsr223 from "./ApiAssertionJsr223";
|
||||
import MsApiJsonpathSuggestList from "./ApiJsonpathSuggestList";
|
||||
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
|
||||
import {getUUID} from "@/common/js/utils";
|
||||
import ApiJsonPathSuggestButton from "./ApiJsonPathSuggestButton";
|
||||
import MsApiJsonpathSuggest from "./ApiJsonpathSuggest";
|
||||
import ApiBaseComponent from "../../../automation/scenario/common/ApiBaseComponent";
|
||||
import MsApiAssertionText from "./ApiAssertionText";
|
||||
import MsApiAssertionRegex from "./ApiAssertionRegex";
|
||||
import MsApiAssertionDuration from "./ApiAssertionDuration";
|
||||
import {ASSERTION_TYPE, JSONPath} from "../../model/ApiTestModel";
|
||||
import MsApiAssertionsEdit from "./ApiAssertionsEdit";
|
||||
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
|
||||
import MsApiAssertionJsr223 from "./ApiAssertionJsr223";
|
||||
import MsApiJsonpathSuggestList from "./ApiJsonpathSuggestList";
|
||||
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
|
||||
import {getUUID} from "@/common/js/utils";
|
||||
import ApiJsonPathSuggestButton from "./ApiJsonPathSuggestButton";
|
||||
import MsApiJsonpathSuggest from "./ApiJsonpathSuggest";
|
||||
import ApiBaseComponent from "../../../automation/scenario/common/ApiBaseComponent";
|
||||
|
||||
export default {
|
||||
export default {
|
||||
name: "MsApiAssertions",
|
||||
components: {
|
||||
ApiBaseComponent,
|
||||
|
@ -146,6 +146,11 @@ export default {
|
|||
jsonItem.expression = data.path;
|
||||
jsonItem.expect = data.value;
|
||||
jsonItem.setJSONPathDescription();
|
||||
let expect = jsonItem.expect.replaceAll('\\', "\\\\").replaceAll('(', "\\(").replaceAll(')', "\\)")
|
||||
.replaceAll('+', "\\+").replaceAll('.', "\\.").replaceAll('[', "\\[").replaceAll(']', "\\]")
|
||||
.replaceAll('?', "\\?").replaceAll('/', "\\/").replaceAll('*', "\\*")
|
||||
.replaceAll('^', "\\^").replaceAll('{', "\\{").replaceAll('}', "\\}").replaceAll('$', "\\$");
|
||||
jsonItem.expect = expect;
|
||||
this.assertions.jsonPath.push(jsonItem);
|
||||
},
|
||||
clearJson() {
|
||||
|
|
Loading…
Reference in New Issue