mirror of
https://gitee.com/fit2cloud-feizhiyun/MeterSphere.git
synced 2024-12-02 20:19:16 +08:00
feat(接口测试): 接口测试导入功能开发
This commit is contained in:
parent
b0be34f90b
commit
325790cf88
@ -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")
|
||||
|
@ -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;
|
@ -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<>();
|
||||
|
||||
}
|
@ -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<>();
|
||||
|
||||
}
|
@ -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<>();
|
||||
}
|
@ -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<>();
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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<>();
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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<>();
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
@ -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>
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}});
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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())) {
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 + "]";
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 + ")";
|
||||
}
|
||||
|
||||
}
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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));
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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
@ -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":
|
||||
[]
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user