fix(接口测试): 修复引用的case无法添加断言的缺陷

--bug=1027145 --user=王孝刚 【接口测试】场景详情-引用的场景和case-无法添加断言
https://www.tapd.cn/55049933/s/1383491
This commit is contained in:
wxg0103 2023-06-20 15:22:39 +08:00 committed by 刘瑞斌
parent 25b515c16a
commit f3df278755
12 changed files with 145 additions and 41 deletions

View File

@ -11,6 +11,7 @@ public class MsAssertionType {
public final static String TEXT = "Text"; public final static String TEXT = "Text";
public final static String XPATH2 = "XPath2"; public final static String XPATH2 = "XPath2";
private boolean enable = true; private boolean enable = true;
public String label;
private String type; private String type;
} }

View File

@ -98,6 +98,15 @@ public class MsHashTreeService {
private static final String RESULT_VARIABLE = "resultVariable"; private static final String RESULT_VARIABLE = "resultVariable";
private static final String ENV_Id = "environmentId"; private static final String ENV_Id = "environmentId";
private final static String JSON_PATH="jsonPath";
private final static String JSR223="jsr223";
private final static String XPATH="xpath2";
private final static String REGEX="regex";
private final static String DURATION="duration";
private final static String DOCUMENT="document";
private final static String LABEL="label";
private final static String SCENARIO_REF="SCENARIO-REF-STEP";
public void setHashTree(JSONArray hashTree) { public void setHashTree(JSONArray hashTree) {
// 将引用转成复制 // 将引用转成复制
if (hashTree == null) { if (hashTree == null) {
@ -191,7 +200,7 @@ public class MsHashTreeService {
List<JSONObject> pre = ElementUtil.mergeHashTree(groupMap.get(PRE), targetGroupMap.get(PRE)); List<JSONObject> pre = ElementUtil.mergeHashTree(groupMap.get(PRE), targetGroupMap.get(PRE));
List<JSONObject> post = ElementUtil.mergeHashTree(groupMap.get(POST), targetGroupMap.get(POST)); List<JSONObject> post = ElementUtil.mergeHashTree(groupMap.get(POST), targetGroupMap.get(POST));
List<JSONObject> rules = ElementUtil.mergeHashTree(groupMap.get(ASSERTIONS), targetGroupMap.get(ASSERTIONS)); List<JSONObject> rules = mergeAssertions(groupMap.get(ASSERTIONS), targetGroupMap.get(ASSERTIONS));
List<JSONObject> step = new LinkedList<>(); List<JSONObject> step = new LinkedList<>();
if (CollectionUtils.isNotEmpty(pre)) { if (CollectionUtils.isNotEmpty(pre)) {
step.addAll(pre); step.addAll(pre);
@ -232,6 +241,68 @@ public class MsHashTreeService {
return element; return element;
} }
public List<JSONObject> mergeAssertions(List<JSONObject> sourceHashTree, List<JSONObject> targetHashTree) {
try {
if (CollectionUtils.isNotEmpty(targetHashTree)) {
JSONObject target = targetHashTree.get(0);
if (CollectionUtils.isNotEmpty(sourceHashTree)) {
JSONObject source = sourceHashTree.get(0);
//jsonPath
JSONArray jsonPathTar = target.optJSONArray(JSON_PATH);
JSONArray jsonPathSource = source.optJSONArray(JSON_PATH);
mergeArray(target, jsonPathTar, jsonPathSource, JSON_PATH);
//jsr223
JSONArray jsr223Tar = target.optJSONArray(JSR223);
JSONArray jsr223Source = source.optJSONArray(JSR223);
mergeArray(target, jsr223Tar, jsr223Source, JSR223);
//xpath
JSONArray xpathTar = target.optJSONArray(XPATH);
JSONArray xpathSource = source.optJSONArray(XPATH);
mergeArray(target, xpathTar, xpathSource, XPATH);
//regex
JSONArray regexTar = target.optJSONArray(REGEX);
JSONArray regexSource = source.optJSONArray(REGEX);
mergeArray(target, regexTar, regexSource, REGEX);
//duration
JSONObject durationTar = target.optJSONObject(DURATION);
JSONObject durationSource = source.optJSONObject(DURATION);
mergeObject(target, durationTar, durationSource, DURATION);
//document
JSONObject documentTar = target.optJSONObject(DOCUMENT);
JSONObject documentSource = source.optJSONObject(DOCUMENT);
mergeObject(target, documentTar, documentSource, DOCUMENT);
sourceHashTree.remove(0);
sourceHashTree.add(target);
}
}
} catch (Exception e) {
LogUtil.error("mergeAssertions error", e);
}
return sourceHashTree;
}
private static void mergeObject(JSONObject target, JSONObject durationTar, JSONObject durationSource, String type) {
if (durationSource != null && durationSource.has(LABEL) &&
StringUtils.equals(durationSource.optString(LABEL), SCENARIO_REF)) {
durationTar = durationSource;
}
target.remove(type);
target.put(type, durationTar);
}
private static void mergeArray(JSONObject target, JSONArray jsonArray, JSONArray source, String type) {
if (!source.isEmpty()) {
source.forEach(obj -> {
JSONObject jsonObject = (JSONObject) obj;
if (StringUtils.equals(jsonObject.optString(LABEL), SCENARIO_REF)) {
jsonArray.put(jsonObject);
}
});
}
target.remove(type);
target.put(type, jsonArray);
}
private void getCaseIds(JSONObject element, List<String> caseIds) { private void getCaseIds(JSONObject element, List<String> caseIds) {
if (StringUtils.equalsIgnoreCase(element.optString(REF_TYPE), CASE) && element.has(ID)) { if (StringUtils.equalsIgnoreCase(element.optString(REF_TYPE), CASE) && element.has(ID)) {

View File

@ -3,7 +3,7 @@
<el-row :gutter="10" type="flex" justify="space-between" align="middle"> <el-row :gutter="10" type="flex" justify="space-between" align="middle">
<el-col> <el-col>
<el-input <el-input
:disabled="isReadOnly" :disabled="isReadOnly && !duration.label"
:value="value" :value="value"
v-bind="$attrs" v-bind="$attrs"
step="100" step="100"
@ -21,18 +21,18 @@
v-model="duration.enable" v-model="duration.enable"
class="enable-switch" class="enable-switch"
size="mini" size="mini"
:disabled="isReadOnly" :disabled="isReadOnly && !duration.label"
style="width: 30px; margin-right: 10px" /> style="width: 30px; margin-right: 10px" />
</el-tooltip> </el-tooltip>
<el-button <el-button
:disabled="isReadOnly" :disabled="isReadOnly && !duration.label"
type="danger" type="danger"
size="mini" size="mini"
icon="el-icon-delete" icon="el-icon-delete"
circle circle
@click="remove" @click="remove"
v-if="edit" /> v-if="edit" />
<el-button :disabled="isReadOnly" type="primary" size="mini" @click="add" v-else> <el-button :disabled="isReadOnly && !duration.label" type="primary" size="mini" @click="add" v-else>
{{ $t('api_test.request.assertions.add') }} {{ $t('api_test.request.assertions.add') }}
</el-button> </el-button>
</el-col> </el-col>
@ -55,6 +55,11 @@ export default {
}, },
}, },
created() {
if (this.duration && !this.duration.valid && this.duration.value === 0 && this.isReadOnly) {
this.duration.label = "SCENARIO-REF-STEP";
}
},
methods: { methods: {
add() { add() {
if (this.validate()) { if (this.validate()) {

View File

@ -4,7 +4,7 @@
<el-col> <el-col>
<el-tooltip :disabled="showTip" placement="top" :content="jsonPath.expression"> <el-tooltip :disabled="showTip" placement="top" :content="jsonPath.expression">
<el-input <el-input
:disabled="isReadOnly" :disabled="isReadOnly && !jsonPath.label"
v-model="jsonPath.expression" v-model="jsonPath.expression"
maxlength="500" maxlength="500"
size="small" size="small"
@ -14,7 +14,7 @@
</el-col> </el-col>
<el-col> <el-col>
<el-select <el-select
:disabled="isReadOnly" :disabled="isReadOnly && !jsonPath.label"
v-model="jsonPath.option" v-model="jsonPath.option"
class="ms-col-type" class="ms-col-type"
size="small" size="small"
@ -29,7 +29,7 @@
<el-option :label="$t('api_test.request.assertions.regular_match')" value="REGEX"/> <el-option :label="$t('api_test.request.assertions.regular_match')" value="REGEX"/>
</el-select> </el-select>
<el-input <el-input
:disabled="isReadOnly" :disabled="isReadOnly && !jsonPath.label"
v-model="jsonPath.expect" v-model="jsonPath.expect"
size="small" size="small"
show-word-limit show-word-limit
@ -48,25 +48,25 @@
v-model="jsonPath.enable" v-model="jsonPath.enable"
class="enable-switch" class="enable-switch"
size="mini" size="mini"
:disabled="isReadOnly" :disabled="isReadOnly && !jsonPath.label"
style="width: 30px; margin-right: 10px"/> style="width: 30px; margin-right: 10px"/>
</el-tooltip> </el-tooltip>
<el-button <el-button
:disabled="isReadOnly" :disabled="isReadOnly && !jsonPath.label"
size="mini" size="mini"
icon="el-icon-copy-document" icon="el-icon-copy-document"
circle circle
@click="copyRow" @click="copyRow"
v-if="edit"/> v-if="edit"/>
<el-button <el-button
:disabled="isReadOnly" :disabled="isReadOnly && !jsonPath.label"
type="danger" type="danger"
size="mini" size="mini"
icon="el-icon-delete" icon="el-icon-delete"
circle circle
@click="remove" @click="remove"
v-if="edit"/> v-if="edit"/>
<el-button :disabled="isReadOnly" type="primary" size="mini" @click="add" v-else> <el-button :disabled="isReadOnly && !jsonPath.label" type="primary" size="mini" @click="add" v-else>
{{ $t('api_test.request.assertions.add') }} {{ $t('api_test.request.assertions.add') }}
</el-button> </el-button>
</el-col> </el-col>
@ -102,6 +102,9 @@ export default {
created() { created() {
if (!this.jsonPath.option) { if (!this.jsonPath.option) {
this.jsonPath.option = 'REGEX'; this.jsonPath.option = 'REGEX';
if (this.jsonPath && this.isReadOnly) {
this.jsonPath.label = "SCENARIO-REF-STEP";
}
} }
this.showTip = !this.jsonPath || !this.jsonPath.expression || this.jsonPath.expression.length < 50; this.showTip = !this.jsonPath || !this.jsonPath.expression || this.jsonPath.expression.length < 50;
}, },

View File

@ -5,10 +5,10 @@
{{ assertion.desc }} {{ assertion.desc }}
</div> </div>
<div class="assertion-item btn"> <div class="assertion-item btn">
<el-button :disabled="isReadOnly" type="success" size="mini" @click="detail"> <el-button :disabled="isReadOnly && !assertion.label" type="success" size="mini" @click="detail">
{{ $t('commons.edit') }} {{ $t('commons.edit') }}
</el-button> </el-button>
<el-button :disabled="isReadOnly" type="primary" size="mini" @click="add"> <el-button :disabled="isReadOnly && !assertion.label" type="primary" size="mini" @click="add">
{{ $t('api_test.request.assertions.add') }} {{ $t('api_test.request.assertions.add') }}
</el-button> </el-button>
</div> </div>
@ -19,16 +19,16 @@
</div> </div>
<div class="assertion-item btn circle"> <div class="assertion-item btn circle">
<i class="el-icon-view el-button el-button--primary el-button--mini is-circle" circle @click="showPage" /> <i class="el-icon-view el-button el-button--primary el-button--mini is-circle" circle @click="showPage" />
<el-button :disabled="isReadOnly" type="success" size="mini" icon="el-icon-edit" circle @click="detail" /> <el-button :disabled="isReadOnly && !assertion.label" type="success" size="mini" icon="el-icon-edit" circle @click="detail" />
<el-tooltip :content="$t('test_resource_pool.enable_disable')" placement="top"> <el-tooltip :content="$t('test_resource_pool.enable_disable')" placement="top">
<el-switch <el-switch
v-model="assertion.enable" v-model="assertion.enable"
class="enable-switch" class="enable-switch"
size="mini" size="mini"
:disabled="isReadOnly" :disabled="isReadOnly && !assertion.label"
style="width: 30px; margin: 0px 10px 0px 10px" /> style="width: 30px; margin: 0px 10px 0px 10px" />
</el-tooltip> </el-tooltip>
<el-button :disabled="isReadOnly" type="danger" size="mini" icon="el-icon-delete" circle @click="remove" /> <el-button :disabled="isReadOnly && !assertion.label" type="danger" size="mini" icon="el-icon-delete" circle @click="remove" />
</div> </div>
</el-row> </el-row>
@ -167,6 +167,11 @@ export default {
}; };
}, },
created() {
if (this.assertion.valid === undefined && this.isReadOnly) {
this.assertion.label = "SCENARIO-REF-STEP";
}
},
methods: { methods: {
add() { add() {
this.list.push(new AssertionJSR223(this.assertion)); this.list.push(new AssertionJSR223(this.assertion));

View File

@ -3,7 +3,7 @@
<el-row :gutter="10" type="flex" justify="space-between" align="middle"> <el-row :gutter="10" type="flex" justify="space-between" align="middle">
<el-col class="assertion-select"> <el-col class="assertion-select">
<el-select <el-select
:disabled="isReadOnly" :disabled="isReadOnly && !regex.label"
class="assertion-item" class="assertion-item"
v-model="regex.subject" v-model="regex.subject"
size="small" size="small"
@ -16,7 +16,7 @@
<el-col> <el-col>
<el-tooltip :disabled="showTip" placement="top" :content="regex.expression"> <el-tooltip :disabled="showTip" placement="top" :content="regex.expression">
<el-input <el-input
:disabled="isReadOnly" :disabled="isReadOnly && !regex.label"
v-model="regex.expression" v-model="regex.expression"
size="small" size="small"
show-word-limit show-word-limit
@ -24,7 +24,7 @@
</el-tooltip> </el-tooltip>
</el-col> </el-col>
<el-col class="assertion-checkbox"> <el-col class="assertion-checkbox">
<el-checkbox v-model="regex.assumeSuccess" :disabled="isReadOnly"> <el-checkbox v-model="regex.assumeSuccess" :disabled="isReadOnly && !regex.label">
{{ $t('api_test.request.assertions.ignore_status') }} {{ $t('api_test.request.assertions.ignore_status') }}
</el-checkbox> </el-checkbox>
</el-col> </el-col>
@ -34,26 +34,26 @@
v-model="regex.enable" v-model="regex.enable"
class="enable-switch" class="enable-switch"
size="mini" size="mini"
:disabled="isReadOnly" :disabled="isReadOnly && !regex.label"
style="width: 30px; margin-right: 10px" /> style="width: 30px; margin-right: 10px" />
</el-tooltip> </el-tooltip>
<el-button <el-button
:disabled="isReadOnly" :disabled="isReadOnly && !regex.label"
size="mini" size="mini"
icon="el-icon-copy-document" icon="el-icon-copy-document"
circle circle
@click="copyRow" @click="copyRow"
v-if="edit" /> v-if="edit" />
<el-button <el-button
:disabled="isReadOnly" :disabled="isReadOnly && !regex.label"
type="danger" type="danger"
size="mini" size="mini"
icon="el-icon-delete" icon="el-icon-delete"
circle circle
@click="remove" @click="remove"
v-if="edit" /> v-if="edit" />
<el-button :disabled="isReadOnly" type="primary" size="mini" @click="add" v-else> <el-button :disabled="isReadOnly && !regex.label" type="primary" size="mini" @click="add" v-else>
{{ $t('api_test.request.assertions.add') }} {{ $t('api_test.request.assertions.add') }}
</el-button> </el-button>
</el-col> </el-col>
@ -102,6 +102,9 @@ export default {
}, },
}, },
created() { created() {
if (!this.regex.description && this.isReadOnly) {
this.regex.label = "SCENARIO-REF-STEP";
}
this.showTip = !this.regex || !this.regex.description || this.regex.description.length < 80; this.showTip = !this.regex || !this.regex.description || this.regex.description.length < 80;
}, },
methods: { methods: {

View File

@ -3,7 +3,7 @@
<el-row :gutter="10" type="flex" justify="space-between" align="middle"> <el-row :gutter="10" type="flex" justify="space-between" align="middle">
<el-col class="assertion-select"> <el-col class="assertion-select">
<el-select <el-select
:disabled="isReadOnly" :disabled="isReadOnly && !label"
class="assertion-item" class="assertion-item"
v-model="subject" v-model="subject"
size="small" size="small"
@ -15,7 +15,7 @@
</el-col> </el-col>
<el-col class="assertion-select"> <el-col class="assertion-select">
<el-select <el-select
:disabled="isReadOnly" :disabled="isReadOnly && !label"
class="assertion-item" class="assertion-item"
v-model="condition" v-model="condition"
size="small" size="small"
@ -29,7 +29,7 @@
</el-col> </el-col>
<el-col> <el-col>
<el-input <el-input
:disabled="isReadOnly" :disabled="isReadOnly && !label"
v-model="value" v-model="value"
maxlength="500" maxlength="500"
size="small" size="small"
@ -37,12 +37,12 @@
:placeholder="$t('api_test.request.assertions.value')" /> :placeholder="$t('api_test.request.assertions.value')" />
</el-col> </el-col>
<el-col class="assertion-checkbox"> <el-col class="assertion-checkbox">
<el-checkbox v-model="assumeSuccess" :disabled="isReadOnly"> <el-checkbox v-model="assumeSuccess" :disabled="isReadOnly && !label">
{{ $t('api_test.request.assertions.ignore_status') }} {{ $t('api_test.request.assertions.ignore_status') }}
</el-checkbox> </el-checkbox>
</el-col> </el-col>
<el-col class="assertion-btn"> <el-col class="assertion-btn">
<el-button :disabled="isReadOnly" type="primary" size="mini" @click="add"> <el-button :disabled="isReadOnly && !label" type="primary" size="mini" @click="add">
{{ $t('api_test.request.assertions.add') }} {{ $t('api_test.request.assertions.add') }}
</el-button> </el-button>
</el-col> </el-col>
@ -72,8 +72,14 @@ export default {
condition: '', condition: '',
assumeSuccess: false, assumeSuccess: false,
value: '', value: '',
label: '',
}; };
}, },
created() {
if (this.isReadOnly) {
this.label = 'SCENARIO-REF-STEP';
}
},
methods: { methods: {
add: function () { add: function () {
@ -110,6 +116,7 @@ export default {
expression: expression, expression: expression,
description: description, description: description,
assumeSuccess: this.assumeSuccess, assumeSuccess: this.assumeSuccess,
label: this.label,
}); });
}, },
}, },

View File

@ -3,7 +3,7 @@
<el-row :gutter="10" type="flex" justify="space-between" align="middle"> <el-row :gutter="10" type="flex" justify="space-between" align="middle">
<el-col> <el-col>
<el-input <el-input
:disabled="isReadOnly" :disabled="isReadOnly && !xPath2.label"
v-model="xPath2.expression" v-model="xPath2.expression"
maxlength="500" maxlength="500"
size="small" size="small"
@ -16,25 +16,25 @@
v-model="xPath2.enable" v-model="xPath2.enable"
class="enable-switch" class="enable-switch"
size="mini" size="mini"
:disabled="isReadOnly" :disabled="isReadOnly && !xPath2.label"
style="width: 30px; margin-right: 10px" /> style="width: 30px; margin-right: 10px" />
</el-tooltip> </el-tooltip>
<el-button <el-button
:disabled="isReadOnly" :disabled="isReadOnly && !xPath2.label"
size="mini" size="mini"
icon="el-icon-copy-document" icon="el-icon-copy-document"
circle circle
@click="copyRow" @click="copyRow"
v-if="edit" /> v-if="edit" />
<el-button <el-button
:disabled="isReadOnly" :disabled="isReadOnly && !xPath2.label"
type="danger" type="danger"
size="mini" size="mini"
icon="el-icon-delete" icon="el-icon-delete"
circle circle
@click="remove" @click="remove"
v-if="edit" /> v-if="edit" />
<el-button :disabled="isReadOnly" type="primary" size="mini" @click="add" v-else> <el-button :disabled="isReadOnly && !xPath2.label" type="primary" size="mini" @click="add" v-else>
{{ $t('api_test.request.assertions.add') }} {{ $t('api_test.request.assertions.add') }}
</el-button> </el-button>
</el-col> </el-col>
@ -67,6 +67,12 @@ export default {
}, },
}, },
created() {
if (this.xPath2.valid === undefined && this.isReadOnly) {
this.xPath2.label = "SCENARIO-REF-STEP";
}
},
methods: { methods: {
add: function () { add: function () {
this.list.push(this.getXPath2()); this.list.push(this.getXPath2());

View File

@ -6,7 +6,6 @@
<api-json-path-suggest-button <api-json-path-suggest-button
:open-tip="$t('api_test.request.assertions.json_path_suggest')" :open-tip="$t('api_test.request.assertions.json_path_suggest')"
:clear-tip="$t('api_test.request.assertions.json_path_clear')" :clear-tip="$t('api_test.request.assertions.json_path_clear')"
:isReadOnly="isReadOnly"
@open="suggestJsonOpen" @open="suggestJsonOpen"
@clear="clearJson" /> @clear="clearJson" />
</span> </span>
@ -15,7 +14,6 @@
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="4"> <el-col :span="4">
<el-select <el-select
:disabled="isReadOnly"
class="assertion-item" class="assertion-item"
v-model="type" v-model="type"
:placeholder="$t('api_test.request.assertions.select_type')" :placeholder="$t('api_test.request.assertions.select_type')"

View File

@ -68,7 +68,7 @@
v-model="assertions.document.enable" v-model="assertions.document.enable"
class="enable-switch" class="enable-switch"
size="mini" size="mini"
:disabled="assertions.disabled" :disabled="isReadOnly && !assertions.document.label"
style="width: 30px; margin-right: 10px" /> style="width: 30px; margin-right: 10px" />
</el-tooltip> </el-tooltip>
<el-tooltip effect="dark" :content="$t('commons.remove')" placement="top-start"> <el-tooltip effect="dark" :content="$t('commons.remove')" placement="top-start">
@ -78,12 +78,12 @@
size="mini" size="mini"
circle circle
@click="remove()" @click="remove()"
:disabled="assertions.disabled && !assertions.root" /> :disabled="isReadOnly && !assertions.document.label && !assertions.root" />
</el-tooltip> </el-tooltip>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<ms-document-body :document="assertions.document" :apiId="apiId" :isReadOnly="isReadOnly" @remove="remove" /> <ms-document-body :document="assertions.document" :apiId="apiId" :isReadOnly="isReadOnly && !assertions.document.label" @remove="remove" />
</div> </div>
</div> </div>
</template> </template>

View File

@ -8,7 +8,7 @@
</el-select> </el-select>
</el-col> </el-col>
<el-col class="assertion-btn"> <el-col class="assertion-btn">
<el-button :disabled="isReadOnly" type="primary" size="mini" @click="add"> <el-button :disabled="isReadOnly && !document.label" type="primary" size="mini" @click="add">
{{ $t('api_test.request.assertions.add') }} {{ $t('api_test.request.assertions.add') }}
</el-button> </el-button>
</el-col> </el-col>
@ -32,6 +32,11 @@ export default {
default: false, default: false,
}, },
}, },
created() {
if (this.document && !this.document.originalData && !this.document.rootData && this.isReadOnly) {
this.document.label = "SCENARIO-REF-STEP";
}
},
methods: { methods: {
add() { add() {

View File

@ -114,7 +114,7 @@
:request="request" :request="request"
:apiId="apiId" :apiId="apiId"
:draggable="true" :draggable="true"
:is-read-only="data.disabled" :is-read-only="request.disabled"
:assertions="data" /> :assertions="data" />
</div> </div>
</span> </span>