feat(接口定义): 认证配置 完成

This commit is contained in:
fit2-zhao 2020-11-20 18:56:21 +08:00
parent bdaff53eaf
commit 58cbe145f7
20 changed files with 367 additions and 170 deletions

View File

@ -4,12 +4,14 @@ import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
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.processors.post.MsJSR223PostProcessor;
import io.metersphere.api.dto.definition.request.processors.pre.MsJSR223PreProcessor;
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import io.metersphere.commons.utils.LogUtil;
import lombok.Data;
import org.apache.jmeter.protocol.http.control.AuthManager;
import org.apache.jmeter.save.SaveService;
import org.apache.jorphan.collections.HashTree;
import org.apache.jorphan.collections.ListedHashTree;
@ -26,11 +28,12 @@ import java.util.List;
@JsonSubTypes.Type(value = MsJSR223PreProcessor.class, name = "JSR223PreProcessor"),
@JsonSubTypes.Type(value = MsTestPlan.class, name = "TestPlan"),
@JsonSubTypes.Type(value = MsThreadGroup.class, name = "ThreadGroup"),
@JsonSubTypes.Type(value = MsAuthManager.class, name = "AuthManager"),
})
@JSONType(seeAlso = {MsHTTPSamplerProxy.class, MsHeaderManager.class, MsJSR223PostProcessor.class, MsJSR223PreProcessor.class}, typeKey = "type")
@JSONType(seeAlso = {MsHTTPSamplerProxy.class, MsHeaderManager.class, MsJSR223PostProcessor.class, MsJSR223PreProcessor.class,MsTestPlan.class,MsThreadGroup.class, AuthManager.class}, typeKey = "type")
@Data
public class MsTestElement {
public abstract class MsTestElement {
private String type;
@JSONField(ordinal = 1)
private String id;
@ -65,7 +68,7 @@ public class MsTestElement {
return null;
}
public HashTree get() {
public HashTree generateHashTree() {
HashTree jmeterTestPlanHashTree = new ListedHashTree();
this.toHashTree(jmeterTestPlanHashTree, this.hashTree);
return jmeterTestPlanHashTree;

View File

@ -0,0 +1,77 @@
package io.metersphere.api.dto.definition.request.auth;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.service.ApiTestEnvironmentService;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.commons.utils.CommonBeanFactory;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.jmeter.protocol.http.control.AuthManager;
import org.apache.jmeter.protocol.http.control.Authorization;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@JSONType(typeName = "AuthManager")
public class MsAuthManager extends MsTestElement {
private String type = "AuthManager";
@JSONField(ordinal = 10)
private String username;
@JSONField(ordinal = 11)
private String password;
@JSONField(ordinal = 12)
private String url;
@JSONField(ordinal = 13)
private String realm;
@JSONField(ordinal = 14)
private String verification;
@JSONField(ordinal = 15)
private String mechanism;
@JSONField(ordinal = 16)
private String encrypt;
@JSONField(ordinal = 17)
private String domain;
@JSONField(ordinal = 18)
private String environment;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
AuthManager authManager = new AuthManager();
authManager.setEnabled(true);
authManager.setName(this.getUsername() + "AuthManager");
authManager.setProperty(TestElement.TEST_CLASS, AuthManager.class.getName());
authManager.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("AuthPanel"));
Authorization auth = new Authorization();
if (this.url != null) {
auth.setURL(this.url);
} else {
if (environment != null) {
ApiTestEnvironmentService environmentService = CommonBeanFactory.getBean(ApiTestEnvironmentService.class);
ApiTestEnvironmentWithBLOBs environmentWithBLOBs = environmentService.get(environment);
EnvironmentConfig config = JSONObject.parseObject(environmentWithBLOBs.getConfig(), EnvironmentConfig.class);
this.url = config.getHttpConfig().getProtocol() + "://" + config.getHttpConfig().getSocket();
}
}
auth.setDomain(this.domain);
auth.setUser(this.username);
auth.setPass(this.password);
auth.setMechanism(AuthManager.Mechanism.DIGEST);
authManager.addAuth(auth);
tree.add(authManager);
}
}

View File

@ -1,5 +1,6 @@
package io.metersphere.api.dto.definition.request.sampler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
@ -11,6 +12,7 @@ import io.metersphere.api.dto.scenario.AuthConfig;
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.scenario.request.BodyFile;
import io.metersphere.api.service.ApiTestEnvironmentService;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.commons.utils.CommonBeanFactory;
@ -84,7 +86,6 @@ public class MsHTTPSamplerProxy extends MsTestElement {
@JSONField(ordinal = 24)
private String url;
public void toHashTree(HashTree tree, List<MsTestElement> hashTree) {
HTTPSamplerProxy sampler = new HTTPSamplerProxy();
sampler.setEnabled(true);
@ -116,7 +117,11 @@ public class MsHTTPSamplerProxy extends MsTestElement {
if (StringUtils.isNotBlank(this.getPath().getValue())) {
envPath += this.getPath().getValue();
}
sampler.setPath(getPostQueryParameters(URLDecoder.decode(envPath, "UTF-8")));
if (CollectionUtils.isNotEmpty(this.getRest()) && this.isRest()) {
sampler.setPath(getRestParameters(URLDecoder.decode(envPath, "UTF-8")));
} else {
sampler.setPath(getPostQueryParameters(URLDecoder.decode(envPath, "UTF-8")));
}
} else {
String url = this.getUrl();
if (!url.startsWith("http://") && !url.startsWith("https://")) {
@ -126,6 +131,8 @@ public class MsHTTPSamplerProxy extends MsTestElement {
sampler.setDomain(URLDecoder.decode(urlObject.getHost(), "UTF-8"));
sampler.setPort(urlObject.getPort());
sampler.setProtocol(urlObject.getProtocol());
sampler.setPath(getRestParameters(URLDecoder.decode(urlObject.getPath(), "UTF-8")));
sampler.setPath(getPostQueryParameters(URLDecoder.decode(urlObject.getPath(), "UTF-8")));
}
} catch (Exception e) {
@ -136,21 +143,22 @@ public class MsHTTPSamplerProxy extends MsTestElement {
if (CollectionUtils.isNotEmpty(this.getArguments())) {
sampler.setArguments(httpArguments(this.getArguments()));
}
// rest参数处理
if (CollectionUtils.isNotEmpty(this.getRest())) {
sampler.setArguments(httpArguments(this.getRest()));
}
// 请求体
if (!StringUtils.equals(this.getMethod().getValue(), "GET")) {
List<KeyValue> body = new ArrayList<>();
if (this.getBody().isKV()) {
if (this.getBody().isKV() || this.getBody().isBinary()) {
body = this.getBody().getKvs().stream().filter(KeyValue::isValid).collect(Collectors.toList());
sampler.setHTTPFiles(httpFileArgs());
} else if (this.getBody().isBinary()) {
// 上传二进制数据处理
HTTPFileArg[] httpFileArgs = httpFileArgs();
// 文件上传
if (httpFileArgs.length > 0) {
sampler.setHTTPFiles(httpFileArgs());
sampler.setDoMultipart(true);
}
} else if (this.getBody().isJson()) {
KeyValue keyValue = new KeyValue("", JSON.toJSONString(this.getBody().getJson()));
keyValue.setEnable(true);
keyValue.setEncode(false);
body.add(keyValue);
} else {
if (StringUtils.isNotBlank(this.getBody().getRaw())) {
sampler.setPostBodyRaw(true);
@ -184,17 +192,24 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
}
private String getRestParameters(String path) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(path);
stringBuffer.append("/");
this.getRest().stream().filter(KeyValue::isEnable).filter(KeyValue::isValid).forEach(keyValue ->
stringBuffer.append(keyValue.getValue()).append("/")
);
return stringBuffer.substring(0, stringBuffer.length() - 1);
}
private String getPostQueryParameters(String path) {
if (!StringUtils.equals(this.getMethod().getValue(), "GET")) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(path);
stringBuffer.append("?");
this.getArguments().stream().filter(KeyValue::isEnable).filter(KeyValue::isValid).forEach(keyValue ->
stringBuffer.append(keyValue.getName()).append("=").append(keyValue.getValue()).append("&")
);
return stringBuffer.substring(0, stringBuffer.length() - 1);
}
return path;
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(path);
stringBuffer.append("?");
this.getArguments().stream().filter(KeyValue::isEnable).filter(KeyValue::isValid).forEach(keyValue ->
stringBuffer.append(keyValue.getName()).append("=").append(keyValue.getValue()).append("&")
);
return stringBuffer.substring(0, stringBuffer.length() - 1);
}
private Arguments httpArguments(List<KeyValue> list) {
@ -211,21 +226,30 @@ public class MsHTTPSamplerProxy extends MsTestElement {
return arguments;
}
private HTTPFileArg[] httpFileArgs() {
private void setFileArg(List<HTTPFileArg> list, List<BodyFile> files, KeyValue keyValue) {
final String BODY_FILE_DIR = "/opt/metersphere/data/body";
if (files != null) {
files.forEach(file -> {
String paramName = keyValue.getName() == null ? this.getId() : keyValue.getName();
String path = BODY_FILE_DIR + '/' + file.getId() + '_' + file.getName();
String mimetype = keyValue.getContentType();
list.add(new HTTPFileArg(path, paramName, mimetype));
});
}
}
private HTTPFileArg[] httpFileArgs() {
List<HTTPFileArg> list = new ArrayList<>();
this.getBody().getKvs().stream().filter(KeyValue::isFile).filter(KeyValue::isEnable).forEach(keyValue -> {
if (keyValue.getFiles() != null) {
keyValue.getFiles().forEach(file -> {
String paramName = keyValue.getName();
String path = BODY_FILE_DIR + '/' + this.getId() + '/' + file.getId() + '_' + file.getName();
String mimetype = keyValue.getContentType();
list.add(new HTTPFileArg(path, paramName, mimetype));
});
}
setFileArg(list, keyValue.getFiles(), keyValue);
});
this.getBody().getBinary().stream().filter(KeyValue::isFile).filter(KeyValue::isEnable).forEach(keyValue -> {
setFileArg(list, keyValue.getFiles(), keyValue);
});
return list.toArray(new HTTPFileArg[0]);
}
private boolean isRest() {
return this.getRest().stream().filter(KeyValue::isEnable).filter(KeyValue::isValid).toArray().length > 0;
}
}

View File

@ -19,7 +19,7 @@ public class Body {
private final static String KV = "KeyValue";
private final static String FORM_DATA = "Form Data";
private final static String RAW = "Raw";
private final static String BINARY = "Binary";
private final static String BINARY = "BINARY";
private final static String JSON = "JSON";
private final static String XML = "XML";

View File

@ -2,6 +2,7 @@ package io.metersphere.api.dto.scenario;
import io.metersphere.api.dto.scenario.request.BodyFile;
import lombok.Data;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
@ -43,6 +44,6 @@ public class KeyValue {
}
public boolean isFile() {
return (StringUtils.isNotBlank(name) || StringUtils.isNotBlank(value)) && StringUtils.equalsIgnoreCase(type, "file");
return (CollectionUtils.isNotEmpty(files)) && StringUtils.equalsIgnoreCase(type, "file");
}
}

View File

@ -22,6 +22,7 @@ import io.metersphere.service.SystemParameterService;
import io.metersphere.track.service.TestPlanTestCaseService;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.assertions.AssertionResult;
import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient;
import org.apache.jmeter.visualizers.backend.BackendListenerContext;
@ -278,6 +279,10 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
requestResult.setTotalAssertions(result.getAssertionResults().length);
requestResult.setSuccess(result.isSuccessful());
requestResult.setError(result.getErrorCount());
if (result instanceof HTTPSampleResult) {
HTTPSampleResult res = (HTTPSampleResult) result;
requestResult.setCookies(res.getCookies());
}
for (SampleResult subResult : result.getSubResults()) {
requestResult.getSubRequestResults().add(getRequestResult(subResult));
}

View File

@ -26,7 +26,7 @@ public class ApiDefinitionExecResultService {
ApiDefinitionExecResult saveResult = new ApiDefinitionExecResult();
saveResult.setId(UUID.randomUUID().toString());
saveResult.setUserId(Objects.requireNonNull(SessionUtils.getUser()).getId());
saveResult.setName(item.getUrl());
saveResult.setName(item.getName());
saveResult.setResourceId(item.getName());
saveResult.setContent(JSON.toJSONString(item));
saveResult.setStartTime(item.getStartTime());

View File

@ -13,13 +13,11 @@ import io.metersphere.base.mapper.ApiTestFileMapper;
import io.metersphere.commons.constants.APITestStatus;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.i18n.Translator;
import io.metersphere.service.FileService;
import io.metersphere.service.QuotaService;
import org.apache.jorphan.collections.HashTree;
import org.aspectj.util.FileUtil;
import org.springframework.stereotype.Service;
@ -85,35 +83,27 @@ public class ApiDefinitionService {
public void create(SaveApiDefinitionRequest request, List<MultipartFile> bodyFiles) {
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
ApiDefinition test = createTest(request);
createBodyFiles(test.getId(), bodyUploadIds, bodyFiles);
}
private void checkQuota() {
QuotaService quotaService = CommonBeanFactory.getBean(QuotaService.class);
if (quotaService != null) {
quotaService.checkAPITestQuota();
}
createTest(request);
createBodyFiles(bodyUploadIds, bodyFiles);
}
public void update(SaveApiDefinitionRequest request, List<MultipartFile> bodyFiles) {
deleteFileByTestId(request.getId());
deleteFileByTestId(request.getRequest().getId());
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
request.setBodyUploadIds(null);
ApiDefinition test = updateTest(request);
createBodyFiles(test.getId(), bodyUploadIds, bodyFiles);
updateTest(request);
createBodyFiles(bodyUploadIds, bodyFiles);
}
private void createBodyFiles(String testId, List<String> bodyUploadIds, List<MultipartFile> bodyFiles) {
private void createBodyFiles(List<String> bodyUploadIds, List<MultipartFile> bodyFiles) {
if (bodyUploadIds.size() > 0) {
String dir = BODY_FILE_DIR + "/" + testId;
File testDir = new File(dir);
File testDir = new File(BODY_FILE_DIR);
if (!testDir.exists()) {
testDir.mkdirs();
}
for (int i = 0; i < bodyUploadIds.size(); i++) {
MultipartFile item = bodyFiles.get(i);
File file = new File(testDir + "/" + bodyUploadIds.get(i) + "_" + item.getOriginalFilename());
File file = new File(BODY_FILE_DIR + "/" + bodyUploadIds.get(i) + "_" + item.getOriginalFilename());
try (InputStream in = item.getInputStream(); OutputStream out = new FileOutputStream(file)) {
file.createNewFile();
FileUtil.copyStream(in, out);
@ -227,9 +217,9 @@ public class ApiDefinitionService {
*/
public String run(RunDefinitionRequest request, List<MultipartFile> bodyFiles) {
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
createBodyFiles(request.getId(), bodyUploadIds, bodyFiles);
createBodyFiles(bodyUploadIds, bodyFiles);
HashTree hashTree = request.getTestElement().get();
HashTree hashTree = request.getTestElement().generateHashTree();
// 调用执行方法
jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), ApiRunMode.DELIMIT.name());
return request.getId();

View File

@ -6,6 +6,10 @@
<div class="kv-row" v-for="(item, index) in parameters" :key="index">
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
<el-col class="kv-checkbox" v-if="isShowEnable">
<input type="checkbox" v-if="!isDisable(index)" v-model="item.enable"
:disabled="isReadOnly"/>
</el-col>
<el-col>
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
@ -59,11 +63,11 @@
<ms-api-body-file-upload :parameter="item"/>
</el-col>
<el-col v-if="type === 'body'" class="kv-select">
<el-input :disabled="isReadOnly" v-model="item.contentType" size="small"
@change="change" :placeholder="$t('api_test.request.content_type')" show-word-limit>
</el-input>
</el-col>
<!--<el-col v-if="type === 'body'" class="kv-select">-->
<!--<el-input :disabled="isReadOnly" v-model="item.contentType" size="small"-->
<!--@change="change" :placeholder="$t('api_test.request.content_type')" show-word-limit>-->
<!--</el-input>-->
<!--</el-col>-->
<el-col class="kv-delete">
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
@ -105,6 +109,10 @@
type: Boolean,
default: false
},
isShowEnable: {
type: Boolean,
default: true
},
suggestions: Array
},
data() {

View File

@ -3,7 +3,6 @@
</template>
<script>
import {getUUID} from "@/common/js/utils";
import HeaderManager from "./jmeter/components/configurations/header-manager";
import ThreadGroup from "./jmeter/components/thread-group";
import TestPlan from "./jmeter/components/test-plan";
@ -58,10 +57,12 @@
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
obj.bodyUploadIds.push(fileId);
if (!item.id) {
let fileId = getUUID().substring(0, 12);
item.name = item.file.name;
item.id = fileId;
}
obj.bodyUploadIds.push(item.id);
bodyUploadFiles.push(item.file);
}
});
@ -71,10 +72,12 @@
if (param.files) {
param.files.forEach(item => {
if (item.file) {
let fileId = getUUID().substring(0, 8);
item.name = item.file.name;
item.id = fileId;
obj.bodyUploadIds.push(fileId);
if (!item.id) {
let fileId = getUUID().substring(0, 12);
item.name = item.file.name;
item.id = fileId;
}
obj.bodyUploadIds.push(item.id);
bodyUploadFiles.push(item.file);
}
});

View File

@ -3,11 +3,9 @@
<!-- 认证-->
<el-tab-pane :label="$t('api_test.definition.request.verified')" name="verified">
<el-form :model="authConfig" size="small" :rules="rule"
ref="authConfig">
<el-form :model="authConfig" size="small" :rules="rule" ref="authConfig">
<el-form-item :label="$t('api_test.definition.request.verification_method')" prop="verification">
<el-select v-model="authConfig.verification"
<el-select v-model="authConfig.verification" @change="change"
:placeholder="$t('api_test.definition.request.verification_method')" filterable size="small">
<el-option
v-for="item in options"
@ -20,13 +18,13 @@
</el-form-item>
<el-form-item :label="$t('api_test.request.tcp.username')" prop="username"
v-if="authConfig.verification !='No Auth'">
v-if="authConfig.verification!=undefined && authConfig.verification !='No Auth'">
<el-input :placeholder="$t('api_test.request.tcp.username')" v-model="authConfig.username"
class="ms-http-input" size="small">
</el-input>
</el-form-item>
<el-form-item :label="$t('commons.password')" prop="password" v-if="authConfig.verification !='No Auth'">
<el-form-item :label="$t('commons.password')" prop="password" v-if=" authConfig.verification!=undefined && authConfig.verification !='No Auth'">
<el-input v-model="authConfig.password" :placeholder="$t('commons.password')" show-password autocomplete="off"
maxlength="20" show-word-limit/>
</el-form-item>
@ -41,7 +39,7 @@
ref="authConfig">
<el-form-item :label="$t('api_test.definition.request.encryption')" prop="encryption">
<el-select v-model="authConfig.isEncrypt"
<el-select v-model="authConfig.encrypt"
:placeholder="$t('api_test.definition.request.verification_method')" filterable size="small">
<el-option
v-for="item in encryptOptions"
@ -59,24 +57,47 @@
</template>
<script>
import {createComponent} from "../jmeter/components";
export default {
name: "MsApiAuthConfig",
components: {},
props: {
authConfig: {},
request: {},
},
created() {
for (let index in this.request.hashTree) {
if (this.request.hashTree[index].type == 'AuthManager') {
this.authConfig = this.request.hashTree[index];
}
}
},
data() {
return {
options: [{name: "No Auth"}, {name: "Basic Auth"}],
encryptOptions: [{id: false, name: "不加密"}],
activeName: "verified",
rule: {},
authConfig: {},
}
},
methods: {}
methods: {
change() {
if (this.authConfig.verification === "Basic Auth") {
let authManager = createComponent("AuthManager");
authManager.verification = "Basic Auth";
authManager.environment = this.request.useEnvironment;
this.request.hashTree.push(authManager);
this.authConfig = authManager;
} else {
for (let index in this.request.hashTree) {
if (this.request.hashTree[index].type === "AuthManager") {
this.request.hashTree.splice(index, 1);
}
}
}
}
}
}
</script>

View File

@ -6,6 +6,10 @@
<div class="kv-row" v-for="(item, index) in parameters" :key="index">
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
<el-col class="kv-checkbox" v-if="isShowEnable">
<input type="checkbox" v-model="item.enable" :disabled="isReadOnly"/>
</el-col>
<el-col>
<el-input v-model="item.description" size="small" maxlength="200"
:placeholder="$t('commons.description')" show-word-limit>
@ -58,6 +62,10 @@
type: Boolean,
default: false
},
isShowEnable: {
type: Boolean,
default: true
},
suggestions: Array
},
data() {
@ -146,11 +154,11 @@
created() {
if (this.parameters.length === 0 || this.parameters[this.parameters.length - 1].name) {
this.parameters.push(new KeyValue({
type: 'text',
type: 'file',
enable: true,
required: true,
uuid: this.uuid(),
contentType: 'text/plain'
contentType: 'application/octet-stream'
}));
}
}

View File

@ -26,10 +26,12 @@
</el-radio>
</el-radio-group>
<ms-dropdown :default-command="body.format" v-if="body.type == 'Raw'" :commands="modes" @command="modeChange"/>
<!--
<ms-dropdown :default-command="body.format" v-if="body.type == 'Raw'" :commands="modes" @command="modeChange"/>
-->
<ms-api-variable :is-read-only="isReadOnly"
:parameters="body.kvs"
:isShowEnable="isShowEnable"
type="body"
v-if="body.type == 'KeyValue'"/>
@ -38,21 +40,22 @@
type="body"
v-if="body.type == 'WWW_FORM'"/>
<div class="body-raw" v-if="body.type == 'JSON'">
<div class="ms-body" v-if="body.type == 'JSON'">
<ms-json-code-edit @json-change="jsonChange" @onError="jsonError" :value="body.json" ref="jsonCodeEdit"/>
</div>
<div class="body-raw" v-if="body.type == 'XML'">
<div class="ms-body" v-if="body.type == 'XML'">
<ms-code-edit :read-only="isReadOnly" :data.sync="body.xml" :modes="modes" ref="codeEdit"/>
</div>
<div class="body-raw" v-if="body.type == 'Raw'">
<div class="ms-body" v-if="body.type == 'Raw'">
<ms-code-edit :read-only="isReadOnly" :data.sync="body.raw" :modes="modes" ref="codeEdit"/>
</div>
<ms-api-binary-variable :is-read-only="isReadOnly"
:parameters="body.binary"
:isShowEnable="isShowEnable"
type="body"
v-if="body.type == 'BINARY'"/>
@ -61,7 +64,7 @@
<script>
import MsApiKeyValue from "../ApiKeyValue";
import {Body, BODY_FORMAT, BODY_TYPE, Scenario} from "../../model/ApiTestModel";
import {Body, BODY_FORMAT, BODY_TYPE, KeyValue, Scenario} from "../../model/ApiTestModel";
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
import MsJsonCodeEdit from "../../../../common/components/MsJsonCodeEdit";
@ -83,9 +86,14 @@
},
props: {
body: {},
headers: Array,
isReadOnly: {
type: Boolean,
default: false
},
isShowEnable: {
type: Boolean,
default: true
}
},
data() {
@ -99,15 +107,45 @@
modeChange(mode) {
switch (this.body.type) {
case "JSON":
return this.body.format = "json";
this.body.format = "json";
this.setContentType("application/json");
break;
case "XML":
return this.body.format = "xml";
this.body.format = "xml";
this.setContentType("text/xml");
break;
case "WWW_FORM":
return this.body.format = "form";
this.body.format = "form";
this.setContentType("application/x-www-form-urlencoded");
break;
case "BINARY":
return this.body.format = "binary";
this.body.format = "binary";
this.setContentType("application/octet-stream");
break;
default:
return this.body.format = mode;
this.removeContentType();
this.body.format = mode;
break;
}
},
setContentType(value) {
let isType = false;
this.headers.forEach(item => {
if (item.name === "Content-Type") {
item.value = value;
isType = true;
}
})
if (!isType) {
this.headers.unshift(new KeyValue({name: "Content-Type", value: value}));
}
},
removeContentType() {
for (let index in this.headers) {
if (this.headers[index].name === "Content-Type") {
this.headers.splice(index, 1);
return;
}
}
},
jsonChange(json) {
@ -139,7 +177,7 @@
margin-top: 10px;
}
.body-raw {
.ms-body {
padding: 15px 0;
height: 400px;
}

View File

@ -10,13 +10,6 @@
<el-col>
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
@change="change" :placeholder="keyText" show-word-limit>
<template v-slot:prepend>
<el-select v-if="type === 'body'" :disabled="isReadOnly" class="kv-type" v-model="item.type"
@change="typeChange(item)">
<el-option value="text"/>
<el-option value="file"/>
</el-select>
</template>
</el-input>
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
@ -55,16 +48,6 @@
</el-col>
<el-col v-if="item.type === 'file'">
<ms-api-body-file-upload :parameter="item"/>
</el-col>
<el-col v-if="type === 'body'" class="kv-select">
<el-input :disabled="isReadOnly" v-model="item.contentType" size="small"
@change="change" :placeholder="$t('api_test.request.content_type')" show-word-limit>
</el-input>
</el-col>
<el-col class="kv-delete">
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
:disabled="isDisable(index) || isReadOnly"/>
@ -145,7 +128,7 @@
type: 'text',
enable: true,
uuid: this.uuid(),
contentType: 'text/plain'
contentType: 'application/x-www-from-urlencoded'
}));
}
this.$emit('change', this.parameters);
@ -183,16 +166,12 @@
this.currentItem = item;
},
typeChange(item) {
if (item.type === 'file') {
item.contentType = 'application/octet-stream';
} else {
item.contentType = 'text/plain';
}
item.contentType = 'application/x-www-from-urlencoded';
}
},
created() {
if (this.parameters.length === 0 || this.parameters[this.parameters.length - 1].name) {
this.parameters.push(new KeyValue({type: 'text', enable: true, required:true,uuid: this.uuid(), contentType: 'text/plain'}));
this.parameters.push(new KeyValue({type: 'text', enable: true, required: true, uuid: this.uuid(), contentType: 'application/x-www-from-urlencoded'}));
}
}
}

View File

@ -22,7 +22,7 @@
</el-form-item>
<el-form-item :label="$t('api_report.request')" prop="url">
<el-input :placeholder="$t('api_test.definition.request.path_info')" v-model="httpForm.request.path.value"
<el-input :placeholder="$t('api_test.definition.request.path_info')" v-model="httpForm.url"
class="ms-http-input" size="small" style="margin-top: 5px">
<el-select v-model="httpForm.request.method.value" slot="prepend" style="width: 100px" size="small">
<el-option v-for="item in reqOptions" :key="item.id" :label="item.label" :value="item.id"/>
@ -127,7 +127,8 @@
},
setParameter() {
this.httpForm.modulePath = this.getPath(this.httpForm.moduleId);
this.httpForm.url = this.httpForm.request.path.value;
this.httpForm.request.path.value = this.httpForm.url;
this.httpForm.request.useEnvironment = undefined;
},
saveApi() {
if (this.currentProject === null) {

View File

@ -0,0 +1,30 @@
import HashTreeElement from "../../hashtree";
const DEFAULT_OPTIONS = {
options: {
attributes: {
guiclass: "AuthPanel",
testclass: "AuthManager",
testname: "AuthManager",
enabled: "true"
},
}
};
export default class AuthManager extends HashTreeElement {
constructor(options = DEFAULT_OPTIONS) {
super(options);
this.type = "AuthManager";
this.username = undefined;
this.password = undefined;
this.url = undefined;
this.realm = undefined;
this.verification = "No Auth";
this.mechanism = "BASIC_DIGEST";
this.encrypt = false;
this.environment = undefined;
}
}
export const schema = {
AuthManager: AuthManager
}

View File

@ -6,31 +6,58 @@
<el-tabs v-model="activeName" style="margin: 20px">
<!-- 请求头-->
<el-tab-pane :label="$t('api_test.request.headers')" name="headers">
<ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :suggestions="headerSuggestions"
:items="headers"/>
<el-tooltip class="item-tabs" effect="dark" :content="$t('api_test.request.headers')" placement="top-start" slot="label">
<span>{{$t('api_test.request.headers')}}
<div class="el-step__icon is-text ms-api-col ms-header" v-if="headers.length>1">
<div class="el-step__icon-inner">{{headers.length-1}}</div>
</div>
</span>
</el-tooltip>
<ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :suggestions="headerSuggestions" :items="headers"/>
</el-tab-pane>
<!--query 参数-->
<el-tab-pane :label="$t('api_test.definition.request.query_param')" name="parameters">
<ms-api-variable :is-read-only="isReadOnly" :parameters="request.arguments"/>
<el-tab-pane :label="$t('api_test.definition.request.query_param')" name="parameters" :disabled="request.rest.length>1">
<el-tooltip class="item-tabs" effect="dark" content="地址栏中跟在?后面的参数,如updateapi?id=112" placement="top-start" slot="label">
<span>{{$t('api_test.definition.request.query_param')}}
<div class="el-step__icon is-text ms-api-col ms-query" v-if="request.arguments.length>1">
<div class="el-step__icon-inner">{{request.arguments.length-1}}</div>
</div></span>
</el-tooltip>
<ms-api-variable :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.arguments"/>
</el-tab-pane>
<!--REST 参数-->
<el-tab-pane :label="$t('api_test.definition.request.rest_param')" name="rest">
<ms-api-variable :is-read-only="isReadOnly" :parameters="request.rest"/>
<el-tab-pane :label="$t('api_test.definition.request.rest_param')" name="rest" :disabled="request.arguments.length>1">
<el-tooltip class="item-tabs" effect="dark" content="地址栏中被斜杠/分隔的参数如updateapi/{id}" placement="top-start" slot="label">
<span>
{{$t('api_test.definition.request.rest_param')}}
<div class="el-step__icon is-text ms-api-col ms-query" v-if="request.rest.length>1">
<div class="el-step__icon-inner">{{request.rest.length-1}}</div>
</div>
</span>
</el-tooltip>
<ms-api-variable :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :parameters="request.rest"/>
</el-tab-pane>
<!--请求体-->
<el-tab-pane :label="$t('api_test.request.body')" name="body">
<ms-api-body :is-read-only="isReadOnly" :body="request.body"/>
<ms-api-body :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :headers="headers" :body="request.body"/>
</el-tab-pane>
<!-- 认证配置 -->
<el-tab-pane :label="$t('api_test.definition.request.auth_config')" name="authConfig">
<ms-api-auth-config :is-read-only="isReadOnly" :auth-config="request.authConfig"/>
<el-tooltip class="item-tabs" effect="dark" content="请求需要进行权限校验" placement="top-start" slot="label">
<span>{{$t('api_test.definition.request.auth_config')}}</span>
</el-tooltip>
<ms-api-auth-config :is-read-only="isReadOnly" :request="request"/>
</el-tab-pane>
</el-tabs>
</div>
<!--定制脚本-->
<div v-for="row in request.hashTree" :key="row.id" v-loading="isReloadData">
<ms-jsr233-processor v-if="row.label ==='JSR223 PreProcessor'" @remove="remove" :is-read-only="false" title="前置脚本" style-type="warning"
:jsr223-processor="row"/>
@ -69,7 +96,6 @@
import {createComponent} from "../jmeter/components";
import MsApiAssertions from "../assertion/ApiAssertions";
import MsApiExtract from "../extract/ApiExtract";
import HTTPSamplerProxy from "../jmeter/components/sampler/http-sampler";
export default {
name: "MsApiHttpRequestForm",
@ -153,38 +179,6 @@
</script>
<style scoped>
.el-tag {
width: 100%;
height: 40px;
line-height: 40px;
}
.environment-display {
font-size: 14px;
}
.environment-name {
font-weight: bold;
font-style: italic;
}
.adjust-margin-bottom {
margin-bottom: 10px;
}
.environment-url-tip {
color: #F56C6C;
}
.follow-redirects-item {
margin-left: 30px;
}
.do-multipart-post {
margin-left: 10px;
}
.ms-left-cell {
margin-top: 30px;
}
@ -193,4 +187,18 @@
margin: 6px 0px 8px 30px;
}
.ms-query {
background: #7F7F7F;
color: white;
height: 18px;
border-radius: 42%;
}
.ms-header {
background: #783887;
color: white;
height: 18px;
border-radius: 42%;
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div class="request-result">
<ms-request-metric :response="response"/>
<ms-response-result :response="response.responseResult"/>
<ms-response-result :response="response"/>
</div>
</template>

View File

@ -3,17 +3,17 @@
<el-tabs v-model="activeName" v-show="isActive">
<el-tab-pane :label="$t('api_test.definition.request.response_header')" name="headers" class="pane">
<pre>{{ response.headers }}</pre>
<pre>{{ response.responseResult.headers }}</pre>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.response_body')" name="body" class="pane">
<ms-code-edit :mode="mode" :read-only="true" :modes="modes" :data.sync="response.body" ref="codeEdit"/>
<ms-code-edit :mode="mode" :read-only="true" :modes="modes" :data.sync="response.responseResult.body" ref="codeEdit"/>
</el-tab-pane>
<el-tab-pane label="Cookie" name="cookie" class="pane cookie">
<pre>{{response.cookies}}</pre>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.console')" name="console" class="pane">
<pre>{{response.console}}</pre>
<pre>{{response.responseResult.console}}</pre>
</el-tab-pane>
<el-tab-pane v-if="activeName == 'body'" :disabled="true" name="mode" class="pane cookie">

View File

@ -15,7 +15,7 @@
<!-- 执行环境 -->
<el-form-item prop="environmentId">
<el-select v-model="api.request.useEnvironment" size="small" class="ms-htt-width"
<el-select v-model="api.environmentId" size="small" class="ms-htt-width"
:placeholder="$t('api_test.definition.request.run_env')"
@change="environmentChange" clearable>
<el-option v-for="(environment, index) in environments" :key="index"
@ -150,6 +150,7 @@
if (valid) {
this.loading = true;
this.api.request.name = this.api.id;
this.api.request.useEnvironment = this.api.environmentId;
this.runData = [];
this.runData.push(this.api.request);
/*触发执行操作*/