feat(接口自动化 ): 多模式执行功能
This commit is contained in:
parent
f19f05db17
commit
2aca963926
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
package io.metersphere.api.dto.definition.request.sampler;
|
||||
|
||||
public class SamplerEnv {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,4 +11,5 @@ public class HttpConfigCondition {
|
|||
private String protocol;
|
||||
private String socket;
|
||||
private String domain;
|
||||
private int port;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
package io.metersphere.commons.constants;
|
||||
|
||||
public enum ConditionType {
|
||||
NO, PATH, MODULE
|
||||
NONE, PATH, MODULE
|
||||
}
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" +
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue