feat(接口测试): 接口定义导入

This commit is contained in:
wxg0103 2024-04-25 15:06:36 +08:00 committed by 刘瑞斌
parent f5e0177de8
commit df16810485
19 changed files with 6261 additions and 124 deletions

View File

@ -7,6 +7,7 @@ import lombok.Data;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@ -35,10 +36,10 @@ public class HttpResponse implements Serializable {
@Schema(description = "响应请求头") @Schema(description = "响应请求头")
@Valid @Valid
private List<MsHeader> headers; private List<MsHeader> headers = new ArrayList<>();
@Schema(description = "响应请求体") @Schema(description = "响应请求体")
@Valid @Valid
private ResponseBody body; private ResponseBody body = new ResponseBody();
} }

View File

@ -23,15 +23,15 @@ public class ResponseBody implements Serializable {
private String bodyType; private String bodyType;
@Valid @Valid
private JsonBody jsonBody; private JsonBody jsonBody = new JsonBody();
@Valid @Valid
private XmlBody xmlBody; private XmlBody xmlBody = new XmlBody();
@Valid @Valid
private RawBody rawBody; private RawBody rawBody = new RawBody();
@Valid @Valid
private ResponseBinaryBody binaryBody; private ResponseBinaryBody binaryBody = new ResponseBinaryBody();
} }

View File

@ -0,0 +1,95 @@
package io.metersphere.api.parser.api;
import io.metersphere.api.dto.converter.ApiDefinitionImportDetail;
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.project.dto.environment.auth.NoAuth;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.LogUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
public abstract class ApiImportAbstractParser<T> implements ImportParser<T> {
protected String projectId;
protected String getApiTestStr(InputStream source) {
StringBuilder testStr = null;
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(source, StandardCharsets.UTF_8))) {
testStr = new StringBuilder();
String inputStr;
while ((inputStr = bufferedReader.readLine()) != null) {
testStr.append(inputStr);
}
source.close();
} catch (Exception e) {
LogUtils.error(e.getMessage(), e);
throw new MSException(e.getMessage());
}
return StringUtils.isNotBlank(testStr) ? testStr.toString() : StringUtils.EMPTY;
}
protected ApiDefinitionImportDetail buildApiDefinition(String name, String path, String method,String modulePath, ImportRequest importRequest) {
ApiDefinitionImportDetail apiDefinition = new ApiDefinitionImportDetail();
apiDefinition.setName(name);
apiDefinition.setPath(formatPath(path));
apiDefinition.setProtocol(importRequest.getProtocol());
apiDefinition.setMethod(method);
apiDefinition.setProjectId(this.projectId);
apiDefinition.setCreateUser(importRequest.getUserId());
apiDefinition.setModulePath(modulePath);
apiDefinition.setResponse(new ArrayList<>());
return apiDefinition;
}
protected MsHTTPElement buildHttpRequest(String name, String path, String method) {
MsHTTPElement request = new MsHTTPElement();
request.setName(name);
// 路径去掉域名/IP 地址保留方法名称及参数
request.setPath(formatPath(path));
request.setMethod(method);
request.setHeaders(new ArrayList<>());
request.setQuery(new ArrayList<>());
request.setRest(new ArrayList<>());
request.setBody(new Body());
MsHTTPConfig httpConfig = new MsHTTPConfig();
httpConfig.setConnectTimeout(60000L);
httpConfig.setResponseTimeout(60000L);
request.setOtherConfig(httpConfig);
request.setAuthConfig(new NoAuth());
Body body = new Body();
body.setBinaryBody(new BinaryBody());
body.setFormDataBody(new FormDataBody());
body.setXmlBody(new XmlBody());
body.setRawBody(new RawBody());
body.setNoneBody(new NoneBody());
body.setJsonBody(new JsonBody());
body.setWwwFormBody(new WWWFormBody());
body.setNoneBody(new NoneBody());
body.setBodyType(Body.BodyType.NONE.name());
request.setBody(body);
return request;
}
protected String formatPath(String url) {
try {
URI urlObject = new URI(url);
return StringUtils.isBlank(urlObject.getPath()) ? url : urlObject.getPath();
} catch (Exception ex) {
//只需要返回前的路径
return url.split("\\?")[0];
}
}
}

View File

@ -0,0 +1,329 @@
package io.metersphere.api.parser.api;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
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.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.Body;
import io.metersphere.api.dto.request.http.body.BodyParamType;
import io.metersphere.api.dto.request.http.body.FormDataKV;
import io.metersphere.api.dto.request.http.body.WWWFormKV;
import io.metersphere.api.parser.api.postman.PostmanItem;
import io.metersphere.api.parser.api.postman.PostmanKeyValue;
import io.metersphere.api.parser.api.postman.PostmanRequest;
import io.metersphere.api.parser.api.postman.PostmanResponse;
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 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.List;
public abstract class PostmanAbstractParserParser<T> extends ApiImportAbstractParser<T> {
private static final String POSTMAN_AUTH_TYPE_BASIC = "basic";
private static final String POSTMAN_AUTH_TYPE_DIGEST = "digest";
private static final String QUERY = "query";
private static final String PATH = "path";
private static final String VARIABLE = "variable";
private static final String KEY = "key";
private static final String VALUE = "value";
private static final String RAW = "raw";
private static final String TYPE = "type";
private static final String DESCRIPTION = "description";
private static final String USERNAME = "username";
private static final String PASSWORD = "password";
private static final String DISABLED = "disabled";
private static final String MODE = "mode";
private static final String FORM_DATA = "formdata";
private static final String URL_ENCODED = "urlencoded";
private static final String FILE = "file";
private static final String OPTIONS = "options";
private static final String La = "language";
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());
//设置认证
setAuth(requestDesc, request);
//设置基础参数 请求头
setHeader(requestDesc, request);
if (urlNode instanceof ObjectNode urlObject) {
//设置query参数
setQuery(urlObject, request);
//设置rest参数
setRest(urlObject, request);
}
//设置body参数
setBody(requestDesc, request);
// 其他设置
PostmanItem.ProtocolProfileBehavior protocolProfileBehavior = requestItem.getProtocolProfileBehavior();
request.getOtherConfig().setFollowRedirects(protocolProfileBehavior != null &&
BooleanUtils.isTrue(protocolProfileBehavior.getFollowRedirects()));
request.getOtherConfig().setAutoRedirects(!request.getOtherConfig().getFollowRedirects());
detail.setRequest(request);
//设置response
setResponseData(requestItem, detail);
return detail;
}
private static void setResponseData(PostmanItem requestItem, ApiDefinitionImportDetail detail) {
List<PostmanResponse> response = requestItem.getResponse();
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);
});
}
}
@NotNull
private static MsHeader getMsHeader(PostmanKeyValue h) {
MsHeader msHeader = new MsHeader();
msHeader.setKey(h.getKey());
msHeader.setValue(h.getValue());
msHeader.setDescription(h.getDescription() != null ? h.getDescription().asText() : StringUtils.EMPTY);
msHeader.setEnable(BooleanUtils.isFalse(h.isDisabled()));
return msHeader;
}
private static void setBody(PostmanRequest requestDesc, MsHTTPElement request) {
JsonNode bodyNode = requestDesc.getBody();
if (bodyNode != null) {
JsonNode modeNode = bodyNode.get(MODE);
if (modeNode != null) {
switch (modeNode.asText()) {
case FORM_DATA:
setFormData(bodyNode, request);
break;
case URL_ENCODED:
setWwwData(bodyNode, request);
break;
case RAW:
setRaw(bodyNode, request);
break;
case FILE:
setBinary(bodyNode, request);
break;
default:
break;
}
}
}
}
private static void setRaw(JsonNode bodyNode, MsHTTPElement request) {
JsonNode rawNode = bodyNode.get(RAW);
JsonNode optionNode = bodyNode.get(OPTIONS);
if (optionNode != null) {
if (optionNode instanceof ObjectNode optionObject) {
JsonNode languageNode = optionObject.get(RAW).get(La);
if (languageNode instanceof TextNode languageText) {
if (StringUtils.equals(languageText.asText(), JSON)) {
request.getBody().setBodyType(Body.BodyType.JSON.name());
request.getBody().getJsonBody().setJsonValue(rawNode instanceof TextNode ? rawNode.asText() : StringUtils.EMPTY);
} else if (StringUtils.equals(languageText.asText(), XML)) {
request.getBody().setBodyType(Body.BodyType.XML.name());
request.getBody().getXmlBody().setValue(rawNode instanceof TextNode ? rawNode.asText() : StringUtils.EMPTY);
} else {
request.getBody().setBodyType(Body.BodyType.RAW.name());
request.getBody().getRawBody().setValue(rawNode instanceof TextNode ? rawNode.asText() : StringUtils.EMPTY);
}
}
}
}
}
private static void setBinary(JsonNode bodyNode, MsHTTPElement request) {
JsonNode fileNode = bodyNode.get(FILE);
if (fileNode != null) {
request.getBody().getBinaryBody().setFile(new ApiFile());
}
request.getBody().setBodyType(Body.BodyType.BINARY.name());
}
private static void setWwwData(JsonNode bodyNode, MsHTTPElement request) {
JsonNode modeNode = bodyNode.get(URL_ENCODED);
if (modeNode instanceof ArrayNode urlEncodedArray) {
urlEncodedArray.forEach(u -> {
WWWFormKV wwwFormKV = new WWWFormKV();
wwwFormKV.setKey(u.get(KEY).asText());
wwwFormKV.setValue(u.get(VALUE).asText());
wwwFormKV.setDescription(u.get(DESCRIPTION) instanceof TextNode ? u.get(DESCRIPTION).asText() : StringUtils.EMPTY);
wwwFormKV.setEnable(!(u.get(DISABLED) instanceof BooleanNode) || !u.get(DISABLED).asBoolean());
request.getBody().getWwwFormBody().getFormValues().add(wwwFormKV);
});
}
request.getBody().setBodyType(Body.BodyType.WWW_FORM.name());
}
private static void setFormData(JsonNode bodyNode, MsHTTPElement request) {
JsonNode modeNode = bodyNode.get(FORM_DATA);
if (modeNode instanceof ArrayNode formArray) {
formArray.forEach(f -> {
FormDataKV formDataKV = new FormDataKV();
formDataKV.setKey(f.get(KEY).asText());
String type = f.get(TYPE).asText();
if (StringUtils.equals(type, FILE)) {
formDataKV.setParamType(BodyParamType.FILE.getValue());
formDataKV.setFiles(new ArrayList<>());
} else {
formDataKV.setParamType(BodyParamType.STRING.getValue());
formDataKV.setValue(f.get(VALUE).asText());
}
formDataKV.setDescription(f.get(DESCRIPTION) instanceof TextNode ? f.get(DESCRIPTION).asText() : StringUtils.EMPTY);
formDataKV.setEnable(!(f.get(DISABLED) instanceof BooleanNode) || !f.get(DISABLED).asBoolean());
request.getBody().getFormDataBody().getFormValues().add(formDataKV);
});
}
request.getBody().setBodyType(Body.BodyType.FORM_DATA.name());
}
private static void setRest(JsonNode urlObject, MsHTTPElement request) {
JsonNode restNode = urlObject.get(VARIABLE);
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());
request.getRest().add(restParam);
});
}
}
private static void setQuery(JsonNode urlObject, MsHTTPElement request) {
JsonNode queryNode = urlObject.get(QUERY);
if (queryNode instanceof ArrayNode queryArray) {
queryArray.forEach(q -> {
QueryParam queryParam = new QueryParam();
queryParam.setKey(q.get(KEY).asText());
queryParam.setValue(q.get(VALUE).asText());
queryParam.setDescription(q.get(DESCRIPTION) instanceof TextNode ? q.get(DESCRIPTION).asText() : StringUtils.EMPTY);
queryParam.setEnable(!(q.get(DISABLED) instanceof BooleanNode) || !q.get(DISABLED).asBoolean());
request.getQuery().add(queryParam);
});
}
}
private static void setHeader(PostmanRequest requestDesc, MsHTTPElement request) {
if (CollectionUtils.isNotEmpty(requestDesc.getHeader())) {
requestDesc.getHeader().forEach(h -> {
MsHeader msHeader = getMsHeader(h);
request.getHeaders().add(msHeader);
});
}
}
private static String getUrl(JsonNode urlNode, String url) {
if (urlNode instanceof ObjectNode urlObject) {
JsonNode pathNode = urlObject.get(PATH);
if (pathNode instanceof ArrayNode pathArray) {
StringBuilder path = new StringBuilder();
pathArray.forEach(p -> {
if (p instanceof TextNode pathString) {
if (pathString.asText().startsWith(":")) {
path.append(StringUtils.join("/{", pathString.asText().substring(1), "}"));
} else {
path.append(StringUtils.join("/", pathString.asText()));
}
}
});
url = path.toString();
}
} else if (urlNode instanceof TextNode urlText) {
url = urlText.asText();
}
return url;
}
private static void setAuth(PostmanRequest requestDesc, MsHTTPElement request) {
JsonNode auth = requestDesc.getAuth();
if (auth != null) {
JsonNode authNode = auth.get(TYPE);
HTTPAuthConfig authConfig = request.getAuthConfig();
if (authNode != null && StringUtils.equals(authNode.asText(), POSTMAN_AUTH_TYPE_BASIC)) {
BasicAuth basicAuth = new BasicAuth();
authConfig.setAuthType(HTTPAuthConfig.HTTPAuthType.BASIC.name());
DigestAuth digestAuth = buildAuth(POSTMAN_AUTH_TYPE_BASIC, auth);
basicAuth.setUserName(digestAuth.getUserName());
basicAuth.setPassword(digestAuth.getPassword());
authConfig.setBasicAuth(basicAuth);
} else if (authNode != null && StringUtils.equals(authNode.asText(), POSTMAN_AUTH_TYPE_DIGEST)) {
DigestAuth digestAuth = buildAuth(POSTMAN_AUTH_TYPE_DIGEST, auth);
authConfig.setAuthType(HTTPAuthConfig.HTTPAuthType.DIGEST.name());
authConfig.setDigestAuth(digestAuth);
}
request.setAuthConfig(authConfig);
}
}
private static DigestAuth buildAuth(String type, JsonNode authNode) {
DigestAuth digestAuth = new DigestAuth();
JsonNode digestNode = authNode.get(type);
if (digestNode != null) {
if (digestNode instanceof ObjectNode digestObject) {
JsonNode usernameNode = digestObject.get(USERNAME);
JsonNode passwordNode = digestObject.get(PASSWORD);
digestAuth.setUserName(usernameNode instanceof TextNode ? usernameNode.asText() : StringUtils.EMPTY);
digestAuth.setPassword(passwordNode instanceof TextNode ?passwordNode.asText() : StringUtils.EMPTY);
} else if (digestNode instanceof ArrayNode digestArray) {
digestArray.forEach(node -> {
JsonNode keyNode = node.get(KEY);
JsonNode valueNode = node.get(VALUE);
if (StringUtils.equals(keyNode.asText(), USERNAME)) {
digestAuth.setUserName(valueNode.asText());
} else if (StringUtils.equals(keyNode.asText(), PASSWORD)) {
digestAuth.setPassword(valueNode.asText());
}
});
}
}
return digestAuth;
}
}

View File

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

View File

@ -1,21 +1,22 @@
package io.metersphere.api.parser.api; 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.ApiDefinitionImport;
import io.metersphere.api.dto.converter.ApiDefinitionImportDetail; import io.metersphere.api.dto.converter.ApiDefinitionImportDetail;
import io.metersphere.api.dto.definition.HttpResponse; import io.metersphere.api.dto.definition.HttpResponse;
import io.metersphere.api.dto.definition.ResponseBody; import io.metersphere.api.dto.definition.ResponseBody;
import io.metersphere.api.dto.request.ImportRequest; import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.dto.request.MsCommonElement; import io.metersphere.api.dto.request.MsCommonElement;
import io.metersphere.api.dto.request.http.*; import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.project.dto.environment.auth.NoAuth; 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.dto.request.http.body.*;
import io.metersphere.api.dto.schema.JsonSchemaItem; import io.metersphere.api.dto.schema.JsonSchemaItem;
import io.metersphere.api.parser.ImportParser;
import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.api.utils.JsonSchemaBuilder; import io.metersphere.api.utils.JsonSchemaBuilder;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.constants.PropertyConstant; import io.metersphere.project.constants.PropertyConstant;
import io.metersphere.project.dto.environment.auth.NoAuth;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils; import io.metersphere.sdk.util.LogUtils;
@ -35,15 +36,11 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import java.io.BufferedReader;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
public class Swagger3Parser<T> implements ImportParser<ApiDefinitionImport> { public class Swagger3Parser<T> extends ApiImportAbstractParser<ApiDefinitionImport> {
protected String projectId; protected String projectId;
private Components components; private Components components;
@ -53,7 +50,6 @@ public class Swagger3Parser<T> implements ImportParser<ApiDefinitionImport> {
public static final String COOKIE = "cookie"; public static final String COOKIE = "cookie";
public static final String QUERY = "query"; public static final String QUERY = "query";
@Override
public ApiDefinitionImport parse(InputStream source, ImportRequest request) throws Exception { public ApiDefinitionImport parse(InputStream source, ImportRequest request) throws Exception {
LogUtils.info("Swagger3Parser parse"); LogUtils.info("Swagger3Parser parse");
List<AuthorizationValue> auths = setAuths(request); List<AuthorizationValue> auths = setAuths(request);
@ -90,22 +86,6 @@ public class Swagger3Parser<T> implements ImportParser<ApiDefinitionImport> {
return CollectionUtils.size(auths) == 0 ? null : auths; return CollectionUtils.size(auths) == 0 ? null : auths;
} }
protected String getApiTestStr(InputStream source) {
StringBuilder testStr = null;
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(source, StandardCharsets.UTF_8))) {
testStr = new StringBuilder();
String inputStr;
while ((inputStr = bufferedReader.readLine()) != null) {
testStr.append(inputStr);
}
source.close();
} catch (Exception e) {
LogUtils.error(e.getMessage(), e);
throw new MSException(e.getMessage());
}
return StringUtils.isNotBlank(testStr) ? testStr.toString() : StringUtils.EMPTY;
}
private List<ApiDefinitionImportDetail> parseRequests(OpenAPI openAPI, ImportRequest importRequest) { private List<ApiDefinitionImportDetail> parseRequests(OpenAPI openAPI, ImportRequest importRequest) {
Paths paths = openAPI.getPaths(); Paths paths = openAPI.getPaths();
@ -133,9 +113,9 @@ public class Swagger3Parser<T> implements ImportParser<ApiDefinitionImport> {
Operation operation = operationsMap.get(method); Operation operation = operationsMap.get(method);
if (operation != null) { if (operation != null) {
//构建基本请求 //构建基本请求
ApiDefinitionImportDetail apiDefinitionDTO = buildApiDefinition(operation, pathName, method, importRequest); ApiDefinitionImportDetail apiDefinitionDTO = buildSwaggerApiDefinition(operation, pathName, method, importRequest);
//构建请求参数 //构建请求参数
MsHTTPElement request = buildRequest(apiDefinitionDTO.getName(), pathName, method); MsHTTPElement request = buildHttpRequest(apiDefinitionDTO.getName(), pathName, method);
parseParameters(operation, request); parseParameters(operation, request);
parseParameters(pathItem, request); parseParameters(pathItem, request);
//构建请求体 //构建请求体
@ -330,7 +310,7 @@ public class Swagger3Parser<T> implements ImportParser<ApiDefinitionImport> {
} }
} }
private ApiDefinitionImportDetail buildApiDefinition(Operation operation, String path, String private ApiDefinitionImportDetail buildSwaggerApiDefinition(Operation operation, String path, String
method, ImportRequest importRequest) { method, ImportRequest importRequest) {
String name; String name;
if (StringUtils.isNotBlank(operation.getSummary())) { if (StringUtils.isNotBlank(operation.getSummary())) {
@ -340,44 +320,8 @@ public class Swagger3Parser<T> implements ImportParser<ApiDefinitionImport> {
} else { } else {
name = path; name = path;
} }
ApiDefinitionImportDetail apiDefinition = new ApiDefinitionImportDetail(); String modulePath = CollectionUtils.isNotEmpty(operation.getTags()) ? StringUtils.join("/", operation.getTags().get(0)) : StringUtils.EMPTY;
apiDefinition.setName(name); return buildApiDefinition(name, path, method, modulePath, importRequest);
apiDefinition.setPath(formatPath(path));
apiDefinition.setProtocol(ApiConstants.HTTP_PROTOCOL);
apiDefinition.setMethod(method);
apiDefinition.setProjectId(this.projectId);
apiDefinition.setCreateUser(importRequest.getUserId());
apiDefinition.setModulePath(CollectionUtils.isNotEmpty(operation.getTags()) ? StringUtils.join("/", operation.getTags().get(0)) : StringUtils.EMPTY);
apiDefinition.setResponse(new ArrayList<>());
return apiDefinition;
}
protected MsHTTPElement buildRequest(String name, String path, String method) {
MsHTTPElement request = new MsHTTPElement();
request.setName(name);
// 路径去掉域名/IP 地址保留方法名称及参数
request.setPath(formatPath(path));
request.setMethod(method);
request.setHeaders(new ArrayList<>());
request.setQuery(new ArrayList<>());
request.setRest(new ArrayList<>());
request.setBody(new Body());
MsHTTPConfig httpConfig = new MsHTTPConfig();
httpConfig.setConnectTimeout(60000L);
httpConfig.setResponseTimeout(60000L);
request.setOtherConfig(httpConfig);
request.setAuthConfig(new NoAuth());
Body body = new Body();
body.setBinaryBody(new BinaryBody());
body.setFormDataBody(new FormDataBody());
body.setXmlBody(new XmlBody());
body.setRawBody(new RawBody());
body.setNoneBody(new NoneBody());
body.setJsonBody(new JsonBody());
body.setWwwFormBody(new WWWFormBody());
body.setNoneBody(new NoneBody());
request.setBody(body);
return request;
} }
private void parseParameters(Operation operation, MsHTTPElement request) { private void parseParameters(Operation operation, MsHTTPElement request) {
@ -754,17 +698,4 @@ public class Swagger3Parser<T> implements ImportParser<ApiDefinitionImport> {
return jsonSchemaArray; return jsonSchemaArray;
} }
private String formatPath(String url) {
try {
URI urlObject = new URI(url);
String path = StringUtils.isBlank(urlObject.getPath()) ? url : urlObject.getPath();
StringBuilder pathBuffer = new StringBuilder(path);
if (StringUtils.isNotEmpty(urlObject.getQuery())) {
pathBuffer.append("?").append(urlObject.getQuery());
}
return pathBuffer.toString();
} catch (Exception ex) {
return url;
}
}
} }

View File

@ -0,0 +1,13 @@
package io.metersphere.api.parser.api.postman;
import lombok.Data;
import java.util.List;
@Data
public class PostmanCollection {
private PostmanCollectionInfo info;
private List<PostmanItem> item;
private List<PostmanKeyValue> variable;
}

View File

@ -0,0 +1,10 @@
package io.metersphere.api.parser.api.postman;
import lombok.Data;
@Data
public class PostmanCollectionInfo {
private String postmanId;
private String name;
private String schema;
}

View File

@ -0,0 +1,19 @@
package io.metersphere.api.parser.api.postman;
import lombok.Data;
import java.util.List;
@Data
public class PostmanItem {
private String name;
private PostmanRequest request;
private List<PostmanResponse> response;
private List<PostmanItem> item;
private ProtocolProfileBehavior protocolProfileBehavior;
@Data
public class ProtocolProfileBehavior {
private Boolean followRedirects = true;
}
}

View File

@ -0,0 +1,22 @@
package io.metersphere.api.parser.api.postman;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
@Data
public class PostmanKeyValue {
private String key;
private String value;
private String type;
private JsonNode description;
private String contentType;
private boolean disabled;
public PostmanKeyValue() {
}
public PostmanKeyValue(String key, String value) {
this.key = key;
this.value = value;
}
}

View File

@ -0,0 +1,18 @@
package io.metersphere.api.parser.api.postman;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import java.util.List;
@Data
public class PostmanRequest {
private String method;
private String schema;
private List<PostmanKeyValue> header;
private JsonNode body;
private JsonNode auth;
private JsonNode url;
}

View File

@ -0,0 +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;
}

View File

@ -504,12 +504,15 @@ public class ApiDefinitionImportUtilService {
} }
} }
/**
* 判断数据唯一性 通过method和path判断 有重复的数据 需要覆盖
*/
public void methodAndPath(List<ApiDefinitionImportDetail> importData, public void methodAndPath(List<ApiDefinitionImportDetail> importData,
List<ApiDefinitionImportDetail> lists, List<ApiDefinitionImportDetail> lists,
ApiDetailWithData apiDetailWithData) { ApiDetailWithData apiDetailWithData) {
Map<String, ApiDefinitionImportDetail> apiDateMap = lists.stream().collect(Collectors.toMap(t -> t.getMethod() + t.getPath(), t -> t)); Map<String, ApiDefinitionImportDetail> apiDateMap = lists.stream().collect(Collectors.toMap(t -> t.getMethod() + t.getPath(), t -> t, (oldValue, newValue) -> newValue));
Map<String, ApiDefinitionImportDetail> importDataMap = importData.stream().collect(Collectors.toMap(t -> t.getMethod() + t.getPath(), t -> t)); Map<String, ApiDefinitionImportDetail> importDataMap = importData.stream().collect(Collectors.toMap(t -> t.getMethod() + t.getPath(), t -> t, (oldValue, newValue) -> newValue));
//判断是否重复 //判断是否重复
List<String> orgList = apiDateMap.keySet().stream().toList(); List<String> orgList = apiDateMap.keySet().stream().toList();
List<String> importList = importDataMap.keySet().stream().toList(); List<String> importList = importDataMap.keySet().stream().toList();

View File

@ -5,10 +5,12 @@ import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.JsonReadFeature; import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType; import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.CollectionType; import com.fasterxml.jackson.databind.type.CollectionType;
import io.metersphere.api.dto.request.MsCommonElement; import io.metersphere.api.dto.request.MsCommonElement;
import io.metersphere.api.dto.request.controller.*; import io.metersphere.api.dto.request.controller.*;
@ -53,6 +55,8 @@ public class ApiDataUtils {
// 如果一个对象中没有任何的属性那么在序列化的时候就会报错 // 如果一个对象中没有任何的属性那么在序列化的时候就会报错
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
// 设置默认使用 BigDecimal 解析浮点数
objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
} }
public static String toJSONString(Object value) { public static String toJSONString(Object value) {
@ -133,4 +137,32 @@ public class ApiDataUtils {
// 加入新的动态组件 // 加入新的动态组件
clazzList.forEach(objectMapper::registerSubtypes); clazzList.forEach(objectMapper::registerSubtypes);
} }
public static JsonNode readTree(String content) {
try {
return objectMapper.readTree(content);
} catch (IOException e) {
throw new MSException(e);
}
}
public static String writerWithDefaultPrettyPrinter(Object value) {
try {
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value);
} catch (IOException e) {
throw new MSException(e);
}
}
public static ObjectNode createObjectNode() {
return objectMapper.createObjectNode();
}
public static <T> T convertValue(JsonNode jsonNode, Class<T> valueType) {
try {
return objectMapper.convertValue(jsonNode, valueType);
} catch (Exception e) {
throw new MSException(e);
}
}
} }

View File

@ -1,12 +1,6 @@
package io.metersphere.api.utils; package io.metersphere.api.utils;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.*; import com.fasterxml.jackson.databind.node.*;
import io.metersphere.jmeter.mock.Mock; import io.metersphere.jmeter.mock.Mock;
import io.metersphere.project.constants.PropertyConstant; import io.metersphere.project.constants.PropertyConstant;
@ -22,29 +16,14 @@ import java.util.regex.Pattern;
public class JsonSchemaBuilder { public class JsonSchemaBuilder {
private static final ObjectMapper objectMapper = JsonMapper.builder()
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS)
.build();
static {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 支持json字符中带注释符
objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
// 如果一个对象中没有任何的属性那么在序列化的时候就会报错
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
// 设置默认使用 BigDecimal 解析浮点数
objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
}
public static String jsonSchemaToJson(String jsonSchemaString) { public static String jsonSchemaToJson(String jsonSchemaString) {
try { try {
// 解析 JSON Schema 字符串为 JsonNode // 解析 JSON Schema 字符串为 JsonNode
JsonNode jsonSchemaNode = objectMapper.readTree(jsonSchemaString); JsonNode jsonSchemaNode = ApiDataUtils.readTree(jsonSchemaString);
Map<String, String> processMap = new HashMap<>(); Map<String, String> processMap = new HashMap<>();
// 生成符合 JSON Schema JSON // 生成符合 JSON Schema JSON
JsonNode jsonNode = generateJson(jsonSchemaNode, processMap); JsonNode jsonNode = generateJson(jsonSchemaNode, processMap);
String jsonString = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode); String jsonString = ApiDataUtils.writerWithDefaultPrettyPrinter(jsonNode);
if (MapUtils.isNotEmpty(processMap)) { if (MapUtils.isNotEmpty(processMap)) {
for (String str : processMap.keySet()) { for (String str : processMap.keySet()) {
jsonString = jsonString.replace(str, processMap.get(str)); jsonString = jsonString.replace(str, processMap.get(str));
@ -58,7 +37,7 @@ public class JsonSchemaBuilder {
} }
private static JsonNode generateJson(JsonNode jsonSchemaNode, Map<String, String> processMap) { private static JsonNode generateJson(JsonNode jsonSchemaNode, Map<String, String> processMap) {
ObjectNode jsonNode = objectMapper.createObjectNode(); ObjectNode jsonNode = ApiDataUtils.createObjectNode();
if (jsonSchemaNode instanceof NullNode) { if (jsonSchemaNode instanceof NullNode) {
return NullNode.getInstance(); return NullNode.getInstance();

View File

@ -3,6 +3,7 @@ package io.metersphere.api.controller;
import io.metersphere.api.constants.ApiConstants; import io.metersphere.api.constants.ApiConstants;
import io.metersphere.api.constants.ApiDefinitionDocType; import io.metersphere.api.constants.ApiDefinitionDocType;
import io.metersphere.api.constants.ApiDefinitionStatus; import io.metersphere.api.constants.ApiDefinitionStatus;
import io.metersphere.api.constants.ApiImportPlatform;
import io.metersphere.api.controller.result.ApiResultCode; import io.metersphere.api.controller.result.ApiResultCode;
import io.metersphere.api.domain.*; import io.metersphere.api.domain.*;
import io.metersphere.api.dto.ApiFile; import io.metersphere.api.dto.ApiFile;
@ -1587,6 +1588,29 @@ public class ApiDefinitionControllerTests extends BaseTest {
paramMap.add("request", JSON.toJSONString(request)); paramMap.add("request", JSON.toJSONString(request));
this.requestMultipart(IMPORT, paramMap, status().is5xxServerError()); this.requestMultipart(IMPORT, paramMap, status().is5xxServerError());
request.setPlatform(ApiImportPlatform.Postman.name());
request.setCoverModule(true);
request.setCoverData(true);
paramMap.clear();
inputStream = new FileInputStream(new File(
Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/postman.json"))
.getPath()));
file = new MockMultipartFile("file", "postman.json", MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap.add("file", file);
paramMap.add("request", JSON.toJSONString(request));
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
paramMap.clear();
request.setCoverModule(true);
request.setCoverData(true);
inputStream = new FileInputStream(new File(
Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/postman2.json"))
.getPath()));
file = new MockMultipartFile("file", "postman2.json", MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream);
paramMap.add("file", file);
paramMap.add("request", JSON.toJSONString(request));
this.requestMultipartWithOkAndReturn(IMPORT, paramMap);
} }
protected MvcResult requestMultipart(String url, MultiValueMap<String, Object> paramMap, ResultMatcher resultMatcher) throws Exception { protected MvcResult requestMultipart(String url, MultiValueMap<String, Object> paramMap, ResultMatcher resultMatcher) throws Exception {

View File

@ -8,20 +8,12 @@ import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import java.util.Objects;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@AutoConfigureMockMvc @AutoConfigureMockMvc
public class ParserTests { public class ParserTests {
@Test
@Order(2)
public void testImportParserPostman() throws Exception {
Objects.requireNonNull(ImportParserFactory.getImportParser(ApiImportPlatform.Postman.name())).parse(null, null);
}
@Test @Test
@Order(3) @Order(3)
public void testImportParserMs() throws Exception { public void testImportParserMs() throws Exception {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff