refactor(TCPMock): TCPMock优化

TCPMock优化,支持自定义脚本
This commit is contained in:
song-tianyang 2021-10-21 11:33:00 +08:00 committed by song-tianyang
parent 8260590257
commit d6b04f0868
9 changed files with 401 additions and 14 deletions

View File

@ -289,6 +289,12 @@ public class MockApiUtils {
} }
public static String getResultByResponseResult(JSONObject bodyObj,String url, Map<String,String> headerMap,RequestMockParams requestMockParams) { public static String getResultByResponseResult(JSONObject bodyObj,String url, Map<String,String> headerMap,RequestMockParams requestMockParams) {
if(headerMap == null){
headerMap = new HashMap<>();
}
if(requestMockParams == null){
requestMockParams = new RequestMockParams();
}
if(bodyObj == null && bodyObj.isEmpty()){ if(bodyObj == null && bodyObj.isEmpty()){
return ""; return "";
}else { }else {

View File

@ -1,6 +1,7 @@
package io.metersphere.api.tcp.server; package io.metersphere.api.tcp.server;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.mock.MockApiUtils;
import io.metersphere.api.service.MockConfigService; import io.metersphere.api.service.MockConfigService;
import io.metersphere.base.domain.MockExpectConfigWithBLOBs; import io.metersphere.base.domain.MockExpectConfigWithBLOBs;
import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.CommonBeanFactory;
@ -50,7 +51,14 @@ public class TCPServicer {
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
returnMsg = responseObj.getString("body"); if(responseObj.containsKey("responseResult")){
JSONObject respResultObj = responseObj.getJSONObject("responseResult");
if(respResultObj.containsKey("body")){
returnMsg = MockApiUtils.getResultByResponseResult(respResultObj.getJSONObject("body"),"",null,null);
}
}else {
returnMsg = responseObj.getString("body");
}
} }
return returnMsg; return returnMsg;
} }

View File

@ -75,10 +75,11 @@
<div v-if="showMock && (currentProtocol === 'HTTP')" class="ms-api-div"> <div v-if="showMock && (currentProtocol === 'HTTP')" class="ms-api-div">
<!-- <mock-config :base-mock-config-data="baseMockConfigData" type="http"/>--> <!-- <mock-config :base-mock-config-data="baseMockConfigData" type="http"/>-->
<mock-tab :base-mock-config-data="baseMockConfigData" type="http"/> <mock-tab :base-mock-config-data="baseMockConfigData" :is-tcp="false"/>
</div> </div>
<div v-if="showMock && (currentProtocol === 'TCP')" class="ms-api-div"> <div v-if="showMock && (currentProtocol === 'TCP')" class="ms-api-div">
<tcp-mock-config :base-mock-config-data="baseMockConfigData" type="tcp"/> <mock-tab :base-mock-config-data="baseMockConfigData" :is-tcp="true"/>
<!-- <tcp-mock-config :base-mock-config-data="baseMockConfigData" type="tcp"/>-->
</div> </div>
<div v-if="showTestCaseList"> <div v-if="showTestCaseList">
<!--测试用例列表--> <!--测试用例列表-->

View File

@ -18,7 +18,7 @@
<el-col :span="menuSpan" class="script-index"> <el-col :span="menuSpan" class="script-index">
<ms-dropdown :default-command="jsr223ProcessorData.scriptLanguage" :commands="languages" style="margin-bottom: 5px;margin-left: 15px;" <ms-dropdown :default-command="jsr223ProcessorData.scriptLanguage" :commands="languages" style="margin-bottom: 5px;margin-left: 15px;"
@command="languageChange"/> @command="languageChange"/>
<mock-script-nav-menu ref="scriptNavMenu" :language="jsr223ProcessorData.scriptLanguage" :menus="codeTemplates" <mock-script-nav-menu ref="scriptNavMenu" :language="jsr223ProcessorData.scriptLanguage" :menus="baseCodeTemplates"
@handleCode="handleCodeTemplate"/> @handleCode="handleCodeTemplate"/>
</el-col> </el-col>
</el-row> </el-row>
@ -39,7 +39,8 @@ export default {
data() { data() {
return { return {
jsr223ProcessorData: {}, jsr223ProcessorData: {},
codeTemplates: [ baseCodeTemplates: [],
httpCodeTemplates: [
{ {
title: "API"+this.$t('api_test.definition.document.request_info'), title: "API"+this.$t('api_test.definition.document.request_info'),
children: [ children: [
@ -80,6 +81,17 @@ export default {
] ]
}, },
], ],
tcpCodeTemplates: [
{
title: this.$t('project.code_segment.code_segment'),
children: [
{
title: this.$t('project.code_segment.insert_segment'),
command: "custom_function",
}
]
},
],
isCodeEditAlive: true, isCodeEditAlive: true,
languages: [ languages: [
'beanshell',"groovy" 'beanshell',"groovy"
@ -99,6 +111,11 @@ export default {
}, },
created() { created() {
this.jsr223ProcessorData = this.jsr223Processor; this.jsr223ProcessorData = this.jsr223Processor;
if(this.showApi){
this.baseCodeTemplates = this.httpCodeTemplates;
}else {
this.baseCodeTemplates = this.tcpCodeTemplates;
}
}, },
props: { props: {
isReadOnly: { isReadOnly: {
@ -109,6 +126,10 @@ export default {
jsr223Processor: { jsr223Processor: {
type: Object, type: Object,
}, },
showApi:{
type:Boolean,
default:true,
},
node: {}, node: {},
}, },
watch: { watch: {

View File

@ -4,14 +4,14 @@
<el-collapse-transition> <el-collapse-transition>
<el-tabs v-model="activeName" v-show="isActive" style="margin: 20px"> <el-tabs v-model="activeName" v-show="isActive" style="margin: 20px">
<el-tab-pane :label="$t('api_test.definition.request.response_header')" name="headers" class="pane"> <el-tab-pane v-if="!isTcp" :label="$t('api_test.definition.request.response_header')" name="headers" class="pane">
<ms-api-key-value :isShowEnable="false" :suggestions="headerSuggestions" :items="response.headers"/> <ms-api-key-value :isShowEnable="false" :suggestions="headerSuggestions" :items="response.headers"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.response_body')" name="body" class="pane"> <el-tab-pane :label="$t('api_test.definition.request.response_body')" name="body" class="pane">
<mock-api-response-body :isReadOnly="false" :isShowEnable="false" :api-id="apiId" :body="response.body" :headers="response.headers"/> <mock-api-response-body :isReadOnly="false" :isShowEnable="false" :api-id="apiId" :body="response.body" :headers="response.headers"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('api_test.definition.request.status_code')" name="status_code" class="pane"> <el-tab-pane v-if="!isTcp" :label="$t('api_test.definition.request.status_code')" name="status_code" class="pane">
<el-row> <el-row>
<el-col :span="2"/> <el-col :span="2"/>
<el-col :span="20"> <el-col :span="20">
@ -67,6 +67,10 @@ export default {
props: { props: {
response: {}, response: {},
apiId:String, apiId:String,
isTcp:{
type: Boolean,
default: false,
}
}, },
data() { data() {
return { return {

View File

@ -0,0 +1,335 @@
<template xmlns:v-slot="http://www.w3.org/1999/XSL/Transform">
<div>
<el-row>
<el-col :span="spanCount">
<div style="border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100%" v-loading="isReloadData">
<el-tabs v-model="activeName" class="request-tabs">
</el-tabs>
</div>
</el-col>
</el-row>
<batch-add-parameter @batchSave="batchSave" ref="batchAddParameter"/>
</div>
</template>
<script>
import MsApiKeyValue from "@/business/components/api/definition/components/ApiKeyValue";
import MsApiAuthConfig from "@/business/components/api/definition/components/auth/ApiAuthConfig";
import ApiRequestMethodSelect from "@/business/components/api/definition/components/collapse/ApiRequestMethodSelect";
import {REQUEST_HEADERS} from "@/common/js/constants";
import MsApiVariable from "@/business/components/api/definition/components/ApiVariable";
import MsApiAssertions from "@/business/components/api/definition/components/assertion/ApiAssertions";
import MsApiExtract from "@/business/components/api/definition/components/extract/ApiExtract";
import {Body, KeyValue} from "@/business/components/api/definition/model/ApiTestModel";
import {hasLicense, getUUID} from "@/common/js/utils";
import BatchAddParameter from "@/business/components/api/definition/components/basis/BatchAddParameter";
import MsApiAdvancedConfig from "@/business/components/api/definition/components/request/http/ApiAdvancedConfig";
import MsJsr233Processor from "@/business/components/api/automation/scenario/component/Jsr233Processor";
import ApiDefinitionStepButton from "@/business/components/api/definition/components/request/components/ApiDefinitionStepButton";
import {hasPermission} from '@/common/js/utils';
import Convert from "@/business/components/common/json-schema/convert/convert";
import MockApiBody from "@/business/components/api/definition/components/mock/Components/MockApiBody";
export default {
name: "MockRequestParam",
components: {
ApiDefinitionStepButton,
MsJsr233Processor,
MsApiAdvancedConfig,
BatchAddParameter,
MsApiVariable,
ApiRequestMethodSelect,
MsApiExtract,
MsApiAuthConfig,
MockApiBody,
MsApiKeyValue,
MsApiAssertions
},
props: {
method: String,
request: {},
response: {},
definitionTest: {
type: Boolean,
default() {
return false;
}
},
showScript: {
type: Boolean,
default: true,
},
referenced: {
type: Boolean,
default: false,
},
isShowEnable: Boolean,
jsonPathList: Array,
isReadOnly: {
type: Boolean,
default: false
},
type: String,
},
data() {
let validateURL = (rule, value, callback) => {
try {
new URL(this.addProtocol(this.request.url));
} catch (e) {
callback(this.$t('api_test.request.url_invalid'));
}
};
return {
activeName: this.request.method === "POST" ? "body" : "parameters",
rules: {
name: [
{max: 300, message: this.$t('commons.input_limit', [1, 300]), trigger: 'blur'}
],
url: [
{max: 500, required: true, message: this.$t('commons.input_limit', [1, 500]), trigger: 'blur'},
{validator: validateURL, trigger: 'blur'}
],
path: [
{max: 500, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'},
]
},
spanCount: 21,
headerSuggestions: REQUEST_HEADERS,
isReloadData: false,
isBodyShow: true,
dialogVisible: false,
hasOwnProperty: Object.prototype.hasOwnProperty,
propIsEnumerable: Object.prototype.propertyIsEnumerable
}
},
created() {
if (!this.referenced && this.showScript) {
this.spanCount = 21;
} else {
this.spanCount = 24;
}
this.init();
},
methods: {
hasPermission,
hasLicense,
generate() {
if (this.request.body && (this.request.body.jsonSchema || this.request.body.raw)) {
if (!this.request.body.jsonSchema) {
const MsConvert = new Convert();
this.request.body.jsonSchema = MsConvert.format(JSON.parse(this.request.body.raw));
}
this.$post('/api/test/data/generator', this.request.body.jsonSchema, response => {
if (response.data) {
if (this.request.body.format !== 'JSON-SCHEMA') {
this.request.body.raw = response.data;
} else {
const MsConvert = new Convert();
let data = MsConvert.format(JSON.parse(response.data));
this.request.body.jsonSchema = this.deepAssign(this.request.body.jsonSchema, data);
}
this.reloadBody();
}
});
}
},
remove(row) {
let index = this.request.hashTree.indexOf(row);
this.request.hashTree.splice(index, 1);
this.reload();
},
copyRow(row) {
let obj = JSON.parse(JSON.stringify(row));
obj.id = getUUID();
this.request.hashTree.push(obj);
this.reload();
},
reload() {
this.isReloadData = true
this.$nextTick(() => {
this.isReloadData = false
})
},
init() {
if (Object.prototype.toString.call(this.request).match(/\[object (\w+)\]/)[1].toLowerCase() !== 'object') {
this.request = JSON.parse(this.request);
}
if (!this.request.body) {
this.request.body = new Body();
}
if (!this.request.headers) {
this.request.headers = [];
}
if (!this.request.body.kvs) {
this.request.body.kvs = [];
}
if (!this.request.rest) {
this.request.rest = [];
}
if (!this.request.arguments) {
this.request.arguments = [];
}
},
reloadBody() {
// body
this.isBodyShow = false;
this.$nextTick(() => {
this.isBodyShow = true;
});
},
batchAdd() {
this.$refs.batchAddParameter.open();
},
format(array, obj) {
if (array) {
let isAdd = true;
for (let i in array) {
let item = array[i];
if (item.name === obj.name) {
item.value = obj.value;
isAdd = false;
}
}
if (isAdd) {
switch (this.activeName) {
case "parameters":
this.request.arguments.unshift(obj);
break;
case "rest":
this.request.rest.unshift(obj);
break;
case "headers":
this.request.headers.unshift(obj);
break;
default:
break;
}
}
}
},
batchSave(data) {
if (data) {
let params = data.split("\n");
let keyValues = [];
params.forEach(item => {
let line = item.split(/|:/);
let required = false;
keyValues.unshift(new KeyValue({
name: line[0],
required: required,
value: line[1],
description: line[2],
type: "text",
valid: false,
file: false,
encode: true,
enable: true,
contentType: "text/plain"
}));
})
keyValues.forEach(item => {
switch (this.activeName) {
case "parameters":
this.format(this.request.arguments, item);
break;
case "rest":
this.format(this.request.rest, item);
break;
case "headers":
this.format(this.request.headers, item);
break;
default:
break;
}
})
}
},
isObj(x) {
let type = typeof x;
return x !== null && (type === 'object' || type === 'function');
},
toObject(val) {
if (val === null || val === undefined) {
return;
}
return Object(val);
},
assignKey(to, from, key) {
let val = from[key];
if (val === undefined || val === null) {
return;
}
if (!this.hasOwnProperty.call(to, key) || !this.isObj(val)) {
to[key] = val;
} else {
to[key] = this.assign(Object(to[key]), from[key]);
}
},
assign(to, from) {
if (to === from) {
return to;
}
from = Object(from);
for (let key in from) {
if (this.hasOwnProperty.call(from, key)) {
this.assignKey(to, from, key);
}
}
if (Object.getOwnPropertySymbols) {
let symbols = Object.getOwnPropertySymbols(from);
for (let i = 0; i < symbols.length; i++) {
if (this.propIsEnumerable.call(from, symbols[i])) {
this.assignKey(to, from, symbols[i]);
}
}
}
return to;
},
deepAssign(target) {
target = this.toObject(target);
for (let s = 1; s < arguments.length; s++) {
this.assign(target, arguments[s]);
}
return target;
}
}
}
</script>
<style scoped>
.ms-query {
background: #783887;
color: white;
height: 18px;
border-radius: 42%;
}
.ms-header {
background: #783887;
color: white;
height: 18px;
border-radius: 42%;
}
.request-tabs {
margin: 20px;
min-height: 200px;
}
.ms-el-link {
float: right;
margin-right: 45px;
}
</style>

View File

@ -15,7 +15,11 @@
<div class="card"> <div class="card">
<div class="base-info"> <div class="base-info">
<el-row> <el-row>
<tcp-params
v-if="isTcp"
:request="mockExpectConfig.request" style="margin: 10px 10px;" ref="tcpParam"></tcp-params>
<mock-request-param <mock-request-param
v-else
:isShowEnable="true" :isShowEnable="true"
:referenced="true" :referenced="true"
:is-read-only="false" :is-read-only="false"
@ -26,7 +30,7 @@
<p class="tip">{{ $t('api_test.mock.rsp_param') }}</p> <p class="tip">{{ $t('api_test.mock.rsp_param') }}</p>
</el-row> </el-row>
<el-row> <el-row>
<mock-response-param :api-id="apiId" <mock-response-param :api-id="apiId" :is-tcp="isTcp"
:response="mockExpectConfig.response.responseResult"/> :response="mockExpectConfig.response.responseResult"/>
</el-row> </el-row>
<el-row> <el-row>
@ -51,20 +55,24 @@ import {REQUEST_HEADERS} from "@/common/js/constants";
import MockRowVariables from "@/business/components/api/definition/components/mock/MockRowVariables"; import MockRowVariables from "@/business/components/api/definition/components/mock/MockRowVariables";
import MsCodeEdit from "@/business/components/common/components/MsCodeEdit"; import MsCodeEdit from "@/business/components/common/components/MsCodeEdit";
import MockConfigHeader from "@/business/components/api/definition/components/mock/MockConfigHeader"; import MockConfigHeader from "@/business/components/api/definition/components/mock/MockConfigHeader";
import MockRequestParam from "@/business/components/api/definition/components/mock/MockRequestParam"; import MockRequestParam from "@/business/components/api/definition/components/mock/Components/MockRequestParam";
import MockResponseParam from "@/business/components/api/definition/components/mock/MockResponseParam"; import MockResponseParam from "@/business/components/api/definition/components/mock/Components/MockResponseParam";
import {getUUID} from "@/common/js/utils"; import {getUUID} from "@/common/js/utils";
import TcpParams from "@/business/components/api/definition/components/request/tcp/TcpParams";
export default { export default {
name: 'MockEditDrawer', name: 'MockEditDrawer',
components: { components: {
MsDrawer,MockConfigHeader,MockRowVariables,MsCodeEdit,MockRequestParam,MockResponseParam MsDrawer,MockConfigHeader,MockRowVariables,MsCodeEdit,MockRequestParam,MockResponseParam,TcpParams
}, },
props: { props: {
apiParams: Array, apiParams: Array,
apiId:String, apiId:String,
mockConfigId:String, mockConfigId:String,
// mockConfigData: Object, isTcp:{
type:Boolean,
default: false,
}
}, },
data() { data() {
return { return {

View File

@ -61,7 +61,7 @@
</ms-table-column> </ms-table-column>
</ms-table> </ms-table>
</div> </div>
<mock-edit-drawer :api-id="this.baseMockConfigData.mockConfig.apiId" @refreshMockInfo="refreshMockInfo" :mock-config-id="mockConfigData.mockConfig.id" ref="mockEditDrawer"/> <mock-edit-drawer :is-tcp="isTcp" :api-id="this.baseMockConfigData.mockConfig.apiId" @refreshMockInfo="refreshMockInfo" :mock-config-id="mockConfigData.mockConfig.id" ref="mockEditDrawer"/>
</div> </div>
</template> </template>
@ -80,7 +80,11 @@ export default {
MsTable,MsTableColumn,MsTag MsTable,MsTableColumn,MsTag
}, },
props: { props: {
baseMockConfigData: {} baseMockConfigData: {},
isTcp:{
type:Boolean,
default:false,
},
}, },
data() { data() {
return { return {