feat(接口测试): 接口测试的TCP请求支持一键把xml文本格式转换成表格格式

--story=1006595 --user=宋天阳 【成都银行】支持一键把xml文本格式转换成表格格式
https://www.tapd.cn/55049933/s/1233105
This commit is contained in:
song-tianyang 2022-08-24 18:29:50 +08:00 committed by f2c-ci-robot[bot]
parent f089e943b5
commit 3ced82ad63
12 changed files with 114 additions and 36 deletions

View File

@ -4,8 +4,10 @@ import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import io.metersphere.api.dto.APIReportResult; import io.metersphere.api.dto.APIReportResult;
import io.metersphere.api.dto.ApiTestImportRequest; import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.ParseTreeDataDTO;
import io.metersphere.api.dto.automation.ApiScenarioRequest; import io.metersphere.api.dto.automation.ApiScenarioRequest;
import io.metersphere.api.dto.automation.ReferenceDTO; import io.metersphere.api.dto.automation.ReferenceDTO;
import io.metersphere.api.dto.automation.TcpTreeTableDataStruct;
import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport; import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.dto.definition.request.assertions.document.DocumentElement; import io.metersphere.api.dto.definition.request.assertions.document.DocumentElement;
@ -394,4 +396,8 @@ public class ApiDefinitionController {
return apiDefinitionService.getCitedScenarioCount(definitionId); return apiDefinitionService.getCitedScenarioCount(definitionId);
} }
@PostMapping("/raw-to-xml")
public List<TcpTreeTableDataStruct> rawToXml(@RequestBody ParseTreeDataDTO parseTreeDataDTO) {
return apiDefinitionService.rawToXml(parseTreeDataDTO.getStringData());
}
} }

View File

@ -0,0 +1,10 @@
package io.metersphere.api.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ParseTreeDataDTO {
private String stringData;
}

View File

@ -5,19 +5,23 @@ import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.automation.TcpTreeTableDataStruct; import io.metersphere.api.dto.automation.TcpTreeTableDataStruct;
import io.metersphere.api.dto.mock.MockConfigRequestParams; import io.metersphere.api.dto.mock.MockConfigRequestParams;
import io.metersphere.api.mock.utils.MockApiUtils; import io.metersphere.api.mock.utils.MockApiUtils;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.XMLUtils; import io.metersphere.commons.utils.XMLUtils;
import io.metersphere.i18n.Translator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document; import org.dom4j.Document;
import org.dom4j.DocumentHelper; import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat; import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter; import org.dom4j.io.XMLWriter;
import org.springframework.util.CollectionUtils;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@ -26,6 +30,9 @@ import java.util.List;
* @Description * @Description
*/ */
public class TcpTreeTableDataParser { public class TcpTreeTableDataParser {
public static final String DATA_TYPE_STRING = "string";
public static final String DATA_TYPE_OBJECT = "object";
public static String treeTableData2Xml(List<TcpTreeTableDataStruct> treeDataList) { public static String treeTableData2Xml(List<TcpTreeTableDataStruct> treeDataList) {
String xmlString = ""; String xmlString = "";
try { try {
@ -84,6 +91,51 @@ public class TcpTreeTableDataParser {
return xmlString; return xmlString;
} }
public static List<TcpTreeTableDataStruct> xml2TreeTableData(String xmlString) {
List<TcpTreeTableDataStruct> returnList = new ArrayList<>();
Document document = XMLUtils.stringToDocument(xmlString);
if (document != null) {
Element rootElement = document.getRootElement();
TcpTreeTableDataStruct struct = new TcpTreeTableDataStruct();
struct.init();
struct.setName(rootElement.getName());
List<Element> elements = rootElement.elements();
if (CollectionUtils.isEmpty(elements)) {
struct.setValue(rootElement.getStringValue());
struct.setType(TcpTreeTableDataParser.DATA_TYPE_STRING);
} else {
struct.setType(TcpTreeTableDataParser.DATA_TYPE_OBJECT);
struct.setChildren(docElement2TreeTableData(elements));
}
returnList.add(struct);
}
if (CollectionUtils.isEmpty(returnList)) {
MSException.throwException(Translator.get("error_xml_struct"));
}
return returnList;
}
private static List<TcpTreeTableDataStruct> docElement2TreeTableData(List<Element> elements) {
List<TcpTreeTableDataStruct> returnList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(elements)) {
elements.forEach(element -> {
TcpTreeTableDataStruct struct = new TcpTreeTableDataStruct();
struct.init();
struct.setName(element.getName());
List<Element> children = element.elements();
if (CollectionUtils.isEmpty(children)) {
struct.setValue(element.getStringValue());
struct.setType(TcpTreeTableDataParser.DATA_TYPE_STRING);
} else {
struct.setType(TcpTreeTableDataParser.DATA_TYPE_OBJECT);
struct.setChildren(docElement2TreeTableData(children));
}
returnList.add(struct);
});
}
return returnList;
}
public static boolean isMatchTreeTableData(JSONObject sourceObj, List<TcpTreeTableDataStruct> tcpDataList) { public static boolean isMatchTreeTableData(JSONObject sourceObj, List<TcpTreeTableDataStruct> tcpDataList) {
if (CollectionUtils.isEmpty(tcpDataList)) { if (CollectionUtils.isEmpty(tcpDataList)) {
return true; return true;

View File

@ -13,6 +13,8 @@ import io.metersphere.api.dto.APIReportResult;
import io.metersphere.api.dto.ApiTestImportRequest; import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.automation.ApiScenarioRequest; import io.metersphere.api.dto.automation.ApiScenarioRequest;
import io.metersphere.api.dto.automation.ReferenceDTO; import io.metersphere.api.dto.automation.ReferenceDTO;
import io.metersphere.api.dto.automation.TcpTreeTableDataStruct;
import io.metersphere.api.dto.automation.parse.TcpTreeTableDataParser;
import io.metersphere.api.dto.datacount.ApiDataCountResult; import io.metersphere.api.dto.datacount.ApiDataCountResult;
import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport; import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
@ -2797,4 +2799,8 @@ public class ApiDefinitionService {
List<ApiScenarioReferenceId> apiScenarioReferenceIds = apiScenarioReferenceIdMapper.selectByExample(apiScenarioReferenceIdExample); List<ApiScenarioReferenceId> apiScenarioReferenceIds = apiScenarioReferenceIdMapper.selectByExample(apiScenarioReferenceIdExample);
return apiScenarioReferenceIds.size(); return apiScenarioReferenceIds.size();
} }
public List<TcpTreeTableDataStruct> rawToXml(String rawData) {
return TcpTreeTableDataParser.xml2TreeTableData(rawData);
}
} }

View File

@ -327,7 +327,7 @@ custom_field_float_tip=[%s] must be number
custom_field_int_tip=[%s] must be integer custom_field_int_tip=[%s] must be integer
custom_field_member_tip=[%s] must be current project member custom_field_member_tip=[%s] must be current project member
custom_field_select_tip=[%s] must be %s custom_field_select_tip=[%s] must be %s
no_legitimate_case_tip =Import fails without legitimate use cases! no_legitimate_case_tip=Import fails without legitimate use cases!
# mock # mock
mock_warning=No matching Mock expectation was found mock_warning=No matching Mock expectation was found
zentao_test_type_error=invalid Zentao request zentao_test_type_error=invalid Zentao request
@ -429,3 +429,4 @@ create_api_case=New interface use case
api_case_create_notice=Interface use case new notification api_case_create_notice=Interface use case new notification
update_api_case=Updated interface use case update_api_case=Updated interface use case
api_case_update_notice=Interface use case update notification api_case_update_notice=Interface use case update notification
error_xml_struct=Data is not xml

View File

@ -428,3 +428,4 @@ create_api_case=新建了接口用例
api_case_create_notice=接口用例新建通知 api_case_create_notice=接口用例新建通知
update_api_case=更新了接口用例 update_api_case=更新了接口用例
api_case_update_notice=接口用例更新通知 api_case_update_notice=接口用例更新通知
error_xml_struct=错误的xml数据

View File

@ -427,3 +427,4 @@ create_api_case=新建了接口用例
api_case_create_notice=接口用例新建通知 api_case_create_notice=接口用例新建通知
update_api_case=更新了接口用例 update_api_case=更新了接口用例
api_case_update_notice=接口用例更新通知 api_case_update_notice=接口用例更新通知
error_xml_struct=錯誤的xml數據

View File

@ -60,7 +60,6 @@
<!-- 请求参数 --> <!-- 请求参数 -->
<div v-if="apiProtocol=='TCP'"> <div v-if="apiProtocol=='TCP'">
<p class="tip">{{ $t('api_test.definition.request.req_param') }} </p> <p class="tip">{{ $t('api_test.definition.request.req_param') }} </p>
<!-- <ms-basis-parameters :show-script="false" :request="request"/>-->
<ms-tcp-format-parameters :show-pre-script="true" :show-script="false" :request="request" <ms-tcp-format-parameters :show-pre-script="true" :show-script="false" :request="request"
ref="tcpFormatParameter"/> ref="tcpFormatParameter"/>
</div> </div>

View File

@ -14,9 +14,8 @@
<ms-api-variable :is-read-only="isReadOnly" :parameters="request.parameters"/> <ms-api-variable :is-read-only="isReadOnly" :parameters="request.parameters"/>
</el-tab-pane> </el-tab-pane>
<!--test--> <!--test-->
<el-tab-pane v-if="isBodyShow" :label="$t('api_test.definition.document.request_body')" name="request"
<!--query 参数--> v-loading="result.loading">
<el-tab-pane v-if="isBodyShow" :label="$t('api_test.definition.document.request_body')" name="request">
<el-radio-group v-model="reportType" size="mini" style="margin: 10px 0px;"> <el-radio-group v-model="reportType" size="mini" style="margin: 10px 0px;">
<el-radio :disabled="isReadOnly" label="json" @change="changeReportType"> <el-radio :disabled="isReadOnly" label="json" @change="changeReportType">
json json
@ -45,6 +44,10 @@
<ms-code-edit mode="text" :read-only="isReadOnly" :data.sync="request.rawDataStruct" <ms-code-edit mode="text" :read-only="isReadOnly" :data.sync="request.rawDataStruct"
:modes="['text', 'json', 'xml', 'html']" theme="eclipse"/> :modes="['text', 'json', 'xml', 'html']" theme="eclipse"/>
</div> </div>
<el-button class="ht-btn-add" size="mini" p="$t('commons.add')" icon="el-icon-circle-plus-outline"
@click="toXml">
{{ $t("api_test.buttons.to_xml") }}
</el-button>
</div> </div>
</el-tab-pane> </el-tab-pane>
@ -63,7 +66,7 @@
<el-input v-model="request.eolByte" size="small"/> <el-input v-model="request.eolByte" size="small"/>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6" label-width="80px" > <el-col :span="6" label-width="80px">
<el-form-item :label="$t('api_test.request.tcp.so_linger')" prop="soLinger" label-width="120px"> <el-form-item :label="$t('api_test.request.tcp.so_linger')" prop="soLinger" label-width="120px">
<el-input v-model="request.soLinger" size="small"/> <el-input v-model="request.soLinger" size="small"/>
</el-form-item> </el-form-item>
@ -98,33 +101,6 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<!-- <el-col :span="12">-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- <el-row :gutter="20">-->
<!-- <el-col :span="12">-->
<!-- </el-col>-->
<!-- <el-col :span="12">-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- <el-row :gutter="10" style="margin-left: 30px">-->
<!-- <el-col :span="6">-->
<!-- </el-col>-->
<!-- <el-col :span="6">-->
<!-- </el-col>-->
<!-- <el-col :span="6">-->
<!-- </el-col>-->
<!-- <el-col :span="6">-->
<!-- </el-col>-->
<!-- </el-row>-->
</el-tab-pane> </el-tab-pane>
<!-- 脚本步骤/断言步骤 --> <!-- 脚本步骤/断言步骤 -->
<el-tab-pane :label="$t('api_test.definition.request.pre_operation')" name="preOperate" v-if="showScript"> <el-tab-pane :label="$t('api_test.definition.request.pre_operation')" name="preOperate" v-if="showScript">
@ -160,7 +136,8 @@
<div class="el-step__icon-inner">{{ request.ruleSize }}</div> <div class="el-step__icon-inner">{{ request.ruleSize }}</div>
</div> </div>
</span> </span>
<ms-jmx-step :request="request" :apiId="request.id" protocol="TCP" :scenario-id="scenarioId" :response="response" <ms-jmx-step :request="request" :apiId="request.id" protocol="TCP" :scenario-id="scenarioId"
:response="response"
@reload="reloadBody" @reload="reloadBody"
:tab-type="'assertionsRule'" ref="assertionsRule"/> :tab-type="'assertionsRule'" ref="assertionsRule"/>
</el-tab-pane> </el-tab-pane>
@ -191,7 +168,7 @@ import ApiDefinitionStepButton from "../components/ApiDefinitionStepButton";
import TcpXmlTable from "@/business/components/api/definition/components/complete/table/TcpXmlTable"; import TcpXmlTable from "@/business/components/api/definition/components/complete/table/TcpXmlTable";
import {TYPE_TO_C} from "@/business/components/api/automation/scenario/Setting"; import {TYPE_TO_C} from "@/business/components/api/automation/scenario/Setting";
import MsJmxStep from "../../step/JmxStep"; import MsJmxStep from "../../step/JmxStep";
import {stepCompute, hisDataProcessing} from "@/business/components/api/definition/api-definition"; import {hisDataProcessing, stepCompute} from "@/business/components/api/definition/api-definition";
export default { export default {
@ -233,6 +210,7 @@ export default {
data() { data() {
return { return {
spanNum: 24, spanNum: 24,
result: {},
activeName: "request", activeName: "request",
classes: TCPSampler.CLASSES, classes: TCPSampler.CLASSES,
reportType: "xml", reportType: "xml",
@ -316,6 +294,15 @@ export default {
} }
}, },
methods: { methods: {
toXml() {
let url = "/api/definition/raw-to-xml";
this.result = this.$post(url, {"stringData": this.request.rawDataStruct}, response => {
if (response.data) {
this.request.xmlDataStruct = response.data;
this.reportType = "xml";
}
});
},
tabClick() { tabClick() {
if (this.activeName === 'preOperate') { if (this.activeName === 'preOperate') {
this.$refs.preStep.filter(); this.$refs.preStep.filter();
@ -596,4 +583,10 @@ export default {
padding: 15px 0px; padding: 15px 0px;
} }
.ht-btn-add {
border: 0px;
margin-top: 10px;
color: #783887;
background-color: white;
}
</style> </style>

View File

@ -1170,6 +1170,9 @@ export default {
hours: "H", hours: "H",
}, },
api_test: { api_test: {
buttons: {
to_xml: "To xml",
},
case_jump_message: "The jump use case has been removed", case_jump_message: "The jump use case has been removed",
scenario_jump_message: "The jumped scene has been deleted", scenario_jump_message: "The jumped scene has been deleted",
is_continue: "Is continue", is_continue: "Is continue",

View File

@ -1180,6 +1180,9 @@ export default {
hours: "时", hours: "时",
}, },
api_test: { api_test: {
buttons: {
to_xml: "转xml结构",
},
case_jump_message: "跳转的用例已经删除!", case_jump_message: "跳转的用例已经删除!",
scenario_jump_message: "跳转的场景已经删除!", scenario_jump_message: "跳转的场景已经删除!",
is_continue: "是否继续", is_continue: "是否继续",

View File

@ -1177,6 +1177,9 @@ export default {
hours: "時", hours: "時",
}, },
api_test: { api_test: {
buttons: {
to_xml: "轉xml結構",
},
case_jump_message: "跳轉的用例已經刪除!", case_jump_message: "跳轉的用例已經刪除!",
scenario_jump_message: "跳轉的場景已經刪除!", scenario_jump_message: "跳轉的場景已經刪除!",
is_continue: "是否繼續", is_continue: "是否繼續",