feat(接口测试): 接口测试导入功能开发

This commit is contained in:
Jianguo-Genius 2024-08-09 18:24:39 +08:00 committed by 刘瑞斌
parent b0be34f90b
commit 325790cf88
71 changed files with 16123 additions and 628 deletions

View File

@ -223,7 +223,7 @@ public class ApiDefinitionController {
@Operation(summary = "接口测试-接口管理-导入接口定义")
public void testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ImportRequest request) {
request.setUserId(SessionUtils.getUserId());
apiDefinitionImportService.apiTestImport(file, request, SessionUtils.getCurrentProjectId());
apiDefinitionImportService.apiDefinitionImport(file, request, SessionUtils.getCurrentProjectId());
}
@PostMapping("/operation-history")

View File

@ -11,7 +11,7 @@ import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false)
public class ApiDefinitionImportDetail extends ApiDefinition {
public class ApiDefinitionDetail extends ApiDefinition {
@Schema(description = "请求内容")
private AbstractMsTestElement request;

View File

@ -0,0 +1,22 @@
package io.metersphere.api.dto.converter;
import io.metersphere.api.dto.definition.ApiDefinitionMockDTO;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.ArrayList;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false)
public class ApiDefinitionExportDetail extends ApiDefinitionDetail {
@Schema(description = "接口用例")
private List<ApiTestCaseDTO> apiTestCaseList = new ArrayList<>();
@Schema(description = "Mock")
private List<ApiDefinitionMockDTO> apiMockList = new ArrayList<>();
}

View File

@ -1,18 +0,0 @@
package io.metersphere.api.dto.converter;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class ApiDefinitionImport {
private String projectName;
private String protocol;
private List<ApiDefinitionImportDetail> data;
// 新版本带用例导出
private List<ApiTestCaseDTO> cases = new ArrayList<>();
}

View File

@ -0,0 +1,43 @@
package io.metersphere.api.dto.converter;
import io.metersphere.api.dto.definition.ApiDefinitionMockDTO;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 接口导入数据准备结果
*/
@Data
public class ApiDefinitionPreImportAnalysisResult {
@Schema(description = "需要创建的模块数据")
List<BaseTreeNode> insertModuleList = new ArrayList<>();
@Schema(description = "需要新增的接口")
List<ApiDefinitionDetail> insertApiData = new ArrayList<>();
@Schema(description = "需要更新的接口")
List<ApiDefinitionDetail> updateApiData = new ArrayList<>();
@Schema(description = "只需要更所属模块的接口")
List<ApiDefinitionDetail> updateModuleApiList = new ArrayList<>();
@Schema(description = "需要新增的接口用例")
List<ApiTestCaseDTO> insertApiCaseList = new ArrayList<>();
@Schema(description = "需要修改的接口用例")
List<ApiTestCaseDTO> updateApiCaseList = new ArrayList<>();
@Schema(description = "需要新增的Mock")
List<ApiDefinitionMockDTO> insertApiMockList = new ArrayList<>();
@Schema(description = "需要修改的Mock")
List<ApiDefinitionMockDTO> updateApiMockList = new ArrayList<>();
@Schema(description = "日志数据")
Map<String, ApiDefinitionDetail> logData = new HashMap<>();
}

View File

@ -1,22 +0,0 @@
package io.metersphere.api.dto.converter;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class ApiDetailWithData {
@Schema(description = "相同的数据的key")
List<String> sameList = new ArrayList<>();
@Schema(description = "不同的数据的key")
List<String> differenceList = new ArrayList<>();
@Schema(description = "数据库中存在的数据")
Map<String, ApiDefinitionImportDetail> apiDateMap = new HashMap<>();
@Schema(description = "导入的数据")
Map<String, ApiDefinitionImportDetail> importDataMap = new HashMap<>();
}

View File

@ -1,21 +0,0 @@
package io.metersphere.api.dto.converter;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Data
public class ApiDetailWithDataUpdate {
@Schema(description = "需要更新模块的数据")
List<ApiDefinitionImportDetail> updateModuleData = new ArrayList<>();
@Schema(description = "需要更新接口的数据")
List<ApiDefinitionImportDetail> updateRequestData = new ArrayList<>();
@Schema(description = "需要新增的接口数据")
List<ApiDefinitionImportDetail> addModuleData = new ArrayList<>();
@Schema(description = "需要新增的日志数据")
Map<String, ApiDefinitionImportDetail> logData;
}

View File

@ -0,0 +1,30 @@
package io.metersphere.api.dto.converter;
import io.metersphere.api.dto.definition.ApiDefinitionMockDTO;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* api导入数据分析结果
*/
@Data
public class ApiImportDataAnalysisResult {
// 新增接口数据
List<ApiDefinitionDetail> insertApiList = new ArrayList<>();
// 存在的接口数据. Map<导入的接口 , 已存在的接口>
List<ExistenceApiDefinitionDetail> existenceApiList = new ArrayList<>();
// 接口的用例数据
Map<String, List<ApiTestCaseDTO>> apiIdAndTestCaseMap = new HashMap<>();
// 接口的mock数据
Map<String, List<ApiDefinitionMockDTO>> apiIdAndMockMap = new HashMap<>();
public void addExistenceApi(ApiDefinitionDetail importApi, ApiDefinitionDetail exportApi) {
this.existenceApiList.add(new ExistenceApiDefinitionDetail(importApi, exportApi));
}
}

View File

@ -0,0 +1,23 @@
package io.metersphere.api.dto.converter;
import io.metersphere.api.dto.definition.ApiDefinitionMockDTO;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* api导入文件解析结果
*/
@Data
public class ApiImportFileParseResult {
// 接口定义数据
private List<ApiDefinitionDetail> data = new ArrayList<>();
// 用例数据
private Map<String, List<ApiTestCaseDTO>> caseMap = new HashMap<>();
// mock数据
private Map<String, List<ApiDefinitionMockDTO>> mockMap = new HashMap<>();
}

View File

@ -0,0 +1,11 @@
package io.metersphere.api.dto.converter;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ExistenceApiDefinitionDetail {
private ApiDefinitionDetail importApiDefinition;
private ApiDefinitionDetail existenceApiDefinition;
}

View File

@ -32,4 +32,10 @@ public class ApiDefinitionBatchRequest extends TableBatchProcessDTO implements S
@Schema(description = "模块ID(根据模块树查询时要把当前节点以及子节点都放在这里。)")
private List<@NotBlank String> moduleIds;
@Schema(description = "是否同步导出接口用例")
private boolean exportApiCase;
@Schema(description = "是否同步导出接口Mock")
private boolean exportApiMock;
}

View File

@ -0,0 +1,30 @@
package io.metersphere.api.dto.definition;
import io.metersphere.api.domain.ApiDefinitionMockConfig;
import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class ApiMockWithBlob extends ApiDefinitionMockConfig {
@Schema(description = "mock名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_definition_mock.name.not_blank}", groups = {Created.class})
@Size(min = 1, max = 255, message = "{api_definition_mock.name.length_range}", groups = {Created.class, Updated.class})
private String name;
@Schema(description = "自定义标签")
private java.util.List<String> tags;
@Schema(description = "启用/禁用")
private Boolean enable;
@Schema(description = "")
private Integer statusCode;
@Schema(description = "接口定义ID")
private String apiDefinitionId;
}

View File

@ -0,0 +1,38 @@
package io.metersphere.api.dto.definition;
import io.metersphere.api.domain.ApiTestCaseBlob;
import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class ApiTestCaseWithBlob extends ApiTestCaseBlob {
@Schema(description = "接口用例名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_test_case.name.not_blank}", groups = {Created.class})
@Size(min = 1, max = 255, message = "{api_test_case.name.length_range}", groups = {Created.class, Updated.class})
private String name;
@Schema(description = "用例等级", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_test_case.priority.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{api_test_case.priority.length_range}", groups = {Created.class, Updated.class})
private String priority;
@Schema(description = "标签")
private java.util.List<String> tags;
@Schema(description = "用例状态", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_test_case.status.not_blank}", groups = {Created.class})
@Size(min = 1, max = 20, message = "{api_test_case.status.length_range}", groups = {Created.class, Updated.class})
private String status;
@Schema(description = "最新执行结果状态")
private String lastReportStatus;
@Schema(description = "接口定义ID")
private String apiDefinitionId;
}

View File

@ -0,0 +1,17 @@
package io.metersphere.api.dto.export;
import io.metersphere.api.dto.converter.ApiDefinitionExportDetail;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author wx
*/
@Data
public class MetersphereApiExportResponse extends ApiExportResponse {
private List<ApiDefinitionExportDetail> apiDefinitions = new ArrayList<>();
}

View File

@ -23,16 +23,9 @@ public class ImportRequest {
private String platform;
@Schema(description = "导入的类型 暂定 API Schedule")
private String type;
@Schema(description = "是否覆盖模块")
private Boolean coverModule = false;
@Schema(description = "是否同步导入用例")
private Boolean syncCase = false;
@Schema(description = "是否覆盖数据")
private Boolean coverData = false;
@Schema(description = "协议")
private String protocol = ModuleConstants.NODE_PROTOCOL_HTTP;
@Schema(description = "是否开启Basic Auth认证")
private boolean authSwitch = false;
@Schema(description = "Basic Auth认证用户名")
private String authUsername;
@Schema(description = "Basic Auth认证密码")
@ -41,4 +34,18 @@ public class ImportRequest {
private String uniquelyIdentifies = "Method & Path";
@Schema(description = "定时任务的资源id")
private String resourceId;
@Schema(description = "是否覆盖模块")
private boolean coverModule;
@Schema(description = "是否同步导入用例")
private boolean syncCase;
@Schema(description = "是否同步导入Mock")
private boolean syncMock;
@Schema(description = "是否覆盖数据")
private boolean coverData;
@Schema(description = "是否开启Basic Auth认证")
private boolean authSwitch;
@Schema(description = "操作人")
private String operator;
}

View File

@ -6,7 +6,7 @@ import io.metersphere.api.domain.ApiTestCase;
import io.metersphere.api.dto.ApiDefinitionExecuteInfo;
import io.metersphere.api.dto.ReferenceDTO;
import io.metersphere.api.dto.ReferenceRequest;
import io.metersphere.api.dto.converter.ApiDefinitionImportDetail;
import io.metersphere.api.dto.converter.ApiDefinitionDetail;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.scenario.ScenarioSystemRequest;
import io.metersphere.project.dto.DropNode;
@ -50,7 +50,7 @@ public interface ExtApiDefinitionMapper {
void updateLatestVersion(@Param("id") String id, @Param("projectId") String projectId);
List<ApiDefinitionImportDetail> importList(@Param("request") ApiDefinitionPageRequest request);
List<ApiDefinitionDetail> importList(@Param("request") ApiDefinitionPageRequest request);
List<String> selectIdsByIdsAndDeleted(@Param("ids") List<String> ids, @Param("deleted") boolean deleted);

View File

@ -135,7 +135,7 @@
</foreach>
and deleted = #{deleted}
</select>
<select id="importList" resultType="io.metersphere.api.dto.converter.ApiDefinitionImportDetail">
<select id="importList" resultType="io.metersphere.api.dto.converter.ApiDefinitionDetail">
select
api_definition.id, api_definition.`name`, api_definition.protocol, api_definition.`method`,
api_definition.`path`, api_definition.version_id,

View File

@ -2,6 +2,7 @@ package io.metersphere.api.mapper;
import io.metersphere.api.domain.ApiDefinitionMock;
import io.metersphere.api.dto.definition.ApiDefinitionMockDTO;
import io.metersphere.api.dto.definition.ApiMockWithBlob;
import io.metersphere.api.dto.definition.ApiTestCaseBatchRequest;
import io.metersphere.api.dto.definition.request.ApiDefinitionMockPageRequest;
import org.apache.ibatis.annotations.Param;
@ -18,4 +19,6 @@ public interface ExtApiDefinitionMockMapper {
List<ApiDefinitionMock> getTagsByIds(@Param("ids") List<String> ids);
List<ApiDefinitionMock> getMockInfoByIds(@Param("ids") List<String> ids);
List<ApiMockWithBlob> selectAllDetailByApiIds(@Param("apiIds") List<String> apiIds);
}

View File

@ -61,6 +61,20 @@
</foreach>
</select>
<resultMap id="ApiMockDetailMap" type="io.metersphere.api.dto.definition.ApiMockWithBlob">
<result column="matching" jdbcType="LONGVARBINARY" property="matching"/>
<result column="response" jdbcType="LONGVARBINARY" property="response"/>
</resultMap>
<select id="selectAllDetailByApiIds" resultMap="ApiMockDetailMap">
SELECT apiMock.*,mockConfig.matching,mockConfig.response
FROM api_definition_mock apiMock
INNER JOIN api_definition_mock_config mockConfig ON apiMock.id = mockConfig.id
WHERE apiMock.api_definition_id IN
<foreach collection="apiIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
<sql id="queryWhereConditionByBatch">
<if test="request.protocols != null and request.protocols.size() > 0">
and a.protocol in

View File

@ -115,4 +115,6 @@ public interface ExtApiTestCaseMapper {
List<ApiTestCase> getRefApiScenarioCreator(@Param("ids") List<String> caseIds);
void clearApiChange(@Param("ids") List<String> ids);
List<ApiTestCaseWithBlob> selectAllDetailByApiIds(@Param("apiIds") List<String> apiIds);
}

View File

@ -749,4 +749,17 @@
on ass.scenario_id = api_scenario.id
and api_scenario.deleted = 0;
</select>
<resultMap id="ApiTestCaseDetailMap" type="io.metersphere.api.dto.definition.ApiTestCaseWithBlob">
<result column="request" jdbcType="LONGVARBINARY" property="request"/>
<result column="tags" jdbcType="VARCHAR" property="tags" typeHandler="io.metersphere.handler.ListTypeHandler"/>
</resultMap>
<select id="selectAllDetailByApiIds" resultMap="ApiTestCaseDetailMap">
SELECT apiTestCase.*,apiTestCaseBlob.request
FROM api_test_case apiTestCase
INNER JOIN api_test_case_blob apiTestCaseBlob ON apiTestCase.id = apiTestCaseBlob.id
WHERE apiTestCase.api_definition_id IN
<foreach collection="apiIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
</mapper>

View File

@ -0,0 +1,37 @@
package io.metersphere.api.parser;
import io.metersphere.api.dto.converter.ApiDefinitionDetail;
import io.metersphere.api.dto.converter.ApiImportDataAnalysisResult;
import io.metersphere.api.dto.converter.ApiImportFileParseResult;
import io.metersphere.api.dto.request.ImportRequest;
import java.io.InputStream;
import java.util.List;
public interface ApiDefinitionImportParser<T> {
/**
* 解析导入文件
*
* @param source 导入文件流
* @param request 导入的请求参数
* @return 解析后的数据
* @throws Exception
*/
T parse(InputStream source, ImportRequest request) throws Exception;
/**
* 判断当前导入的接口定义中哪些是数据库中存在需要更新哪些是不存在需要插入
*
* @param importParser 准备导入的数据
* @param existenceApiDefinitionList 数据库中已存在的数据
* @return 需要入库的模块需要入库的接口需要更新的接口
*/
ApiImportDataAnalysisResult generateInsertAndUpdateData(ApiImportFileParseResult importParser, List<ApiDefinitionDetail> existenceApiDefinitionList);
/**
* 获取解析协议类型
*/
String getParseProtocol();
}

View File

@ -1,10 +0,0 @@
package io.metersphere.api.parser;
import io.metersphere.api.dto.request.ImportRequest;
import java.io.InputStream;
public interface ImportParser<T> {
T parse(InputStream source, ImportRequest request) throws Exception;
}

View File

@ -1,17 +1,23 @@
package io.metersphere.api.parser;
import io.metersphere.api.constants.ApiImportPlatform;
import io.metersphere.api.parser.api.PostmanParser;
import io.metersphere.api.parser.api.Swagger3Parser;
import io.metersphere.api.parser.api.HarParserApiDefinition;
import io.metersphere.api.parser.api.MetersphereParserApiDefinition;
import io.metersphere.api.parser.api.PostmanParserApiDefinition;
import io.metersphere.api.parser.api.Swagger3ParserApiDefinition;
import org.apache.commons.lang3.StringUtils;
public class ImportParserFactory {
public static ImportParser<?> getImportParser(String platform) {
if (StringUtils.equals(ApiImportPlatform.Swagger3.name(), platform)) {
return new Swagger3Parser();
} else if (StringUtils.equals(ApiImportPlatform.Postman.name(), platform)) {
return new PostmanParser();
public static ApiDefinitionImportParser<?> getImportParser(String platform) {
if (StringUtils.equalsIgnoreCase(ApiImportPlatform.Swagger3.name(), platform)) {
return new Swagger3ParserApiDefinition();
} else if (StringUtils.equalsIgnoreCase(ApiImportPlatform.Postman.name(), platform)) {
return new PostmanParserApiDefinition();
} else if (StringUtils.equalsIgnoreCase(ApiImportPlatform.MeterSphere.name(), platform)) {
return new MetersphereParserApiDefinition();
} else if (StringUtils.equalsIgnoreCase(ApiImportPlatform.Har.name(), platform)) {
return new HarParserApiDefinition();
}
return null;
}
}
}

View File

@ -0,0 +1,532 @@
package io.metersphere.api.parser.api;
import io.metersphere.api.domain.ApiDefinitionBlob;
import io.metersphere.api.dto.converter.ApiDefinitionDetail;
import io.metersphere.api.dto.converter.ApiImportDataAnalysisResult;
import io.metersphere.api.dto.converter.ApiImportFileParseResult;
import io.metersphere.api.dto.converter.ExistenceApiDefinitionDetail;
import io.metersphere.api.dto.definition.HttpResponse;
import io.metersphere.api.dto.definition.ResponseBody;
import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.dto.request.http.MsHeader;
import io.metersphere.api.dto.request.http.QueryParam;
import io.metersphere.api.dto.request.http.RestParam;
import io.metersphere.api.dto.request.http.body.*;
import io.metersphere.api.mapper.ApiDefinitionBlobMapper;
import io.metersphere.api.parser.api.har.HarUtils;
import io.metersphere.api.parser.api.har.model.*;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.api.utils.JSONUtil;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.uid.IDGenerator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import java.io.InputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
public class HarParserApiDefinition extends HttpApiDefinitionImportAbstractParser<ApiImportFileParseResult> {
@Override
public ApiImportFileParseResult parse(InputStream source, ImportRequest request) throws Exception {
Har har = null;
try {
har = HarUtils.read(source);
} catch (Exception e) {
LogUtils.error(e.getMessage(), e);
throw new MSException(e.getMessage());
}
if (ObjectUtils.isEmpty(har) || har.log == null) {
throw new MSException("解析失败,请确认选择的是 Har 格式!");
}
ApiImportFileParseResult definitionImport = new ApiImportFileParseResult();
definitionImport.setData(parseRequests(har, request));
return definitionImport;
}
@Override
public ApiImportDataAnalysisResult generateInsertAndUpdateData(ApiImportFileParseResult importParser, List<ApiDefinitionDetail> existenceApiDefinitionList) {
ApiImportDataAnalysisResult insertAndUpdateData = super.generateInsertAndUpdateData(importParser, existenceApiDefinitionList);
ApiDefinitionBlobMapper apiDefinitionBlobMapper = CommonBeanFactory.getBean(ApiDefinitionBlobMapper.class);
for (ExistenceApiDefinitionDetail definitionDetail : insertAndUpdateData.getExistenceApiList()) {
ApiDefinitionDetail importApi = definitionDetail.getImportApiDefinition();
ApiDefinitionDetail savedApi = definitionDetail.getExistenceApiDefinition();
ApiDefinitionBlob blob = apiDefinitionBlobMapper.selectByPrimaryKey(savedApi.getId());
if (blob != null) {
if (blob.getRequest() != null) {
AbstractMsTestElement msTestElement = ApiDataUtils.parseObject(new String(blob.getRequest()), AbstractMsTestElement.class);
savedApi.setRequest(msTestElement);
}
if (blob.getResponse() != null) {
List<HttpResponse> httpResponses = ApiDataUtils.parseArray(new String(blob.getResponse()), HttpResponse.class);
savedApi.setResponse(httpResponses);
}
}
this.mergeExistenceApiMap(importApi, savedApi);
}
return insertAndUpdateData;
}
private void mergeExistenceApiMap(ApiDefinitionDetail importApi, ApiDefinitionDetail savedApi) {
MsHTTPElement importHttpElement = (MsHTTPElement) importApi.getRequest();
if (savedApi.getRequest() != null) {
MsHTTPElement existenceHttpElement = (MsHTTPElement) savedApi.getRequest();
importHttpElement.setOtherConfig(existenceHttpElement.getOtherConfig());
importHttpElement.setModuleId(existenceHttpElement.getModuleId());
importHttpElement.setNum(existenceHttpElement.getNum());
importHttpElement.setMockNum(existenceHttpElement.getMockNum());
importHttpElement.setBody(this.mergeBody(importHttpElement.getBody(), existenceHttpElement.getBody()));
importHttpElement.setHeaders(this.mergeHeaders(importHttpElement.getHeaders(), existenceHttpElement.getHeaders()));
importHttpElement.setQuery(this.mergeQuery(importHttpElement.getQuery(), existenceHttpElement.getQuery()));
importHttpElement.setRest(this.mergeRest(importHttpElement.getRest(), existenceHttpElement.getRest()));
}
importApi.setRequest(importHttpElement);
if (CollectionUtils.isEmpty(importApi.getResponse())) {
importApi.setResponse(savedApi.getResponse());
} else {
if (CollectionUtils.isEmpty(savedApi.getResponse())) {
importApi.getResponse().getFirst().setDefaultFlag(true);
} else {
List<HttpResponse> existenceResponseList = savedApi.getResponse();
for (HttpResponse importRsp : savedApi.getResponse()) {
boolean isExistence = false;
for (HttpResponse existenceRsp : existenceResponseList) {
if (StringUtils.equals(importRsp.getName(), existenceRsp.getName())) {
isExistence = true;
existenceRsp.setBody(importRsp.getBody());
existenceRsp.setHeaders(importRsp.getHeaders());
existenceRsp.setStatusCode(importRsp.getStatusCode());
}
}
if (!isExistence) {
importRsp.setId(IDGenerator.nextStr());
existenceResponseList.add(importRsp);
}
}
}
}
}
private List<RestParam> mergeRest(List<RestParam> importRestList, List<RestParam> existenceRestList) {
if (CollectionUtils.isNotEmpty(importRestList) && CollectionUtils.isNotEmpty(existenceRestList)) {
for (RestParam importRest : importRestList) {
for (RestParam existenceRest : existenceRestList) {
if (StringUtils.equals(importRest.getKey(), existenceRest.getKey())) {
importRest.setDescription(existenceRest.getDescription());
importRest.setMaxLength(existenceRest.getMaxLength());
importRest.setMinLength(existenceRest.getMinLength());
importRest.setEncode(existenceRest.getEncode());
}
}
}
}
return importRestList;
}
private List<QueryParam> mergeQuery(List<QueryParam> importQueryList, List<QueryParam> existenceQueryList) {
if (CollectionUtils.isNotEmpty(importQueryList) && CollectionUtils.isNotEmpty(existenceQueryList)) {
for (QueryParam importQuery : importQueryList) {
for (QueryParam existenceQuery : existenceQueryList) {
if (StringUtils.equals(importQuery.getKey(), existenceQuery.getKey())) {
importQuery.setDescription(existenceQuery.getDescription());
importQuery.setMaxLength(existenceQuery.getMaxLength());
importQuery.setMinLength(existenceQuery.getMinLength());
importQuery.setEncode(existenceQuery.getEncode());
}
}
}
}
return importQueryList;
}
private List<MsHeader> mergeHeaders(List<MsHeader> importHeaders, List<MsHeader> existenceHeaders) {
if (CollectionUtils.isNotEmpty(importHeaders) && CollectionUtils.isNotEmpty(existenceHeaders)) {
for (MsHeader importHeader : importHeaders) {
for (MsHeader existenceHeader : existenceHeaders) {
if (StringUtils.equals(importHeader.getKey(), existenceHeader.getKey())) {
importHeader.setDescription(existenceHeader.getDescription());
}
}
}
}
return importHeaders;
}
private Body mergeBody(Body importBody, Body existenceBody) {
if (importBody == null) {
return existenceBody;
} else if (existenceBody == null) {
return importBody;
} else {
Body returnBody = new Body();
BeanUtils.copyBean(returnBody, existenceBody);
if (importBody.getBinaryBody() != null) {
returnBody.setBinaryBody(importBody.getBinaryBody());
}
if (importBody.getFormDataBody() != null) {
if (returnBody.getFormDataBody() != null) {
for (FormDataKV importKv : importBody.getFormDataBody().getFormValues()) {
for (FormDataKV existenceKv : returnBody.getFormDataBody().getFormValues()) {
if (StringUtils.equals(existenceKv.getKey(), importKv.getKey())) {
importKv.setDescription(existenceKv.getDescription());
importKv.setMaxLength(existenceKv.getMaxLength());
importKv.setMinLength(existenceKv.getMinLength());
}
}
}
}
returnBody.setFormDataBody(importBody.getFormDataBody());
}
if (importBody.getJsonBody() != null) {
if (returnBody.getJsonBody() != null) {
returnBody.getJsonBody().setJsonValue(importBody.getJsonBody().getJsonValue());
} else {
returnBody.setJsonBody(importBody.getJsonBody());
}
}
if (importBody.getNoneBody() != null) {
returnBody.setNoneBody(importBody.getNoneBody());
}
if (importBody.getRawBody() != null) {
returnBody.setRawBody(importBody.getRawBody());
}
if (importBody.getWwwFormBody() != null) {
if (returnBody.getWwwFormBody() != null) {
for (WWWFormKV importKv : importBody.getWwwFormBody().getFormValues()) {
for (WWWFormKV existenceKv : returnBody.getWwwFormBody().getFormValues()) {
if (StringUtils.equals(existenceKv.getKey(), importKv.getKey())) {
importKv.setDescription(existenceKv.getDescription());
importKv.setMaxLength(existenceKv.getMaxLength());
importKv.setMinLength(existenceKv.getMinLength());
}
}
}
}
returnBody.setWwwFormBody(importBody.getWwwFormBody());
}
if (importBody.getXmlBody() != null) {
returnBody.setXmlBody(importBody.getXmlBody());
}
return returnBody;
}
}
private List<ApiDefinitionDetail> parseRequests(Har har, ImportRequest importRequest) {
List<ApiDefinitionDetail> resultList = new ArrayList<>();
List<HarEntry> harEntryList = new ArrayList<>();
if (har.log != null && har.log.entries != null) {
harEntryList = har.log.entries;
}
for (HarEntry entry : harEntryList) {
HarRequest harRequest = entry.request;
if (harRequest != null) {
String url = harRequest.url;
if (url == null) {
continue;
}
//默认取路径的最后一块
String[] nameArr = url.split("/");
String reqName = nameArr[nameArr.length - 1];
try {
url = URLDecoder.decode(url, StandardCharsets.UTF_8);
if (url.contains("?")) {
url = url.split("\\?")[0];
}
} catch (Exception e) {
LogUtils.error(e.getMessage(), e);
}
ApiDefinitionDetail detail = buildApiDefinition(reqName, url, harRequest.method, null, importRequest);
MsHTTPElement request = super.buildHttpRequest(reqName, url, harRequest.method);
parseParameters(harRequest, request);
parseRequestBody(harRequest, request.getBody());
detail.setRequest(request);
detail.setResponse(Collections.singletonList(parseResponse(entry.response)));
resultList.add(detail);
}
}
return resultList;
}
// private void addBodyHeader(MsHTTPElement request) {
// String contentType = StringUtils.EMPTY;
// if (request.getBody() != null && StringUtils.isNotBlank(request.getBody().getType())) {
// switch (request.getBody().getType()) {
// case Body.WWW_FROM:
// contentType = "application/x-www-form-urlencoded";
// break;
// case Body.JSON_STR:
// contentType = "application/json";
// break;
// case Body.XML:
// contentType = "application/xml";
// break;
// case Body.BINARY:
// contentType = "application/octet-stream";
// break;
// }
// List<KeyValue> headers = request.getHeaders();
// if (headers == null) {
// headers = new ArrayList<>();
// request.setHeaders(headers);
// }
// if (StringUtils.isNotEmpty(contentType)) {
// addContentType(request.getHeaders(), contentType);
// }
// }
// }
private void parseParameters(HarRequest harRequest, MsHTTPElement request) {
List<HarQueryParam> queryStringList = harRequest.queryString;
queryStringList.forEach(harQueryParam -> {
parseQueryParameters(harQueryParam, request.getQuery());
});
List<HarHeader> harHeaderList = harRequest.headers;
harHeaderList.forEach(harHeader -> {
parseHeaderParameters(harHeader, request.getHeaders());
});
List<HarCookie> harCookieList = harRequest.cookies;
harCookieList.forEach(harCookie -> {
parseCookieParameters(harCookie, request.getHeaders());
});
}
private void parseCookieParameters(HarCookie harCookie, List<MsHeader> headers) {
boolean hasCookie = false;
for (MsHeader header : headers) {
if (StringUtils.equalsIgnoreCase("Cookie", header.getKey())) {
hasCookie = true;
String cookies = Optional.ofNullable(header.getValue()).orElse(StringUtils.EMPTY);
header.setValue(cookies + harCookie.name + "=" + harCookie.value + ";");
}
}
if (!hasCookie) {
addHeader(headers, "Cookie", harCookie.name + "=" + harCookie.value + ";", harCookie.comment);
}
}
protected void addHeader(List<MsHeader> headers, String key, String value, String description) {
boolean hasContentType = false;
for (MsHeader header : headers) {
if (StringUtils.equalsIgnoreCase(header.getKey(), key)) {
hasContentType = true;
}
}
if (!hasContentType) {
headers.add(new MsHeader() {{
this.setKey(key);
this.setValue(value);
this.setDescription(description);
}});
}
}
private void parseHeaderParameters(HarHeader harHeader, List<MsHeader> headers) {
String key = harHeader.name;
String value = harHeader.value;
String description = harHeader.comment;
this.addHeader(headers, key, value, description);
}
private HttpResponse parseResponse(HarResponse response) {
HttpResponse msResponse = new HttpResponse();
msResponse.setBody(new ResponseBody());
msResponse.setName("har");
msResponse.setHeaders(new ArrayList<>());
if (response != null) {
msResponse.setStatusCode(String.valueOf(response.status));
parseResponseHeader(response, msResponse.getHeaders());
parseResponseBody(response.content, msResponse.getBody());
}
return msResponse;
}
private void parseResponseHeader(HarResponse response, List<MsHeader> msHeaders) {
List<HarHeader> harHeaders = response.headers;
if (harHeaders != null) {
for (HarHeader header : harHeaders) {
msHeaders.add(new MsHeader() {{
this.setKey(header.name);
this.setValue(header.value);
this.setDescription(header.comment);
}});
}
}
}
private void parseRequestBody(HarRequest requestBody, Body body) {
if (requestBody == null) {
return;
}
HarPostData content = requestBody.postData;
if (StringUtils.equalsIgnoreCase("GET", requestBody.method) || requestBody.postData == null) {
return;
}
String bodyType = content.mimeType;
if (StringUtils.isEmpty(bodyType)) {
body.setRawBody(new RawBody() {{
this.setValue(content.text);
}});
} else {
if (bodyType.startsWith(org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
bodyType = Body.BodyType.WWW_FORM.name();
body.setBodyType(Body.BodyType.WWW_FORM.name());
List<HarPostParam> postParams = content.params;
WWWFormBody kv = new WWWFormBody();
for (HarPostParam postParam : postParams) {
kv.getFormValues().add(new WWWFormKV() {{
this.setKey(postParam.name);
this.setValue(postParam.value);
}});
}
body.setWwwFormBody(kv);
} else if (bodyType.startsWith(org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE)) {
if (bodyType.contains("boundary=") && StringUtils.contains(content.text, this.getBoundaryFromContentType(bodyType))) {
String[] textArr = StringUtils.split(content.text, "\r\n");
String paramData = this.parseMultipartByTextArr(textArr);
JSONObject obj = null;
try {
obj = JSONUtil.parseObject(paramData);
FormDataBody kv = new FormDataBody();
for (String key : obj.keySet()) {
String value = obj.optString(key);
kv.getFormValues().add(new FormDataKV() {{
this.setKey(key);
this.setValue(value);
}});
}
body.setFormDataBody(kv);
} catch (Exception e) {
obj = null;
}
if (obj == null) {
body.setRawBody(new RawBody() {{
this.setValue(paramData);
}});
}
} else {
List<HarPostParam> postParams = content.params;
if (CollectionUtils.isNotEmpty(postParams)) {
FormDataBody kv = new FormDataBody();
for (HarPostParam postParam : postParams) {
kv.getFormValues().add(new FormDataKV() {{
this.setKey(postParam.name);
this.setValue(postParam.value);
}});
}
body.setFormDataBody(kv);
}
}
bodyType = Body.BodyType.FORM_DATA.name();
} else if (bodyType.startsWith(org.springframework.http.MediaType.APPLICATION_JSON_VALUE)) {
bodyType = Body.BodyType.JSON.name();
body.setJsonBody(new JsonBody() {{
this.setJsonValue(content.text);
}});
} else if (bodyType.startsWith(org.springframework.http.MediaType.APPLICATION_XML_VALUE)) {
bodyType = Body.BodyType.XML.name();
body.setXmlBody(new XmlBody() {{
this.setValue(content.text);
}});
} else if (bodyType.startsWith(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE)) {
bodyType = Body.BodyType.BINARY.name();
List<HarPostParam> postParams = content.params;
// for (HarPostParam postParam : postParams) {
// KeyValue kv = new KeyValue(postParam.name, postParam.value);
// body.getFormDataBody().add(kv);
// }
} else {
body.setRawBody(new RawBody() {{
this.setValue(content.text);
}});
}
}
body.setBodyType(bodyType);
}
private String getBoundaryFromContentType(String contentType) {
if (StringUtils.contains(contentType, "boundary=")) {
String[] strArr = StringUtils.split(contentType, "boundary=");
return strArr[strArr.length - 1];
}
return null;
}
private String parseMultipartByTextArr(String[] textArr) {
String data = null;
if (textArr != null && textArr.length > 2) {
data = textArr[textArr.length - 2];
}
return data;
}
private void parseResponseBody(HarContent content, ResponseBody body) {
if (content == null) {
return;
}
String contentType = content.mimeType;
if (body != null) {
switch (contentType) {
case "application/x-www-form-urlencoded":
body.setBodyType(Body.BodyType.WWW_FORM.name());
break;
case "multipart/form-data":
body.setBodyType(Body.BodyType.FORM_DATA.name());
break;
case "application/json":
body.setBodyType(Body.BodyType.JSON.name());
body.setJsonBody(new JsonBody() {{
this.setJsonValue(content.text);
}});
break;
case "application/xml":
body.setBodyType(Body.BodyType.XML.name());
body.setXmlBody(new XmlBody() {{
this.setValue(content.text);
}});
break;
case "application/octet-stream":
body.setBodyType(Body.BodyType.BINARY.name());
default:
body.setBodyType(Body.BodyType.RAW.name());
body.setRawBody(new RawBody() {{
this.setValue(content.text);
}});
}
}
}
private void parseQueryParameters(HarQueryParam harQueryParam, List<QueryParam> arguments) {
arguments.add(new QueryParam() {{
this.setKey(harQueryParam.name);
this.setValue(harQueryParam.value);
this.setDescription(harQueryParam.comment);
}});
}
}

View File

@ -1,15 +1,21 @@
package io.metersphere.api.parser.api;
import io.metersphere.api.dto.converter.ApiDefinitionImportDetail;
import io.metersphere.api.dto.converter.ApiDefinitionDetail;
import io.metersphere.api.dto.converter.ApiImportDataAnalysisResult;
import io.metersphere.api.dto.converter.ApiImportFileParseResult;
import io.metersphere.api.dto.definition.ApiDefinitionMockDTO;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.dto.request.http.MsHTTPConfig;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.dto.request.http.body.*;
import io.metersphere.api.parser.ImportParser;
import io.metersphere.api.parser.ApiDefinitionImportParser;
import io.metersphere.project.dto.environment.auth.NoAuth;
import io.metersphere.project.dto.environment.http.HttpConfig;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.LogUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedReader;
@ -18,10 +24,53 @@ import java.io.InputStreamReader;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public abstract class ApiImportAbstractParser<T> implements ImportParser<T> {
public abstract class HttpApiDefinitionImportAbstractParser<T> implements ApiDefinitionImportParser<T> {
protected String projectId;
@Override
public String getParseProtocol() {
return HttpConfig.HttpProtocolType.HTTP.name();
}
@Override
public ApiImportDataAnalysisResult generateInsertAndUpdateData(ApiImportFileParseResult importParser, List<ApiDefinitionDetail> existenceApiDefinitionList) {
// API类型通过 Method & Path 组合判断接口是否存在
Map<String, ApiDefinitionDetail> savedApiDefinitionMap = existenceApiDefinitionList.stream().collect(Collectors.toMap(t -> t.getMethod() + t.getPath(), t -> t, (oldValue, newValue) -> newValue));
Map<String, ApiDefinitionDetail> importDataMap = importParser.getData().stream().collect(Collectors.toMap(t -> t.getMethod() + t.getPath(), t -> t, (oldValue, newValue) -> newValue));
ApiImportDataAnalysisResult insertAndUpdateData = new ApiImportDataAnalysisResult();
importDataMap.forEach((key, api) -> {
if (savedApiDefinitionMap.containsKey(key)) {
insertAndUpdateData.addExistenceApi(api, savedApiDefinitionMap.get(key));
} else {
insertAndUpdateData.getInsertApiList().add(api);
}
List<ApiTestCaseDTO> caseList = importParser.getCaseMap().get(api.getId());
if (CollectionUtils.isNotEmpty(caseList)) {
insertAndUpdateData.getApiIdAndTestCaseMap().put(api.getId(), caseList);
}
List<ApiDefinitionMockDTO> mockDTOList = importParser.getMockMap().get(api.getId());
if (CollectionUtils.isNotEmpty(mockDTOList)) {
insertAndUpdateData.getApiIdAndMockMap().put(api.getId(), mockDTOList);
}
});
return insertAndUpdateData;
}
public String getUniqueName(String originalName, List<String> existenceNameList) {
String returnName = originalName;
int index = 1;
while (existenceNameList.contains(returnName)) {
returnName = originalName + " - " + index;
index++;
}
return returnName;
}
protected String getApiTestStr(InputStream source) {
StringBuilder testStr = null;
@ -40,15 +89,13 @@ public abstract class ApiImportAbstractParser<T> implements ImportParser<T> {
}
protected ApiDefinitionImportDetail buildApiDefinition(String name, String path, String method,String modulePath, ImportRequest importRequest) {
ApiDefinitionImportDetail apiDefinition = new ApiDefinitionImportDetail();
protected ApiDefinitionDetail buildApiDefinition(String name, String path, String method, String modulePath, ImportRequest importRequest) {
ApiDefinitionDetail apiDefinition = new ApiDefinitionDetail();
apiDefinition.setName(name);
apiDefinition.setPath(formatPath(path));
apiDefinition.setProtocol(importRequest.getProtocol());
apiDefinition.setMethod(method);
apiDefinition.setProjectId(this.projectId);
apiDefinition.setCreateUser(importRequest.getUserId());
apiDefinition.setProjectId(importRequest.getProjectId());
apiDefinition.setModulePath(modulePath);
apiDefinition.setResponse(new ArrayList<>());
return apiDefinition;

View File

@ -0,0 +1,81 @@
package io.metersphere.api.parser.api;
import io.metersphere.api.dto.converter.ApiDefinitionExportDetail;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.export.ApiExportResponse;
import io.metersphere.api.dto.export.MetersphereApiExportResponse;
import io.metersphere.api.dto.mockserver.MockMatchRule;
import io.metersphere.api.dto.mockserver.MockResponse;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.util.Translator;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class MetersphereExportParser {
public ApiExportResponse parse(List<ApiDefinitionWithBlob> apiDefinitionList, List<ApiTestCaseWithBlob> apiTestCaseList, List<ApiMockWithBlob> apiMockList, Map<String, String> moduleMap) {
Map<String, List<ApiTestCaseWithBlob>> apiTestCaseMap = apiTestCaseList.stream().collect(Collectors.groupingBy(ApiTestCaseWithBlob::getApiDefinitionId));
Map<String, List<ApiMockWithBlob>> apiMockMap = apiMockList.stream().collect(Collectors.groupingBy(ApiMockWithBlob::getApiDefinitionId));
MetersphereApiExportResponse response = new MetersphereApiExportResponse();
for (ApiDefinitionWithBlob blob : apiDefinitionList) {
ApiDefinitionExportDetail detail = new ApiDefinitionExportDetail();
if (blob.getRequest() != null) {
detail.setRequest(ApiDataUtils.parseObject(new String(blob.getRequest()), AbstractMsTestElement.class));
}
if (blob.getResponse() != null) {
detail.setResponse(ApiDataUtils.parseArray(new String(blob.getResponse()), HttpResponse.class));
}
detail.setName(blob.getName());
detail.setProtocol(blob.getProtocol());
detail.setMethod(blob.getMethod());
detail.setPath(blob.getPath());
detail.setStatus(blob.getStatus());
detail.setTags(blob.getTags());
detail.setPos(blob.getPos());
detail.setDescription(blob.getDescription());
String moduleName;
if (StringUtils.equals(blob.getModuleId(), ModuleConstants.DEFAULT_NODE_ID)) {
moduleName = Translator.get("api_unplanned_request");
} else {
moduleName = moduleMap.get(blob.getModuleId());
}
detail.setModulePath(moduleName);
response.getApiDefinitions().add(detail);
if (apiTestCaseMap.containsKey(blob.getId())) {
for (ApiTestCaseWithBlob apiTestCaseWithBlob : apiTestCaseMap.get(blob.getId())) {
ApiTestCaseDTO dto = new ApiTestCaseDTO();
dto.setName(apiTestCaseWithBlob.getName());
dto.setPriority(apiTestCaseWithBlob.getPriority());
dto.setStatus(apiTestCaseWithBlob.getStatus());
dto.setLastReportStatus(apiTestCaseWithBlob.getLastReportStatus());
dto.setTags(apiTestCaseWithBlob.getTags());
dto.setRequest(ApiDataUtils.parseObject(new String(apiTestCaseWithBlob.getRequest()), MsHTTPElement.class));
detail.getApiTestCaseList().add(dto);
}
}
if (apiMockMap.containsKey(blob.getId())) {
for (ApiMockWithBlob apiMockWithBlob : apiMockMap.get(blob.getId())) {
ApiDefinitionMockDTO mockDTO = new ApiDefinitionMockDTO();
mockDTO.setName(apiMockWithBlob.getName());
mockDTO.setTags(apiMockWithBlob.getTags());
mockDTO.setEnable(apiMockWithBlob.getEnable());
mockDTO.setStatusCode(apiMockWithBlob.getStatusCode());
mockDTO.setMockMatchRule(ApiDataUtils.parseObject(new String(apiMockWithBlob.getMatching()), MockMatchRule.class));
mockDTO.setResponse(ApiDataUtils.parseObject(new String(apiMockWithBlob.getResponse()), MockResponse.class));
detail.getApiMockList().add(mockDTO);
}
}
}
return response;
}
}

View File

@ -0,0 +1,136 @@
package io.metersphere.api.parser.api;
import io.metersphere.api.dto.converter.ApiDefinitionExportDetail;
import io.metersphere.api.dto.converter.ApiImportFileParseResult;
import io.metersphere.api.dto.definition.ApiDefinitionMockDTO;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.export.MetersphereApiExportResponse;
import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.uid.IDGenerator;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MetersphereParserApiDefinition extends HttpApiDefinitionImportAbstractParser<ApiImportFileParseResult> {
@Override
public ApiImportFileParseResult parse(InputStream source, ImportRequest request) throws Exception {
MetersphereApiExportResponse metersphereApiExportResponse = null;
try {
metersphereApiExportResponse = ApiDataUtils.parseObject(source, MetersphereApiExportResponse.class);
} catch (Exception e) {
LogUtils.error(e.getMessage(), e);
throw new MSException(e.getMessage());
}
if (metersphereApiExportResponse == null) {
throw new MSException("解析失败,请确认选择的是 Metersphere 格式!");
}
return this.genApiDefinitionImport(metersphereApiExportResponse.getApiDefinitions());
}
private ApiImportFileParseResult genApiDefinitionImport(List<ApiDefinitionExportDetail> apiDefinitions) {
List<ApiDefinitionExportDetail> distinctImportList = this.mergeApiCaseWithUniqueIdentification(apiDefinitions);
ApiImportFileParseResult returnDTO = new ApiImportFileParseResult();
distinctImportList.forEach(definitionImportDetail -> {
String apiID = IDGenerator.nextStr();
definitionImportDetail.setId(apiID);
List<ApiTestCaseDTO> caseList = new ArrayList<>();
List<ApiDefinitionMockDTO> mockList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(definitionImportDetail.getApiTestCaseList())) {
definitionImportDetail.getApiTestCaseList().forEach(item -> {
item.setApiDefinitionId(apiID);
item.setProtocol(definitionImportDetail.getProtocol());
caseList.add(item);
});
}
if (CollectionUtils.isNotEmpty(definitionImportDetail.getApiMockList())) {
definitionImportDetail.getApiMockList().forEach(item -> {
item.setApiDefinitionId(apiID);
mockList.add(item);
});
}
returnDTO.getData().add(definitionImportDetail);
if (CollectionUtils.isNotEmpty(caseList)) {
returnDTO.getCaseMap().put(apiID, this.apiCaseRename(caseList));
}
if (CollectionUtils.isNotEmpty(mockList)) {
returnDTO.getMockMap().put(apiID, this.apiMockRename(mockList));
}
});
return returnDTO;
}
private List<ApiTestCaseDTO> apiCaseRename(List<ApiTestCaseDTO> caseList) {
List<ApiTestCaseDTO> returnList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(caseList)) {
List<String> caseNameList = new ArrayList<>();
for (ApiTestCaseDTO apiCase : caseList) {
String uniqueName = this.getUniqueName(apiCase.getName(), caseNameList);
apiCase.setName(uniqueName);
caseNameList.add(uniqueName);
returnList.add(apiCase);
}
}
return returnList;
}
private List<ApiDefinitionMockDTO> apiMockRename(List<ApiDefinitionMockDTO> caseList) {
List<ApiDefinitionMockDTO> returnList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(caseList)) {
List<String> caseNameList = new ArrayList<>();
for (ApiDefinitionMockDTO apiMock : caseList) {
String uniqueName = this.getUniqueName(apiMock.getName(), caseNameList);
apiMock.setName(uniqueName);
caseNameList.add(uniqueName);
returnList.add(apiMock);
}
}
return returnList;
}
//合并相同路径下的用例和mock
private List<ApiDefinitionExportDetail> mergeApiCaseWithUniqueIdentification(List<ApiDefinitionExportDetail> apiDefinitions) {
List<ApiDefinitionExportDetail> returnList = new ArrayList<>();
Map<String, ApiDefinitionExportDetail> filterApiMap = new HashMap<>();
Map<String, List<ApiTestCaseDTO>> uniqueCaseMap = new HashMap<>();
Map<String, List<ApiDefinitionMockDTO>> uniqueMockMap = new HashMap<>();
apiDefinitions.forEach((api) -> {
String key = api.getMethod() + StringUtils.SPACE + api.getPath();
if (!filterApiMap.containsKey(key)) {
filterApiMap.put(key, api);
}
if (CollectionUtils.isNotEmpty(api.getApiTestCaseList())) {
if (uniqueCaseMap.containsKey(key)) {
uniqueCaseMap.get(key).addAll(api.getApiTestCaseList());
} else {
uniqueCaseMap.put(key, api.getApiTestCaseList());
}
}
if (CollectionUtils.isNotEmpty(api.getApiMockList())) {
if (uniqueMockMap.containsKey(key)) {
uniqueMockMap.get(key).addAll(api.getApiMockList());
} else {
uniqueMockMap.put(key, api.getApiMockList());
}
}
});
filterApiMap.forEach((key, api) -> {
api.setApiTestCaseList(uniqueCaseMap.get(key));
api.setApiMockList(uniqueMockMap.get(key));
returnList.add(api);
});
return returnList;
}
}

View File

@ -7,8 +7,7 @@ import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import io.metersphere.api.dto.ApiFile;
import io.metersphere.api.dto.converter.ApiDefinitionImportDetail;
import io.metersphere.api.dto.definition.HttpResponse;
import io.metersphere.api.dto.converter.ApiDefinitionDetail;
import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.dto.request.MsCommonElement;
import io.metersphere.api.dto.request.http.MsHTTPElement;
@ -27,17 +26,14 @@ import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.dto.environment.auth.BasicAuth;
import io.metersphere.project.dto.environment.auth.DigestAuth;
import io.metersphere.project.dto.environment.auth.HTTPAuthConfig;
import io.metersphere.system.uid.IDGenerator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.*;
public abstract class PostmanAbstractParserParser<T> extends ApiImportAbstractParser<T> {
public abstract class PostmanAbstractParserParserApiDefinition<T> extends HttpApiDefinitionImportAbstractParser<T> {
private static final String POSTMAN_AUTH_TYPE_BASIC = "basic";
@ -63,18 +59,8 @@ public abstract class PostmanAbstractParserParser<T> extends ApiImportAbstractPa
private static final String JSON = "json";
private static final String XML = "xml";
protected ApiDefinitionImportDetail parsePostman(PostmanItem requestItem, String modulePath, ImportRequest importRequest) {
PostmanRequest requestDesc = requestItem.getRequest();
if (requestDesc == null) {
return null;
}
String url = StringUtils.EMPTY;
JsonNode urlNode = requestDesc.getUrl();
//获取url
url = getUrl(urlNode, url);
ApiDefinitionImportDetail detail = buildApiDefinition(requestItem.getName(), url, requestDesc.getMethod(), modulePath, importRequest);
MsHTTPElement request = buildHttpRequest(requestItem.getName(), url, requestDesc.getMethod());
private MsHTTPElement parseElement(PostmanRequest requestDesc, String name, String url, JsonNode urlNode) {
MsHTTPElement request = buildHttpRequest(name, url, requestDesc.getMethod());
//设置认证
setAuth(requestDesc, request);
//设置基础参数 请求头
@ -87,6 +73,18 @@ public abstract class PostmanAbstractParserParser<T> extends ApiImportAbstractPa
}
//设置body参数
setBody(requestDesc, request);
return request;
}
protected Map<ApiDefinitionDetail, List<ApiDefinitionDetail>> parsePostman(PostmanItem requestItem, String modulePath, ImportRequest importRequest) {
PostmanRequest requestDesc = requestItem.getRequest();
if (requestDesc == null) {
return null;
}
String url = getUrl(requestDesc.getUrl());
ApiDefinitionDetail detail = buildApiDefinition(requestItem.getName(), url, requestDesc.getMethod(), modulePath, importRequest);
MsHTTPElement request = parseElement(requestDesc, requestItem.getName(), url, requestDesc.getUrl());
// 其他设置
PostmanItem.ProtocolProfileBehavior protocolProfileBehavior = requestItem.getProtocolProfileBehavior();
request.getOtherConfig().setFollowRedirects(protocolProfileBehavior != null &&
@ -97,42 +95,46 @@ public abstract class PostmanAbstractParserParser<T> extends ApiImportAbstractPa
LinkedList<AbstractMsTestElement> children = new LinkedList<>();
children.add(new MsCommonElement());
request.setChildren(children);
detail.setRequest(request);
//设置response
setResponseData(requestItem, detail);
return detail;
List<ApiDefinitionDetail> postmanExamples = this.parsePostmanRequest(requestItem, modulePath, importRequest);
return new HashMap<>() {{
this.put(detail, postmanExamples);
}};
}
private static void setResponseData(PostmanItem requestItem, ApiDefinitionImportDetail detail) {
private String initApiCaseName(String originalName, List<String> savedResponseName) {
if (savedResponseName.contains(originalName)) {
int i = 1;
while (savedResponseName.contains(originalName + "_" + i)) {
i++;
}
return originalName + "_" + i;
}
return originalName;
}
private List<ApiDefinitionDetail> parsePostmanRequest(PostmanItem requestItem, String modulePath, ImportRequest importRequest) {
List<ApiDefinitionDetail> postmanExamples = new ArrayList<>();
List<PostmanResponse> response = requestItem.getResponse();
List<String> savedResponseName = new ArrayList<>();
if (CollectionUtils.isNotEmpty(response)) {
response.forEach(r -> {
HttpResponse httpResponse = new HttpResponse();
httpResponse.setId(IDGenerator.nextStr());
httpResponse.setName(r.getName());
httpResponse.setStatusCode(r.getCode().toString());
if (CollectionUtils.isNotEmpty(r.getHeader())) {
r.getHeader().forEach(h -> {
MsHeader msHeader = getMsHeader(h);
httpResponse.getHeaders().add(msHeader);
});
}
httpResponse.getBody().getJsonBody().setJsonValue(r.getBody() != null && r.getBody() instanceof TextNode ? r.getBody().asText() : StringUtils.EMPTY);
httpResponse.getBody().setBodyType(Body.BodyType.RAW.name());
detail.getResponse().add(httpResponse);
PostmanRequest postmanRequest = r.getOriginalRequest();
String name = this.initApiCaseName(r.getName(), savedResponseName);
String url = getUrl(postmanRequest.getUrl());
ApiDefinitionDetail detail = buildApiDefinition(name, url, postmanRequest.getMethod(), modulePath, importRequest);
MsHTTPElement request = parseElement(postmanRequest, name, url, postmanRequest.getUrl());
//构造 children
LinkedList<AbstractMsTestElement> children = new LinkedList<>();
children.add(new MsCommonElement());
request.setChildren(children);
detail.setRequest(request);
postmanExamples.add(detail);
savedResponseName.add(name);
});
detail.getResponse().forEach(httpResponse -> {
if (StringUtils.equals("200", httpResponse.getStatusCode())) {
httpResponse.setDefaultFlag(true);
}
});
if (detail.getResponse().stream().noneMatch(httpResponse -> StringUtils.equals("200", httpResponse.getStatusCode()))) {
detail.getResponse().getFirst().setDefaultFlag(true);
}
}
return postmanExamples;
}
@NotNull
@ -242,10 +244,14 @@ public abstract class PostmanAbstractParserParser<T> extends ApiImportAbstractPa
if (restNode instanceof ArrayNode restArray) {
restArray.forEach(r -> {
RestParam restParam = new RestParam();
restParam.setKey(r.get(KEY).asText());
restParam.setValue(r.get(VALUE).asText());
restParam.setDescription(r.get(DESCRIPTION) instanceof TextNode ? r.get(DESCRIPTION).asText() : StringUtils.EMPTY);
restParam.setEnable(!(r.get(DISABLED) instanceof BooleanNode) || !r.get(DISABLED).asBoolean());
restParam.setKey(r.get(KEY) != null ? r.get(KEY).asText() : StringUtils.EMPTY);
restParam.setValue(r.get(VALUE) != null ? r.get(VALUE).asText() : StringUtils.EMPTY);
if (r.get(DESCRIPTION) != null) {
restParam.setDescription(r.get(DESCRIPTION) instanceof TextNode ? r.get(DESCRIPTION).asText() : StringUtils.EMPTY);
}
if (r.get(DESCRIPTION) != null) {
restParam.setEnable(!(r.get(DISABLED) instanceof BooleanNode) || !r.get(DISABLED).asBoolean());
}
request.getRest().add(restParam);
});
}
@ -274,7 +280,8 @@ public abstract class PostmanAbstractParserParser<T> extends ApiImportAbstractPa
}
}
private static String getUrl(JsonNode urlNode, String url) {
private static String getUrl(JsonNode urlNode) {
String url = StringUtils.EMPTY;
if (urlNode instanceof ObjectNode urlObject) {
JsonNode pathNode = urlObject.get(PATH);
if (pathNode instanceof ArrayNode pathArray) {

View File

@ -1,60 +0,0 @@
package io.metersphere.api.parser.api;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.metersphere.api.dto.converter.ApiDefinitionImport;
import io.metersphere.api.dto.converter.ApiDefinitionImportDetail;
import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.parser.api.postman.PostmanCollection;
import io.metersphere.api.parser.api.postman.PostmanItem;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class PostmanParser extends PostmanAbstractParserParser<ApiDefinitionImport> {
@Override
public ApiDefinitionImport parse(InputStream source, ImportRequest request) throws JsonProcessingException {
LogUtils.info("PostmanParser parse");
String testStr = getApiTestStr(source);
this.projectId = request.getProjectId();
PostmanCollection postmanCollection = JSON.parseObject(testStr, PostmanCollection.class);
if (postmanCollection == null || postmanCollection.getItem() == null || postmanCollection.getItem().isEmpty() || postmanCollection.getInfo() == null){
throw new MSException("Postman collection is empty");
}
ApiDefinitionImport apiImport = new ApiDefinitionImport();
List<ApiDefinitionImportDetail> results = new ArrayList<>();
String modulePath = null;
if (StringUtils.isNotBlank(postmanCollection.getInfo().getName())) {
modulePath = "/" + postmanCollection.getInfo().getName();
}
parseItem(postmanCollection.getItem(), modulePath, results, request);
apiImport.setData(results);
//apiImport.setCases(cases);
LogUtils.info("PostmanParser parse end");
return apiImport;
}
protected void parseItem(List<PostmanItem> items, String modulePath, List<ApiDefinitionImportDetail> results, ImportRequest request) {
for (PostmanItem item : items) {
List<PostmanItem> childItems = item.getItem();
if (childItems != null) {
String itemModulePath;
if (StringUtils.isNotBlank(modulePath) && StringUtils.isNotBlank(item.getName())) {
itemModulePath = modulePath + "/" + item.getName();
} else {
itemModulePath = item.getName();
}
parseItem(childItems, itemModulePath, results, request);
} else {
results.add(parsePostman(item, modulePath, request));
}
}
}
}

View File

@ -0,0 +1,131 @@
package io.metersphere.api.parser.api;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.metersphere.api.dto.converter.ApiDefinitionDetail;
import io.metersphere.api.dto.converter.ApiImportFileParseResult;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.parser.api.postman.PostmanCollection;
import io.metersphere.api.parser.api.postman.PostmanItem;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.uid.IDGenerator;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.InputStream;
import java.util.*;
public class PostmanParserApiDefinition extends PostmanAbstractParserParserApiDefinition<ApiImportFileParseResult> {
@Override
public ApiImportFileParseResult parse(InputStream source, ImportRequest request) throws JsonProcessingException {
LogUtils.info("PostmanParser parse");
String testStr = getApiTestStr(source);
PostmanCollection postmanCollection = JSON.parseObject(testStr, PostmanCollection.class);
if (postmanCollection == null || postmanCollection.getItem() == null || postmanCollection.getItem().isEmpty() || postmanCollection.getInfo() == null){
throw new MSException("Postman collection is empty");
}
String modulePath = null;
if (StringUtils.isNotBlank(postmanCollection.getInfo().getName())) {
modulePath = "/" + postmanCollection.getInfo().getName();
}
LinkedHashMap<ApiDefinitionDetail, List<ApiDefinitionDetail>> allImportDetails = this.parseItem(postmanCollection.getItem(), modulePath, request);
// 对于postman的导入 本质就是将postman的数据导入为用例
ApiImportFileParseResult apiImport = this.genApiDefinitionImport(allImportDetails);
LogUtils.info("PostmanParser parse end");
return apiImport;
}
protected LinkedHashMap<ApiDefinitionDetail, List<ApiDefinitionDetail>> parseItem(List<PostmanItem> items, String modulePath, ImportRequest request) {
LinkedHashMap<ApiDefinitionDetail, List<ApiDefinitionDetail>> results = new LinkedHashMap<>();
for (PostmanItem item : items) {
List<PostmanItem> childItems = item.getItem();
if (childItems != null) {
String itemModulePath;
if (StringUtils.isNotBlank(modulePath) && StringUtils.isNotBlank(item.getName())) {
itemModulePath = modulePath + "/" + item.getName();
} else {
itemModulePath = item.getName();
}
results.putAll(parseItem(childItems, itemModulePath, request));
} else {
results.putAll(parsePostman(item, modulePath, request));
}
}
return results;
}
private ApiImportFileParseResult genApiDefinitionImport(LinkedHashMap<ApiDefinitionDetail, List<ApiDefinitionDetail>> allImportDetails) {
Map<ApiDefinitionDetail, List<ApiDefinitionDetail>> groupWithUniqueIdentification = this.mergeApiCaseWithUniqueIdentification(allImportDetails);
ApiImportFileParseResult returnDTO = new ApiImportFileParseResult();
groupWithUniqueIdentification.forEach((definitionImportDetail, caseData) -> {
String apiID = IDGenerator.nextStr();
definitionImportDetail.setId(apiID);
List<ApiTestCaseDTO> caseList = new ArrayList<>();
caseData.forEach(item -> {
ApiTestCaseDTO apiTestCaseDTO = new ApiTestCaseDTO();
apiTestCaseDTO.setName(item.getName());
apiTestCaseDTO.setPriority("P0");
apiTestCaseDTO.setStatus(item.getStatus());
apiTestCaseDTO.setProjectId(item.getProjectId());
apiTestCaseDTO.setApiDefinitionId(definitionImportDetail.getId());
apiTestCaseDTO.setFollow(false);
apiTestCaseDTO.setMethod(item.getMethod());
apiTestCaseDTO.setPath(item.getPath());
apiTestCaseDTO.setRequest(item.getRequest());
apiTestCaseDTO.setModulePath(item.getModulePath());
apiTestCaseDTO.setModuleId(item.getModuleId());
apiTestCaseDTO.setProtocol(item.getProtocol());
apiTestCaseDTO.setProjectId(item.getProjectId());
caseList.add(apiTestCaseDTO);
});
returnDTO.getData().add(definitionImportDetail);
if (CollectionUtils.isNotEmpty(caseList)) {
returnDTO.getCaseMap().put(apiID, caseList);
}
});
return returnDTO;
}
private Map<ApiDefinitionDetail, List<ApiDefinitionDetail>> mergeApiCaseWithUniqueIdentification(LinkedHashMap<ApiDefinitionDetail, List<ApiDefinitionDetail>> allImportDetails) {
Map<ApiDefinitionDetail, List<ApiDefinitionDetail>> returnMap = new HashMap<>();
Map<String, ApiDefinitionDetail> filterApiMap = new HashMap<>();
Map<String, List<ApiDefinitionDetail>> uniqueCaseMap = new HashMap<>();
allImportDetails.forEach((api, apiCase) -> {
String key = api.getMethod() + StringUtils.SPACE + api.getPath();
if (!filterApiMap.containsKey(key)) {
filterApiMap.put(key, api);
}
if (uniqueCaseMap.containsKey(key)) {
uniqueCaseMap.get(key).addAll(apiCase);
} else {
uniqueCaseMap.put(key, apiCase);
}
});
filterApiMap.forEach((key, api) -> {
returnMap.put(api, this.apiCaseRename(uniqueCaseMap.get(key)));
});
return returnMap;
}
private List<ApiDefinitionDetail> apiCaseRename(List<ApiDefinitionDetail> caseList) {
List<ApiDefinitionDetail> returnList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(caseList)) {
List<String> caseNameList = new ArrayList<>();
for (ApiDefinitionDetail apiCase : caseList) {
String uniqueName = this.getUniqueName(apiCase.getName(), caseNameList);
apiCase.setName(uniqueName);
caseNameList.add(uniqueName);
returnList.add(apiCase);
}
}
return returnList;
}
}

View File

@ -1,8 +1,8 @@
package io.metersphere.api.parser.api;
import io.metersphere.api.constants.ApiConstants;
import io.metersphere.api.dto.converter.ApiDefinitionImport;
import io.metersphere.api.dto.converter.ApiDefinitionImportDetail;
import io.metersphere.api.dto.converter.ApiDefinitionDetail;
import io.metersphere.api.dto.converter.ApiImportFileParseResult;
import io.metersphere.api.dto.definition.HttpResponse;
import io.metersphere.api.dto.definition.ResponseBody;
import io.metersphere.api.dto.request.ImportRequest;
@ -39,10 +39,12 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.*;
public class Swagger3Parser extends ApiImportAbstractParser<ApiDefinitionImport> {
public class Swagger3ParserApiDefinition extends HttpApiDefinitionImportAbstractParser<ApiImportFileParseResult> {
protected String projectId;
private Components components;
@ -52,7 +54,31 @@ public class Swagger3Parser extends ApiImportAbstractParser<ApiDefinitionImport>
public static final String COOKIE = "cookie";
public static final String QUERY = "query";
public ApiDefinitionImport parse(InputStream source, ImportRequest request) throws Exception {
private void testUrlTimeout(String swaggerUrl) {
HttpURLConnection connection = null;
try {
URI uriObj = new URI(swaggerUrl);
connection = (HttpURLConnection) uriObj.toURL().openConnection();
connection.setUseCaches(false);
connection.setConnectTimeout(3000); // 设置超时时间
connection.connect(); // 建立连接
} catch (Exception e) {
LogUtils.error(e);
throw new MSException(Translator.get("url_format_error"));
} finally {
if (connection != null) {
connection.disconnect(); // 关闭连接
}
}
}
public ApiImportFileParseResult parse(InputStream source, ImportRequest request) throws Exception {
//将之前在service中的swagger地址判断放在这里
if (StringUtils.isNotBlank(request.getSwaggerUrl())) {
this.testUrlTimeout(request.getSwaggerUrl());
}
LogUtils.info("Swagger3Parser parse");
List<AuthorizationValue> auths = setAuths(request);
SwaggerParseResult result = null;
@ -75,10 +101,10 @@ public class Swagger3Parser extends ApiImportAbstractParser<ApiDefinitionImport>
throw new MSException(Translator.get("swagger_parse_error"));
}
}
ApiDefinitionImport apiDefinitionImport = new ApiDefinitionImport();
ApiImportFileParseResult apiImportFileParseResult = new ApiImportFileParseResult();
OpenAPI openAPI = result.getOpenAPI();
apiDefinitionImport.setData(parseRequests(openAPI, request));
return apiDefinitionImport;
apiImportFileParseResult.setData(parseRequests(openAPI, request));
return apiImportFileParseResult;
}
private List<AuthorizationValue> setAuths(ImportRequest request) {
@ -95,7 +121,7 @@ public class Swagger3Parser extends ApiImportAbstractParser<ApiDefinitionImport>
return CollectionUtils.size(auths) == 0 ? null : auths;
}
private List<ApiDefinitionImportDetail> parseRequests(OpenAPI openAPI, ImportRequest importRequest) {
private List<ApiDefinitionDetail> parseRequests(OpenAPI openAPI, ImportRequest importRequest) {
Paths paths = openAPI.getPaths();
@ -103,7 +129,7 @@ public class Swagger3Parser extends ApiImportAbstractParser<ApiDefinitionImport>
this.components = openAPI.getComponents();
List<ApiDefinitionImportDetail> results = new ArrayList<>();
List<ApiDefinitionDetail> results = new ArrayList<>();
for (String pathName : pathNames) {
PathItem pathItem = paths.get(pathName);
@ -122,7 +148,7 @@ public class Swagger3Parser extends ApiImportAbstractParser<ApiDefinitionImport>
Operation operation = operationsMap.get(method);
if (operation != null) {
//构建基本请求
ApiDefinitionImportDetail apiDefinitionDTO = buildSwaggerApiDefinition(operation, pathName, method, importRequest);
ApiDefinitionDetail apiDefinitionDTO = buildSwaggerApiDefinition(operation, pathName, method, importRequest);
//构建请求参数
MsHTTPElement request = buildHttpRequest(apiDefinitionDTO.getName(), pathName, method);
parseParameters(operation, request);
@ -385,7 +411,7 @@ public class Swagger3Parser extends ApiImportAbstractParser<ApiDefinitionImport>
return XMLUtil.jsonToPrettyXml(object);
}
private ApiDefinitionImportDetail buildSwaggerApiDefinition(Operation operation, String path, String
private ApiDefinitionDetail buildSwaggerApiDefinition(Operation operation, String path, String
method, ImportRequest importRequest) {
String name;
if (StringUtils.isNotBlank(operation.getSummary())) {

View File

@ -0,0 +1,38 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har;
import com.google.gson.JsonSyntaxException;
import io.metersphere.api.parser.api.har.model.Har;
import io.metersphere.sdk.util.JSON;
import java.io.IOException;
import java.io.InputStream;
public class HarUtils {
public static Har read(InputStream source) throws JsonSyntaxException, IOException {
if (source == null) {
throw new IllegalArgumentException("HAR Json cannot be null/empty");
}
return JSON.parseObject(source, Har.class);
}
}

View File

@ -0,0 +1,30 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
public class Har {
public HarLog log;
@Override
public String toString() {
return "Har [log=" + log + "]";
}
}

View File

@ -0,0 +1,30 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
public class HarCache {
public HarCacheDetails beforeRequest;
public HarCacheDetails afterRequest;
public String comment;
}

View File

@ -0,0 +1,34 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
public class HarCacheDetails {
public String expires;
public String lastAccess;
public String etag;
public String hitCount;
public String comment;
}

View File

@ -0,0 +1,36 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
public class HarContent {
public long size;
public String mimeType;
public long compression;
public String text;
public String comment;
public String encoding;
}

View File

@ -0,0 +1,65 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
public class HarCookie {
public String name;
public String value;
public String path;
public String expires;
public boolean httpOnly;
public boolean secure;
public String comment;
@Override
public String toString() {
return "[Cookie: " + this.name + "=" + this.value + "]";
}
@Override
public int hashCode() {
if (this.name == null) {
return -1;
}
return this.name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof HarCookie)) {
return false;
}
if (this.name == null) {
return false;
}
HarCookie harCookie = (HarCookie) obj;
return this.name.equals(harCookie.name);
}
}

View File

@ -0,0 +1,36 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
public class HarCreator {
public String name;
public String version;
public String comment;
@Override
public String toString() {
return "HarCreator [name=" + name + ", version=" + version + ", comment=" + comment + "]";
}
}

View File

@ -0,0 +1,62 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
public class HarEntry implements Comparable<HarEntry> {
public String pageref;
public String startedDateTime;
public double time;
public HarRequest request;
public HarResponse response;
public HarCache cache;
public HarTiming timings;
public String serverIPAddress;
public String connection;
public String comment;
@Override
public String toString() {
return "HarEntry [pageref=" + pageref + ", startedDateTime=" + startedDateTime + ", time=" + time + ", request="
+ request + ", response=" + response + ", cache=" + cache + ", timings=" + timings
+ ", serverIPAddress=" + serverIPAddress + ", connection=" + connection + ", comment=" + comment + "]";
}
@Override
public int compareTo(HarEntry o) {
if (o == null) {
return -1;
}
// parse the time and then return
return this.startedDateTime.compareTo(o.startedDateTime);
}
}

View File

@ -0,0 +1,58 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
public class HarHeader {
public String name;
public String value;
public String comment;
@Override
public String toString() {
return "[Header: " + this.name + "=" + this.value + "]";
}
@Override
public int hashCode() {
if (this.name == null) {
return -1;
}
return this.name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof HarHeader)) {
return false;
}
if (this.name == null) {
return false;
}
HarHeader harHeader = (HarHeader) obj;
return this.name.equals(harHeader.name);
}
}

View File

@ -0,0 +1,47 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
import java.util.List;
public class HarLog {
public static final String DEFAULT_HAR_VERSION = "1.2";
public String version = DEFAULT_HAR_VERSION;
public HarCreator creator;
public HarCreator browser;
public List<HarPage> pages;
public List<HarEntry> entries;
public String comment;
@Override
public String toString() {
return "HarLog [version=" + version + ", creator=" + creator + ", browser=" + browser + ", pages=" + pages
+ ", entries=" + entries + ", comment=" + comment + "]";
}
}

View File

@ -0,0 +1,68 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
import java.util.List;
public class HarPage {
public String startedDateTime;
public String id;
public String title;
public HarPageTiming pageTimings;
public String comment;
public transient List<HarEntry> entries;
@Override
public String toString() {
return "HarPage [startedDateTime=" + startedDateTime + ", id=" + id + ", title=" + title + ", pageTimings="
+ pageTimings + ", comment=" + comment + "]";
}
@Override
public int hashCode() {
if (this.id == null) {
return -1;
}
return this.id.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof HarPage)) {
return false;
}
if (this.id == null) {
return false;
}
HarPage harPage = (HarPage) obj;
return this.id.equals(harPage.id);
}
}

View File

@ -0,0 +1,36 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
public class HarPageTiming {
public double onContentLoad;
public double onLoad;
public String comment;
@Override
public String toString() {
return "HarPageTiming [onContentLoad=" + onContentLoad + ", onLoad=" + onLoad + ", comment=" + comment + "]";
}
}

View File

@ -0,0 +1,34 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
import java.util.List;
public class HarPostData {
public String mimeType;
public List<HarPostParam> params;
public String text;
public String comment;
}

View File

@ -0,0 +1,62 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
public class HarPostParam {
public String name;
public String value;
public String fileName;
public String contentType;
public String comment;
@Override
public String toString() {
return "[Post Param: " + this.name + "=" + this.value + "]";
}
@Override
public int hashCode() {
if (this.name == null) {
return -1;
}
return this.name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof HarPostParam)) {
return false;
}
if (this.name == null) {
return false;
}
HarPostParam harPostParam = (HarPostParam) obj;
return this.name.equals(harPostParam.name);
}
}

View File

@ -0,0 +1,58 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
public class HarQueryParam {
public String name;
public String value;
public String comment;
@Override
public String toString() {
return "[Query Param: " + this.name + "=" + this.value + "]";
}
@Override
public int hashCode() {
if (this.name == null) {
return -1;
}
return this.name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof HarQueryParam)) {
return false;
}
if (this.name == null) {
return false;
}
HarQueryParam harQueryParam = (HarQueryParam) obj;
return this.name.equals(harQueryParam.name);
}
}

View File

@ -0,0 +1,53 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
public class HarRequest {
public String method;
public String url;
public String httpVersion;
public List<HarCookie> cookies;
public List<HarHeader> headers;
public List<HarQueryParam> queryString;
public HarPostData postData;
public long headersSize;
public long bodySize;
public String comment;
@Override
public String toString() {
return this.method + StringUtils.SPACE + this.url + StringUtils.SPACE + this.httpVersion;
}
}

View File

@ -0,0 +1,49 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
import java.util.List;
public class HarResponse {
public int status;
public String statusText;
public String httpVersion;
public List<HarHeader> headers;
public List<HarCookie> cookies;
public HarContent content;
public String redirectURL;
public long headersSize;
public long bodySize;
@Override
public String toString() {
return "HTTP " + this.status + " (" + this.statusText + ")";
}
}

View File

@ -0,0 +1,47 @@
/**
* har - HAR file reader, writer and viewer
* Copyright (c) 2014, Sandeep Gupta
* <p>
* http://sangupta.com/projects/har
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.parser.api.har.model;
public class HarTiming {
public double blocked;
public double dns;
public double connect;
public double send;
public double wait;
public double receive;
public double ssl;
public String comment;
@Override
public String toString() {
return "HarTiming [blocked=" + blocked + ", dns=" + dns + ", connect=" + connect + ", send=" + send + ", wait="
+ wait + ", receive=" + receive + ", ssl=" + ssl + ", comment=" + comment + "]";
}
}

View File

@ -1,16 +1,16 @@
package io.metersphere.api.parser.api.postman;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import java.util.List;
@Data
public class PostmanResponse {
private Integer code;
private String name;
private String status;
private List<PostmanKeyValue> header;
private JsonNode body;
private PostmanRequest originalRequest;
//在要解析的postman文件中response中的下面几个参数感觉没有用到所以暂时注释
// private String status;
// private List<PostmanKeyValue> header;
// private JsonNode body;
}

View File

@ -1,15 +1,13 @@
package io.metersphere.api.service.definition;
import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.domain.ApiDefinitionExample;
import io.metersphere.api.domain.ApiDefinitionModule;
import io.metersphere.api.domain.ApiDefinitionModuleExample;
import io.metersphere.api.domain.*;
import io.metersphere.api.dto.definition.ApiDefinitionBatchRequest;
import io.metersphere.api.dto.definition.ApiDefinitionWithBlob;
import io.metersphere.api.dto.definition.ApiMockWithBlob;
import io.metersphere.api.dto.definition.ApiTestCaseWithBlob;
import io.metersphere.api.dto.export.ApiExportResponse;
import io.metersphere.api.mapper.ApiDefinitionMapper;
import io.metersphere.api.mapper.ApiDefinitionModuleMapper;
import io.metersphere.api.mapper.ExtApiDefinitionMapper;
import io.metersphere.api.mapper.*;
import io.metersphere.api.parser.api.MetersphereExportParser;
import io.metersphere.api.parser.api.Swagger3ExportParser;
import io.metersphere.project.domain.Project;
import io.metersphere.project.mapper.ProjectMapper;
@ -20,6 +18,7 @@ import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -37,10 +36,15 @@ public class ApiDefinitionExportService {
@Resource
private ApiDefinitionModuleMapper apiDefinitionModuleMapper;
@Resource
private ExtApiTestCaseMapper extApiTestCaseMapper;
@Resource
private ExtApiDefinitionMockMapper extApiDefinitionMockMapper;
@Resource
private ProjectMapper projectMapper;
@Resource
private ApiDefinitionMapper apiDefinitionMapper;
public ApiExportResponse export(ApiDefinitionBatchRequest request, String type, String userId) {
List<String> ids = getBatchApiIds(request, request.getProjectId(), List.of(ModuleConstants.NODE_PROTOCOL_HTTP), false, userId);
if (CollectionUtils.isEmpty(ids)) {
@ -52,13 +56,11 @@ public class ApiDefinitionExportService {
example.createCriteria().andIdIn(moduleIds);
List<ApiDefinitionModule> definitionModules = apiDefinitionModuleMapper.selectByExample(example);
Map<String, String> moduleMap = definitionModules.stream().collect(Collectors.toMap(ApiDefinitionModule::getId, ApiDefinitionModule::getName));
switch (type) {
case "swagger":
return exportSwagger(request, list, moduleMap);
default:
return new ApiExportResponse();
}
return switch (type.toLowerCase()) {
case "swagger" -> exportSwagger(request, list, moduleMap);
case "metersphere" -> exportMetersphere(request, list, moduleMap);
default -> new ApiExportResponse();
};
}
private List<String> getBatchApiIds(ApiDefinitionBatchRequest request, String projectId, List<String> protocols, boolean deleted, String userId) {
@ -87,4 +89,25 @@ public class ApiDefinitionExportService {
throw new MSException(e);
}
}
@Resource
private ApiTestCaseBlobMapper apiTestCaseBlobMapper;
private ApiExportResponse exportMetersphere(ApiDefinitionBatchRequest request, List<ApiDefinitionWithBlob> list, Map<String, String> moduleMap) {
try {
List<String> apiIds = list.stream().map(ApiDefinitionWithBlob::getId).toList();
List<ApiTestCaseWithBlob> apiTestCaseWithBlobs = new ArrayList<>();
List<ApiMockWithBlob> apiMockWithBlobs = new ArrayList<>();
List<ApiTestCaseBlob> apiTestCaseBlobs = new ArrayList<>();
if (request.isExportApiCase()) {
apiTestCaseWithBlobs = extApiTestCaseMapper.selectAllDetailByApiIds(apiIds);
}
if (request.isExportApiMock()) {
apiMockWithBlobs = extApiDefinitionMockMapper.selectAllDetailByApiIds(apiIds);
}
return new MetersphereExportParser().parse(list, apiTestCaseWithBlobs, apiMockWithBlobs, moduleMap);
} catch (Exception e) {
throw new MSException(e);
}
}
}

View File

@ -653,7 +653,7 @@ public class ApiDefinitionService extends MoveNodeService {
return copyName;
}
private void handleDeleteApiDefinition(List<String> ids, boolean deleteAllVersion, String projectId, String userId, boolean isBatch) {
public void handleDeleteApiDefinition(List<String> ids, boolean deleteAllVersion, String projectId, String userId, boolean isBatch) {
if (deleteAllVersion) {
//全部删除 进入回收站
List<String> refIds = extApiDefinitionMapper.getRefIds(ids, false);
@ -842,7 +842,7 @@ public class ApiDefinitionService extends MoveNodeService {
}
}
private void handleTrashDelApiDefinition(List<String> ids, String userId, String projectId, boolean isBatch) {
public void handleTrashDelApiDefinition(List<String> ids, String userId, String projectId, boolean isBatch) {
if (CollectionUtils.isNotEmpty(ids)) {
SubListUtils.dealForSubList(ids, 2000, subList -> doTrashDel(subList, userId, projectId, isBatch));
}

View File

@ -34,7 +34,7 @@ public class SwaggerUrlImportJob extends BaseScheduleJob {
request.setUserId(jobDataMap.getString("userId"));
request.setType("SCHEDULE");
request.setResourceId(resourceId);
apiDefinitionImportService.apiTestImport(null, request, request.getProjectId());
apiDefinitionImportService.apiDefinitionImport(null, request, request.getProjectId());
}
public static JobKey getJobKey(String resourceId) {

View File

@ -0,0 +1,51 @@
package io.metersphere.api.utils;
import io.metersphere.api.constants.ApiImportPlatform;
import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.project.domain.Project;
import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.log.dto.LogDTO;
public class ApiDefinitionImportUtils {
private static final String FILE_JMX = "jmx";
private static final String FILE_HAR = "har";
private static final String FILE_JSON = "json";
public static void checkFileSuffixName(ImportRequest request, String suffixName) {
if (FILE_JMX.equalsIgnoreCase(suffixName)) {
if (!ApiImportPlatform.Jmeter.name().equalsIgnoreCase(request.getPlatform())) {
throw new MSException(Translator.get("file_format_does_not_meet_requirements"));
}
}
if (FILE_HAR.equalsIgnoreCase(suffixName)) {
if (!ApiImportPlatform.Har.name().equalsIgnoreCase(request.getPlatform())) {
throw new MSException(Translator.get("file_format_does_not_meet_requirements"));
}
}
if (FILE_JSON.equalsIgnoreCase(suffixName)) {
if (ApiImportPlatform.Har.name().equalsIgnoreCase(request.getPlatform()) || ApiImportPlatform.Jmeter.name().equalsIgnoreCase(request.getPlatform())) {
throw new MSException(Translator.get("file_format_does_not_meet_requirements"));
}
}
}
public static LogDTO genImportLog(Project project, String dataId, String dataName, Object importData, String module, String operator, String operationType) {
LogDTO dto = new LogDTO(
project.getId(),
project.getOrganizationId(),
dataId,
operator,
operationType,
module,
dataName);
dto.setHistory(true);
dto.setPath("/api/definition/import");
dto.setMethod(HttpMethodConstants.POST.name());
dto.setOriginalValue(JSON.toJSONBytes(importData));
return dto;
}
}

View File

@ -9,6 +9,7 @@ import io.metersphere.api.domain.*;
import io.metersphere.api.dto.ApiFile;
import io.metersphere.api.dto.ReferenceRequest;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.export.MetersphereApiExportResponse;
import io.metersphere.api.dto.request.ApiEditPosRequest;
import io.metersphere.api.dto.request.ApiTransferRequest;
import io.metersphere.api.dto.request.ImportRequest;
@ -17,20 +18,21 @@ import io.metersphere.api.dto.request.http.MsHeader;
import io.metersphere.api.dto.schema.JsonSchemaItem;
import io.metersphere.api.mapper.*;
import io.metersphere.api.model.CheckLogModel;
import io.metersphere.api.parser.ImportParserFactory;
import io.metersphere.api.service.ApiCommonService;
import io.metersphere.api.service.ApiDefinitionImportTestService;
import io.metersphere.api.service.ApiFileResourceService;
import io.metersphere.api.service.BaseFileManagementTestService;
import io.metersphere.api.service.definition.ApiDefinitionService;
import io.metersphere.api.service.definition.ApiTestCaseService;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.domain.Project;
import io.metersphere.project.dto.filemanagement.FileInfo;
import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.project.service.FileAssociationService;
import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.constants.*;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.FileRequest;
@ -40,17 +42,20 @@ import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.controller.handler.result.MsHttpResultCode;
import io.metersphere.system.domain.OperationHistory;
import io.metersphere.system.domain.OperationHistoryExample;
import io.metersphere.system.dto.AddProjectRequest;
import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.request.OperationHistoryVersionRequest;
import io.metersphere.system.dto.sdk.BaseCondition;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.mapper.OperationHistoryMapper;
import io.metersphere.system.service.CommonProjectService;
import io.metersphere.system.service.UserLoginService;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator;
import io.metersphere.system.utils.Pager;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@ -171,6 +176,9 @@ public class ApiDefinitionControllerTests extends BaseTest {
private ApiScenarioStepMapper apiScenarioStepMapper;
@Resource
private UserLoginService userLoginService;
@Resource
private ApiDefinitionImportTestService apiDefinitionImportTestService;
private static String fileMetadataId;
private static String uploadFileId;
@ -1686,7 +1694,323 @@ public class ApiDefinitionControllerTests extends BaseTest {
paramMap.add("request", JSON.toJSONString(request));
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
this.importTest();
}
@Resource
private CommonProjectService commonProjectService;
@Resource
private ProjectMapper projectMapper;
@Resource
private ApiDefinitionMockMapper apiDefinitionMockMapper;
private void importTest() throws Exception {
//测试ImportParserFactory不按规定获取会返回null
Assertions.assertNull(ImportParserFactory.getImportParser("test"));
// 创建用于导入的项目
//测试计划专用项目
AddProjectRequest initProject = new AddProjectRequest();
initProject.setOrganizationId("100001");
initProject.setName("接口测试导入专用项目");
initProject.setDescription("建国创建的接口测试专用项目");
initProject.setEnable(true);
initProject.setUserIds(List.of("admin"));
Project importProject = commonProjectService.add(initProject, "admin", "/organization-project/add", OperationLogModule.SETTING_ORGANIZATION_PROJECT);
ArrayList<String> moduleList = new ArrayList<>(List.of("workstation", "testPlan", "bugManagement", "caseManagement", "apiTest", "uiTest", "loadTest"));
Project updateProject = new Project();
updateProject.setId(importProject.getId());
updateProject.setModuleSetting(JSON.toJSONString(moduleList));
projectMapper.updateByPrimaryKeySelective(updateProject);
initProject.setName("接口测试导出专用项目");
Project exportProject = commonProjectService.add(initProject, "admin", "/organization-project/add", OperationLogModule.SETTING_ORGANIZATION_PROJECT);
updateProject.setId(exportProject.getId());
projectMapper.updateByPrimaryKeySelective(updateProject);
/*
导入测试 不同类型的文件要按照如下指定的文件类型走同样的导入逻辑判断确保数据的高可用和对代码的高覆盖
以下是对导入文件的要求
file/import/{importType}/single: 1个接口无用例
file/import/{importType}/simple: 4个接口(其中2个url重复合法数据只有3条,并且都在同一个模块下)
2个路径重复的接口下都有2个用例 用于校验路径相同的接口下用例要合并起来导入 (Metersphere的还要有15个Mock其中有名字一样的)
file/import/{importType}/repeatFileDiffApi: 和simple一样路径的4个接口其中2个路径重复合法数据只有3条但是参数不一样;
每个接口下都有2个用例一共4个 simple中的4个用例相比有1个用例名字一样对应的接口路径也一样剩下的用例名称全部一样用于校验相同路径的接口下相同名称的用例处理方式
file/import/{importType}/repeatFileDiffModule: 和repeatFileDiffApi一样的4个接口其中2个路径重复合法数据只有3条参数也一样但是所属模块不一样;
没有用例用于校验覆盖模块时接口的变更
以下是导入操作
导入不存在的接口
· 不指定导入模块 导入 {importType}/simple 同步导入用例校验有新增的模块下面一共有3个API其中有个API包含4个case
· 指定导入模块 导入 {importType}/single校验模块新增了1个并有api
导入存在的接口
· 不覆盖接口 导入 {importType}/repeatFileDiffApi校验模块没有变化api数量没有变化case数量没有变化
· 覆盖接口
· 不覆盖模块
导入 {importType}/repeatFileDiffApi不导入用例校验模块没有变化api更新时间变了case数量没有变化
再导入 {importType}/repeatFileDiffApi导入用例校验模块没有变化api更新时间没变case数量增加应该是4+3+3 -1名称重复的
· 覆盖模块
接口一样模块不一样 导入 {importType}/repeatFileDiffModule (测试指定导入模块测试一下会不会创建多个模块 判断接口对应的模块有更新 api内容没更新
接口不一样模块不一样 重新导入{importType}/simple (测试测试不指定导入模块测试一下会不会回到原模块 判断接口对应的模块有更新 api内容有更新
接口不一样模块一样 重新导入{importType}/repeatFileDiffApi 判断接口对应的模块没更新 api内容有更新
接口一样模块一样 再导入{importType}/repeatFileDiffApi 判断接口对应的模块没更新 api内容没更新
*/
//导入类型以及文件后缀
Map<String, String> importTypeAndSuffix = new LinkedHashMap<>();
importTypeAndSuffix.put("metersphere", "json");
importTypeAndSuffix.put("postman", "json");
importTypeAndSuffix.put("har", "har");
List<String> importCaseType = Arrays.asList("postman", "metersphere");
List<String> importModulesType = Arrays.asList("postman", "metersphere");
ApiDefinitionModuleExample moduleExample = new ApiDefinitionModuleExample();
moduleExample.createCriteria().andProjectIdEqualTo(importProject.getId());
ApiTestCaseExample apiTestCaseExample = new ApiTestCaseExample();
apiTestCaseExample.createCriteria().andProjectIdEqualTo(importProject.getId());
for (Map.Entry<String, String> entry : importTypeAndSuffix.entrySet()) {
ImportRequest request = new ImportRequest();
request.setProjectId(importProject.getId());
request.setProtocol(ApiConstants.HTTP_PROTOCOL);
request.setUserId("admin");
List<ApiDefinitionModule> apiDefinitionModuleList = apiDefinitionModuleMapper.selectByExample(moduleExample);
List<ApiDefinitionBlob> apiBlobList = apiDefinitionImportTestService.selectBlobByProjectId(importProject.getId());
List<ApiTestCase> apiTestCaseList = apiTestCaseMapper.selectByExample(apiTestCaseExample);
Assertions.assertEquals(apiDefinitionModuleList.size(), 0);
Assertions.assertEquals(apiBlobList.size(), 0);
Assertions.assertEquals(apiTestCaseList.size(), 0);
boolean checkTestCase = importCaseType.contains(entry.getKey());
boolean checkModules = importModulesType.contains(entry.getKey());
String importType = entry.getKey();
String fileSuffix = entry.getValue();
request.setPlatform(importType);
request.setSyncCase(true);
FileInputStream inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import/" + importType + "/simple." + fileSuffix)).getPath()));
MockMultipartFile file = new MockMultipartFile("file", "simple." + fileSuffix, MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
paramMap.add("request", JSON.toJSONString(request));
paramMap.add("file", file);
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
request.setSyncCase(false);
apiDefinitionModuleList = apiDefinitionModuleMapper.selectByExample(moduleExample);
apiBlobList = apiDefinitionImportTestService.selectBlobByProjectId(importProject.getId());
Assertions.assertEquals(apiDefinitionModuleList.size(), checkModules ? 1 : 0);
Assertions.assertEquals(apiBlobList.size(), 3);
if (checkTestCase) {
apiTestCaseList = apiTestCaseMapper.selectByExample(apiTestCaseExample);
Assertions.assertEquals(apiTestCaseList.size(), 4);
}
if (StringUtils.equalsIgnoreCase(importType, "metersphere")) {
request.setSyncMock(true);
request.setCoverData(true);
List<String> apiString = apiBlobList.stream().map(ApiDefinitionBlob::getId).toList();
ApiDefinitionMockExample apiDefinitionMockExample = new ApiDefinitionMockExample();
apiDefinitionMockExample.createCriteria().andApiDefinitionIdIn(apiString).andProjectIdEqualTo(importProject.getId());
List<ApiDefinitionMock> mockList = apiDefinitionMockMapper.selectByExample(apiDefinitionMockExample);
Assertions.assertEquals(mockList.size(), 0);
paramMap = new LinkedMultiValueMap<>();
paramMap.add("file", file);
paramMap.add("request", JSON.toJSONString(request));
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
mockList = apiDefinitionMockMapper.selectByExample(apiDefinitionMockExample);
Assertions.assertEquals(mockList.size(), 15);
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
mockList = apiDefinitionMockMapper.selectByExample(apiDefinitionMockExample);
Assertions.assertEquals(mockList.size(), 15);
request.setSyncMock(false);
request.setCoverData(false);
}
if (CollectionUtils.isEmpty(apiDefinitionModuleList)) {
request.setModuleId(ModuleConstants.DEFAULT_NODE_ID);
} else {
request.setModuleId(apiDefinitionModuleList.getFirst().getId());
}
inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import/" + importType + "/single." + fileSuffix)).getPath()));
file = new MockMultipartFile("file", "single." + fileSuffix, MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap = new LinkedMultiValueMap<>();
paramMap.add("request", JSON.toJSONString(request));
paramMap.add("file", file);
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
request.setModuleId(null);
apiDefinitionModuleList = apiDefinitionModuleMapper.selectByExample(moduleExample);
apiBlobList = apiDefinitionImportTestService.selectBlobByProjectId(importProject.getId());
Assertions.assertEquals(apiDefinitionModuleList.size(), checkModules ? 1 : 0);
Assertions.assertEquals(apiBlobList.size(), 4);
if (checkTestCase) {
apiTestCaseList = apiTestCaseMapper.selectByExample(apiTestCaseExample);
Assertions.assertEquals(apiTestCaseList.size(), 4);
}
if (StringUtils.equalsIgnoreCase(importType, "metersphere")) {
//恰逢ms格式的导入可以顺便测试导出
this.testExportAndImport(importProject.getId(), apiBlobList);
}
// · 不覆盖接口 导入 {importType}/repeatFileDiffApi校验模块没有变化api无变化case数量没有变化
inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import/" + importType + "/repeatFileDiffApi." + fileSuffix)).getPath()));
file = new MockMultipartFile("file", "repeatFileDiffApi." + fileSuffix, MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap = new LinkedMultiValueMap<>();
paramMap.add("request", JSON.toJSONString(request));
paramMap.add("file", file);
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
request.setModuleId(null);
apiDefinitionModuleList = apiDefinitionModuleMapper.selectByExample(moduleExample);
Assertions.assertEquals(apiDefinitionModuleList.size(), checkModules ? 1 : 0);
List<ApiDefinitionBlob> newApiBlobList = apiDefinitionImportTestService.selectBlobByProjectId(importProject.getId());
apiDefinitionImportTestService.compareApiBlobList(apiBlobList, newApiBlobList, 0);
apiBlobList = newApiBlobList;
if (checkTestCase) {
List<ApiTestCase> newApiTestCaseList = apiTestCaseMapper.selectByExample(apiTestCaseExample);
apiDefinitionImportTestService.compareApiTestCaseList(apiTestCaseList, newApiTestCaseList, 0, 0);
apiTestCaseList = newApiTestCaseList;
}
//· 覆盖接口
request.setCoverData(true);
// · 不覆盖模块
// 导入 {importType}/repeatFileDiffApi不导入用例校验模块没有变化api有3个变了case数量没有变化
inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import/" + importType + "/repeatFileDiffApi." + fileSuffix)).getPath()));
file = new MockMultipartFile("file", "repeatFileDiffApi." + fileSuffix, MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap = new LinkedMultiValueMap<>();
paramMap.add("request", JSON.toJSONString(request));
paramMap.add("file", file);
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
apiDefinitionModuleList = apiDefinitionModuleMapper.selectByExample(moduleExample);
Assertions.assertEquals(apiDefinitionModuleList.size(), checkModules ? 1 : 0);
newApiBlobList = apiDefinitionImportTestService.selectBlobByProjectId(importProject.getId());
apiDefinitionImportTestService.compareApiBlobList(apiBlobList, newApiBlobList, 3);
apiBlobList = newApiBlobList;
if (checkTestCase) {
List<ApiTestCase> newApiTestCaseList = apiTestCaseMapper.selectByExample(apiTestCaseExample);
apiDefinitionImportTestService.compareApiTestCaseList(apiTestCaseList, newApiTestCaseList, 0, 0);
apiTestCaseList = newApiTestCaseList;
}
// 再导入 {importType}/repeatFileDiffApi导入用例校验模块没有变化api无更新case数量增加应该是4+3+3 -1名称重复的
request.setSyncCase(true);
inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import/" + importType + "/repeatFileDiffApi." + fileSuffix)).getPath()));
file = new MockMultipartFile("file", "repeatFileDiffApi." + fileSuffix, MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap = new LinkedMultiValueMap<>();
paramMap.add("request", JSON.toJSONString(request));
paramMap.add("file", file);
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
request.setSyncCase(false);
apiDefinitionModuleList = apiDefinitionModuleMapper.selectByExample(moduleExample);
Assertions.assertEquals(apiDefinitionModuleList.size(), checkModules ? 1 : 0);
newApiBlobList = apiDefinitionImportTestService.selectBlobByProjectId(importProject.getId());
apiDefinitionImportTestService.compareApiBlobList(apiBlobList, newApiBlobList, 0);
apiBlobList = newApiBlobList;
if (checkTestCase) {
List<ApiTestCase> newApiTestCaseList = apiTestCaseMapper.selectByExample(apiTestCaseExample);
apiDefinitionImportTestService.compareApiTestCaseList(apiTestCaseList, newApiTestCaseList, 1, 3);
apiTestCaseList = newApiTestCaseList;
}
// · 覆盖模块
request.setCoverModule(true);
List<ApiDefinition> newApiDefinition = new ArrayList<>();
List<ApiDefinition> oldApiDefinition = new ArrayList<>();
if (checkModules) {
// 以下只针对需要检查模块的导入文件方式 部分比如har文件由于导入接口没有模块数据故不需要检查模块
// 接口一样模块不一样 导入 {importType}/repeatFileDiffModule (测试测试指定导入模块测试一下会不会创建多个模块 判断接口对应的模块有更新 api内容没更新
inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import/" + importType + "/repeatFileDiffModule." + fileSuffix)).getPath()));
file = new MockMultipartFile("file", "repeatFileDiffModule." + fileSuffix, MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap = new LinkedMultiValueMap<>();
paramMap.add("request", JSON.toJSONString(request));
paramMap.add("file", file);
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
apiDefinitionModuleList = apiDefinitionModuleMapper.selectByExample(moduleExample);
Assertions.assertTrue(apiDefinitionModuleList.size() > 1);
newApiBlobList = apiDefinitionImportTestService.selectBlobByProjectId(importProject.getId());
apiDefinitionImportTestService.compareApiBlobList(apiBlobList, newApiBlobList, 0);
apiBlobList = newApiBlobList;
if (checkTestCase) {
List<ApiTestCase> newApiTestCaseList = apiTestCaseMapper.selectByExample(apiTestCaseExample);
apiDefinitionImportTestService.compareApiTestCaseList(apiTestCaseList, newApiTestCaseList, 0, 0);
}
// 接口不一样模块不一样 重新导入{importType}/simple (测试测试不指定导入模块测试一下会不会回到原模块 判断接口对应的模块有更新 api内容有更新
oldApiDefinition = apiDefinitionImportTestService.selectApiDefinitionByProjectId(importProject.getId());
inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import/" + importType + "/simple." + fileSuffix)).getPath()));
file = new MockMultipartFile("file", "simple." + fileSuffix, MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap = new LinkedMultiValueMap<>();
paramMap.add("request", JSON.toJSONString(request));
paramMap.add("file", file);
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
apiDefinitionModuleList = apiDefinitionModuleMapper.selectByExample(moduleExample);
Assertions.assertTrue(apiDefinitionModuleList.size() > 1);
newApiDefinition = apiDefinitionImportTestService.selectApiDefinitionByProjectId(importProject.getId());
apiDefinitionImportTestService.checkApiModuleChange(oldApiDefinition, newApiDefinition, 3);
newApiBlobList = apiDefinitionImportTestService.selectBlobByProjectId(importProject.getId());
apiDefinitionImportTestService.compareApiBlobList(apiBlobList, newApiBlobList, 3);
apiBlobList = newApiBlobList;
oldApiDefinition = newApiDefinition;
// 接口不一样模块一样 重新导入{importType}/repeatFileDiffApi 判断接口对应的模块没更新 api内容有更新
inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import/" + importType + "/repeatFileDiffApi." + fileSuffix)).getPath()));
file = new MockMultipartFile("file", "repeatFileDiffApi." + fileSuffix, MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap = new LinkedMultiValueMap<>();
paramMap.add("request", JSON.toJSONString(request));
paramMap.add("file", file);
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
newApiDefinition = apiDefinitionImportTestService.selectApiDefinitionByProjectId(importProject.getId());
apiDefinitionImportTestService.checkApiModuleChange(oldApiDefinition, newApiDefinition, 0);
newApiBlobList = apiDefinitionImportTestService.selectBlobByProjectId(importProject.getId());
apiDefinitionImportTestService.compareApiBlobList(apiBlobList, newApiBlobList, 3);
apiBlobList = newApiBlobList;
}
// 接口一样模块一样 再导入{importType}/repeatFileDiffApi 判断接口对应的模块没更新 api内容没更新
oldApiDefinition = newApiDefinition;
inputStream = new FileInputStream(new File(Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/import/" + importType + "/repeatFileDiffApi." + fileSuffix)).getPath()));
file = new MockMultipartFile("file", "repeatFileDiffApi." + fileSuffix, MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap = new LinkedMultiValueMap<>();
paramMap.add("request", JSON.toJSONString(request));
paramMap.add("file", file);
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
newApiDefinition = apiDefinitionImportTestService.selectApiDefinitionByProjectId(importProject.getId());
apiDefinitionImportTestService.checkApiModuleChange(oldApiDefinition, newApiDefinition, 0);
newApiBlobList = apiDefinitionImportTestService.selectBlobByProjectId(importProject.getId());
apiDefinitionImportTestService.compareApiBlobList(apiBlobList, newApiBlobList, 0);
//删除本次导入的数据
List<String> apiIds = newApiDefinition.stream().map(ApiDefinition::getId).collect(Collectors.toList());
apiDefinitionService.handleDeleteApiDefinition(apiIds, true, request.getProjectId(), "admin", true);
apiDefinitionService.handleTrashDelApiDefinition(apiIds, "admin", importProject.getId(), true);
newApiDefinition = apiDefinitionImportTestService.selectApiDefinitionByProjectId(importProject.getId());
newApiBlobList = apiDefinitionImportTestService.selectBlobByProjectId(importProject.getId());
List<ApiTestCase> newApiTestCaseList = apiTestCaseMapper.selectByExample(apiTestCaseExample);
ApiDefinitionModuleExample deleteModuleExample = new ApiDefinitionModuleExample();
deleteModuleExample.createCriteria().andProjectIdEqualTo(importProject.getId());
apiDefinitionModuleMapper.deleteByExample(deleteModuleExample);
apiDefinitionModuleList = apiDefinitionModuleMapper.selectByExample(moduleExample);
Assertions.assertEquals(apiDefinitionModuleList.size(), 0);
Assertions.assertEquals(newApiDefinition.size(), 0);
Assertions.assertEquals(newApiBlobList.size(), 0);
Assertions.assertEquals(newApiTestCaseList.size(), 0);
}
}
private void testExportAndImport(String exportProjectId, List<ApiDefinitionBlob> exportApiBlobs) throws Exception {
ApiDefinitionBatchRequest exportRequest = new ApiDefinitionBatchRequest();
exportRequest.setProjectId(exportProjectId);
exportRequest.setSelectAll(true);
exportRequest.setExportApiCase(true);
exportRequest.setExportApiMock(true);
MvcResult mvcResult = this.requestPostWithOkAndReturn(EXPORT + "metersphere", exportRequest);
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
MetersphereApiExportResponse exportResponse = ApiDataUtils.parseObject(JSON.toJSONString(resultHolder.getData()), MetersphereApiExportResponse.class);
apiDefinitionImportTestService.compareApiExport(exportResponse, exportApiBlobs);
}
protected MvcResult requestMultipart(String url, MultiValueMap<String, Object> paramMap, ResultMatcher resultMatcher) throws Exception {

View File

@ -0,0 +1,116 @@
package io.metersphere.api.service;
import io.metersphere.api.domain.*;
import io.metersphere.api.dto.converter.ApiDefinitionExportDetail;
import io.metersphere.api.dto.export.MetersphereApiExportResponse;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.mapper.ApiDefinitionBlobMapper;
import io.metersphere.api.mapper.ApiDefinitionMapper;
import io.metersphere.api.service.definition.ApiDefinitionImportService;
import io.metersphere.api.utils.ApiDataUtils;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Assertions;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
public class ApiDefinitionImportTestService extends ApiDefinitionImportService {
@Resource
private ApiDefinitionMapper apiDefinitionMapper;
@Resource
private ApiDefinitionBlobMapper apiDefinitionBlobMapper;
public void compareApiBlobList(List<ApiDefinitionBlob> apiDefinitionList, List<ApiDefinitionBlob> newApiDefinitionList, int apiChangedCount) {
if (apiChangedCount == 0) {
Assertions.assertEquals(apiDefinitionList.size(), newApiDefinitionList.size());
}
Map<String, ApiDefinitionBlob> oldApiBlobMap = apiDefinitionList.stream().collect(Collectors.toMap(ApiDefinitionBlob::getId, Function.identity()));
Map<String, ApiDefinitionBlob> newApiBlobMap = newApiDefinitionList.stream().collect(Collectors.toMap(ApiDefinitionBlob::getId, Function.identity()));
int diffApiCount = 0;
for (Map.Entry<String, ApiDefinitionBlob> entry : oldApiBlobMap.entrySet()) {
ApiDefinitionBlob oldBlob = entry.getValue();
ApiDefinitionBlob newBlob = newApiBlobMap.get(entry.getKey());
boolean dataIsSame = dataIsSame(ApiDataUtils.parseObject(new String(oldBlob.getRequest()), MsHTTPElement.class), ApiDataUtils.parseObject(new String(newBlob.getRequest()), MsHTTPElement.class));
if (!dataIsSame) {
diffApiCount++;
}
}
Assertions.assertEquals(apiChangedCount, diffApiCount);
}
public void compareApiTestCaseList(List<ApiTestCase> apiTestCaseList, List<ApiTestCase> newApiTestCaseList, int apiTestCaseChangeCount, int apiTestCaseAddCount) {
Assertions.assertEquals(apiTestCaseList.size() + apiTestCaseAddCount, newApiTestCaseList.size());
Map<String, ApiTestCase> oldDataMap = apiTestCaseList.stream().collect(Collectors.toMap(ApiTestCase::getId, Function.identity()));
Map<String, ApiTestCase> newDataMap = newApiTestCaseList.stream().collect(Collectors.toMap(ApiTestCase::getId, Function.identity()));
int diffCaseCount = 0;
for (Map.Entry<String, ApiTestCase> entry : oldDataMap.entrySet()) {
ApiTestCase oldCase = entry.getValue();
ApiTestCase newCase = newDataMap.get(entry.getKey());
if (!Objects.equals(oldCase.getUpdateTime(), newCase.getUpdateTime())) {
diffCaseCount++;
}
}
Assertions.assertEquals(apiTestCaseChangeCount, diffCaseCount);
}
public List<ApiDefinitionBlob> selectBlobByProjectId(String projectId) {
ApiDefinitionExample apiDefinitionExample = new ApiDefinitionExample();
apiDefinitionExample.createCriteria().andProjectIdEqualTo(projectId);
List<String> apiIdList = apiDefinitionMapper.selectByExample(apiDefinitionExample).stream().map(ApiDefinition::getId).toList();
if (CollectionUtils.isEmpty(apiIdList)) {
return new ArrayList<>();
} else {
ApiDefinitionBlobExample example = new ApiDefinitionBlobExample();
example.createCriteria().andIdIn(apiIdList);
return apiDefinitionBlobMapper.selectByExampleWithBLOBs(example);
}
}
public List<ApiDefinition> selectApiDefinitionByProjectId(String projectId) {
ApiDefinitionExample apiDefinitionExample = new ApiDefinitionExample();
apiDefinitionExample.createCriteria().andProjectIdEqualTo(projectId);
return apiDefinitionMapper.selectByExample(apiDefinitionExample);
}
public void checkApiModuleChange(List<ApiDefinition> oldApiDefinition, List<ApiDefinition> newApiDefinition, int moduleChangeCount) {
Map<String, ApiDefinition> oldDataMap = oldApiDefinition.stream().collect(Collectors.toMap(ApiDefinition::getId, Function.identity()));
Map<String, ApiDefinition> newDataMap = newApiDefinition.stream().collect(Collectors.toMap(ApiDefinition::getId, Function.identity()));
int diffApiCount = 0;
for (Map.Entry<String, ApiDefinition> entry : oldDataMap.entrySet()) {
ApiDefinition oldData = entry.getValue();
ApiDefinition newData = newDataMap.get(entry.getKey());
if (!StringUtils.equals(oldData.getModuleId(), newData.getModuleId())) {
diffApiCount++;
}
}
Assertions.assertEquals(moduleChangeCount, diffApiCount);
}
public void compareApiExport(MetersphereApiExportResponse exportResponse, List<ApiDefinitionBlob> exportApiBlobs) {
Assertions.assertEquals(exportResponse.getApiDefinitions().size(), exportApiBlobs.size());
List<ApiDefinitionExportDetail> compareList = new ArrayList<>();
for (ApiDefinitionBlob blob : exportApiBlobs) {
for (ApiDefinitionExportDetail exportDetail : exportResponse.getApiDefinitions()) {
boolean dataIsSame = dataIsSame(ApiDataUtils.parseObject(new String(blob.getRequest()), MsHTTPElement.class), (MsHTTPElement) exportDetail.getRequest());
if (dataIsSame) {
compareList.add(exportDetail);
break;
}
}
}
Assertions.assertEquals(exportResponse.getApiDefinitions().size(), compareList.size());
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,346 @@
{
"log":
{
"version": "1.2",
"creator":
{
"name": "WebInspector",
"version": "537.36"
},
"pages":
[],
"entries":
[
{
"_initiator":
{
"type": "script",
"stack":
{
"callFrames":
[
{
"functionName": "",
"scriptId": "9",
"url": "http://172.16.200.18:8081/assets/index-33SgBhcr.js",
"lineNumber": 1,
"columnNumber": 597486
},
{
"functionName": "xhr",
"scriptId": "9",
"url": "http://172.16.200.18:8081/assets/index-33SgBhcr.js",
"lineNumber": 1,
"columnNumber": 595339
},
{
"functionName": "dispatchRequest",
"scriptId": "9",
"url": "http://172.16.200.18:8081/assets/index-33SgBhcr.js",
"lineNumber": 1,
"columnNumber": 603441
}
],
"parent":
{
"description": "Promise.then",
"callFrames":
[
{
"functionName": "_request",
"scriptId": "9",
"url": "http://172.16.200.18:8081/assets/index-33SgBhcr.js",
"lineNumber": 1,
"columnNumber": 606591
},
{
"functionName": "request",
"scriptId": "9",
"url": "http://172.16.200.18:8081/assets/index-33SgBhcr.js",
"lineNumber": 1,
"columnNumber": 605135
},
{
"functionName": "",
"scriptId": "9",
"url": "http://172.16.200.18:8081/assets/index-33SgBhcr.js",
"lineNumber": 1,
"columnNumber": 570981
},
{
"functionName": "",
"scriptId": "9",
"url": "http://172.16.200.18:8081/assets/index-33SgBhcr.js",
"lineNumber": 19,
"columnNumber": 40121
},
{
"functionName": "uploadFile",
"scriptId": "9",
"url": "http://172.16.200.18:8081/assets/index-33SgBhcr.js",
"lineNumber": 19,
"columnNumber": 40082
},
{
"functionName": "x",
"scriptId": "160",
"url": "http://172.16.200.18:8081/assets/fileManagement-BwbfbuXX.js",
"lineNumber": 0,
"columnNumber": 1108
},
{
"functionName": "uploadFileFromQueue",
"scriptId": "163",
"url": "http://172.16.200.18:8081/assets/fileList-BCu6BLjh.js",
"lineNumber": 0,
"columnNumber": 3648
},
{
"functionName": "",
"scriptId": "11",
"url": "http://172.16.200.18:8081/assets/vue-CKjJUztO.js",
"lineNumber": 48,
"columnNumber": 2244
},
{
"functionName": "startUpload",
"scriptId": "163",
"url": "http://172.16.200.18:8081/assets/fileList-BCu6BLjh.js",
"lineNumber": 0,
"columnNumber": 4116
},
{
"functionName": "",
"scriptId": "11",
"url": "http://172.16.200.18:8081/assets/vue-CKjJUztO.js",
"lineNumber": 48,
"columnNumber": 2244
},
{
"functionName": "Q",
"scriptId": "163",
"url": "http://172.16.200.18:8081/assets/fileList-BCu6BLjh.js",
"lineNumber": 0,
"columnNumber": 6637
},
{
"functionName": "cl",
"scriptId": "158",
"url": "http://172.16.200.18:8081/assets/index-DVkmz8YF.js",
"lineNumber": 0,
"columnNumber": 34186
},
{
"functionName": "jt",
"scriptId": "11",
"url": "http://172.16.200.18:8081/assets/vue-CKjJUztO.js",
"lineNumber": 12,
"columnNumber": 2794
},
{
"functionName": "St",
"scriptId": "11",
"url": "http://172.16.200.18:8081/assets/vue-CKjJUztO.js",
"lineNumber": 12,
"columnNumber": 2865
},
{
"functionName": "xm",
"scriptId": "11",
"url": "http://172.16.200.18:8081/assets/vue-CKjJUztO.js",
"lineNumber": 12,
"columnNumber": 5396
},
{
"functionName": "handleClick",
"scriptId": "12",
"url": "http://172.16.200.18:8081/assets/arco-g73YkF1H.js",
"lineNumber": 0,
"columnNumber": 51176
},
{
"functionName": "e.href.B.onClick.t.<computed>.t.<computed>",
"scriptId": "12",
"url": "http://172.16.200.18:8081/assets/arco-g73YkF1H.js",
"lineNumber": 0,
"columnNumber": 51921
},
{
"functionName": "jt",
"scriptId": "11",
"url": "http://172.16.200.18:8081/assets/vue-CKjJUztO.js",
"lineNumber": 12,
"columnNumber": 2794
},
{
"functionName": "St",
"scriptId": "11",
"url": "http://172.16.200.18:8081/assets/vue-CKjJUztO.js",
"lineNumber": 12,
"columnNumber": 2865
},
{
"functionName": "n",
"scriptId": "11",
"url": "http://172.16.200.18:8081/assets/vue-CKjJUztO.js",
"lineNumber": 16,
"columnNumber": 8410
}
]
}
}
},
"_priority": "High",
"_resourceType": "xhr",
"cache":
{},
"connection": "1124737",
"request":
{
"method": "POST",
"url": "http://172.16.200.18:8081/project/file/upload",
"httpVersion": "HTTP/1.1",
"headers":
[
{
"name": "Accept",
"value": "application/json, text/plain, */*"
},
{
"name": "Accept-Encoding",
"value": "gzip, deflate"
},
{
"name": "Accept-Language",
"value": "zh-CN"
},
{
"name": "CSRF-TOKEN",
"value": "WOtvvhbURKiZae0Sdo8uOD28+DK1/M+gXEdZkuu8iUBub/ak4c06ys1okPHqSfEKUiDI4HjdnqBddZeYuF2g0p+oRDeGoUHUtrM1wiGylvQ="
},
{
"name": "Connection",
"value": "keep-alive"
},
{
"name": "Content-Length",
"value": "54729"
},
{
"name": "Content-Type",
"value": "multipart/form-data; boundary=----WebKitFormBoundaryNkMIhRd5puIb9Jo6"
},
{
"name": "Host",
"value": "172.16.200.18:8081"
},
{
"name": "ORGANIZATION",
"value": "717345437786112"
},
{
"name": "Origin",
"value": "http://172.16.200.18:8081"
},
{
"name": "PROJECT",
"value": "997050905108480"
},
{
"name": "Referer",
"value": "http://172.16.200.18:8081/"
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36"
},
{
"name": "X-AUTH-TOKEN",
"value": "61afde41-15d2-47c1-b1d1-751a4bed99f9"
}
],
"queryString":
[],
"cookies":
[],
"headersSize": 726,
"bodySize": 406,
"postData":
{
"mimeType": "multipart/form-data; boundary=----WebKitFormBoundaryNkMIhRd5puIb9Jo6",
"text": "------WebKitFormBoundaryNkMIhRd5puIb9Jo6\r\nContent-Disposition: form-data; name=\"file\"; filename=\"IMG_5695.PNG\"\r\nContent-Type: image/png\r\n\r\n\r\n------WebKitFormBoundaryNkMIhRd5puIb9Jo6\r\nContent-Disposition: form-data; name=\"request\"; filename=\"blob\"\r\nContent-Type: application/json;charset=utf-8\r\n\r\n{\"projectId\":\"997050905108480\",\"moduleId\":\"root\",\"enable\":false}\r\n------WebKitFormBoundaryNkMIhRd5puIb9Jo6--\r\n",
"params":
[
{
"name": "file",
"value": "(binary)"
},
{
"name": "request",
"value": "(binary)"
}
]
}
},
"response":
{
"status": 200,
"statusText": "OK",
"httpVersion": "HTTP/1.1",
"headers":
[
{
"name": "Content-Length",
"value": "77"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Date",
"value": "Thu, 08 Aug 2024 03:33:47 GMT"
},
{
"name": "Vary",
"value": "Accept-Encoding"
}
],
"cookies":
[],
"content":
{
"size": 77,
"mimeType": "application/json",
"compression": 0,
"text": "{\"code\":100200,\"message\":null,\"messageDetail\":null,\"data\":\"2199504316596224\"}"
},
"redirectURL": "",
"headersSize": 131,
"bodySize": 77,
"_transferSize": 208,
"_error": null,
"_fetchedViaServiceWorker": false
},
"serverIPAddress": "172.16.200.18",
"startedDateTime": "2024-08-08T03:33:47.873Z",
"time": 691.4930000063777,
"timings":
{
"blocked": 1.7159999903719871,
"dns": 0.17,
"ssl": -1,
"connect": 0.701,
"send": 1.067,
"wait": 687.6599999917876,
"receive": 0.17900002421811223,
"_blocked_queueing": 1.5619999903719872,
"_workerStart": -1,
"_workerReady": -1,
"_workerFetchStart": -1,
"_workerRespondWithSettled": -1
}
}
]
}
}

View File

@ -0,0 +1,617 @@
{
"apiDefinitions": [
{
"id": null,
"name": "put的mock请求",
"protocol": "HTTP",
"method": "PUT",
"path": "/mock-server/100080/100005/mock-for-put",
"status": "PROCESSING",
"num": null,
"tags": [],
"pos": 8192,
"projectId": null,
"moduleId": null,
"latest": null,
"versionId": null,
"refId": null,
"description": null,
"createTime": null,
"createUser": null,
"updateTime": null,
"updateUser": null,
"deleteUser": null,
"deleteTime": null,
"deleted": null,
"request": {
"polymorphicName": "MsHTTPElement",
"stepId": null,
"resourceId": null,
"projectId": null,
"name": "put的mock请求",
"enable": true,
"children": [
{
"polymorphicName": "MsCommonElement",
"stepId": null,
"resourceId": null,
"projectId": null,
"name": null,
"enable": true,
"children": null,
"parent": null,
"csvIds": null,
"preProcessorConfig": {
"enableGlobal": true,
"processors": []
},
"postProcessorConfig": {
"enableGlobal": true,
"processors": []
},
"assertionConfig": {
"enableGlobal": true,
"assertions": []
}
}
],
"parent": null,
"csvIds": null,
"customizeRequest": false,
"customizeRequestEnvEnable": false,
"path": "/mock-server/100080/100005/mock-for-put",
"method": "PUT",
"body": {
"bodyType": "FORM_DATA",
"noneBody": {},
"formDataBody": {
"formValues": [
{
"key": "methodttt",
"value": "postttttttt",
"enable": true,
"description": "",
"paramType": "string",
"required": false,
"minLength": null,
"maxLength": null,
"files": null,
"contentType": null,
"file": false,
"valid": true,
"notBlankValue": true
}
]
},
"wwwFormBody": {
"formValues": []
},
"jsonBody": {
"enableJsonSchema": false,
"jsonValue": null,
"jsonSchema": null
},
"xmlBody": {
"value": null
},
"rawBody": {
"value": null
},
"binaryBody": {
"description": null,
"file": null
},
"bodyClassByType": "io.metersphere.api.dto.request.http.body.FormDataBody",
"bodyDataByType": {
"formValues": [
{
"key": "method",
"value": "putttttttt",
"enable": true,
"description": "",
"paramType": "string",
"required": false,
"minLength": null,
"maxLength": null,
"files": null,
"contentType": null,
"file": false,
"valid": true,
"notBlankValue": true
}
]
}
},
"headers": [],
"rest": [],
"query": [],
"otherConfig": {
"connectTimeout": 60000,
"responseTimeout": 60000,
"certificateAlias": null,
"followRedirects": false,
"autoRedirects": true
},
"authConfig": {
"authType": "NONE",
"basicAuth": {
"userName": null,
"password": null,
"valid": false
},
"digestAuth": {
"userName": null,
"password": null,
"valid": false
},
"httpauthValid": false
},
"moduleId": null,
"num": null,
"mockNum": null
},
"response": [],
"modulePath": "正常/请求的/集合",
"apiTestCaseList": [],
"apiMockList": []
},
{
"id": null,
"name": "put的mock请求-另一个",
"protocol": "HTTP",
"method": "PUT",
"path": "/mock-server/100080/100005/mock-for-put",
"status": "PROCESSING",
"num": null,
"tags": [],
"pos": 8192,
"projectId": null,
"moduleId": null,
"latest": null,
"versionId": null,
"refId": null,
"description": null,
"createTime": null,
"createUser": null,
"updateTime": null,
"updateUser": null,
"deleteUser": null,
"deleteTime": null,
"deleted": null,
"request": {
"polymorphicName": "MsHTTPElement",
"stepId": null,
"resourceId": null,
"projectId": null,
"name": "put的mock请求",
"enable": true,
"children": [
{
"polymorphicName": "MsCommonElement",
"stepId": null,
"resourceId": null,
"projectId": null,
"name": null,
"enable": true,
"children": null,
"parent": null,
"csvIds": null,
"preProcessorConfig": {
"enableGlobal": true,
"processors": []
},
"postProcessorConfig": {
"enableGlobal": true,
"processors": []
},
"assertionConfig": {
"enableGlobal": true,
"assertions": []
}
}
],
"parent": null,
"csvIds": null,
"customizeRequest": false,
"customizeRequestEnvEnable": false,
"path": "/mock-server/100080/100005/mock-for-put",
"method": "PUT",
"body": {
"bodyType": "FORM_DATA",
"noneBody": {},
"formDataBody": {
"formValues": [
{
"key": "methodttt",
"value": "putttttttt",
"enable": true,
"description": "",
"paramType": "string",
"required": false,
"minLength": null,
"maxLength": null,
"files": null,
"contentType": null,
"file": false,
"valid": true,
"notBlankValue": true
}
]
},
"wwwFormBody": {
"formValues": []
},
"jsonBody": {
"enableJsonSchema": false,
"jsonValue": null,
"jsonSchema": null
},
"xmlBody": {
"value": null
},
"rawBody": {
"value": null
},
"binaryBody": {
"description": null,
"file": null
},
"bodyClassByType": "io.metersphere.api.dto.request.http.body.FormDataBody",
"bodyDataByType": {
"formValues": [
{
"key": "method",
"value": "putttttt",
"enable": true,
"description": "",
"paramType": "string",
"required": false,
"minLength": null,
"maxLength": null,
"files": null,
"contentType": null,
"file": false,
"valid": true,
"notBlankValue": true
}
]
}
},
"headers": [],
"rest": [],
"query": [],
"otherConfig": {
"connectTimeout": 60000,
"responseTimeout": 60000,
"certificateAlias": null,
"followRedirects": false,
"autoRedirects": true
},
"authConfig": {
"authType": "NONE",
"basicAuth": {
"userName": null,
"password": null,
"valid": false
},
"digestAuth": {
"userName": null,
"password": null,
"valid": false
},
"httpauthValid": false
},
"moduleId": null,
"num": null,
"mockNum": null
},
"response": [],
"modulePath": "正常/请求的/集合",
"apiTestCaseList": [],
"apiMockList": []
},
{
"id": null,
"name": "post的mock请求",
"protocol": "HTTP",
"method": "POST",
"path": "/mock-server/100080/100004/mock-for-post",
"status": "PROCESSING",
"num": null,
"tags": [],
"pos": 12288,
"projectId": null,
"moduleId": null,
"latest": null,
"versionId": null,
"refId": null,
"description": null,
"createTime": null,
"createUser": null,
"updateTime": null,
"updateUser": null,
"deleteUser": null,
"deleteTime": null,
"deleted": null,
"request": {
"polymorphicName": "MsHTTPElement",
"stepId": null,
"resourceId": null,
"projectId": null,
"name": "post的mock请求",
"enable": true,
"children": [
{
"polymorphicName": "MsCommonElement",
"stepId": null,
"resourceId": null,
"projectId": null,
"name": null,
"enable": true,
"children": null,
"parent": null,
"csvIds": null,
"preProcessorConfig": {
"enableGlobal": true,
"processors": []
},
"postProcessorConfig": {
"enableGlobal": true,
"processors": []
},
"assertionConfig": {
"enableGlobal": true,
"assertions": []
}
}
],
"parent": null,
"csvIds": null,
"customizeRequest": false,
"customizeRequestEnvEnable": false,
"path": "/mock-server/100080/100004/mock-for-post",
"method": "POST",
"body": {
"bodyType": "FORM_DATA",
"noneBody": {},
"formDataBody": {
"formValues": [
{
"key": "methodttt",
"value": "postttttttt",
"enable": true,
"description": "",
"paramType": "string",
"required": false,
"minLength": null,
"maxLength": null,
"files": null,
"contentType": null,
"file": false,
"valid": true,
"notBlankValue": true
}
]
},
"wwwFormBody": {
"formValues": []
},
"jsonBody": {
"enableJsonSchema": false,
"jsonValue": null,
"jsonSchema": null
},
"xmlBody": {
"value": null
},
"rawBody": {
"value": null
},
"binaryBody": {
"description": null,
"file": null
},
"bodyClassByType": "io.metersphere.api.dto.request.http.body.FormDataBody",
"bodyDataByType": {
"formValues": [
{
"key": "method",
"value": "post",
"enable": true,
"description": "",
"paramType": "string",
"required": false,
"minLength": null,
"maxLength": null,
"files": null,
"contentType": null,
"file": false,
"valid": true,
"notBlankValue": true
}
]
}
},
"headers": [],
"rest": [],
"query": [],
"otherConfig": {
"connectTimeout": 60000,
"responseTimeout": 60000,
"certificateAlias": null,
"followRedirects": false,
"autoRedirects": true
},
"authConfig": {
"authType": "NONE",
"basicAuth": {
"userName": null,
"password": null,
"valid": false
},
"digestAuth": {
"userName": null,
"password": null,
"valid": false
},
"httpauthValid": false
},
"moduleId": null,
"num": null,
"mockNum": null
},
"response": [],
"modulePath": "不正常请求的集合",
"apiTestCaseList": [],
"apiMockList": []
},
{
"id": null,
"name": "https://api.tapd.cn/stories?workspace_id=1&id=2",
"protocol": "HTTP",
"method": "GET",
"path": "/stories",
"status": "PROCESSING",
"num": null,
"tags": [],
"pos": 16384,
"projectId": null,
"moduleId": null,
"latest": null,
"versionId": null,
"refId": null,
"description": null,
"createTime": null,
"createUser": null,
"updateTime": null,
"updateUser": null,
"deleteUser": null,
"deleteTime": null,
"deleted": null,
"request": {
"polymorphicName": "MsHTTPElement",
"stepId": null,
"resourceId": null,
"projectId": null,
"name": "https://api.tapd.cn/stories?workspace_id=1&id=2",
"enable": true,
"children": [
{
"polymorphicName": "MsCommonElement",
"stepId": null,
"resourceId": null,
"projectId": null,
"name": null,
"enable": true,
"children": null,
"parent": null,
"csvIds": null,
"preProcessorConfig": {
"enableGlobal": true,
"processors": []
},
"postProcessorConfig": {
"enableGlobal": true,
"processors": []
},
"assertionConfig": {
"enableGlobal": true,
"assertions": []
}
}
],
"parent": null,
"csvIds": null,
"customizeRequest": false,
"customizeRequestEnvEnable": false,
"path": "/stories",
"method": "GET",
"body": {
"bodyType": "NONE",
"noneBody": {},
"formDataBody": {
"formValues": []
},
"wwwFormBody": {
"formValues": []
},
"jsonBody": {
"enableJsonSchema": false,
"jsonValue": null,
"jsonSchema": null
},
"xmlBody": {
"value": null
},
"rawBody": {
"value": null
},
"binaryBody": {
"description": null,
"file": null
},
"bodyClassByType": "io.metersphere.api.dto.request.http.body.NoneBody",
"bodyDataByType": {}
},
"headers": [],
"rest": [],
"query": [
{
"key": "w",
"value": "1",
"enable": true,
"description": "",
"paramType": "string",
"required": false,
"minLength": null,
"maxLength": null,
"encode": false,
"valid": true,
"notBlankValue": true
},
{
"key": "id",
"value": "2",
"enable": true,
"description": "",
"paramType": "string",
"required": false,
"minLength": null,
"maxLength": null,
"encode": false,
"valid": true,
"notBlankValue": true
}
],
"otherConfig": {
"connectTimeout": 60000,
"responseTimeout": 60000,
"certificateAlias": null,
"followRedirects": false,
"autoRedirects": true
},
"authConfig": {
"authType": "BASIC",
"basicAuth": {
"userName": "HIGKLMN",
"password": "ABCDEFG",
"valid": true
},
"digestAuth": {
"userName": null,
"password": null,
"valid": false
},
"httpauthValid": true
},
"moduleId": null,
"num": null,
"mockNum": null
},
"response": [],
"modulePath": "不正常请求/集合",
"apiTestCaseList": [],
"apiMockList": []
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,156 @@
{
"apiDefinitions":
[
{
"id": null,
"name": "github",
"protocol": "HTTP",
"method": "GET",
"path": "",
"status": "PROCESSING",
"num": null,
"tags":
[],
"pos": 24576,
"projectId": null,
"moduleId": null,
"latest": null,
"versionId": null,
"refId": null,
"description": null,
"createTime": null,
"createUser": null,
"updateTime": null,
"updateUser": null,
"deleteUser": null,
"deleteTime": null,
"deleted": null,
"request":
{
"polymorphicName": "MsHTTPElement",
"stepId": null,
"resourceId": null,
"projectId": null,
"name": "github",
"enable": true,
"children":
[
{
"polymorphicName": "MsCommonElement",
"stepId": null,
"resourceId": null,
"projectId": null,
"name": null,
"enable": true,
"children": null,
"parent": null,
"csvIds": null,
"preProcessorConfig":
{
"enableGlobal": true,
"processors":
[]
},
"postProcessorConfig":
{
"enableGlobal": true,
"processors":
[]
},
"assertionConfig":
{
"enableGlobal": true,
"assertions":
[]
}
}
],
"parent": null,
"csvIds": null,
"customizeRequest": false,
"customizeRequestEnvEnable": false,
"path": "",
"method": "GET",
"body":
{
"bodyType": "NONE",
"noneBody":
{},
"formDataBody":
{
"formValues":
[]
},
"wwwFormBody":
{
"formValues":
[]
},
"jsonBody":
{
"enableJsonSchema": false,
"jsonValue": null,
"jsonSchema": null
},
"xmlBody":
{
"value": null
},
"rawBody":
{
"value": null
},
"binaryBody":
{
"description": null,
"file": null
},
"bodyClassByType": "io.metersphere.api.dto.request.http.body.NoneBody",
"bodyDataByType":
{}
},
"headers":
[],
"rest":
[],
"query":
[],
"otherConfig":
{
"connectTimeout": 60000,
"responseTimeout": 60000,
"certificateAlias": null,
"followRedirects": false,
"autoRedirects": true
},
"authConfig":
{
"authType": "NONE",
"basicAuth":
{
"userName": null,
"password": null,
"valid": false
},
"digestAuth":
{
"userName": null,
"password": null,
"valid": false
},
"httpauthValid": false
},
"moduleId": null,
"num": null,
"mockNum": null
},
"response":
[],
"modulePath": "正常请求的集合",
"apiTestCaseList":
[],
"apiMockList":
[]
}
]
}

View File

@ -0,0 +1,300 @@
{
"info": {
"_postman_id": "d416135d-5659-4f36-882b-49bad266b8de",
"name": "正常请求的集合",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "15342444"
},
"item": [
{
"name": "https://api.tapd.cn/stories?workspace_id=AAA&idddd=BBB Copy",
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "zxdczxczxczcx",
"type": "string"
},
{
"key": "username",
"value": "basddasda",
"type": "string"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": "https://api.tapd.cn/stories?workspace_id=AAAAA&idddd=BBBBB",
"protocol": "https",
"host": [
"api",
"tapd",
"cn"
],
"path": [
"stories"
],
"query": [
{
"key": "workspace_id",
"value": "AAAAA"
},
{
"key": "idddd",
"value": "BBBBB"
}
]
}
},
"response": []
},
{
"name": "post的mock请求 Copy",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "MMM",
"value": "PPP",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100004/mock-for-post",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100004",
"mock-for-post"
]
}
},
"response": []
},
{
"name": "put的mock请求",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "methoddd",
"value": "pppp",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"response": [
{
"name": "用于验证名字一样的用例1",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put-ex1",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"_postman_previewlanguage": null,
"header": null,
"cookie": [],
"body": null
},
{
"name": "用于验证名字一样的用例1",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put-ex1",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"_postman_previewlanguage": null,
"header": null,
"cookie": [],
"body": null
}
]
},
{
"name": "put的mock请求 Copy 2",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "nnnn",
"value": "uuuuu",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"response": [
{
"name": "put的mock-copy请求ex",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "putttttttttttt",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"_postman_previewlanguage": null,
"header": null,
"cookie": [],
"body": null
},
{
"name": "用于验证名字一样的用例1",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"_postman_previewlanguage": null,
"header": null,
"cookie": [],
"body": null
}
]
}
]
}

View File

@ -0,0 +1,131 @@
{
"info": {
"_postman_id": "eb491590-d146-4dac-9c80-e4c79908fa65",
"name": "repeatFileDiffModule",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "15342444"
},
"item": [
{
"name": "firstlevel",
"item": [
{
"name": "secondlevel",
"item": [
{
"name": "https://api.tapd.cn/stories?workspace_id=AAAAA&idddd=BBBBB Copy 2",
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "metersphere",
"type": "string"
},
{
"key": "username",
"value": "metersphere",
"type": "string"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": "https://api.tapd.cn/stories?workspace_id=AAAAA&idddd=BBBBB",
"protocol": "https",
"host": [
"api",
"tapd",
"cn"
],
"path": [
"stories"
],
"query": [
{
"key": "workspace_id",
"value": "AAAAA"
},
{
"key": "idddd",
"value": "BBBBB"
}
]
}
},
"response": []
},
{
"name": "post的mock请求",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "MMM",
"value": "PPP",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100004/mock-for-post",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100004",
"mock-for-post"
]
}
},
"response": []
},
{
"name": "put的mock请求",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "methoddd",
"value": "pppp",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"response": []
}
]
}
]
}
]
}

View File

@ -0,0 +1,300 @@
{
"info": {
"_postman_id": "5cf79f3a-bc44-467e-91a0-9aed51daf23c",
"name": "正常请求的集合",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "15342444"
},
"item": [
{
"name": "https://api.tapd.cn/stories?workspace_id=1&id=2",
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "ABCDEFG",
"type": "string"
},
{
"key": "username",
"value": "HIGKLMN",
"type": "string"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": "https://api.tapd.cn/stories?workspace_id=1&id=2",
"protocol": "https",
"host": [
"api",
"tapd",
"cn"
],
"path": [
"stories"
],
"query": [
{
"key": "workspace_id",
"value": "1"
},
{
"key": "id",
"value": "2"
}
]
}
},
"response": []
},
{
"name": "post的mock请求",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "post",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100004/mock-for-post",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100004",
"mock-for-post"
]
}
},
"response": []
},
{
"name": "put的mock请求",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"response": [
{
"name": "put的mock请求ex1",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put-ex1",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"_postman_previewlanguage": null,
"header": null,
"cookie": [],
"body": null
},
{
"name": "put的mock请求ex2",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put-ex1",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"_postman_previewlanguage": null,
"header": null,
"cookie": [],
"body": null
}
]
},
{
"name": "put的mock请求 Copy",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"response": [
{
"name": "put的mock-copy请求ex",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"_postman_previewlanguage": null,
"header": null,
"cookie": [],
"body": null
},
{
"name": "put的mock-copy请求ex2",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"_postman_previewlanguage": null,
"header": null,
"cookie": [],
"body": null
}
]
}
]
}

View File

@ -0,0 +1,27 @@
{
"info": {
"_postman_id": "1f4931b9-f142-4c6a-8041-c41d75f84990",
"name": "single",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "15342444"
},
"item": [
{
"name": "github",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "https://www.github.com",
"protocol": "https",
"host": [
"www",
"github",
"com"
]
}
},
"response": []
}
]
}

View File

@ -0,0 +1,300 @@
{
"info": {
"_postman_id": "5cf79f3a-bc44-467e-91a0-9aed51daf23c",
"name": "正常请求的集合",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "15342444"
},
"item": [
{
"name": "https://api.tapd.cn/stories?workspace_id=55049933&id=1155049933001012963",
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "9CD9AB02-7497-0B85-2C4F-45CC3EA763F8",
"type": "string"
},
{
"key": "username",
"value": "oOjrikkm",
"type": "string"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": "https://api.tapd.cn/stories?workspace_id=55049933&id=1155049933001012963",
"protocol": "https",
"host": [
"api",
"tapd",
"cn"
],
"path": [
"stories"
],
"query": [
{
"key": "workspace_id",
"value": "55049933"
},
{
"key": "id",
"value": "1155049933001012963"
}
]
}
},
"response": []
},
{
"name": "post的mock请求",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "post",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100004/mock-for-post",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100004",
"mock-for-post"
]
}
},
"response": []
},
{
"name": "put的mock请求",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"response": [
{
"name": "put的mock请求ex1",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put-ex1",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"_postman_previewlanguage": null,
"header": null,
"cookie": [],
"body": null
},
{
"name": "put的mock请求ex2",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put-ex1",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"_postman_previewlanguage": null,
"header": null,
"cookie": [],
"body": null
}
]
},
{
"name": "put的mock请求 Copy",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"response": [
{
"name": "put的mock-copy请求ex",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"_postman_previewlanguage": null,
"header": null,
"cookie": [],
"body": null
},
{
"name": "put的mock-copy请求ex2",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "method",
"value": "put",
"type": "text"
}
]
},
"url": {
"raw": "https://ms-v3.fit2cloud.com/mock-server/100080/100005/mock-for-put",
"protocol": "https",
"host": [
"ms-v3",
"fit2cloud",
"com"
],
"path": [
"mock-server",
"100080",
"100005",
"mock-for-put"
]
}
},
"_postman_previewlanguage": null,
"header": null,
"cookie": [],
"body": null
}
]
}
]
}

View File

@ -3,6 +3,8 @@ package io.metersphere.system.utils;
import io.metersphere.project.domain.Project;
import io.metersphere.system.domain.Organization;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.uid.IDGenerator;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
@ -25,4 +27,53 @@ public class TreeNodeParseUtils {
}
return returnList;
}
// nodePath需要以"/"开头
public static List<BaseTreeNode> getInsertNodeByPath(Map<String, BaseTreeNode> modulePathMap, String nodePath) {
//解析modulePath 格式为/a/b/c
String[] split = nodePath.split("/");
//一层一层的创建
List<BaseTreeNode> insertList = new ArrayList<>();
//因为nodePath是以/开头的所以split[0]为空从1开始
for (int i = 1; i < split.length; i++) {
String modulePath = StringUtils.join(split, "/", 1, i + 1);
String path = StringUtils.join("/", modulePath);
BaseTreeNode baseTreeNode = modulePathMap.get(path);
if (baseTreeNode == null) {
//创建模块
BaseTreeNode module = new BaseTreeNode();
module.setId(IDGenerator.nextStr());
module.setName(split[i]);
if (i != 1) {
String parentPath = path.substring(0, path.lastIndexOf("/" + split[i]));
module.setParentId(modulePathMap.get(parentPath).getId());
}
module.setPath(path);
insertList.add(module);
modulePathMap.put(path, module);
}
}
return insertList;
}
public static String genFullModulePath(String selectModulePath, String modulePath) {
String firstPath = selectModulePath;
String lathPath = modulePath;
if (!StringUtils.startsWith(firstPath, "/")) {
firstPath = "/" + firstPath;
}
if (StringUtils.endsWith(firstPath, "/")) {
firstPath = firstPath.substring(0, firstPath.length() - 1);
}
if (!StringUtils.startsWith(lathPath, "/")) {
lathPath = "/" + lathPath;
}
if (StringUtils.endsWith(lathPath, "/")) {
lathPath = lathPath.substring(0, firstPath.length() - 1);
}
return firstPath + lathPath;
}
}