feat(接口自动化 ): 多模式执行功能

This commit is contained in:
fit2-zhao 2021-04-12 15:04:15 +08:00
parent f19f05db17
commit 2aca963926
13 changed files with 144 additions and 42 deletions

View File

@ -7,8 +7,18 @@ import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.definition.request.auth.MsAuthManager;
import io.metersphere.api.dto.definition.request.dns.MsDNSCacheManager;
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.HttpConfig;
import io.metersphere.api.dto.scenario.HttpConfigCondition;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.service.ApiDefinitionService;
import io.metersphere.api.service.ApiModuleService;
import io.metersphere.api.service.ApiTestEnvironmentService;
import io.metersphere.base.domain.ApiDefinition;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.commons.constants.ConditionType;
import io.metersphere.commons.constants.MsTestElementConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.ScriptEngineUtils;
import lombok.Data;
@ -128,7 +138,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
// 数据兼容处理
if(config.getConfig() != null && StringUtils.isNotEmpty(this.getProjectId()) && config.getConfig().containsKey(this.getProjectId())){
if (config.getConfig() != null && StringUtils.isNotEmpty(this.getProjectId()) && config.getConfig().containsKey(this.getProjectId())) {
// 1.8 之后 当前正常数据
} else if (config.getConfig() != null && config.getConfig().containsKey(getParentProjectId())) {
// 1.8 前后 混合数据
@ -155,12 +165,15 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
try {
if (config.isEffective(this.getProjectId())) {
String url = config.getConfig().get(this.getProjectId()).getHttpConfig().getProtocol() + "://" + config.getConfig().get(this.getProjectId()).getHttpConfig().getSocket();
HttpConfig httpConfig = getHttpConfig(config.getConfig().get(this.getProjectId()).getHttpConfig());
if (httpConfig == null) {
MSException.throwException("未匹配到环境,请检查环境配置");
}
String url = httpConfig.getProtocol() + "://" + httpConfig.getSocket();
// 补充如果是完整URL 则用自身URL
boolean isUrl = false;
if (StringUtils.isNotEmpty(this.getUrl()) && isURL(this.getUrl())) {
boolean isUrl;
if (isUrl = (StringUtils.isNotEmpty(this.getUrl()) && isURL(this.getUrl()))) {
url = this.getUrl();
isUrl = true;
}
if (isUrl) {
if (StringUtils.isNotEmpty(this.getPort()) && this.getPort().startsWith("${")) {
@ -177,15 +190,15 @@ public class MsHTTPSamplerProxy extends MsTestElement {
sampler.setProtocol(urlObject.getProtocol());
sampler.setPath(urlObject.getPath());
} else {
sampler.setDomain(config.getConfig().get(this.getProjectId()).getHttpConfig().getDomain());
url = config.getConfig().get(this.getProjectId()).getHttpConfig().getProtocol() + "://" + config.getConfig().get(this.getProjectId()).getHttpConfig().getSocket();
sampler.setDomain(httpConfig.getDomain());
url = httpConfig.getProtocol() + "://" + httpConfig.getSocket();
URL urlObject = new URL(url);
String envPath = StringUtils.equals(urlObject.getPath(), "/") ? "" : urlObject.getPath();
if (StringUtils.isNotBlank(this.getPath())) {
envPath += this.getPath();
}
sampler.setPort(config.getConfig().get(this.getProjectId()).getHttpConfig().getPort());
sampler.setProtocol(config.getConfig().get(this.getProjectId()).getHttpConfig().getProtocol());
sampler.setPort(httpConfig.getPort());
sampler.setProtocol(httpConfig.getProtocol());
sampler.setPath(envPath);
}
String envPath = sampler.getPath();
@ -233,6 +246,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
} catch (Exception e) {
LogUtil.error(e);
MSException.throwException(e.getMessage());
}
// REST参数
if (CollectionUtils.isNotEmpty(this.getRest())) {
@ -298,7 +312,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
private String getParentProjectId() {
MsTestElement parent = this.getParent();
while(parent != null) {
while (parent != null) {
if (StringUtils.isNotBlank(parent.getProjectId())) {
return parent.getProjectId();
}
@ -378,7 +392,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
}
public boolean isURL(String str) {
private boolean isURL(String str) {
try {
new URL(str);
return true;
@ -391,6 +405,37 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
}
/**
* 按照环境规则匹配环境
*
* @param httpConfig
* @return
*/
private HttpConfig getHttpConfig(HttpConfig httpConfig) {
if (CollectionUtils.isNotEmpty(httpConfig.getConditions())) {
for (HttpConfigCondition item : httpConfig.getConditions()) {
if (item.getType().equals(ConditionType.NONE.name())) {
return httpConfig.initHttpConfig(item);
} else if (item.getType().equals(ConditionType.PATH.name())) {
HttpConfig config = httpConfig.getPathCondition(this.getPath());
if (config != null) {
return config;
}
} else if (item.getType().equals(ConditionType.MODULE.name())) {
ApiDefinitionService apiDefinitionService = CommonBeanFactory.getBean(ApiDefinitionService.class);
ApiDefinition apiDefinition = apiDefinitionService.get(this.getId());
if (apiDefinition != null) {
HttpConfig config = httpConfig.getModuleCondition(apiDefinition.getModuleId());
if (config != null) {
return config;
}
}
}
}
}
return httpConfig;
}
private boolean isRest() {
return this.getRest().stream().filter(KeyValue::isEnable).filter(KeyValue::isValid).toArray().length > 0;
}

View File

@ -1,4 +0,0 @@
package io.metersphere.api.dto.definition.request.sampler;
public class SamplerEnv {
}

View File

@ -1,15 +1,59 @@
package io.metersphere.api.dto.scenario;
import io.metersphere.commons.constants.ConditionType;
import io.metersphere.commons.utils.BeanUtils;
import lombok.Data;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.beans.Beans;
import java.util.List;
import java.util.stream.Collectors;
@Data
public class HttpConfig {
private String socket;
private String domain;
private String protocol = "https";
private String defaultCondition;
private int port;
private List<HttpConfigCondition> conditions;
private List<KeyValue> headers;
public boolean isNode(String type) {
return StringUtils.equals(defaultCondition, type);
}
public HttpConfig initHttpConfig(HttpConfigCondition configCondition) {
HttpConfig config = new HttpConfig();
BeanUtils.copyBean(config, configCondition);
return config;
}
public HttpConfig getPathCondition(String path) {
List<HttpConfigCondition> conditions = this.getConditions().stream().filter(condition -> ConditionType.PATH.name().equals(condition.getType()) && CollectionUtils.isNotEmpty(condition.getDetails())).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(conditions)) {
for (HttpConfigCondition item : conditions) {
List<KeyValue> details = item.getDetails().stream().filter(detail -> (detail.getValue().equals("contains") && StringUtils.contains(path, detail.getName())) || (detail.getValue().equals("equals") && StringUtils.equals(path, detail.getName()))).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(details)) {
return initHttpConfig(item);
}
}
}
return null;
}
public HttpConfig getModuleCondition(String moduleId) {
List<HttpConfigCondition> conditions = this.getConditions().stream().filter(condition -> ConditionType.MODULE.name().equals(condition.getType()) && CollectionUtils.isNotEmpty(condition.getDetails())).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(conditions)) {
for (HttpConfigCondition item : conditions) {
List<KeyValue> details = item.getDetails().stream().filter(detail -> StringUtils.contains(detail.getValue(), moduleId)).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(details)) {
return initHttpConfig(item);
}
}
}
return null;
}
}

View File

@ -11,4 +11,5 @@ public class HttpConfigCondition {
private String protocol;
private String socket;
private String domain;
private int port;
}

View File

@ -1,6 +1,5 @@
package io.metersphere.api.jmeter;
import io.fabric8.kubernetes.client.extended.run.RunConfig;
import io.metersphere.api.dto.automation.RunModeConfig;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.exception.MSException;
@ -15,7 +14,6 @@ import org.apache.jmeter.visualizers.backend.BackendListener;
import org.apache.jorphan.collections.HashTree;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;

View File

@ -1,5 +1,5 @@
package io.metersphere.commons.constants;
public enum ConditionType {
NO, PATH, MODULE
NONE, PATH, MODULE
}

View File

@ -222,7 +222,7 @@
},
created() {
let dataRange = this.$route.params.dataSelectRange;
if (dataRange.length > 0) {
if (dataRange && dataRange.length > 0) {
this.activeDom = 'middle';
}
if (this.activeDom === 'left') {

View File

@ -42,7 +42,7 @@
import {Environment} from "../../model/EnvironmentModel";
import MsApiHostTable from "./ApiHostTable";
import MsDatabaseConfig from "../request/database/DatabaseConfig";
import MsEnvironmentHttpConfig from "./EnvironmentHttpConfig";
import MsEnvironmentHttpConfig from "../../../test/components/environment/EnvironmentHttpConfig";
import MsEnvironmentCommonConfig from "./EnvironmentCommonConfig";
import EnvironmentTcpConfig from "./EnvironmentTcpConfig";

View File

@ -3,15 +3,15 @@
<el-row type="flex">
<el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_code')}} :</div>
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{response.responseResult.responseCode ? response.responseResult.responseCode :'0'}}</div>
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{response.responseResult && response.responseResult.responseCode ? response.responseResult.responseCode :'0'}}</div>
</el-col>
<el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_time')}} :</div>
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{response.responseResult.responseTime?response.responseResult.responseTime:0}} ms</div>
<div style="font-size: 14px;color:#61C550;margin-top:2px;margin-left:10px;float: left">{{response.responseResult && response.responseResult.responseTime?response.responseResult.responseTime:0}} ms</div>
</el-col>
<el-col>
<div style="font-size: 14px;color: #AAAAAA;float: left">{{$t('api_report.response_size')}} :</div>
<div style="font-size: 14px;color:#61C550; margin-top:2px;margin-left:10px;float: left">{{response.responseResult.responseSize?response.responseResult.responseSize:0}} bytes</div>
<div style="font-size: 14px;color:#61C550; margin-top:2px;margin-left:10px;float: left">{{response.responseResult && response.responseResult.responseSize?response.responseResult.responseSize:0}} bytes</div>
</el-col>
</el-row>
</div>

View File

@ -117,8 +117,11 @@
if (!this.response.body) {
this.response.body = "";
}
if(!this.response.responseResult.vars){
this.response.responseResult.vars="";
if (!this.response.responseResult) {
this.response.responseResult = {};
}
if (!this.response.responseResult.vars) {
this.response.responseResult.vars = "";
}
this.reqMessages = this.$t('api_test.request.address') + ":\n" + this.response.url + "\n" +
this.$t('api_test.scenario.headers') + ":\n" + this.response.headers + "\n" + "Cookies :\n" +

View File

@ -145,7 +145,10 @@
})
},
runRefresh(data) {
this.responseData = data;
this.responseData = {type: 'HTTP', responseResult: {responseCode: ""}, subRequestResults: []};
if (data) {
this.responseData = data;
}
this.loading = false;
},
saveAs() {

View File

@ -14,7 +14,7 @@
<el-form-item prop="enable">
<span class="ms-env-span">{{$t('api_test.environment.condition_enable')}}</span>
<el-radio-group v-model="condition.type" @change="typeChange">
<el-radio label="NO">{{ $t('api_test.definition.document.data_set.none') }}</el-radio>
<el-radio label="NONE">{{ $t('api_test.definition.document.data_set.none') }}</el-radio>
<el-radio label="MODULE">{{$t('test_track.module.module')}}</el-radio>
<el-radio label="PATH">{{$t('api_test.definition.api_path')}}</el-radio>
</el-radio-group>
@ -52,6 +52,11 @@
{{getDetails(row)}}
</template>
</el-table-column>
<el-table-column prop="createTime" show-overflow-tooltip min-width="120px" :label="$t('commons.create_time')">
<template v-slot:default="{row}">
<span>{{ row.time | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')" width="100px">
<template v-slot:default="{row}">
<ms-table-operator-button :tip="$t('api_test.automation.copy')" icon="el-icon-document-copy" @exec="copy(row)"/>
@ -106,7 +111,7 @@
label: "name",
},
pathDetails: new KeyValue({name: "", value: "contains"}),
condition: {type: "NO", details: [new KeyValue({name: "", value: "contains"})], protocol: "", socket: "", domain: ""},
condition: {type: "NONE", details: [new KeyValue({name: "", value: "contains"})], protocol: "http", socket: "", domain: "", port: 0},
};
},
watch: {
@ -114,9 +119,7 @@
this.list();
},
httpConfig: function (o) {
this.condition.protocol = this.httpConfig.protocol;
this.condition.socket = this.httpConfig.socket;
this.condition.domain = this.httpConfig.domain;
this.condition = {type: "NONE", details: [new KeyValue({name: "", value: "contains"})], protocol: "http", socket: "", domain: "", port: 0};
},
},
methods: {
@ -125,7 +128,7 @@
},
getName(row) {
switch (row.type) {
case "NO":
case "NONE":
return this.$t("api_test.definition.document.data_set.none");
case "MODULE":
return this.$t("test_track.module.module");
@ -155,6 +158,7 @@
if (row) {
this.httpConfig.socket = row.socket;
this.httpConfig.protocol = row.protocol;
this.httpConfig.port = row.port;
this.condition = row;
if (row.type === "PATH" && row.details.length > 0) {
this.pathDetails = row.details[0];
@ -168,7 +172,7 @@
},
typeChange() {
switch (this.condition.type) {
case "NO":
case "NONE":
this.condition.details = [];
break;
case "MODULE":
@ -197,20 +201,27 @@
},
update() {
const index = this.httpConfig.conditions.findIndex((d) => d.id === this.condition.id);
let obj = {id: this.condition.id, type: this.condition.type, domain: this.httpConfig.domain, socket: this.httpConfig.socket, protocol: this.httpConfig.protocol, details: this.condition.details};
this.validateSocket(this.condition.socket);
let obj = {
id: this.condition.id, type: this.condition.type, domain: this.condition.domain, socket: this.condition.socket,
protocol: this.condition.protocol, details: this.condition.details, port: this.condition.port, time: this.condition.time
};
if (index !== -1) {
Vue.set(this.httpConfig.conditions[index], obj, 1);
this.condition = {type: "NO", details: [new KeyValue({name: "", value: "contains"})], protocol: "", socket: "", domain: ""};
this.condition = {type: "NONE", details: [new KeyValue({name: "", value: "contains"})], protocol: "", socket: "", domain: ""};
}
},
add() {
let obj = {id: getUUID(), type: this.condition.type, socket: this.httpConfig.socket, protocol: this.httpConfig.protocol, domain: this.httpConfig.domain,};
let obj = {
id: getUUID(), type: this.condition.type, socket: this.condition.socket, protocol: this.condition.protocol,
domain: this.condition.domain, port: this.condition.port, time: new Date().getTime()
};
if (this.condition.type === "PATH") {
obj.details = [JSON.parse(JSON.stringify(this.pathDetails))];
} else {
obj.details = this.condition.details ? JSON.parse(JSON.stringify(this.condition.details)) : this.condition.details;
}
this.httpConfig.conditions.push(obj);
this.httpConfig.conditions.unshift(obj);
},
remove(row) {
const index = this.httpConfig.conditions.findIndex((d) => d.id === row.id);
@ -227,21 +238,21 @@
},
validateSocket(socket) {
if (!socket) return true;
let urlStr = this.httpConfig.protocol + "://" + socket;
let urlStr = this.condition.protocol + "://" + socket;
let url = {};
try {
url = new URL(urlStr);
} catch (e) {
return false;
}
this.httpConfig.domain = decodeURIComponent(url.hostname);
this.condition.domain = decodeURIComponent(url.hostname);
this.httpConfig.port = url.port;
this.condition.port = url.port;
let path = url.pathname === "/" ? "" : url.pathname;
if (url.port) {
this.httpConfig.socket = this.httpConfig.domain + ":" + url.port + path;
this.condition.socket = this.condition.domain + ":" + url.port + path;
} else {
this.httpConfig.socket = this.httpConfig.domain + path;
this.condition.socket = this.condition.domain + path;
}
return true;
},

View File

@ -68,6 +68,7 @@ export class HttpConfig extends BaseConfig {
this.protocol = 'https';
this.port = undefined;
this.conditions = [];
this.defaultCondition = "NONE";
this.set(options);
this.sets({headers: KeyValue}, options);
this.sets({conditions: KeyValue}, options);