feat(接口定义): 增加断言

This commit is contained in:
fit2-zhao 2020-11-23 11:36:04 +08:00
parent 58cbe145f7
commit 06e8d48344
17 changed files with 613 additions and 76 deletions

View File

@ -4,6 +4,7 @@ import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType; import com.alibaba.fastjson.annotation.JSONType;
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.metersphere.api.dto.definition.request.assertions.MsAssertions;
import io.metersphere.api.dto.definition.request.auth.MsAuthManager; import io.metersphere.api.dto.definition.request.auth.MsAuthManager;
import io.metersphere.api.dto.definition.request.configurations.MsHeaderManager; import io.metersphere.api.dto.definition.request.configurations.MsHeaderManager;
import io.metersphere.api.dto.definition.request.processors.post.MsJSR223PostProcessor; import io.metersphere.api.dto.definition.request.processors.post.MsJSR223PostProcessor;
@ -29,9 +30,10 @@ import java.util.List;
@JsonSubTypes.Type(value = MsTestPlan.class, name = "TestPlan"), @JsonSubTypes.Type(value = MsTestPlan.class, name = "TestPlan"),
@JsonSubTypes.Type(value = MsThreadGroup.class, name = "ThreadGroup"), @JsonSubTypes.Type(value = MsThreadGroup.class, name = "ThreadGroup"),
@JsonSubTypes.Type(value = MsAuthManager.class, name = "AuthManager"), @JsonSubTypes.Type(value = MsAuthManager.class, name = "AuthManager"),
@JsonSubTypes.Type(value = MsAssertions.class, name = "Assertions"),
}) })
@JSONType(seeAlso = {MsHTTPSamplerProxy.class, MsHeaderManager.class, MsJSR223PostProcessor.class, MsJSR223PreProcessor.class,MsTestPlan.class,MsThreadGroup.class, AuthManager.class}, typeKey = "type") @JSONType(seeAlso = {MsHTTPSamplerProxy.class, MsHeaderManager.class, MsJSR223PostProcessor.class, MsJSR223PreProcessor.class, MsTestPlan.class, MsThreadGroup.class, AuthManager.class, MsAssertions.class}, typeKey = "type")
@Data @Data
public abstract class MsTestElement { public abstract class MsTestElement {
private String type; private String type;

View File

@ -0,0 +1,18 @@
package io.metersphere.api.dto.definition.request.assertions;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class MsAssertionDuration extends MsAssertionType {
private long value;
public MsAssertionDuration() {
setType(MsAssertionType.DURATION);
}
public boolean isValid() {
return value > 0;
}
}

View File

@ -0,0 +1,25 @@
package io.metersphere.api.dto.definition.request.assertions;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
@EqualsAndHashCode(callSuper = true)
@Data
public class MsAssertionJSR223 extends MsAssertionType {
private String variable;
private String operator;
private String value;
private String desc;
private String name;
private String script;
private String language;
public MsAssertionJSR223() {
setType(MsAssertionType.JSR223);
}
public boolean isValid() {
return StringUtils.isNotBlank(script) && StringUtils.isNotBlank(language);
}
}

View File

@ -0,0 +1,21 @@
package io.metersphere.api.dto.definition.request.assertions;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
@EqualsAndHashCode(callSuper = true)
@Data
public class MsAssertionJsonPath extends MsAssertionType {
private String expect;
private String expression;
private String description;
public MsAssertionJsonPath() {
setType(MsAssertionType.JSON_PATH);
}
public boolean isValid() {
return StringUtils.isNotBlank(expression);
}
}

View File

@ -0,0 +1,22 @@
package io.metersphere.api.dto.definition.request.assertions;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
@EqualsAndHashCode(callSuper = true)
@Data
public class MsAssertionRegex extends MsAssertionType {
private String subject;
private String expression;
private String description;
private boolean assumeSuccess;
public MsAssertionRegex() {
setType(MsAssertionType.REGEX);
}
public boolean isValid() {
return StringUtils.isNotBlank(subject) && StringUtils.isNotBlank(expression);
}
}

View File

@ -0,0 +1,15 @@
package io.metersphere.api.dto.definition.request.assertions;
import lombok.Data;
@Data
public class MsAssertionType {
public final static String REGEX = "Regex";
public final static String DURATION = "Duration";
public final static String JSON_PATH = "JSONPath";
public final static String JSR223 = "JSR223";
public final static String TEXT = "Text";
public final static String XPATH2 = "XPath2";
private String type;
}

View File

@ -0,0 +1,19 @@
package io.metersphere.api.dto.definition.request.assertions;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.StringUtils;
@EqualsAndHashCode(callSuper = true)
@Data
public class MsAssertionXPath2 extends MsAssertionType {
private String expression;
public MsAssertionXPath2() {
setType(MsAssertionType.XPATH2);
}
public boolean isValid() {
return StringUtils.isNotBlank(expression);
}
}

View File

@ -0,0 +1,31 @@
package io.metersphere.api.dto.definition.request.assertions;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.jorphan.collections.HashTree;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@JSONType(typeName = "Assertions")
public class MsAssertions extends MsTestElement {
private List<MsAssertionRegex> regex;
private List<MsAssertionJsonPath> jsonPath;
private List<MsAssertionJSR223> jsr223;
private List<MsAssertionXPath2> xpath2;
private MsAssertionDuration duration;
private String type = "Assertions";
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
// final HashTree testPlanTree = tree.add(getPlan());
// if (CollectionUtils.isNotEmpty(hashTree)) {
// hashTree.forEach(el -> {
// el.toHashTree(testPlanTree, el.getHashTree());
// });
// }
}
}

View File

@ -8,7 +8,6 @@ import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.definition.request.dns.MsDNSCacheManager; import io.metersphere.api.dto.definition.request.dns.MsDNSCacheManager;
import io.metersphere.api.dto.definition.request.prop.BoolProp; import io.metersphere.api.dto.definition.request.prop.BoolProp;
import io.metersphere.api.dto.definition.request.prop.StringProp; import io.metersphere.api.dto.definition.request.prop.StringProp;
import io.metersphere.api.dto.scenario.AuthConfig;
import io.metersphere.api.dto.scenario.Body; import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.KeyValue; import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig; import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
@ -72,7 +71,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
private List<KeyValue> rest; private List<KeyValue> rest;
@JSONField(ordinal = 20) @JSONField(ordinal = 20)
private AuthConfig authConfig; private String url;
@JSONField(ordinal = 21) @JSONField(ordinal = 21)
private BoolProp followRedirects; private BoolProp followRedirects;
@ -83,8 +82,6 @@ public class MsHTTPSamplerProxy extends MsTestElement {
@JSONField(ordinal = 23) @JSONField(ordinal = 23)
private String useEnvironment; private String useEnvironment;
@JSONField(ordinal = 24)
private String url;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) { public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
HTTPSamplerProxy sampler = new HTTPSamplerProxy(); HTTPSamplerProxy sampler = new HTTPSamplerProxy();

View File

@ -0,0 +1,261 @@
<template>
<div>
<el-row type="flex" align="middle" v-if="!edit">
<div class="assertion-item label">
{{ assertion.desc }}
</div>
<div class="assertion-item btn">
<el-button :disabled="isReadOnly" type="success" size="small" @click="detail">
{{ $t('commons.edit') }}
</el-button>
<el-button :disabled="isReadOnly" type="primary" size="small" @click="add">
{{ $t('api_test.request.assertions.add') }}
</el-button>
</div>
</el-row>
<el-row type="flex" justify="space-between" align="middle" v-else>
<div class="assertion-item label">
{{ assertion.desc }}
</div>
<div class="assertion-item btn circle">
<el-button :disabled="isReadOnly" type="success" size="mini" icon="el-icon-edit" circle @click="detail"/>
<el-button :disabled="isReadOnly" type="danger" size="mini" icon="el-icon-delete" circle @click="remove"/>
</div>
</el-row>
<el-dialog :title="$t('api_test.request.assertions.script')" :visible.sync="visible" width="900px">
<el-row type="flex" justify="space-between" align="middle" class="quick-script-block">
<div class="assertion-item input">
<el-input size="small" v-model="assertion.variable"
:placeholder="$t('api_test.request.assertions.variable_name')" @change="quickScript"/>
</div>
<div class="assertion-item select">
<el-select v-model="assertion.operator" :placeholder="$t('commons.please_select')" size="small"
@change="changeOperator">
<el-option v-for="o in operators" :key="o.value" :label="$t(o.label)" :value="o.value"/>
</el-select>
</div>
<div class="assertion-item input">
<el-input size="small" v-model="assertion.value" :placeholder="$t('api_test.value')"
@change="quickScript" v-if="!hasEmptyOperator"/>
</div>
</el-row>
<el-input size="small" v-model="assertion.desc" :placeholder="$t('api_test.request.assertions.script_name')"
class="quick-script-block"/>
<ms-jsr233-processor ref="jsr233" :is-read-only="isReadOnly" :jsr223-processor="assertion" :templates="templates"
:height="300"/>
<template v-slot:footer v-if="!edit">
<ms-dialog-footer
@cancel="close"
@confirm="confirm"/>
</template>
</el-dialog>
</div>
</template>
<script>
import {AssertionJSR223} from "../../model/ApiTestModel";
import MsJsr233Processor from "../processor/Jsr233Processor";
import MsDialogFooter from "@/business/components/common/components/MsDialogFooter";
export default {
name: "MsApiAssertionJsr223",
components: {MsDialogFooter, MsJsr233Processor},
props: {
assertion: {
type: AssertionJSR223,
default: () => {
return new AssertionJSR223();
}
},
edit: {
type: Boolean,
default: false
},
index: Number,
list: Array,
callback: Function,
isReadOnly: {
type: Boolean,
default: false
}
},
data() {
return {
visible: false,
operators: {
EQ: {
label: "commons.adv_search.operators.equals",
value: "=="
},
NE: {
label: "commons.adv_search.operators.not_equals",
value: "!="
},
CONTAINS: {
label: "commons.adv_search.operators.like",
value: "contains"
},
NOT_CONTAINS: {
label: "commons.adv_search.operators.not_like",
value: "not contains"
},
GT: {
label: "commons.adv_search.operators.gt",
value: ">"
},
LT: {
label: "commons.adv_search.operators.lt",
value: "<"
},
IS_EMPTY: {
label: "commons.adv_search.operators.is_empty",
value: "is empty"
},
IS_NOT_EMPTY: {
label: "commons.adv_search.operators.is_not_empty",
value: "is not empty"
}
},
templates: [
{
title: this.$t('api_test.request.assertions.set_failure_status'),
value: 'AssertionResult.setFailure(true)',
},
{
title: this.$t('api_test.request.assertions.set_failure_msg'),
value: 'AssertionResult.setFailureMessage("msg")',
}
],
}
},
methods: {
add() {
this.list.push(new AssertionJSR223(this.assertion));
this.callback();
},
remove() {
this.list.splice(this.index, 1);
},
changeOperator(value) {
if (value.indexOf("empty") > 0 && !!this.assertion.value) {
this.assertion.value = "";
}
this.quickScript();
},
quickScript() {
if (this.assertion.variable && this.assertion.operator) {
let variable = this.assertion.variable;
let operator = this.assertion.operator;
let value = this.assertion.value || "";
let desc = "${" + variable + "} " + operator + " '" + value + "'";
let script = "value = vars.get(\"" + variable + "\");\n"
switch (this.assertion.operator) {
case "==":
script += "result = \"" + value + "\".equals(value);\n";
break;
case "!=":
script += "result = !\"" + value + "\".equals(value);\n";
break;
case "contains":
script += "result = value.contains(\"" + value + "\");\n";
break;
case "not contains":
script += "result = !value.contains(\"" + value + "\");\n";
break;
case ">":
desc = "${" + variable + "} " + operator + " " + value;
script += "number = Integer.parseInt(value);\n" +
"result = number > " + value + ";\n";
break;
case "<":
desc = "${" + variable + "} " + operator + " " + value;
script += "number = Integer.parseInt(value);\n" +
"result = number < " + value + ";\n";
break;
case "is empty":
desc = "${" + variable + "} " + operator
script += "result = value == void || value.length() == 0;\n";
break;
case "is not empty":
desc = "${" + variable + "} " + operator
script += "result = value != void && value.length() > 0;\n";
break;
}
let msg = "assertion [" + desc + "]: false;"
script += "if (!result){\n" +
"\tmsg = \"" + msg + "\";\n" +
"\tAssertionResult.setFailureMessage(msg);\n" +
"\tAssertionResult.setFailure(true);\n" +
"}";
this.assertion.desc = desc;
this.assertion.script = script;
this.$refs.jsr233.reload();
}
},
detail() {
this.visible = true;
},
close() {
this.visible = false;
},
confirm() {
if (!this.edit) {
this.add();
}
if (!this.assertion.desc) {
this.assertion.desc = this.assertion.script;
}
this.close();
}
},
computed: {
hasEmptyOperator() {
return !!this.assertion.operator && this.assertion.operator.indexOf("empty") > 0;
}
}
}
</script>
<style scoped>
.assertion-item {
display: inline-block;
}
.assertion-item + .assertion-item {
margin-left: 10px;
}
.assertion-item.input {
width: 100%;
}
.assertion-item.select {
min-width: 150px;
}
.assertion-item.label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.assertion-item.btn {
min-width: 130px;
}
.assertion-item.btn.circle {
text-align: right;
min-width: 80px;
}
.quick-script-block {
margin-bottom: 10px;
}
</style>

View File

@ -0,0 +1,72 @@
<template>
<div>
<el-row :gutter="10" type="flex" justify="space-between" align="middle">
<el-col>
<el-input :disabled="isReadOnly" v-model="xPath2.expression" maxlength="200" size="small" show-word-limit
:placeholder="$t('api_test.request.extract.xpath_expression')"/>
</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 {XPath2} from "../../model/ApiTestModel";
export default {
name: "MsApiAssertionXPath2",
props: {
xPath2: {
type: XPath2,
default: () => {
return new XPath2();
}
},
edit: {
type: Boolean,
default: false
},
index: Number,
list: Array,
callback: Function,
isReadOnly: {
type: Boolean,
default: false
}
},
methods: {
add: function () {
this.list.push(this.getXPath2());
this.callback();
},
remove: function () {
this.list.splice(this.index, 1);
},
getXPath2() {
return new XPath2(this.xPath2);
},
}
}
</script>
<style scoped>
.assertion-select {
width: 250px;
}
.assertion-item {
width: 100%;
}
.assertion-btn {
text-align: center;
width: 60px;
}
</style>

View File

@ -1,47 +1,56 @@
<template> <template>
<div class="assertion-add" style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 99% ;margin-top: 10px"> <div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100% ;margin-top: 20px">
<el-button class="ms-left-buttion" size="small" type="danger" plain>断言规则</el-button> <div class="assertion-add">
<div class="assertion-add" style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 98%;">
<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')" :placeholder="$t('api_test.request.assertions.select_type')"
size="small"> size="small">
<!--<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>--> <el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
<!--<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>--> <el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
<el-option :label="'JSONPath'" :value="options.JSON_PATH"/> <el-option :label="'JSONPath'" :value="options.JSON_PATH"/>
<el-option :label="'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-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 :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT"
<!--:callback="after"/>--> :callback="after"/>
<!--<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX"--> <ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX"
<!--:callback="after"/>--> :callback="after"/>
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions" <ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath"
v-if="type === options.JSON_PATH" :callback="after"/> v-if="type === options.JSON_PATH" :callback="after"/>
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions" <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"/> v-if="type === options.DURATION" :callback="after"/>
<el-button v-if="!type" :disabled="true" type="primary" size="small">Add</el-button> <ms-api-assertion-jsr223 :is-read-only="isReadOnly" :list="assertions.jsr223" v-if="type === options.JSR223"
:callback="after"/>
<el-button v-if="!type" :disabled="true" type="primary" size="small">
{{ $t('api_test.request.assertions.add') }}
</el-button>
</el-col> </el-col>
</el-row> </el-row>
<!--<div v-if="!scenario">-->
<!--<el-row :gutter="10" class="json-path-suggest-button">-->
<!--<el-link size="small" type="primary" @click="suggestJsonOpen" style="margin-right: 20px">-->
<!--{{ $t('api_test.request.assertions.json_path_suggest') }}-->
<!--</el-link>-->
<!--<el-link size="small" type="danger" @click="clearJson">-->
<!--{{ $t('api_test.request.assertions.json_path_clear') }}-->
<!--</el-link>-->
<!--</el-row>-->
<!--</div>-->
</div> </div>
<div>
<el-row :gutter="10" class="json-path-suggest-button">
<el-link size="small" type="primary" @click="suggestJsonOpen">
{{$t('api_test.request.assertions.json_path_suggest')}}
</el-link>
<el-link size="small" type="danger" @click="clearJson" style="margin-left: 20px">
{{$t('api_test.request.assertions.json_path_clear')}}
</el-link>
</el-row>
</div>
<ms-api-jsonpath-suggest-list @addJsonpathSuggest="addJsonpathSuggest" :request="assertions" <!--<ms-api-jsonpath-suggest-list @addJsonpathSuggest="addJsonpathSuggest" :request="request"-->
ref="jsonpathSuggestList"/> <!--ref="jsonpathSuggestList"/>-->
<ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions"/> <ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions" style="margin-bottom: 20px"/>
</div> </div>
</template> </template>
@ -49,23 +58,28 @@
import MsApiAssertionText from "./ApiAssertionText"; import MsApiAssertionText from "./ApiAssertionText";
import MsApiAssertionRegex from "./ApiAssertionRegex"; import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionDuration from "./ApiAssertionDuration"; import MsApiAssertionDuration from "./ApiAssertionDuration";
import {ASSERTION_TYPE, Assertions, HttpRequest, JSONPath} from "../../model/ApiTestModel"; import {ASSERTION_TYPE, Assertions, JSONPath, Scenario} from "../../model/ApiTestModel";
import MsApiAssertionsEdit from "./ApiAssertionsEdit"; import MsApiAssertionsEdit from "./ApiAssertionsEdit";
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath"; import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
import MsApiAssertionJsr223 from "./ApiAssertionJsr223";
import MsApiJsonpathSuggestList from "./ApiJsonpathSuggestList"; import MsApiJsonpathSuggestList from "./ApiJsonpathSuggestList";
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
export default { export default {
name: "MsApiAssertions", name: "MsApiAssertions",
components: { components: {
MsApiAssertionXPath2,
MsApiAssertionJsr223,
MsApiJsonpathSuggestList, MsApiJsonpathSuggestList,
MsApiAssertionJsonPath, MsApiAssertionJsonPath,
MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText
}, },
props: { props: {
assertions: {}, assertions: Assertions,
request: {}, request: {},
scenario: Scenario,
isReadOnly: { isReadOnly: {
type: Boolean, type: Boolean,
default: false default: false
@ -115,7 +129,6 @@
.assertion-add { .assertion-add {
padding: 10px; padding: 10px;
border: #DCDFE6 solid 1px;
margin: 5px 0; margin: 5px 0;
border-radius: 5px; border-radius: 5px;
} }
@ -143,10 +156,8 @@
} }
.json-path-suggest-button { .json-path-suggest-button {
text-align: left;
margin-left: 20px;
margin-top: 20px; margin-top: 20px;
margin-bottom: 20px; margin-left: 20px;
} }
</style> </style>

View File

@ -25,10 +25,6 @@
{{ $t('api_test.definition.request.body_binary') }} {{ $t('api_test.definition.request.body_binary') }}
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
<!--
<ms-dropdown :default-command="body.format" v-if="body.type == 'Raw'" :commands="modes" @command="modeChange"/>
-->
<ms-api-variable :is-read-only="isReadOnly" <ms-api-variable :is-read-only="isReadOnly"
:parameters="body.kvs" :parameters="body.kvs"
:isShowEnable="isShowEnable" :isShowEnable="isShowEnable"
@ -64,7 +60,7 @@
<script> <script>
import MsApiKeyValue from "../ApiKeyValue"; import MsApiKeyValue from "../ApiKeyValue";
import {Body, BODY_FORMAT, BODY_TYPE, KeyValue, Scenario} from "../../model/ApiTestModel"; import {BODY_FORMAT, BODY_TYPE, KeyValue} from "../../model/ApiTestModel";
import MsCodeEdit from "../../../../common/components/MsCodeEdit"; import MsCodeEdit from "../../../../common/components/MsCodeEdit";
import MsJsonCodeEdit from "../../../../common/components/MsJsonCodeEdit"; import MsJsonCodeEdit from "../../../../common/components/MsJsonCodeEdit";

View File

@ -1,7 +1,6 @@
import {boolProp, elementProp, stringProp} from "../../../props"; import {boolProp, elementProp, stringProp} from "../../../props";
import Sampler from "../sampler"; import Sampler from "../sampler";
import {BaseConfig, BODY_TYPE, KeyValue, Body} from "../../../../../model/ApiTestModel"; import {Body} from "../../../../../model/ApiTestModel";
const DEFAULT_OPTIONS = { const DEFAULT_OPTIONS = {
options: { options: {
attributes: { attributes: {
@ -32,8 +31,7 @@ export default class HTTPSamplerProxy extends Sampler {
this.embeddedUrlRe = this.initStringProp('HTTPSampler.embedded_url_re'); this.embeddedUrlRe = this.initStringProp('HTTPSampler.embedded_url_re');
this.connectTimeout = this.initStringProp('HTTPSampler.connect_timeout'); this.connectTimeout = this.initStringProp('HTTPSampler.connect_timeout');
this.responseTimeout = this.initStringProp('HTTPSampler.response_timeout'); this.responseTimeout = this.initStringProp('HTTPSampler.response_timeout');
// 初始化认证对象 和主体对象 // 初始化主体对象
this.authConfig = {};
this.body = new Body(); this.body = new Body();
this.arguments = []; this.arguments = [];

View File

@ -3,7 +3,7 @@
<el-col :span="21"> <el-col :span="21">
<!-- HTTP 请求参数 --> <!-- HTTP 请求参数 -->
<div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100%"> <div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100%">
<el-tabs v-model="activeName" style="margin: 20px"> <el-tabs v-model="activeName" style="margin: 20px;min-height: 200px">
<!-- 请求头--> <!-- 请求头-->
<el-tab-pane :label="$t('api_test.request.headers')" name="headers"> <el-tab-pane :label="$t('api_test.request.headers')" name="headers">
<el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.request.headers')" placement="top-start" slot="label"> <el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.request.headers')" placement="top-start" slot="label">
@ -46,6 +46,7 @@
<el-tab-pane :label="$t('api_test.request.body')" name="body"> <el-tab-pane :label="$t('api_test.request.body')" name="body">
<ms-api-body :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :headers="headers" :body="request.body"/> <ms-api-body :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :headers="headers" :body="request.body"/>
</el-tab-pane> </el-tab-pane>
<!-- 认证配置 --> <!-- 认证配置 -->
<el-tab-pane :label="$t('api_test.definition.request.auth_config')" name="authConfig"> <el-tab-pane :label="$t('api_test.definition.request.auth_config')" name="authConfig">
<el-tooltip class="item-tabs" effect="dark" content="请求需要进行权限校验" placement="top-start" slot="label"> <el-tooltip class="item-tabs" effect="dark" content="请求需要进行权限校验" placement="top-start" slot="label">
@ -65,11 +66,13 @@
<ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @remove="remove" :is-read-only="false" title="后置脚本" style-type="success" <ms-jsr233-processor v-if="row.label ==='JSR223 PostProcessor'" @remove="remove" :is-read-only="false" title="后置脚本" style-type="success"
:jsr223-processor="row"/> :jsr223-processor="row"/>
<!--<ms-api-assertions v-if="row.label.indexOf('Assertion')>0" :is-read-only="isReadOnly" :request="request"/>--> <ms-api-assertions v-if="row.type==='Assertions'" :is-read-only="isReadOnly" :assertions="row"/>
<!--<ms-api-extract :is-read-only="isReadOnly" v-if="row.label==='JSONPostProcessor'" :extract="row"/>-->
</div> </div>
<!--
<ms-api-extract :is-read-only="isReadOnly" v-if="row.label==='JSONPostProcessor'" :extract="row"/>
-->
</el-col> </el-col>
<el-col :span="3" class="ms-left-cell"> <el-col :span="3" class="ms-left-cell">
@ -77,9 +80,9 @@
<br/> <br/>
<el-button class="ms-left-buttion" size="small" type="success" @click="addPost" plain>+后置脚本</el-button> <el-button class="ms-left-buttion" size="small" type="success" @click="addPost" plain>+后置脚本</el-button>
<br/> <br/>
<!--<el-button class="ms-left-buttion" size="small" type="danger" @click="addAssertions" plain>+断言规则</el-button>--> <el-button class="ms-left-buttion" size="small" type="danger" @click="addAssertions" plain>+断言规则</el-button>
<!--<br/>--> <br/>
<!--<el-button class="ms-left-buttion" size="small" type="info" @click="addExtract" plain>+提取参数</el-button>--> <el-button class="ms-left-buttion" size="small" type="info" @click="addExtract" plain>+提取参数</el-button>
</el-col> </el-col>
</el-row> </el-row>
</template> </template>
@ -96,6 +99,7 @@
import {createComponent} from "../jmeter/components"; import {createComponent} from "../jmeter/components";
import MsApiAssertions from "../assertion/ApiAssertions"; import MsApiAssertions from "../assertion/ApiAssertions";
import MsApiExtract from "../extract/ApiExtract"; import MsApiExtract from "../extract/ApiExtract";
import {Assertions} from "../../model/ApiTestModel";
export default { export default {
name: "MsApiHttpRequestForm", name: "MsApiHttpRequestForm",
@ -139,9 +143,17 @@
}, },
headerSuggestions: REQUEST_HEADERS, headerSuggestions: REQUEST_HEADERS,
isReloadData: false, isReloadData: false,
assertions: [],
} }
}, },
created() {
this.request.hashTree.forEach(row => {
if (row.type === "Assertions") {
row = new Assertions({text: row.text, regex: row.regex, jsonPath: row.jsonPath, jsr223: row.jsr223, xpath2: row.xpath2, duration: row.duration});
}
})
console.log(this.assertions)
},
methods: { methods: {
addPre() { addPre() {
let jsr223PreProcessor = createComponent("JSR223PreProcessor"); let jsr223PreProcessor = createComponent("JSR223PreProcessor");
@ -154,8 +166,9 @@
this.reload(); this.reload();
}, },
addAssertions() { addAssertions() {
let jsonPathAssertion = createComponent("JSONPathAssertion"); //let jsonPathAssertion = createComponent("JSONPathAssertion");
this.request.hashTree.push(jsonPathAssertion); let assertions = new Assertions();
this.request.hashTree.push(assertions);
this.reload(); this.reload();
}, },
addExtract() { addExtract() {

View File

@ -5,13 +5,10 @@
<el-collapse-transition> <el-collapse-transition>
<el-tabs v-model="activeName" v-show="isActive" style="margin: 20px"> <el-tabs v-model="activeName" v-show="isActive" style="margin: 20px">
<el-tab-pane :label="$t('api_test.definition.request.response_header')" name="headers" class="pane"> <el-tab-pane :label="$t('api_test.definition.request.response_header')" name="headers" class="pane">
<ms-api-key-value :isShowEnable="false" :suggestions="headerSuggestions" <ms-api-key-value :isShowEnable="false" :suggestions="headerSuggestions" :items="response.headers"/>
:items="response.headers"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.response_body')" name="body" class="pane"> <el-tab-pane :label="$t('api_test.definition.request.response_body')" name="body" class="pane">
<ms-api-body <ms-api-body :isShowEnable="false" :body="response.body" :headers="response.headers"/>
:body="response.body"
:extract="response.extract"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.status_code')" name="status_code" class="pane"> <el-tab-pane :label="$t('api_test.definition.request.status_code')" name="status_code" class="pane">

View File

@ -98,7 +98,9 @@ export const ASSERTION_TYPE = {
TEXT: "Text", TEXT: "Text",
REGEX: "Regex", REGEX: "Regex",
JSON_PATH: "JSON", JSON_PATH: "JSON",
DURATION: "Duration" DURATION: "Duration",
JSR223: "JSR223",
XPATH2: "XPath2",
} }
export const ASSERTION_REGEX_SUBJECT = { export const ASSERTION_REGEX_SUBJECT = {
@ -434,7 +436,7 @@ export class HttpResponse extends Response {
this.headers = []; this.headers = [];
this.body = new Body(options.body); this.body = new Body(options.body);
this.statusCode = []; this.statusCode = [];
this.sets({statusCode: KeyValue,headers: KeyValue}, options); this.sets({statusCode: KeyValue, headers: KeyValue}, options);
} }
} }
@ -713,7 +715,7 @@ export class Body extends BaseConfig {
this.xml = undefined; this.xml = undefined;
this.json = undefined; this.json = undefined;
this.set(options); this.set(options);
this.sets({kvs: KeyValue},{fromUrlencoded: KeyValue},{binary: KeyValue}, options); this.sets({kvs: KeyValue}, {fromUrlencoded: KeyValue}, {binary: KeyValue}, options);
} }
isValid() { isValid() {
@ -759,13 +761,16 @@ export class KeyValue extends BaseConfig {
export class Assertions extends BaseConfig { export class Assertions extends BaseConfig {
constructor(options) { constructor(options) {
super(); super();
this.type = "Assertions";
this.text = []; this.text = [];
this.regex = []; this.regex = [];
this.jsonPath = []; this.jsonPath = [];
this.jsr223 = [];
this.xpath2 = [];
this.duration = undefined; this.duration = undefined;
this.set(options); this.set(options);
this.sets({text: Text, regex: Regex, jsonPath: JSONPath}, options); this.sets({text: Text, regex: Regex, jsonPath: JSONPath, jsr223: AssertionJSR223, xpath2: XPath2}, options);
} }
initOptions(options) { initOptions(options) {
@ -782,6 +787,37 @@ export class AssertionType extends BaseConfig {
} }
} }
export class AssertionJSR223 extends AssertionType {
constructor(options) {
super(ASSERTION_TYPE.JSR223);
this.variable = undefined;
this.operator = undefined;
this.value = undefined;
this.desc = undefined;
this.name = undefined;
this.script = undefined;
this.language = "beanshell";
this.set(options);
}
isValid() {
return !!this.script && !!this.language;
}
}
export class Text extends AssertionType {
constructor(options) {
super(ASSERTION_TYPE.TEXT);
this.subject = undefined;
this.condition = undefined;
this.value = undefined;
this.set(options);
}
}
export class BeanShellProcessor extends BaseConfig { export class BeanShellProcessor extends BaseConfig {
constructor(options) { constructor(options) {
super(); super();
@ -800,17 +836,6 @@ export class JSR223Processor extends BaseConfig {
} }
} }
export class Text extends AssertionType {
constructor(options) {
super(ASSERTION_TYPE.TEXT);
this.subject = undefined;
this.condition = undefined;
this.value = undefined;
this.set(options);
}
}
export class Regex extends AssertionType { export class Regex extends AssertionType {
constructor(options) { constructor(options) {
super(ASSERTION_TYPE.REGEX); super(ASSERTION_TYPE.REGEX);
@ -846,6 +871,20 @@ export class JSONPath extends AssertionType {
} }
} }
export class XPath2 extends AssertionType {
constructor(options) {
super(ASSERTION_TYPE.XPATH2);
this.expression = undefined;
this.description = undefined;
this.set(options);
}
isValid() {
return !!this.expression;
}
}
export class Duration extends AssertionType { export class Duration extends AssertionType {
constructor(options) { constructor(options) {
super(ASSERTION_TYPE.DURATION); super(ASSERTION_TYPE.DURATION);