diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java index 0f9aea2b15..17d16f53c3 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java @@ -27,6 +27,9 @@ public class FilterChainUtils { filterChainDefinitionMap.put("/authsource/list/allenable", "anon"); filterChainDefinitionMap.put("/sso/callback/**", "anon"); filterChainDefinitionMap.put("/license/validate", "anon"); + //mock-server + filterChainDefinitionMap.put("/mock-server/**", "anon"); + //功能用例副文本访问 filterChainDefinitionMap.put("/attachment/download/file/**", "anon"); //用例评审副文本访问 diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/XMLUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/XMLUtils.java index 63f113b0fd..6f59c0a7cb 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/XMLUtils.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/XMLUtils.java @@ -4,6 +4,7 @@ package io.metersphere.sdk.util; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; import org.dom4j.DocumentException; +import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; @@ -11,10 +12,15 @@ import org.dom4j.io.XMLWriter; import org.xml.sax.SAXException; import org.xml.sax.helpers.XMLFilterImpl; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; public class XMLUtils { public static final boolean IS_TRANS = false; @@ -26,7 +32,7 @@ public class XMLUtils { reader.setFeature("http://xml.org/sax/features/external-general-entities", false); reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - }catch (Exception e){ + } catch (Exception e) { LogUtils.error(e); } if (!IS_TRANS) { @@ -100,4 +106,40 @@ public class XMLUtils { } }; } + + public static Document stringToDocument(String xml) throws Exception { + return getDocument(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8.name()))); + } + + public static Map xmlStringToJson(String xml) { + try { + return elementToMap(stringToDocument(xml).getRootElement()); + } catch (Exception e) { + LogUtils.error(e); + } + return new LinkedHashMap<>(); + } + + private static Map elementToMap(Element node) { + LinkedHashMap result = new LinkedHashMap<>(); + + List listElement = node.elements();// 所有一级子节点的list + if (!listElement.isEmpty()) { + List> list = new ArrayList<>(); + for (Element e : listElement) {// 遍历所有一级子节点 + Map jsonObject = elementToMap(e); + list.add(jsonObject); + } + if (list.size() == 1) { + result.put(node.getName(), list.get(0)); + } else { + result.put(node.getName(), list); + } + } else { + if (!StringUtils.isAllBlank(node.getName(), node.getText())) { + result.put(node.getName(), node.getText()); + } + } + return result; + } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiResource.java b/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiResource.java index eeafdaab76..173b859892 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiResource.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiResource.java @@ -4,5 +4,6 @@ public enum ApiResource { PROJECT, API_DEFINITION, API_TEST_CASE, - API_SCENARIO + API_SCENARIO, + API_DEFINITION_MOCK, } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/constants/mockserver/ParamConditionEnums.java b/backend/services/api-test/src/main/java/io/metersphere/api/constants/mockserver/ParamConditionEnums.java new file mode 100644 index 0000000000..422e640b31 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/constants/mockserver/ParamConditionEnums.java @@ -0,0 +1,6 @@ +package io.metersphere.api.constants.mockserver; + +// mock匹配规则 +public enum ParamConditionEnums { + EQUALS, NOT_EQUALS, CONTAINS, LENGTH_EQUALS, LENGTH_NOT_EQUALS, LENGTH_LARGE, LENGTH_SHOT, REGULAR_MATCH +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionMockController.java b/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionMockController.java index 547b8f9520..8b9a5ab422 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionMockController.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionMockController.java @@ -2,12 +2,15 @@ package io.metersphere.api.controller.definition; import com.github.pagehelper.Page; import com.github.pagehelper.page.PageMethod; +import io.metersphere.api.constants.ApiResource; import io.metersphere.api.domain.ApiDefinitionMock; import io.metersphere.api.dto.definition.ApiDefinitionMockDTO; import io.metersphere.api.dto.definition.request.ApiDefinitionMockAddRequest; import io.metersphere.api.dto.definition.request.ApiDefinitionMockPageRequest; import io.metersphere.api.dto.definition.request.ApiDefinitionMockRequest; import io.metersphere.api.dto.definition.request.ApiDefinitionMockUpdateRequest; +import io.metersphere.api.service.ApiFileResourceService; +import io.metersphere.api.service.ApiValidateService; import io.metersphere.api.service.definition.ApiDefinitionMockLogService; import io.metersphere.api.service.definition.ApiDefinitionMockNoticeService; import io.metersphere.api.service.definition.ApiDefinitionMockService; @@ -43,12 +46,17 @@ public class ApiDefinitionMockController { @Resource private ApiDefinitionMockService apiDefinitionMockService; + @Resource + private ApiFileResourceService apiFileResourceService; + @Resource + private ApiValidateService apiValidateService; @PostMapping("/page") @Operation(summary = "接口测试-接口管理-接口 Mock") @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_MOCK_READ) @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") public Pager> getPage(@Validated @RequestBody ApiDefinitionMockPageRequest request) { + apiValidateService.validateApiMenuInProject(request.getProjectId(), ApiResource.PROJECT.name()); Page page = PageMethod.startPage(request.getCurrent(), request.getPageSize(), StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc"); return PageUtils.setPageInfo(page, apiDefinitionMockService.getPage(request)); @@ -59,6 +67,7 @@ public class ApiDefinitionMockController { @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_MOCK_READ) @CheckOwner(resourceId = "#request.getId()", resourceType = "api_definition_mock") public ApiDefinitionMockDTO detail(@Validated @RequestBody ApiDefinitionMockRequest request) { + apiValidateService.validateApiMenuInProject(request.getProjectId(), ApiResource.PROJECT.name()); return apiDefinitionMockService.detail(request); } @@ -69,6 +78,7 @@ public class ApiDefinitionMockController { @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") @SendNotice(taskType = NoticeConstants.TaskType.API_DEFINITION_TASK, event = NoticeConstants.Event.MOCK_CREATE, target = "#targetClass.getApiMockDTO(#request)", targetClass = ApiDefinitionMockNoticeService.class) public ApiDefinitionMock add(@Validated @RequestBody ApiDefinitionMockAddRequest request) { + apiValidateService.validateApiMenuInProject(request.getApiDefinitionId(), ApiResource.API_DEFINITION.name()); return apiDefinitionMockService.create(request, SessionUtils.getUserId()); } @@ -79,6 +89,7 @@ public class ApiDefinitionMockController { @CheckOwner(resourceId = "#request.getId()", resourceType = "api_definition_mock") @SendNotice(taskType = NoticeConstants.TaskType.API_DEFINITION_TASK, event = NoticeConstants.Event.MOCK_UPDATE, target = "#targetClass.getApiMockDTO(#request)", targetClass = ApiDefinitionMockNoticeService.class) public ApiDefinitionMock update(@Validated @RequestBody ApiDefinitionMockUpdateRequest request) { + apiValidateService.validateApiMenuInProject(request.getId(), ApiResource.API_DEFINITION_MOCK.name()); return apiDefinitionMockService.update(request, SessionUtils.getUserId()); } @@ -88,6 +99,7 @@ public class ApiDefinitionMockController { @Log(type = OperationLogType.UPDATE, expression = "#msClass.updateEnableLog(#id)", msClass = ApiDefinitionMockLogService.class) @CheckOwner(resourceId = "#id", resourceType = "api_definition_mock") public void updateEnable(@PathVariable String id) { + apiValidateService.validateApiMenuInProject(id, ApiResource.API_DEFINITION_MOCK.name()); apiDefinitionMockService.updateEnable(id); } @@ -98,6 +110,7 @@ public class ApiDefinitionMockController { @CheckOwner(resourceId = "#request.getId()", resourceType = "api_definition_mock") @SendNotice(taskType = NoticeConstants.TaskType.API_DEFINITION_TASK, event = NoticeConstants.Event.MOCK_DELETE, target = "#targetClass.getApiMockDTO(#request.id)", targetClass = ApiDefinitionMockNoticeService.class) public void delete(@Validated @RequestBody ApiDefinitionMockRequest request) { + apiValidateService.validateApiMenuInProject(request.getId(), ApiResource.API_DEFINITION_MOCK.name()); apiDefinitionMockService.delete(request, SessionUtils.getUserId()); } @@ -107,6 +120,7 @@ public class ApiDefinitionMockController { @Log(type = OperationLogType.UPDATE, expression = "#msClass.copyLog(#request)", msClass = ApiDefinitionMockLogService.class) @CheckOwner(resourceId = "#request.getId()", resourceType = "api_definition_mock") public ApiDefinitionMock copy(@Validated @RequestBody ApiDefinitionMockRequest request) { + apiValidateService.validateApiMenuInProject(request.getId(), ApiResource.API_DEFINITION_MOCK.name()); return apiDefinitionMockService.copy(request, SessionUtils.getUserId()); } @@ -114,8 +128,6 @@ public class ApiDefinitionMockController { @Operation(summary = "上传接口 Mock 所需的文件资源,并返回文件ID") @RequiresPermissions(logical = Logical.OR, value = {PermissionConstants.PROJECT_API_DEFINITION_MOCK_ADD, PermissionConstants.PROJECT_API_DEFINITION_MOCK_UPDATE}) public String uploadTempFile(@RequestParam("file") MultipartFile file) { - return apiDefinitionMockService.uploadTempFile(file); + return apiFileResourceService.uploadTempFile(file); } - - } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/controller/mockserver/MockServerController.java b/backend/services/api-test/src/main/java/io/metersphere/api/controller/mockserver/MockServerController.java new file mode 100644 index 0000000000..5fb631452c --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/controller/mockserver/MockServerController.java @@ -0,0 +1,40 @@ +package io.metersphere.api.controller.mockserver; + +import io.metersphere.api.service.mockserver.MockServerService; +import io.metersphere.api.utils.MockServerUtils; +import io.metersphere.sdk.constants.HttpMethodConstants; +import io.metersphere.system.controller.handler.annotation.NoResultHolder; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping(value = "/mock-server") +@Tag(name = "接口测试-接口管理-接口定义-Mock") +public class MockServerController { + + @Resource + private MockServerService mockServerService; + + @RequestMapping(value = "/{projectNum}/{apiInfo}/**", method = RequestMethod.OPTIONS) + @NoResultHolder + public Object optionsRequest(@PathVariable String projectNum, @PathVariable String apiInfo, HttpServletRequest request, HttpServletResponse response) { + Map requestHeaderMap = MockServerUtils.getHttpRequestHeader(request); + return mockServerService.execute(HttpMethodConstants.OPTIONS.name(), requestHeaderMap, projectNum, apiInfo, request, response); + } + + @RequestMapping(value = "/{projectNum}/{apiInfo}/**") + @NoResultHolder + public Object mockRequest(@PathVariable String projectNum, @PathVariable String apiInfo, HttpServletRequest request, HttpServletResponse response) { + Map requestHeaderMap = MockServerUtils.getHttpRequestHeader(request); + String method = request.getMethod(); + return mockServerService.execute(method, requestHeaderMap, projectNum, apiInfo, request, response); + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionMockDTO.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionMockDTO.java index eb224c3c65..a19f6c8d10 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionMockDTO.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionMockDTO.java @@ -1,13 +1,12 @@ package io.metersphere.api.dto.definition; import io.metersphere.api.domain.ApiDefinitionMock; -import io.metersphere.plugin.api.spi.AbstractMsTestElement; +import io.metersphere.api.dto.mockserver.MockMatchRule; +import io.metersphere.api.dto.mockserver.MockResponse; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; -import java.util.List; - /** * @author lan */ @@ -16,10 +15,10 @@ import java.util.List; public class ApiDefinitionMockDTO extends ApiDefinitionMock { @Schema(description = "请求内容") - private AbstractMsTestElement matching; + private MockMatchRule matching; @Schema(description = "响应内容") - private List response; + private MockResponse response; @Schema(description = "创建人名称") private String createUserName; diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/HttpResponse.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/HttpResponse.java index 1241ceb2b3..5ae85cc389 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/HttpResponse.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/HttpResponse.java @@ -1,7 +1,6 @@ package io.metersphere.api.dto.definition; import io.metersphere.api.dto.request.http.Header; -import io.metersphere.api.dto.request.http.body.Body; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -11,6 +10,7 @@ import java.util.List; /** * @author: LAN + * @editer: 建国 * @date: 2023/11/21 18:12 * @version: 1.0 */ @@ -20,30 +20,22 @@ public class HttpResponse implements Serializable { @Serial private static final long serialVersionUID = 1L; + @Schema(description = "唯一ID") + private String id; + + @Schema(description = "响应码") + private String statusCode; + + @Schema(description = "默认响应标识") + private boolean defaultFlag; + @Schema(description = "响应名称") private String name; - /** - * 请求头 - */ @Schema(description = "响应请求头") private List
headers; - /** - * 请求体 - */ @Schema(description = "响应请求体") - private Body body; + private ResponseBody body; - /** - * 请求方法 - */ - @Schema(description = "响应请求方法") - private String statusCode; - - /** - * 默认标识 - */ - @Schema(description = "默认响应标识") - private Boolean defaultFlag; } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ResponseBinaryBody.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ResponseBinaryBody.java new file mode 100644 index 0000000000..acf5ffec16 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ResponseBinaryBody.java @@ -0,0 +1,10 @@ +package io.metersphere.api.dto.definition; + +import io.metersphere.api.dto.ApiFile; +import lombok.Data; + +@Data +public class ResponseBinaryBody extends ApiFile { + private boolean sendAsBody; + private String description; +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ResponseBody.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ResponseBody.java new file mode 100644 index 0000000000..0407a912de --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ResponseBody.java @@ -0,0 +1,37 @@ +package io.metersphere.api.dto.definition; + +import io.metersphere.api.dto.request.http.body.JsonBody; +import io.metersphere.api.dto.request.http.body.RawBody; +import io.metersphere.api.dto.request.http.body.XmlBody; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.io.Serializable; + +/** + * 接口响应体、mock期望响应体中使用到的body + */ +@Data +public class ResponseBody implements Serializable { + + private static final long serialVersionUID = 1L; + + @NotBlank + @Size(max = 20) + private String bodyType; + + @Valid + private JsonBody jsonBody; + + @Valid + private XmlBody xmlBody; + + @Valid + private RawBody rawBody; + + @Valid + private ResponseBinaryBody binaryBody; + +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/request/ApiDefinitionMockAddRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/request/ApiDefinitionMockAddRequest.java index b1f48e194f..a160680b62 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/request/ApiDefinitionMockAddRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/request/ApiDefinitionMockAddRequest.java @@ -1,5 +1,7 @@ package io.metersphere.api.dto.definition.request; +import io.metersphere.api.dto.mockserver.MockMatchRule; +import io.metersphere.api.dto.mockserver.MockResponse; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; @@ -32,17 +34,14 @@ public class ApiDefinitionMockAddRequest implements Serializable { @Schema(description = "标签") private LinkedHashSet< - @NotBlank @Size(min = 1, max = 64, message = "{api_test_case.tag.length_range}") String> tags; @Schema(description = "请求内容") - @NotBlank - private String matching; + private MockMatchRule mockMatchRule; @Schema(description = "请求内容") - @NotBlank - private String response; + private MockResponse response; @Schema(description = "接口fk", requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank(message = "{api_definition_mock.api_definition_id.not_blank}") diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/request/ApiDefinitionMockRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/request/ApiDefinitionMockRequest.java index 56aead8c73..4afb228e9e 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/request/ApiDefinitionMockRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/request/ApiDefinitionMockRequest.java @@ -26,10 +26,4 @@ public class ApiDefinitionMockRequest implements Serializable { @NotBlank(message = "{api_definition_mock.project_id.not_blank}") @Size(min = 1, max = 50, message = "{api_definition_mock.project_id.length_range}") private String projectId; - - @Schema(description = "接口fk", requiredMode = Schema.RequiredMode.REQUIRED) - @NotBlank(message = "{api_definition_mock.api_definition_id.not_blank}") - @Size(min = 1, max = 50, message = "{api_definition_mock.api_definition_id.length_range}") - private String apiDefinitionId; - } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/BodyParamMatchRole.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/BodyParamMatchRole.java new file mode 100644 index 0000000000..e59681e207 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/BodyParamMatchRole.java @@ -0,0 +1,115 @@ +package io.metersphere.api.dto.mockserver; + +import io.metersphere.sdk.util.JSON; +import io.metersphere.sdk.util.XMLUtils; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; + +@Data +public class BodyParamMatchRole { + @Schema(description = "参数类型(kv/json/xml/raw 默认为raw)") + private String paramType; + @Schema(description = "formData的匹配规则") + private keyValueMatchRole formDataMatch; + @Schema(description = "文本匹配规则") + private String raw; + + public boolean matchXml(Map requestMap) { + Map mockMap = XMLUtils.xmlStringToJson(raw); + return this.matchMap(mockMap, requestMap); + } + + public boolean matchJson(String requestJson) { + + if (StringUtils.startsWith(requestJson, "{") && StringUtils.endsWith(requestJson, "}")) { + //入参是Object,如果mock期望设置的不是Object,视为无法匹配 + if (StringUtils.startsWith(this.raw, "{") && StringUtils.endsWith(this.raw, "}")) { + Map mockMap = JSON.parseMap(this.raw); + Map requestMap = JSON.parseMap(requestJson); + return this.matchObject(mockMap, requestMap); + } else { + return false; + } + } + + if (StringUtils.startsWith(requestJson, "[") && StringUtils.endsWith(requestJson, "]")) { + List requestList = JSON.parseArray(requestJson, Object.class); + if (StringUtils.startsWith(this.raw, "{") && StringUtils.endsWith(this.raw, "}")) { + //入参是Array,如果mock期望设置是Object,则入参中的任意一个匹配,视为匹配 + Map mockMap = JSON.parseMap(this.raw); + for (Object requestObj : requestList) { + if (this.matchObject(mockMap, requestObj)) { + return true; + } + } + return false; + } else if (StringUtils.startsWith(this.raw, "[") && StringUtils.endsWith(this.raw, "]")) { + //入参是Array,如果mock期望设置也是Array,则Mock中的每个数据都匹配才视为匹配 + List mockList = JSON.parseArray(requestJson, Object.class); + for (Object mockObj : mockList) { + boolean match = false; + for (int i = 0; i < requestList.size(); i++) { + Object requestObj = requestList.get(i); + match = this.matchObject(mockObj, requestObj); + if (match) { + requestList.remove(i); + break; + } + } + if (!match) { + return false; + } + } + return true; + } + return false; + } + return false; + } + + private boolean matchMap(Map mockMap, Map requestMap) { + for (Map.Entry entry : mockMap.entrySet()) { + if (!this.matchObject(entry.getValue(), requestMap.get(entry.getKey()))) { + return false; + } + } + return true; + } + + private boolean matchObject(Object mockRule, Object requestParam) { + if (ObjectUtils.anyNull(mockRule, requestParam)) { + return false; + } + + if (mockRule instanceof List && requestParam instanceof List) { + List mockList = (List) mockRule; + List requestList = (List) requestParam; + if (mockList.size() != requestList.size()) { + return false; + } + for (int i = 0; i < mockList.size(); i++) { + if (!this.matchObject(mockList.get(i), requestList.get(i))) { + return false; + } + } + return true; + } else if (mockRule instanceof Map && requestParam instanceof Map) { + Map mockMap = (Map) mockRule; + Map requestMap = (Map) requestParam; + for (Map.Entry entry : mockMap.entrySet()) { + if (!this.matchObject(entry.getValue(), requestMap.get(entry.getKey()))) { + return false; + } + } + return true; + } + + //既不是list也不是object,直接进行值比对 + return StringUtils.equals(String.valueOf(mockRule), String.valueOf(requestParam)); + } +} \ No newline at end of file diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/HttpRequestParam.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/HttpRequestParam.java new file mode 100644 index 0000000000..9633c5ab66 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/HttpRequestParam.java @@ -0,0 +1,40 @@ +package io.metersphere.api.dto.mockserver; + +import io.metersphere.api.dto.request.http.body.Body; +import io.metersphere.sdk.util.XMLUtils; +import lombok.Data; + +import java.util.LinkedHashMap; +import java.util.Map; + +@Data +public class HttpRequestParam { + + private boolean isPost; + + private LinkedHashMap restParams; + + //form-data的kv类型参数也存储在queryParamObj中 + private LinkedHashMap queryParamsObj; + + private String paramType; + + //JSONArray 或 JSONObject + private String jsonString; + + private Map xmlToJsonParam; + + private String raw; + + public void setXmlParam(String xmlParam) { + this.setParamType(Body.BodyType.XML.name()); + this.setRaw(xmlParam); + Map jsonMap = XMLUtils.xmlStringToJson(xmlParam); + this.setXmlToJsonParam(jsonMap); + } + + public void setJsonParam(String requestPostString) { + this.setParamType(Body.BodyType.JSON.name()); + this.jsonString = requestPostString; + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/KeyValueInfo.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/KeyValueInfo.java new file mode 100644 index 0000000000..011a53b088 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/KeyValueInfo.java @@ -0,0 +1,70 @@ +package io.metersphere.api.dto.mockserver; + +import io.metersphere.api.constants.mockserver.ParamConditionEnums; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Data +public class KeyValueInfo { + @Schema(description = "Key") + private String key; + @Schema(description = "Value") + private String value; + @Schema(description = "条件") + private String condition; + @Schema(description = "描述") + private String description; + + public boolean matchValue(String value) { + if (StringUtils.isBlank(this.condition) || StringUtils.equals(this.condition, ParamConditionEnums.EQUALS.name())) { + return StringUtils.equals(this.value, value); + } else if (StringUtils.equals(this.condition, ParamConditionEnums.NOT_EQUALS.name())) { + return !StringUtils.equals(this.value, value); + } else if (StringUtils.equals(this.condition, ParamConditionEnums.CONTAINS.name())) { + return StringUtils.contains(this.value, value); + } else if (StringUtils.equals(this.condition, ParamConditionEnums.LENGTH_EQUALS.name())) { + try { + int length = Integer.parseInt(value); + return this.value.length() == length; + } catch (Exception e) { + return false; + } + } else if (StringUtils.equals(this.condition, ParamConditionEnums.LENGTH_NOT_EQUALS.name())) { + try { + int length = Integer.parseInt(value); + return this.value.length() != length; + } catch (Exception e) { + return false; + } + } else if (StringUtils.equals(this.condition, ParamConditionEnums.LENGTH_SHOT.name())) { + try { + int length = Integer.parseInt(value); + return this.value.length() < length; + } catch (Exception e) { + return false; + } + } else if (StringUtils.equals(this.condition, ParamConditionEnums.LENGTH_LARGE.name())) { + try { + int length = Integer.parseInt(value); + return this.value.length() > length; + } catch (Exception e) { + return false; + } + } else if (StringUtils.equals(this.condition, ParamConditionEnums.REGULAR_MATCH.name())) { + try { + Pattern pattern = Pattern.compile(value); + Matcher matcher = pattern.matcher(this.value); + boolean isMatch = matcher.matches(); + return isMatch; + } catch (Exception e) { + return false; + } + } else { + return false; + } + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/MockMatchRule.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/MockMatchRule.java new file mode 100644 index 0000000000..600cf3e8a2 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/MockMatchRule.java @@ -0,0 +1,71 @@ +package io.metersphere.api.dto.mockserver; + +import io.metersphere.api.dto.request.http.body.Body; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import java.io.Serializable; +import java.util.Map; + +//mock匹配规则 +@Data +public class MockMatchRule implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "请求头匹配规则") + private keyValueMatchRole header = new keyValueMatchRole(); + @Schema(description = "query参数匹配规则") + private keyValueMatchRole query = new keyValueMatchRole(); + @Schema(description = "REST参数匹配规则") + private keyValueMatchRole rest = new keyValueMatchRole(); + @Schema(description = "body参数匹配规则") + private BodyParamMatchRole body = new BodyParamMatchRole(); + + public boolean keyValueMatch(String matchType, Map matchParam) { + keyValueMatchRole matchRole = null; + switch (matchType) { + case "header": + matchRole = header; + break; + case "query": + matchRole = query; + break; + case "rest": + matchRole = rest; + break; + case "body": + if (body != null) { + matchRole = body.getFormDataMatch(); + } + break; + default: + break; + } + if (matchRole == null) { + return true; + } + return matchRole.match(matchParam); + } + + public boolean requestParamMatch(HttpRequestParam httpRequestParam) { + if (!this.keyValueMatch("rest", httpRequestParam.getRestParams())) { + return false; + } + if (httpRequestParam.isPost()) { + if (StringUtils.equalsIgnoreCase(body.getParamType(), Body.BodyType.XML.name())) { + return body.matchXml(httpRequestParam.getXmlToJsonParam()); + } else if (StringUtils.equalsIgnoreCase(body.getParamType(), Body.BodyType.JSON.name())) { + return body.matchJson(httpRequestParam.getJsonString()); + } else if (StringUtils.equalsIgnoreCase(body.getParamType(), Body.BodyType.FORM_DATA.name())) { + return this.keyValueMatch("body", httpRequestParam.getQueryParamsObj()); + } else if (StringUtils.isNotBlank(body.getRaw())) { + return StringUtils.contains(body.getRaw(), httpRequestParam.getRaw()); + } + } else { + return this.keyValueMatch("query", httpRequestParam.getQueryParamsObj()); + } + return true; + } +} \ No newline at end of file diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/MockResponse.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/MockResponse.java new file mode 100644 index 0000000000..050cd1e296 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/MockResponse.java @@ -0,0 +1,30 @@ +package io.metersphere.api.dto.mockserver; + +import io.metersphere.api.dto.definition.ResponseBody; +import io.metersphere.api.dto.request.http.Header; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +public class MockResponse implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "响应码") + private int statusCode; + + @Schema(description = "响应请求头") + private List
headers; + + @Schema(description = "是否使用api响应体") + private boolean useApiResponse; + + @Schema(description = "接口响应ID(useApiResponse为true时使用)") + private String apiResponseId; + + @Schema(description = "响应请求体") + private ResponseBody body; +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/keyValueMatchRole.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/keyValueMatchRole.java new file mode 100644 index 0000000000..7b11328570 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/mockserver/keyValueMatchRole.java @@ -0,0 +1,37 @@ +package io.metersphere.api.dto.mockserver; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.apache.commons.collections4.MapUtils; + +import java.util.List; +import java.util.Map; + +@Data +public class keyValueMatchRole { + @Schema(description = "是否是全部匹配 (false为任意匹配)") + private boolean isMatchAll; + @Schema(description = "匹配规则") + private List matchRules; + + public boolean match(Map matchParam) { + if (MapUtils.isEmpty(matchParam)) { + return true; + } + if (isMatchAll) { + for (KeyValueInfo matchRule : matchRules) { + if (!matchRule.matchValue(matchParam.get(matchRule.getKey()))) { + return false; + } + } + return true; + } else { + for (KeyValueInfo matchRule : matchRules) { + if (matchRule.matchValue(matchParam.get(matchRule.getKey()))) { + return true; + } + } + return false; + } + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.java b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.java index 44fdbba4ec..a621ecc31c 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.java @@ -64,4 +64,6 @@ public interface ExtApiDefinitionMapper { Long getLastPos(@Param("projectId") String projectId, @Param("basePos") Long basePos); List getModuleInfoByIds(@Param("ids") List ids); + + ApiDefinition selectByProjectNumAndApiNum(@Param("projectNum") String projectNum, @Param("apiNum") String apiNum); } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml index 981a873d15..e4345cb522 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml @@ -550,4 +550,14 @@ #{id} + diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/Swagger3Parser.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/Swagger3Parser.java index 1c4a744faa..f119c0217b 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/Swagger3Parser.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/Swagger3Parser.java @@ -1,9 +1,10 @@ package io.metersphere.api.parser.api; import io.metersphere.api.constants.ApiConstants; -import io.metersphere.api.dto.definition.HttpResponse; import io.metersphere.api.dto.converter.ApiDefinitionImport; import io.metersphere.api.dto.converter.ApiDefinitionImportDetail; +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.*; import io.metersphere.api.dto.request.http.auth.NoAuth; @@ -184,14 +185,13 @@ public class Swagger3Parser implements ImportParser { body.setWwwFormBody(wwwFormBody); } - private void parseResponse(ApiResponses responseBody, List response) { if (responseBody != null) { responseBody.forEach((key, value) -> { HttpResponse httpResponse = new HttpResponse(); //TODO headers httpResponse.setStatusCode(key); - Body body = new Body(); + ResponseBody body = new ResponseBody(); Map headers = value.getHeaders(); if (MapUtils.isNotEmpty(headers)) { List
headerList = new ArrayList<>(); @@ -218,6 +218,49 @@ public class Swagger3Parser implements ImportParser { } + private void setBodyData(String k, io.swagger.v3.oas.models.media.MediaType value, ResponseBody body) { + //TODO body 默认如果json格式 + JsonSchemaItem jsonSchemaItem = parseSchema(value.getSchema()); + switch (k) { + case MediaType.APPLICATION_JSON_VALUE, MediaType.ALL_VALUE -> { + body.setBodyType(Body.BodyType.JSON.name()); + JsonBody jsonBody = new JsonBody(); + jsonBody.setJsonSchema(jsonSchemaItem); + jsonBody.setEnableJsonSchema(true); + if (ObjectUtils.isNotEmpty(value.getExample())) { + jsonBody.setJsonValue(ApiDataUtils.toJSONString(value.getExample())); + } + body.setJsonBody(jsonBody); + } + case MediaType.APPLICATION_XML_VALUE -> { + if (StringUtils.isBlank(body.getBodyType())) { + body.setBodyType(Body.BodyType.XML.name()); + } + XmlBody xml = new XmlBody(); + //xml.setValue(XMLUtils.jsonToXmlStr(jsonValue)); + body.setXmlBody(xml); + } + case MediaType.MULTIPART_FORM_DATA_VALUE -> { + if (StringUtils.isBlank(body.getBodyType())) { + body.setBodyType(Body.BodyType.FORM_DATA.name()); + } + } + case MediaType.APPLICATION_OCTET_STREAM_VALUE -> { + if (StringUtils.isBlank(body.getBodyType())) { + body.setBodyType(Body.BodyType.BINARY.name()); + } + } + case MediaType.TEXT_PLAIN_VALUE -> { + if (StringUtils.isBlank(body.getBodyType())) { + body.setBodyType(Body.BodyType.RAW.name()); + } + RawBody rawBody = new RawBody(); + body.setRawBody(rawBody); + } + default -> body.setBodyType(Body.BodyType.NONE.name()); + } + } + private void setBodyData(String k, io.swagger.v3.oas.models.media.MediaType value, Body body) { //TODO body 默认如果json格式 JsonSchemaItem jsonSchemaItem = parseSchema(value.getSchema()); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiValidateService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiValidateService.java index 4426e37c15..298180f705 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiValidateService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiValidateService.java @@ -1,10 +1,8 @@ package io.metersphere.api.service; -import io.metersphere.api.constants.ApiResource; import io.metersphere.project.constants.ProjectMenuConstants; import io.metersphere.system.service.CommonProjectService; import jakarta.annotation.Resource; -import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import java.util.Collections; @@ -16,16 +14,7 @@ public class ApiValidateService { //校验接口菜单是否开启 public void validateApiMenuInProject(String resourceId, String resourceType) { - String tableName = null; - if (StringUtils.equals(resourceType, ApiResource.PROJECT.name())) { - tableName = "project"; - } else if (StringUtils.equals(resourceType, ApiResource.API_DEFINITION.name())) { - tableName = "api_definition"; - } else if (StringUtils.equals(resourceType, ApiResource.API_TEST_CASE.name())) { - tableName = "api_test_case"; - } else if (StringUtils.equals(resourceType, ApiResource.API_SCENARIO.name())) { - tableName = "api_scenario"; - } + String tableName = resourceType.toLowerCase(); commonProjectService.checkProjectHasModuleMenu(Collections.singletonList(ProjectMenuConstants.MODULE_MENU_API_TEST), resourceId, tableName); } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionMockService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionMockService.java index 8f4841ecc9..ba852ca16d 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionMockService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionMockService.java @@ -5,24 +5,25 @@ import io.metersphere.api.controller.result.ApiResultCode; import io.metersphere.api.domain.*; import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest; import io.metersphere.api.dto.definition.ApiDefinitionMockDTO; -import io.metersphere.api.dto.definition.HttpResponse; import io.metersphere.api.dto.definition.request.ApiDefinitionMockAddRequest; import io.metersphere.api.dto.definition.request.ApiDefinitionMockPageRequest; import io.metersphere.api.dto.definition.request.ApiDefinitionMockRequest; import io.metersphere.api.dto.definition.request.ApiDefinitionMockUpdateRequest; +import io.metersphere.api.dto.mockserver.MockMatchRule; +import io.metersphere.api.dto.mockserver.MockResponse; import io.metersphere.api.mapper.ApiDefinitionMapper; import io.metersphere.api.mapper.ApiDefinitionMockConfigMapper; import io.metersphere.api.mapper.ApiDefinitionMockMapper; import io.metersphere.api.mapper.ExtApiDefinitionMockMapper; import io.metersphere.api.service.ApiFileResourceService; import io.metersphere.api.utils.ApiDataUtils; -import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.project.service.ProjectService; import io.metersphere.sdk.constants.ApplicationNumScope; import io.metersphere.sdk.constants.DefaultRepositoryDir; import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.util.BeanUtils; import io.metersphere.sdk.util.FileAssociationSourceUtil; +import io.metersphere.sdk.util.JSON; import io.metersphere.system.log.constants.OperationLogModule; import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.NumGenerator; @@ -31,7 +32,6 @@ import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.Optional; @@ -68,7 +68,7 @@ public class ApiDefinitionMockService { ApiDefinitionMock apiDefinitionMock = checkApiDefinitionMock(request.getId()); ApiDefinitionMockDTO apiDefinitionMockDTO = new ApiDefinitionMockDTO(); handleMockConfig(request.getId(), apiDefinitionMockDTO); - handleApiDefinition(request.getApiDefinitionId(), apiDefinitionMockDTO); + handleApiDefinition(apiDefinitionMock.getApiDefinitionId(), apiDefinitionMockDTO); BeanUtils.copyBean(apiDefinitionMockDTO, apiDefinitionMock); return apiDefinitionMockDTO; } @@ -76,8 +76,8 @@ public class ApiDefinitionMockService { public void handleMockConfig(String id, ApiDefinitionMockDTO apiDefinitionMockDTO) { Optional apiDefinitionMockConfigOptional = Optional.ofNullable(apiDefinitionMockConfigMapper.selectByPrimaryKey(id)); apiDefinitionMockConfigOptional.ifPresent(config -> { - apiDefinitionMockDTO.setMatching(ApiDataUtils.parseObject(new String(config.getMatching()), AbstractMsTestElement.class)); - apiDefinitionMockDTO.setResponse(ApiDataUtils.parseArray(new String(config.getResponse()), HttpResponse.class)); + apiDefinitionMockDTO.setMatching(ApiDataUtils.parseObject(new String(config.getMatching()), MockMatchRule.class)); + apiDefinitionMockDTO.setResponse(ApiDataUtils.parseObject(new String(config.getResponse()), MockResponse.class)); }); } @@ -102,6 +102,13 @@ public class ApiDefinitionMockService { public ApiDefinitionMock create(ApiDefinitionMockAddRequest request, String userId) { ProjectService.checkResourceExist(request.getProjectId()); + if (request.getMockMatchRule() == null) { + request.setMockMatchRule(new MockMatchRule()); + } + if (request.getResponse() == null) { + request.setResponse(new MockResponse()); + } + ApiDefinitionMock apiDefinitionMock = new ApiDefinitionMock(); BeanUtils.copyBean(apiDefinitionMock, request); checkAddExist(apiDefinitionMock); @@ -119,8 +126,8 @@ public class ApiDefinitionMockService { apiDefinitionMockMapper.insertSelective(apiDefinitionMock); ApiDefinitionMockConfig apiDefinitionMockConfig = new ApiDefinitionMockConfig(); apiDefinitionMockConfig.setId(apiDefinitionMock.getId()); - apiDefinitionMockConfig.setMatching(request.getMatching().getBytes()); - apiDefinitionMockConfig.setResponse(request.getResponse().getBytes()); + apiDefinitionMockConfig.setMatching(JSON.toJSONString(request.getMockMatchRule()).getBytes()); + apiDefinitionMockConfig.setResponse(JSON.toJSONString(request.getResponse()).getBytes()); apiDefinitionMockConfigMapper.insertSelective(apiDefinitionMockConfig); // 处理文件 @@ -176,8 +183,12 @@ public class ApiDefinitionMockService { apiDefinitionMockMapper.updateByPrimaryKeySelective(apiDefinitionMock); ApiDefinitionMockConfig apiDefinitionMockConfig = new ApiDefinitionMockConfig(); apiDefinitionMockConfig.setId(apiDefinitionMock.getId()); - apiDefinitionMockConfig.setMatching(request.getMatching().getBytes()); - apiDefinitionMockConfig.setResponse(request.getResponse().getBytes()); + if (request.getMockMatchRule() != null) { + apiDefinitionMockConfig.setMatching(JSON.toJSONString(request.getMockMatchRule()).getBytes()); + } + if (request.getResponse() != null) { + apiDefinitionMockConfig.setResponse(JSON.toJSONString(request.getResponse()).getBytes()); + } apiDefinitionMockConfigMapper.updateByPrimaryKeySelective(apiDefinitionMockConfig); // 处理文件 @@ -244,10 +255,6 @@ public class ApiDefinitionMockService { apiDefinitionMockMapper.updateByPrimaryKeySelective(update); } - public String uploadTempFile(MultipartFile file) { - return apiFileResourceService.uploadTempFile(file); - } - public void deleteByApiIds(List apiIds, String userId) { ApiDefinitionMockExample apiDefinitionMockExample = new ApiDefinitionMockExample(); apiDefinitionMockExample.createCriteria().andApiDefinitionIdIn(apiIds); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java index f78738df3d..b94b016990 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java @@ -187,7 +187,11 @@ public class ApiDefinitionService { ApiDefinitionBlob apiDefinitionBlob = new ApiDefinitionBlob(); apiDefinitionBlob.setId(apiDefinition.getId()); apiDefinitionBlob.setRequest(getMsTestElementStr(request.getRequest()).getBytes()); - apiDefinitionBlob.setResponse(JSON.toJSONString(request.getResponse()).getBytes()); + if (request.getResponse() != null) { + List msHttpResponse = JSON.parseArray(JSON.toJSONString(request.getResponse()), HttpResponse.class); + msHttpResponse.forEach(item -> item.setId(IDGenerator.nextStr())); + apiDefinitionBlob.setResponse(JSON.toJSONString(msHttpResponse).getBytes()); + } apiDefinitionBlobMapper.insertSelective(apiDefinitionBlob); // 处理文件 diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/mockserver/MockServerService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/mockserver/MockServerService.java new file mode 100644 index 0000000000..a05eaf5017 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/mockserver/MockServerService.java @@ -0,0 +1,213 @@ +package io.metersphere.api.service.mockserver; + +import io.metersphere.api.domain.*; +import io.metersphere.api.dto.definition.HttpResponse; +import io.metersphere.api.dto.definition.ResponseBody; +import io.metersphere.api.dto.mockserver.HttpRequestParam; +import io.metersphere.api.dto.mockserver.MockResponse; +import io.metersphere.api.dto.request.http.Header; +import io.metersphere.api.dto.request.http.body.Body; +import io.metersphere.api.mapper.*; +import io.metersphere.api.utils.MockServerUtils; +import io.metersphere.project.domain.FileMetadata; +import io.metersphere.project.mapper.FileMetadataMapper; +import io.metersphere.project.service.FileManagementService; +import io.metersphere.sdk.constants.DefaultRepositoryDir; +import io.metersphere.sdk.constants.HttpMethodConstants; +import io.metersphere.sdk.file.FileCenter; +import io.metersphere.sdk.file.FileRepository; +import io.metersphere.sdk.file.FileRequest; +import io.metersphere.sdk.util.JSON; +import io.metersphere.sdk.util.LogUtils; +import io.metersphere.sdk.util.TempFileUtils; +import io.metersphere.sdk.util.Translator; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; + +@Service +@Transactional(rollbackFor = Exception.class) +public class MockServerService { + + @Resource + private ExtApiDefinitionMapper extApiDefinitionMapper; + @Resource + private ApiDefinitionBlobMapper apiDefinitionBlobMapper; + @Resource + private ApiDefinitionMockMapper apiDefinitionMockMapper; + @Resource + private ApiDefinitionMockConfigMapper apiDefinitionMockConfigMapper; + @Resource + private ApiFileResourceMapper apiFileResourceMapper; + @Resource + FileMetadataMapper fileMetadataMapper; + @Resource + FileManagementService fileManagementService; + + private boolean isUrlParamMethod(String method) { + return StringUtils.equalsAnyIgnoreCase(method, HttpMethodConstants.GET.name(), HttpMethodConstants.DELETE.name(), HttpMethodConstants.OPTIONS.name(), HttpMethodConstants.HEAD.name()); + } + + + public Object execute(String method, Map requestHeaderMap, String projectNum, String apiNumInfo, HttpServletRequest request, HttpServletResponse response) { + ApiDefinition apiDefinition = extApiDefinitionMapper.selectByProjectNumAndApiNum(projectNum, apiNumInfo); + if (apiDefinition == null) { + return this.requestNotFound(response); + } + String url = request.getRequestURL().toString(); + String requestUrlSuffix = MockServerUtils.getUrlSuffix(StringUtils.joinWith("/", "/mock-server", projectNum, apiNumInfo), request); + + if (StringUtils.equalsIgnoreCase(method, apiDefinition.getMethod()) && !MockServerUtils.checkUrlMatch(apiDefinition.getPath(), requestUrlSuffix)) { + return this.requestNotFound(response); + } + HttpRequestParam requestMockParams = MockServerUtils.getHttpRequestParam(request, requestUrlSuffix, apiDefinition.getPath(), !this.isUrlParamMethod(method)); + LogUtils.info("Mock [" + url + "] Header:{}", requestHeaderMap); + LogUtils.info("Mock [" + url + "] request:{}", JSON.toJSONString(requestMockParams)); + ApiDefinitionMockConfig compareMockConfig = this.match(apiDefinition.getId(), requestHeaderMap, requestMockParams); + try { + Object returnObj = this.getReturn(compareMockConfig, apiDefinition.getId(), apiDefinition.getProjectId(), response); + return returnObj; + } catch (Exception e) { + return this.requestNotFound(response); + } + } + + private ApiDefinitionMockConfig match(String apiId, Map requestHeaderMap, HttpRequestParam requestMockParams) { + ApiDefinitionMockConfig compareMockConfig = null; + ApiDefinitionMockExample mockExample = new ApiDefinitionMockExample(); + mockExample.createCriteria().andApiDefinitionIdEqualTo(apiId).andEnableEqualTo(true); + List apiDefinitionMockList = apiDefinitionMockMapper.selectByExample(mockExample); + if (CollectionUtils.isNotEmpty(apiDefinitionMockList)) { + ApiDefinitionMockConfigExample mockConfigExample = new ApiDefinitionMockConfigExample(); + mockConfigExample.createCriteria().andIdIn(apiDefinitionMockList.stream().map(ApiDefinitionMock::getId).toList()); + List mockConfigs = apiDefinitionMockConfigMapper.selectByExampleWithBLOBs(mockConfigExample); + for (ApiDefinitionMockConfig mockConfig : mockConfigs) { + if (MockServerUtils.matchMockConfig(mockConfig.getMatching(), requestHeaderMap, requestMockParams)) { + compareMockConfig = mockConfig; + break; + } + } + } + return compareMockConfig; + } + + private Object getReturn(ApiDefinitionMockConfig compareMockConfig, String apiId, String projectId, HttpServletResponse response) { + ResponseBody responseBody = null; + List
responseHeader = null; + int responseCode = -1; + String useApiResponseId = null; + + if (compareMockConfig != null) { + MockResponse mockResponse = JSON.parseObject(new String(compareMockConfig.getResponse()), MockResponse.class); + if (mockResponse.isUseApiResponse()) { + useApiResponseId = mockResponse.getApiResponseId(); + } else { + responseCode = mockResponse.getStatusCode(); + responseHeader = mockResponse.getHeaders(); + responseBody = mockResponse.getBody(); + } + } + if (StringUtils.isNotBlank(useApiResponseId) || responseCode == -1) { + HttpResponse mockSelectResponse = null; + ApiDefinitionBlob blob = apiDefinitionBlobMapper.selectByPrimaryKey(apiId); + if (blob != null) { + List responseList = JSON.parseArray(new String(blob.getResponse()), HttpResponse.class); + HttpResponse defaultHttpResponse = null; + for (HttpResponse httpResponse : responseList) { + if (httpResponse.isDefaultFlag()) { + defaultHttpResponse = httpResponse; + } + if (StringUtils.equals(httpResponse.getId(), useApiResponseId)) { + mockSelectResponse = httpResponse; + break; + } + } + if (mockSelectResponse == null) { + mockSelectResponse = defaultHttpResponse; + } + } + if (mockSelectResponse == null) { + return this.requestNotFound(response); + } else { + responseCode = Integer.parseInt(mockSelectResponse.getStatusCode()); + responseHeader = mockSelectResponse.getHeaders(); + responseBody = mockSelectResponse.getBody(); + } + } + + //返回响应码 + response.setStatus(responseCode); + if (CollectionUtils.isNotEmpty(responseHeader)) { + responseHeader.forEach(header -> { + if (header.getEnable()) { + response.addHeader(header.getKey(), header.getValue()); + } + }); + } + if (responseBody == null) { + return StringUtils.EMPTY; + } else { + if (StringUtils.equalsIgnoreCase(responseBody.getBodyType(), Body.BodyType.JSON.name())) { + return responseBody.getJsonBody().getJsonValue(); + } else if (StringUtils.equalsIgnoreCase(responseBody.getBodyType(), Body.BodyType.XML.name())) { + return responseBody.getXmlBody().getValue(); + } else if (StringUtils.equalsIgnoreCase(responseBody.getBodyType(), Body.BodyType.RAW.name())) { + return responseBody.getRawBody().getValue(); + } else { + String fileId = responseBody.getBinaryBody().getFileId(); + String fileName = responseBody.getBinaryBody().getFileName(); + String fileType = StringUtils.substring(fileName, fileName.lastIndexOf(".") + 1); + MediaType mediaType = MediaType.parseMediaType("application/octet-stream"); + if (responseBody.getBinaryBody().isSendAsBody()) { + String contentType = "text/plain"; + if (TempFileUtils.isImage(fileType)) { + contentType = "image/" + fileType; + } + mediaType = MediaType.parseMediaType(contentType); + } + byte[] bytes = new byte[0]; + FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(fileId); + if (fileMetadata != null) { + try { + String filePath = TempFileUtils.createFile(TempFileUtils.getTmpFilePath(fileMetadata.getId()), fileManagementService.getFile(fileMetadata)); + bytes = TempFileUtils.getFile(filePath); + } catch (Exception ignore) { + return StringUtils.EMPTY; + } + } else { + ApiFileResource apiFileResource = apiFileResourceMapper.selectByPrimaryKey(compareMockConfig.getId(), fileId); + if (apiFileResource != null) { + FileRepository defaultRepository = FileCenter.getDefaultRepository(); + FileRequest fileRequest = new FileRequest(); + fileRequest.setFileName(apiFileResource.getFileName()); + fileRequest.setFolder(DefaultRepositoryDir.getApiDefinitionDir(projectId, compareMockConfig.getId()) + "/" + fileId); + try { + bytes = defaultRepository.getFile(fileRequest); + } catch (Exception ignore) { + } + } + } + + return ResponseEntity.ok() + .contentType(mediaType) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"") + .body(bytes); + } + } + } + + private String requestNotFound(HttpServletResponse response) { + response.setStatus(404); + return Translator.get("mock_warning"); + } +} \ No newline at end of file diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/utils/MockServerUtils.java b/backend/services/api-test/src/main/java/io/metersphere/api/utils/MockServerUtils.java new file mode 100644 index 0000000000..cd9f88c92b --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/utils/MockServerUtils.java @@ -0,0 +1,179 @@ +package io.metersphere.api.utils; + +import io.metersphere.api.dto.mockserver.HttpRequestParam; +import io.metersphere.api.dto.mockserver.MockMatchRule; +import io.metersphere.api.dto.request.http.body.Body; +import io.metersphere.sdk.util.JSON; +import io.metersphere.sdk.util.LogUtils; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +public class MockServerUtils { + + public static Map getHttpRequestHeader(HttpServletRequest request) { + Map returnMap = new HashMap<>(); + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String header = headers.nextElement(); + String headerValue = request.getHeader(header); + returnMap.put(header, headerValue); + } + return returnMap; + } + + public static HttpRequestParam getHttpRequestParam(HttpServletRequest request, String requestUrlSuffix, String apiDefinitionPath, boolean isPost) { + HttpRequestParam requestParam = new HttpRequestParam(); + requestParam.setPost(isPost); + + LinkedHashMap restParamMap = MockServerUtils.getRestParam(apiDefinitionPath, requestUrlSuffix); + requestParam.setRestParams(restParamMap); + + //解析k-v参数 + LinkedHashMap queryParamsMap = new LinkedHashMap<>(); + Enumeration paramNameItr = request.getParameterNames(); + while (paramNameItr.hasMoreElements()) { + String key = paramNameItr.nextElement(); + String value = request.getParameter(key); + queryParamsMap.put(key, value); + } + requestParam.setQueryParamsObj(queryParamsMap); + + //解析body参数 + String requestPostString = getRequestStr(request); + requestParam.setRaw(requestPostString); + + //解析paramType + if (StringUtils.startsWithIgnoreCase(request.getContentType(), "application/json")) { + requestParam.setJsonParam(requestPostString); + } else if (StringUtils.endsWith(request.getContentType(), "/xml")) { + requestParam.setXmlParam(requestPostString); + } else if (StringUtils.startsWithIgnoreCase(request.getContentType(), "application/x-www-form-urlencoded")) { + requestParam.setParamType(Body.BodyType.FORM_DATA.name()); + } else if (StringUtils.startsWithIgnoreCase(request.getContentType(), "text/plain")) { + requestParam.setParamType(Body.BodyType.RAW.name()); + } else if (isPost) { + requestParam.setParamType(Body.BodyType.RAW.name()); + } + return requestParam; + } + + public static LinkedHashMap getRestParam(String apiPath, String requestUrl) { + LinkedHashMap restParams = new LinkedHashMap<>(); + if (StringUtils.isNotEmpty(apiPath)) { + if (apiPath.startsWith("/")) { + apiPath = apiPath.substring(1); + } + if (requestUrl.startsWith("/")) { + requestUrl = requestUrl.substring(1); + } + String[] pathArr = apiPath.split("/"); + String[] sendParamArr = requestUrl.split("/"); + + //获取 url的<参数名-参数值>,通过匹配api的接口设置和实际发送的url + for (int i = 0; i < pathArr.length; i++) { + String param = pathArr[i]; + if (param.startsWith("{") && param.endsWith("}")) { + param = param.substring(1, param.length() - 1); + String value = StringUtils.EMPTY; + if (sendParamArr.length > i) { + value = sendParamArr[i]; + } + restParams.put(param, value); + } + } + } + return restParams; + } + + private static String getRequestStr(HttpServletRequest request) { + String inputLine; + // 接收到的数据 + StringBuilder receiveData = new StringBuilder(); + try (BufferedReader in = new BufferedReader(new InputStreamReader( + request.getInputStream(), StandardCharsets.UTF_8))) { + while ((inputLine = in.readLine()) != null) { + receiveData.append(inputLine); + } + } catch (IOException ignored) { + } + + return receiveData.toString(); + } + + public static String getUrlSuffix(String mockUrlInfo, HttpServletRequest request) { + String requestUri = request.getRequestURI(); + String[] urlParamArr = requestUri.split(mockUrlInfo); + return urlParamArr.length == 0 ? "" : urlParamArr[urlParamArr.length - 1]; + } + + public static boolean checkUrlMatch(String apiDefinitionPath, String requestUrlSuffix) { + if (StringUtils.equalsAny(apiDefinitionPath, requestUrlSuffix, "/" + requestUrlSuffix)) { + return true; + } else { + if (StringUtils.isNotEmpty(apiDefinitionPath)) { + + String urlSuffix = requestUrlSuffix; + //去掉前缀的“/" + if (urlSuffix.startsWith("/")) { + urlSuffix = urlSuffix.substring(1); + } + if (apiDefinitionPath.startsWith("/")) { + apiDefinitionPath = apiDefinitionPath.substring(1); + } + + //如果请求后缀以"/"结尾,需要特殊处理 + boolean urlSuffixEndEmpty = false; + if (urlSuffix.endsWith("/")) { + urlSuffixEndEmpty = true; + urlSuffix = urlSuffix + "emptyStrForSplit"; + } + String[] requestUrlDomainArr = urlSuffix.split("/"); + if (urlSuffixEndEmpty) { + requestUrlDomainArr[requestUrlDomainArr.length - 1] = StringUtils.EMPTY; + } + urlSuffixEndEmpty = false; + if (apiDefinitionPath.endsWith("/")) { + urlSuffixEndEmpty = true; + apiDefinitionPath = apiDefinitionPath + "emptyStrForSplit"; + } + String[] apiPathDomainArr = apiDefinitionPath.split("/"); + if (urlSuffixEndEmpty) { + apiPathDomainArr[apiPathDomainArr.length - 1] = StringUtils.EMPTY; + } + + if (apiPathDomainArr.length == requestUrlDomainArr.length) { + boolean isFetch = true; + for (int i = 0; i < requestUrlDomainArr.length; i++) { + String pathItem = apiPathDomainArr[i]; + if (!(pathItem.startsWith("{") && pathItem.endsWith("}"))) { + if (!StringUtils.equals(apiPathDomainArr[i], requestUrlDomainArr[i])) { + return false; + } + } + } + return isFetch; + } + } + } + return false; + } + + public static boolean matchMockConfig(byte[] mockMatchBytes, Map requestHeaderMap, HttpRequestParam httpRequestParam) { + try { + MockMatchRule matchRule = JSON.parseObject(new String(mockMatchBytes), MockMatchRule.class); + return matchRule.keyValueMatch("header", requestHeaderMap) && matchRule.requestParamMatch(httpRequestParam); + } catch (Exception e) { + LogUtils.info(e.getMessage()); + } + return false; + } +} diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionMockControllerTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionMockControllerTests.java index 2714daf9b5..03d2231747 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionMockControllerTests.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionMockControllerTests.java @@ -1,57 +1,68 @@ package io.metersphere.api.controller; +import io.metersphere.api.constants.ApiConstants; +import io.metersphere.api.constants.ApiDefinitionStatus; import io.metersphere.api.controller.result.ApiResultCode; -import io.metersphere.api.domain.ApiDefinition; -import io.metersphere.api.domain.ApiDefinitionMock; -import io.metersphere.api.domain.ApiDefinitionMockConfig; -import io.metersphere.api.domain.ApiFileResource; -import io.metersphere.api.dto.definition.ApiDefinitionAddRequest; -import io.metersphere.api.dto.definition.ApiDefinitionMockDTO; -import io.metersphere.api.dto.definition.HttpResponse; +import io.metersphere.api.domain.*; +import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.definition.request.ApiDefinitionMockAddRequest; import io.metersphere.api.dto.definition.request.ApiDefinitionMockPageRequest; import io.metersphere.api.dto.definition.request.ApiDefinitionMockRequest; import io.metersphere.api.dto.definition.request.ApiDefinitionMockUpdateRequest; +import io.metersphere.api.dto.mockserver.KeyValueInfo; +import io.metersphere.api.dto.mockserver.MockMatchRule; +import io.metersphere.api.dto.mockserver.MockResponse; +import io.metersphere.api.dto.request.http.Header; import io.metersphere.api.dto.request.http.MsHTTPElement; -import io.metersphere.api.mapper.ApiDefinitionMapper; -import io.metersphere.api.mapper.ApiDefinitionMockConfigMapper; -import io.metersphere.api.mapper.ApiDefinitionMockMapper; +import io.metersphere.api.dto.request.http.body.Body; +import io.metersphere.api.mapper.*; import io.metersphere.api.service.ApiFileResourceService; +import io.metersphere.api.service.MockServerTestService; import io.metersphere.api.utils.ApiDataUtils; -import io.metersphere.plugin.api.spi.AbstractMsTestElement; -import io.metersphere.project.dto.filemanagement.FileInfo; +import io.metersphere.project.domain.FileMetadata; import io.metersphere.project.dto.filemanagement.request.FileUploadRequest; -import io.metersphere.project.service.FileAssociationService; +import io.metersphere.project.mapper.ExtBaseProjectVersionMapper; +import io.metersphere.project.mapper.FileMetadataMapper; +import io.metersphere.project.service.FileManagementService; import io.metersphere.project.service.FileMetadataService; import io.metersphere.sdk.constants.DefaultRepositoryDir; +import io.metersphere.sdk.constants.HttpMethodConstants; +import io.metersphere.sdk.constants.ModuleConstants; import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.file.FileCenter; +import io.metersphere.sdk.file.FileRepository; import io.metersphere.sdk.file.FileRequest; import io.metersphere.sdk.util.BeanUtils; -import io.metersphere.sdk.util.CommonBeanFactory; import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.LogUtils; +import io.metersphere.sdk.util.TempFileUtils; import io.metersphere.system.base.BaseTest; import io.metersphere.system.controller.handler.ResultHolder; import io.metersphere.system.controller.handler.result.MsHttpResultCode; import io.metersphere.system.log.constants.OperationLogType; 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.collections4.MapUtils; import org.junit.jupiter.api.*; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; +import java.security.MessageDigest; +import java.util.*; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) @@ -71,26 +82,60 @@ public class ApiDefinitionMockControllerTests extends BaseTest { private static final String UPLOAD_TEMP_FILE = BASE_PATH + "/upload/temp/file"; private static final String DEFAULT_API_ID = "1001"; + private static Long NO_MOCK_NO_RESPONSE_API_NUM; private static ApiDefinitionMock apiDefinitionMock; + @Resource + private MockServerTestService mockServerTestService; @Resource private ApiDefinitionMapper apiDefinitionMapper; @Resource + private ApiDefinitionBlobMapper apiDefinitionBlobMapper; + @Resource private ApiDefinitionMockMapper apiDefinitionMockMapper; - @Resource private ApiDefinitionMockConfigMapper apiDefinitionMockConfigMapper; - + @Resource + private ExtBaseProjectVersionMapper extBaseProjectVersionMapper; @Resource private ApiFileResourceService apiFileResourceService; - + @Resource + private FileMetadataMapper fileMetadataMapper; + @Resource + private FileManagementService fileManagementService; @Resource private FileMetadataService fileMetadataService; + @Resource + private ApiFileResourceMapper apiFileResourceMapper; + + //文件管理中已存在的ID private static String fileMetadataId; private static String uploadFileId; + private static Map METHOD_API_MAP = new LinkedHashMap<>(); + private static Map> API_MOCK_MAP = new LinkedHashMap<>(); + private static String[] HTTP_METHODS = {"POST", "GET", "HEAD", "PUT", "PATCH", "DELETE", "OPTIONS", "TRACE"}; + + /** + * 文件管理插入一条数据 + * 便于测试关联文件 + */ + private void uploadFileMetadata() throws Exception { + FileUploadRequest fileUploadRequest = new FileUploadRequest(); + fileUploadRequest.setProjectId(DEFAULT_PROJECT_ID); + //导入正常文件 + MockMultipartFile file = new MockMultipartFile("file", "mock_file_upload.JPG", MediaType.APPLICATION_OCTET_STREAM_VALUE, "file-metadata.file".getBytes()); + fileMetadataId = fileMetadataService.upload(fileUploadRequest, "admin", file); + } + + public String doUploadTempFile(MockMultipartFile file) throws Exception { + return JSON.parseObject(requestUploadFileWithOkAndReturn(UPLOAD_TEMP_FILE, file) + .getResponse() + .getContentAsString(), ResultHolder.class) + .getData().toString(); + } @Test @Order(0) @@ -98,7 +143,7 @@ public class ApiDefinitionMockControllerTests extends BaseTest { // 准备数据,上传文件管理文件 uploadFileMetadata(); // @@请求成功 - MockMultipartFile file = getMockMultipartFile("file_upload.JPG"); + MockMultipartFile file = mockServerTestService.getMockMultipartFile("file_upload.JPG"); String fileId = doUploadTempFile(file); // 校验文件存在 @@ -109,95 +154,69 @@ public class ApiDefinitionMockControllerTests extends BaseTest { requestUploadPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_MOCK_ADD, UPLOAD_TEMP_FILE, file); requestUploadPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_MOCK_UPDATE, UPLOAD_TEMP_FILE, file); - } - private String doUploadTempFile(MockMultipartFile file) throws Exception { - return JSON.parseObject(requestUploadFileWithOkAndReturn(UPLOAD_TEMP_FILE, file) - .getResponse() - .getContentAsString(), ResultHolder.class) - .getData().toString(); - } + // 这个api是用于测试没有配置任何mock以及默认响应的情况 + String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(DEFAULT_PROJECT_ID); + ApiDefinitionAddRequest noMockNoResponseApiRequest = new ApiDefinitionAddRequest(); + noMockNoResponseApiRequest.setName("MockApi_No_Response"); + noMockNoResponseApiRequest.setProtocol(ApiConstants.HTTP_PROTOCOL); + noMockNoResponseApiRequest.setProjectId(DEFAULT_PROJECT_ID); + noMockNoResponseApiRequest.setMethod("GET"); + noMockNoResponseApiRequest.setPath("/mock/api/notMatch/"); + noMockNoResponseApiRequest.setStatus(ApiDefinitionStatus.PREPARE.getValue()); + noMockNoResponseApiRequest.setModuleId(ModuleConstants.DEFAULT_NODE_ID); + noMockNoResponseApiRequest.setVersionId(defaultVersion); + noMockNoResponseApiRequest.setDescription("desc"); + noMockNoResponseApiRequest.setRequest(JSON.parseObject(ApiDataUtils.toJSONString(MsHTTPElementTest.getMsHttpElement()))); + noMockNoResponseApiRequest.setResponse(new ArrayList<>()); + NO_MOCK_NO_RESPONSE_API_NUM = getResultData(this.requestPostWithOkAndReturn("/api/definition/add", noMockNoResponseApiRequest), ApiDefinition.class).getNum(); - private static MockMultipartFile getMockMultipartFile(String fileName) { - return new MockMultipartFile( - "file", - fileName, - MediaType.APPLICATION_OCTET_STREAM_VALUE, - "Hello, World!".getBytes() - ); - } - - /** - * 文件管理插入一条数据 - * 便于测试关联文件 - */ - private void uploadFileMetadata() throws Exception { - FileUploadRequest fileUploadRequest = new FileUploadRequest(); - fileUploadRequest.setProjectId(DEFAULT_PROJECT_ID); - //导入正常文件 - MockMultipartFile file = new MockMultipartFile("file", "mock_file_upload.JPG", MediaType.APPLICATION_OCTET_STREAM_VALUE, "mock".getBytes()); - fileMetadataId = fileMetadataService.upload(fileUploadRequest, "admin", file); - } - - /** - * 校验上传的文件 - * @param id - * @param fileIds 全部的文件ID - */ - public static void assertUploadFile(String id, List fileIds) throws Exception { - if (fileIds != null) { - ApiFileResourceService apiFileResourceService = CommonBeanFactory.getBean(ApiFileResourceService.class); - // 验证文件的关联关系,以及是否存入对象存储 - List apiFileResources = apiFileResourceService.getByResourceId(id); - Assertions.assertEquals(apiFileResources.size(), fileIds.size()); - - String apiDefinitionDir = DefaultRepositoryDir.getApiDefinitionDir(DEFAULT_PROJECT_ID, id); - FileRequest fileRequest = new FileRequest(); - if (!fileIds.isEmpty()) { - for (ApiFileResource apiFileResource : apiFileResources) { - Assertions.assertEquals(DEFAULT_PROJECT_ID, apiFileResource.getProjectId()); - fileRequest.setFolder(apiDefinitionDir + "/" + apiFileResource.getFileId()); - fileRequest.setFileName(apiFileResource.getFileName()); - Assertions.assertNotNull(FileCenter.getDefaultRepository().getFile(fileRequest)); - } - fileRequest.setFolder(apiDefinitionDir); - } else { - fileRequest.setFolder(apiDefinitionDir); - Assertions.assertTrue(CollectionUtils.isEmpty(FileCenter.getDefaultRepository().getFolderFileNames(fileRequest))); + if (MapUtils.isEmpty(METHOD_API_MAP)) { + for (String method : HTTP_METHODS) { + // 创建并返回一个 ApiDefinitionAddRequest 对象,用于测试 + ApiDefinitionAddRequest request = new ApiDefinitionAddRequest(); + request.setName("MockApi:" + method); + request.setProtocol(ApiConstants.HTTP_PROTOCOL); + request.setProjectId(DEFAULT_PROJECT_ID); + request.setMethod(method); + request.setPath("/mock/api/" + method + "/{param1}/{param2}"); + request.setStatus(ApiDefinitionStatus.PREPARE.getValue()); + request.setModuleId(ModuleConstants.DEFAULT_NODE_ID); + request.setVersionId(defaultVersion); + request.setDescription("desc"); + MsHTTPElement msHttpElement = MsHTTPElementTest.getMsHttpElement(); + request.setRequest(JSON.parseObject(ApiDataUtils.toJSONString(msHttpElement))); + request.setResponse(MsHTTPElementTest.get2MsHttpResponse(request.getPath())); + MvcResult mvcResult = this.requestPostWithOkAndReturn("/api/definition/add", request); + ApiDefinition resultData = getResultData(mvcResult, ApiDefinition.class); + METHOD_API_MAP.put(method, resultData); } } } - /** - * 校验上传的文件 - * @param id - * @param fileIds 全部的文件ID - */ - private static void assertLinkFile(String id, List fileIds) { - FileAssociationService fileAssociationService = CommonBeanFactory.getBean(FileAssociationService.class); - List linkFileIds = fileAssociationService.getFiles(id) - .stream() - .map(FileInfo::getFileId) - .toList(); - Assertions.assertEquals(fileIds, linkFileIds); - } @Test @Order(1) @Sql(scripts = {"/dml/init_api_definition.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) public void testAdd() throws Exception { + LogUtils.info("create api mock test"); // 创建测试数据 ApiDefinitionMockAddRequest request = new ApiDefinitionMockAddRequest(); request.setName("接口定义test"); request.setProjectId(DEFAULT_PROJECT_ID); request.setApiDefinitionId(DEFAULT_API_ID); - MsHTTPElement msHttpElement = MsHTTPElementTest.getMsHttpElement(); - request.setMatching(ApiDataUtils.toJSONString(msHttpElement)); - List msHttpResponse = MsHTTPElementTest.getMsHttpResponse(); - request.setResponse(ApiDataUtils.toJSONString(msHttpResponse)); - - uploadFileId = doUploadTempFile(getMockMultipartFile("file_upload.JPG")); + MockMatchRule mockMatchRule = new MockMatchRule(); + request.setMockMatchRule(mockMatchRule); + uploadFileId = doUploadTempFile(mockServerTestService.getMockMultipartFile("file_upload.JPG")); + MockResponse mockResponse = new MockResponse(); + mockResponse.setBody(new ResponseBody() {{ + this.setBinaryBody(new ResponseBinaryBody() {{ + this.setFileId(uploadFileId); + this.setFileName("file_upload.JPG"); + }}); + }}); + request.setResponse(mockResponse); request.setUploadFileIds(List.of(uploadFileId)); request.setLinkFileIds(List.of(fileMetadataId)); @@ -205,9 +224,9 @@ public class ApiDefinitionMockControllerTests extends BaseTest { MvcResult mvcResult = this.requestPostWithOkAndReturn(ADD, request); // 校验请求成功数据 ApiDefinitionMock resultData = getResultData(mvcResult, ApiDefinitionMock.class); - apiDefinitionMock = assertAddApiDefinitionMock(request, msHttpElement, resultData.getId()); - assertUploadFile(apiDefinitionMock.getId(), List.of(uploadFileId)); - assertLinkFile(apiDefinitionMock.getId(), List.of(fileMetadataId)); + apiDefinitionMock = mockServerTestService.assertAddApiDefinitionMock(request, mockMatchRule, resultData.getId()); + mockServerTestService.assertUploadFile(apiDefinitionMock.getId(), List.of(uploadFileId)); + mockServerTestService.assertLinkFile(apiDefinitionMock.getId(), List.of(fileMetadataId)); // 再插入一条数据,便于修改时重名校验 request.setName("重名接口定义test"); @@ -216,7 +235,7 @@ public class ApiDefinitionMockControllerTests extends BaseTest { request.setLinkFileIds(null); mvcResult = this.requestPostWithOkAndReturn(ADD, request); resultData = getResultData(mvcResult, ApiDefinitionMock.class); - assertAddApiDefinitionMock(request, msHttpElement, resultData.getId()); + mockServerTestService.assertAddApiDefinitionMock(request, mockMatchRule, resultData.getId()); // @@重名校验异常 assertErrorCode(this.requestPost(ADD, request), ApiResultCode.API_DEFINITION_MOCK_EXIST); @@ -233,19 +252,9 @@ public class ApiDefinitionMockControllerTests extends BaseTest { request.setProjectId(DEFAULT_PROJECT_ID); request.setName("permission-st-6"); requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_MOCK_ADD, ADD, request); + } - private ApiDefinitionMock assertAddApiDefinitionMock(Object request, MsHTTPElement msHttpElement, String id) { - ApiDefinitionMock apiDefinitionMock = apiDefinitionMockMapper.selectByPrimaryKey(id); - ApiDefinitionMockConfig apiDefinitionMockConfig = apiDefinitionMockConfigMapper.selectByPrimaryKey(id); - ApiDefinitionMock copyApiDefinitionMock = BeanUtils.copyBean(new ApiDefinitionMock(), apiDefinitionMock); - BeanUtils.copyBean(copyApiDefinitionMock, request); - Assertions.assertEquals(apiDefinitionMock, copyApiDefinitionMock); - if(apiDefinitionMockConfig != null){ - Assertions.assertEquals(msHttpElement, ApiDataUtils.parseObject(new String(apiDefinitionMockConfig.getMatching()), AbstractMsTestElement.class)); - } - return apiDefinitionMock; - } @Test @Order(2) @@ -253,7 +262,6 @@ public class ApiDefinitionMockControllerTests extends BaseTest { ApiDefinitionMockRequest apiDefinitionMockRequest = new ApiDefinitionMockRequest(); apiDefinitionMockRequest.setId(apiDefinitionMock.getId()); apiDefinitionMockRequest.setProjectId(DEFAULT_PROJECT_ID); - apiDefinitionMockRequest.setApiDefinitionId(apiDefinitionMock.getApiDefinitionId()); // @@请求成功 MvcResult mvcResult = this.requestPostWithOkAndReturn(DETAIL, apiDefinitionMockRequest); ApiDefinitionMockDTO apiDefinitionMockDTO = ApiDataUtils.parseObject(JSON.toJSONString(parseResponse(mvcResult).get("data")), ApiDefinitionMockDTO.class); @@ -269,8 +277,8 @@ public class ApiDefinitionMockControllerTests extends BaseTest { ApiDefinitionMockConfig apiDefinitionMockConfig = apiDefinitionMockConfigMapper.selectByPrimaryKey(apiDefinitionMock.getId()); if(apiDefinitionMockConfig != null){ - copyApiDefinitionMockDTO.setMatching(ApiDataUtils.parseObject(new String(apiDefinitionMockConfig.getMatching()), AbstractMsTestElement.class)); - copyApiDefinitionMockDTO.setResponse(ApiDataUtils.parseArray(new String(apiDefinitionMockConfig.getResponse()), HttpResponse.class)); + copyApiDefinitionMockDTO.setMatching(ApiDataUtils.parseObject(new String(apiDefinitionMockConfig.getMatching()), MockMatchRule.class)); + copyApiDefinitionMockDTO.setResponse(ApiDataUtils.parseObject(new String(apiDefinitionMockConfig.getResponse()), MockResponse.class)); } Assertions.assertEquals(apiDefinitionMockDTO, copyApiDefinitionMockDTO); @@ -287,14 +295,12 @@ public class ApiDefinitionMockControllerTests extends BaseTest { public void testUpdate() throws Exception { LogUtils.info("update api mock test"); + MockMatchRule mockMatchRule = new MockMatchRule(); ApiDefinitionMockUpdateRequest request = new ApiDefinitionMockUpdateRequest(); BeanUtils.copyBean(request, apiDefinitionMock); request.setName("test1test1test1test1test1test1"); - - MsHTTPElement msHttpElement = MsHTTPElementTest.getMsHttpElement(); - request.setMatching(ApiDataUtils.toJSONString(msHttpElement)); - List msHttpResponse = MsHTTPElementTest.getMsHttpResponse(); - request.setResponse(ApiDataUtils.toJSONString(msHttpResponse)); + request.setMockMatchRule(mockMatchRule); + request.setResponse(new MockResponse()); // 清除文件的更新 request.setUnLinkRefIds(List.of(fileMetadataId)); @@ -302,43 +308,43 @@ public class ApiDefinitionMockControllerTests extends BaseTest { this.requestPostWithOk(UPDATE, request); // 校验请求成功数据 - apiDefinitionMock = assertAddApiDefinitionMock(request, msHttpElement, request.getId()); - assertUploadFile(apiDefinitionMock.getId(), List.of()); - assertLinkFile(apiDefinitionMock.getId(), List.of()); + apiDefinitionMock = mockServerTestService.assertAddApiDefinitionMock(request, mockMatchRule, request.getId()); + mockServerTestService.assertUploadFile(apiDefinitionMock.getId(), List.of()); + mockServerTestService.assertLinkFile(apiDefinitionMock.getId(), List.of()); // 带文件的更新 - String fileId = doUploadTempFile(getMockMultipartFile("file_upload.JPG")); + String fileId = doUploadTempFile(mockServerTestService.getMockMultipartFile("file_upload.JPG")); request.setUploadFileIds(List.of(fileId)); request.setLinkFileIds(List.of(fileMetadataId)); request.setDeleteFileIds(null); request.setUnLinkRefIds(null); this.requestPostWithOk(UPDATE, request); // 校验请求成功数据 - apiDefinitionMock = assertAddApiDefinitionMock(request, msHttpElement, request.getId()); - assertUploadFile(apiDefinitionMock.getId(), List.of(fileId)); - assertLinkFile(apiDefinitionMock.getId(), List.of(fileMetadataId)); + apiDefinitionMock = mockServerTestService.assertAddApiDefinitionMock(request, mockMatchRule, request.getId()); + mockServerTestService.assertUploadFile(apiDefinitionMock.getId(), List.of(fileId)); + mockServerTestService.assertLinkFile(apiDefinitionMock.getId(), List.of(fileMetadataId)); // 删除了上一次上传的文件,重新上传一个文件 request.setDeleteFileIds(List.of(fileId)); - String newFileId1 = doUploadTempFile(getMockMultipartFile("file_upload.JPG")); + String newFileId1 = doUploadTempFile(mockServerTestService.getMockMultipartFile("file_upload.JPG")); request.setUploadFileIds(List.of(newFileId1)); request.setUnLinkRefIds(List.of(fileMetadataId)); request.setLinkFileIds(List.of(fileMetadataId)); this.requestPostWithOk(UPDATE, request); - apiDefinitionMock = assertAddApiDefinitionMock(request, msHttpElement, request.getId()); - assertUploadFile(apiDefinitionMock.getId(), List.of(newFileId1)); - assertLinkFile(apiDefinitionMock.getId(), List.of(fileMetadataId)); + apiDefinitionMock = mockServerTestService.assertAddApiDefinitionMock(request, mockMatchRule, request.getId()); + mockServerTestService.assertUploadFile(apiDefinitionMock.getId(), List.of(newFileId1)); + mockServerTestService.assertLinkFile(apiDefinitionMock.getId(), List.of(fileMetadataId)); // 已有一个文件,再上传一个文件 - String newFileId2 = doUploadTempFile(getMockMultipartFile("file_update_upload.JPG")); + String newFileId2 = doUploadTempFile(mockServerTestService.getMockMultipartFile("file_update_upload.JPG")); request.setUploadFileIds(List.of(newFileId2)); request.setUnLinkRefIds(null); request.setDeleteFileIds(null); request.setLinkFileIds(null); this.requestPostWithOk(UPDATE, request); - apiDefinitionMock = assertAddApiDefinitionMock(request, msHttpElement, request.getId()); - assertUploadFile(apiDefinitionMock.getId(), List.of(newFileId1, newFileId2)); - assertLinkFile(apiDefinitionMock.getId(), List.of(fileMetadataId)); + apiDefinitionMock = mockServerTestService.assertAddApiDefinitionMock(request, mockMatchRule, request.getId()); + mockServerTestService.assertUploadFile(apiDefinitionMock.getId(), List.of(newFileId1, newFileId2)); + mockServerTestService.assertLinkFile(apiDefinitionMock.getId(), List.of(fileMetadataId)); // 修改 tags request.setUploadFileIds(null); request.setUnLinkRefIds(null); @@ -346,14 +352,10 @@ public class ApiDefinitionMockControllerTests extends BaseTest { request.setLinkFileIds(null); request.setTags(new LinkedHashSet<>(List.of("tag1", "tag2-update"))); request.setName("接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test接口定义test"); - MsHTTPElement msHttpElementTag = MsHTTPElementTest.getMsHttpElement(); - request.setMatching(ApiDataUtils.toJSONString(msHttpElementTag)); - List msHttpResponseTag = MsHTTPElementTest.getMsHttpResponse(); - request.setResponse(ApiDataUtils.toJSONString(msHttpResponseTag)); this.requestPostWithOk(UPDATE, request); // 校验请求成功数据 - assertAddApiDefinitionMock(request, msHttpElement, request.getId()); + mockServerTestService.assertAddApiDefinitionMock(request, mockMatchRule, request.getId()); request.setName("重名接口定义test"); // @@重名校验异常 @@ -367,7 +369,7 @@ public class ApiDefinitionMockControllerTests extends BaseTest { // 校验数据是否存在 request.setId("111"); request.setName("test123"); - assertErrorCode(this.requestPost(UPDATE, request), MsHttpResultCode.NOT_FOUND); + this.requestPost(UPDATE, request).andExpect(status().is5xxServerError()); // @@校验日志 checkLog(apiDefinitionMock.getId(), OperationLogType.UPDATE, UPDATE); @@ -393,7 +395,7 @@ public class ApiDefinitionMockControllerTests extends BaseTest { // @@校验日志 checkLog(apiDefinitionMock.getId(), OperationLogType.UPDATE, ENABLE + apiDefinitionMock.getId()); - assertErrorCode(this.requestGet(ENABLE + "111"), MsHttpResultCode.NOT_FOUND); + assertErrorCode(this.requestGet(ENABLE + "111"), MsHttpResultCode.FAILED); // @@开启 // @@请求成功 @@ -403,7 +405,7 @@ public class ApiDefinitionMockControllerTests extends BaseTest { // @@校验日志 checkLog(apiDefinitionMock.getId(), OperationLogType.UPDATE, ENABLE + apiDefinitionMock.getId()); - assertErrorCode(this.requestGet(ENABLE + "111"), MsHttpResultCode.NOT_FOUND); + assertErrorCode(this.requestGet(ENABLE + "111"), MsHttpResultCode.FAILED); // @@校验权限 requestGetPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_MOCK_UPDATE, ENABLE + apiDefinitionMock.getId()); } @@ -415,7 +417,6 @@ public class ApiDefinitionMockControllerTests extends BaseTest { ApiDefinitionMockRequest request = new ApiDefinitionMockRequest(); request.setId(apiDefinitionMock.getId()); request.setProjectId(DEFAULT_PROJECT_ID); - request.setApiDefinitionId(apiDefinitionMock.getApiDefinitionId()); MvcResult mvcResult = this.requestPostWithOkAndReturn(COPY, request); ApiDefinitionMock resultData = getResultData(mvcResult, ApiDefinitionMock.class); // @数据验证 @@ -426,28 +427,10 @@ public class ApiDefinitionMockControllerTests extends BaseTest { } Assertions.assertTrue(resultData.getName().contains("copy_")); - ApiDefinitionMockUpdateRequest apiDefinitionMockUpdateRequest = new ApiDefinitionMockUpdateRequest(); - BeanUtils.copyBean(apiDefinitionMockUpdateRequest, apiDefinitionMock); - apiDefinitionMockUpdateRequest.setName("test1test1test1test1test1test1"); - - MsHTTPElement msHttpElement = MsHTTPElementTest.getMsHttpElement(); - apiDefinitionMockUpdateRequest.setMatching(ApiDataUtils.toJSONString(msHttpElement)); - List msHttpResponse = MsHTTPElementTest.getMsHttpResponse(); - apiDefinitionMockUpdateRequest.setResponse(ApiDataUtils.toJSONString(msHttpResponse)); - - this.requestPostWithOk(UPDATE, apiDefinitionMockUpdateRequest); - // 校验请求成功数据 - apiDefinitionMock = assertAddApiDefinitionMock(request, msHttpElement, request.getId()); - request.setId(apiDefinitionMock.getId()); - MvcResult mvcResultCopy = this.requestPostWithOkAndReturn(COPY, request); - ApiDefinitionMock resultDataCopy = getResultData(mvcResultCopy, ApiDefinitionMock.class); - // @数据验证 - Assertions.assertTrue(resultDataCopy.getName().contains("copy_")); - // @@校验日志 checkLog(resultData.getId(), OperationLogType.UPDATE); request.setId("121"); - assertErrorCode(this.requestPost(COPY, request), MsHttpResultCode.NOT_FOUND); + assertErrorCode(this.requestPost(COPY, request), MsHttpResultCode.FAILED); // @@校验权限 request.setId(apiDefinitionMock.getId()); requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_MOCK_UPDATE, COPY, request); @@ -511,7 +494,6 @@ public class ApiDefinitionMockControllerTests extends BaseTest { ApiDefinitionMockRequest apiDefinitionMockRequest = new ApiDefinitionMockRequest(); apiDefinitionMockRequest.setId(apiDefinitionMock.getId()); apiDefinitionMockRequest.setProjectId(DEFAULT_PROJECT_ID); - apiDefinitionMockRequest.setApiDefinitionId(apiDefinitionMock.getApiDefinitionId()); // @@请求成功 this.requestPostWithOkAndReturn(DELETE, apiDefinitionMockRequest); checkLog(apiDefinitionMock.getId(), OperationLogType.DELETE); @@ -528,12 +510,433 @@ public class ApiDefinitionMockControllerTests extends BaseTest { checkLog(apiDefinitionMockRequest.getId(), OperationLogType.DELETE); apiDefinitionMockRequest.setId("121"); - assertErrorCode(this.requestPost(DELETE, apiDefinitionMockRequest), MsHttpResultCode.NOT_FOUND); + assertErrorCode(this.requestPost(DELETE, apiDefinitionMockRequest), MsHttpResultCode.FAILED); // @@校验权限 requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_MOCK_DELETE, DELETE, apiDefinitionMockRequest); } + @Test + @Order(99) + public void mockServerTest() throws Exception { + this.initMockConfigTestData(); + + //测试匹配不到任何一个api + mockServerTestService.testNoMatchApi(); + //测试匹配到了api但是路径不一样 + mockServerTestService.testApiNoMockConfigAndNoResponse("/mock-server/100001/" + NO_MOCK_NO_RESPONSE_API_NUM + "/mock/api/testStr/"); + //测试匹配到的api没用配置mockConfig以及没有定义默认返回项 + mockServerTestService.testApiNoMockConfigAndNoResponse("/mock-server/100001/" + NO_MOCK_NO_RESPONSE_API_NUM + "/mock/api/notMatch/"); + + /* + 测试用例: + * rest全匹配: 返回接口定义的响应 + * rest半匹配: 尝试返回文件 + * header全匹配: 尝试返回body-xml + * header半匹配: 尝试返回body-json + * get类型的请求测试: Query全匹配 Query半匹配 + * post类型的请求测试: Body-kv全匹配、Body-kv半匹配、Body-json包含匹配、Body-xml包含匹配、RAW包含匹配 + */ + for (Map.Entry> entry : API_MOCK_MAP.entrySet()) { + ApiDefinition apiDefinition = entry.getKey(); + List apiDefinitionMockList = entry.getValue(); + + String method = apiDefinition.getMethod(); + if (StringUtils.equalsIgnoreCase(method, "TRACE")) { + //这种不测试 + continue; + } + String url = "/mock-server/100001/" + apiDefinition.getNum() + apiDefinition.getPath(); + //先做一个没有匹配到任何的mock期望的测试 + mockServerTestService.testNoMatchMockConfig(method, url, apiDefinition.getPath()); + + for (ApiDefinitionMock mock : apiDefinitionMockList) { + //重置url + url = "/mock-server/100001/" + apiDefinition.getNum() + apiDefinition.getPath(); + + ApiDefinitionBlob apiDefinitionBlob = apiDefinitionBlobMapper.selectByPrimaryKey(apiDefinition.getId()); + ApiDefinitionMockConfig mockConfig = apiDefinitionMockConfigMapper.selectByPrimaryKey(mock.getId()); + MockMatchRule mockMatchRule = JSON.parseObject(new String(mockConfig.getMatching()), MockMatchRule.class); + MockResponse mockResponse = JSON.parseObject(new String(mockConfig.getResponse()), MockResponse.class); + List apiResponseList = JSON.parseArray(new String(apiDefinitionBlob.getResponse()), HttpResponse.class); + HttpResponse MockUseApiRsponse = null; + for (HttpResponse apiResponse : apiResponseList) { + if (mockResponse.isUseApiResponse() && StringUtils.equals(mockResponse.getApiResponseId(), apiResponse.getId())) { + MockUseApiRsponse = apiResponse; + } + } + String[] mockNameArr = mock.getName().split("_"); + String methodType = mockNameArr[1]; // + String conditionType = mockNameArr[2]; + + //替换rest参数 + for (KeyValueInfo keyValueInfo : mockMatchRule.getRest().getMatchRules()) { + url = StringUtils.replace(url, "{" + keyValueInfo.getKey() + "}", keyValueInfo.getValue()); + } + + //设置query参数 + StringBuilder queryParamBuilder = new StringBuilder(); + if (CollectionUtils.isNotEmpty(mockMatchRule.getQuery().getMatchRules())) { + for (KeyValueInfo keyValueInfo : mockMatchRule.getQuery().getMatchRules()) { + if (!queryParamBuilder.isEmpty()) { + queryParamBuilder.append("&"); + } + queryParamBuilder.append(keyValueInfo.getKey()); + queryParamBuilder.append("="); + queryParamBuilder.append(keyValueInfo.getValue()); + if (!mockMatchRule.getQuery().isMatchAll()) { + break; + } + } + url = url + "?" + queryParamBuilder; + } + + //开始创建请求 + MockHttpServletRequestBuilder requestBuilder = mockServerTestService.getRequestBuilder(method, url); + + //设置请求头 如果匹配类型是body-json或者body-xml,需要设置content-type + if (StringUtils.equalsIgnoreCase(conditionType, "body-json")) { + requestBuilder.header("content-type", "application/json"); + } else if (StringUtils.equalsIgnoreCase(conditionType, "body-xml")) { + requestBuilder.header("content-type", "application/xml"); + } else if (StringUtils.equalsIgnoreCase(conditionType, "body-kv-x-www")) { + requestBuilder.header("content-type", "application/x-www-form-urlencoded"); + } else if (StringUtils.equalsIgnoreCase(conditionType, "Body-raw")) { + requestBuilder.header("content-type", "text/plain"); + } + + if (CollectionUtils.isNotEmpty(mockMatchRule.getHeader().getMatchRules())) { + for (KeyValueInfo keyValueInfo : mockMatchRule.getHeader().getMatchRules()) { + requestBuilder.header(keyValueInfo.getKey(), keyValueInfo.getValue()); + if (!mockMatchRule.getHeader().isMatchAll()) { + break; + } + } + } + + //设置body参数 (get类型的请求不设置) + if (this.isNotGetTypeMethod(methodType) && StringUtils.equalsIgnoreCase(mockMatchRule.getBody().getParamType(), Body.BodyType.FORM_DATA.name())) { + for (KeyValueInfo keyValueInfo : mockMatchRule.getBody().getFormDataMatch().getMatchRules()) { + requestBuilder.param(keyValueInfo.getKey(), keyValueInfo.getValue()); + if (!mockMatchRule.getBody().getFormDataMatch().isMatchAll()) { + break; + } + } + } else if (StringUtils.isNotBlank(mockMatchRule.getBody().getRaw())) { + requestBuilder.content(mockMatchRule.getBody().getRaw()); + } + + //发送请求 + ResultActions action = mockMvc.perform(requestBuilder); + + //判断响应 + List
headers; + int statusCode; + ResponseBody responseBody; + if (mockResponse.isUseApiResponse()) { + headers = MockUseApiRsponse.getHeaders(); + statusCode = Integer.parseInt(MockUseApiRsponse.getStatusCode()); + responseBody = MockUseApiRsponse.getBody(); + } else { + headers = mockResponse.getHeaders(); + statusCode = mockResponse.getStatusCode(); + responseBody = mockResponse.getBody(); + } + + MockHttpServletResponse mockServerResponse = action.andReturn().getResponse(); + //判断响应码 + Assertions.assertEquals(mockServerResponse.getStatus(), statusCode); + //判断响应头 + for (Header header : headers) { + if (header.getEnable()) { + Assertions.assertEquals(mockServerResponse.getHeader(header.getKey()), header.getValue()); + } + } + //判断响应体 + if (StringUtils.equals(responseBody.getBodyType(), Body.BodyType.BINARY.name())) { + byte[] returnFileBytes = mockServerResponse.getContentAsByteArray(); + String fileId = responseBody.getBinaryBody().getFileId(); + byte[] bytes = new byte[0]; + FileMetadata fileMetadata = fileMetadataMapper.selectByPrimaryKey(fileId); + if (fileMetadata != null) { + String filePath = TempFileUtils.createFile(TempFileUtils.getTmpFilePath(fileMetadata.getId()), fileManagementService.getFile(fileMetadata)); + bytes = TempFileUtils.getFile(filePath); + } else { + ApiFileResource apiFileResource = apiFileResourceMapper.selectByPrimaryKey(mock.getId(), fileId); + if (apiFileResource != null) { + FileRepository defaultRepository = FileCenter.getDefaultRepository(); + FileRequest fileRequest = new FileRequest(); + fileRequest.setFileName(apiFileResource.getFileName()); + fileRequest.setFolder(DefaultRepositoryDir.getApiDefinitionDir(apiDefinition.getProjectId(), mock.getId()) + "/" + fileId); + try { + bytes = defaultRepository.getFile(fileRequest); + } catch (Exception ignore) { + } + } + } + + //通过MD5判断是否是同一个文件 + String fileMD5 = this.getFileMD5(bytes); + String downloadMD5 = this.getFileMD5(returnFileBytes); + Assertions.assertEquals(fileMD5, downloadMD5); + + } else { + String returnStr = mockServerResponse.getContentAsString(StandardCharsets.UTF_8); + String compareStr = ""; + switch (responseBody.getBodyType()) { + case "JSON": + compareStr = responseBody.getJsonBody().getJsonValue(); + break; + case "XML": + compareStr = responseBody.getXmlBody().getValue(); + break; + case "RAW": + compareStr = responseBody.getRawBody().getValue(); + break; + default: + break; + } + Assertions.assertEquals(returnStr, compareStr); + } + } + + } + + } + + private boolean isNotGetTypeMethod(String methodType) { + return !StringUtils.equalsAnyIgnoreCase(methodType, HttpMethodConstants.GET.name(), HttpMethodConstants.DELETE.name(), HttpMethodConstants.OPTIONS.name(), HttpMethodConstants.HEAD.name()); + } + + private void initMockConfigTestData() throws Exception { + if (MapUtils.isEmpty(METHOD_API_MAP)) { + this.uploadTempFile(); + } + + //为METHOD_API_MAP每个api创建一个mock期望,用于做mockServer的测试 + for (Map.Entry apiDefinitionEntry : METHOD_API_MAP.entrySet()) { + String method = apiDefinitionEntry.getKey(); + ApiDefinition apiDefinition = apiDefinitionEntry.getValue(); + ApiDefinitionBlob apiDefinitionBlob = apiDefinitionBlobMapper.selectByPrimaryKey(apiDefinition.getId()); + + List mockList = new ArrayList<>(); + //rest全匹配 返回接口定义的响应 + { + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Rest_Full_Match1"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("Rest_Full_Match1", false, true, null, true)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse(null, 444, "Rest_Full_Match1", uploadFileId, null, apiDefinitionBlob)); + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + + //rest全匹配 返回本地上传的文件(send by body) + { + String mockFileMatch2Id = doUploadTempFile(mockServerTestService.getMockMultipartFile("mockFileMatch2.txt", "mockFileMatch2")); + + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Rest_Full_Match2"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("Rest_Full_Match2", false, true, null, false)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse("file-body", 200, "Rest_Full_Match2", mockFileMatch2Id, "mockFileMatch2.txt", null)); + + mockServerRequest.setUploadFileIds(List.of(mockFileMatch2Id)); + + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + //rest全匹配 返回本地上传的文件(send by download) + { + String mockFileMatch3Id = doUploadTempFile(mockServerTestService.getMockMultipartFile("mockFileMatch3.txt", "mockFileMatch3")); + + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Rest_Full_Match3"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("Rest_Full_Match3", false, true, null, false)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse("file", 200, "Rest_Full_Match3", mockFileMatch3Id, "mockFileMatch3.txt", null)); + + mockServerRequest.setUploadFileIds(List.of(mockFileMatch3Id)); + + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + + //rest全匹配 返回文件管理的文件(send by download) + { + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Rest_Full_Match4"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("Rest_Full_Match4", false, true, null, false)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse("file", 200, "Rest_Full_Match4", fileMetadataId, "fileMetadata.txt", null)); + mockServerRequest.setLinkFileIds(List.of(fileMetadataId)); + + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + + //header全匹配 尝试返回body-xml + { + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Header_Full_Match"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("Header_Full_Match", false, false, null, true)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse("xml", 201, "Header_Full_Match", uploadFileId, null, null)); + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + + //header半匹配 尝试返回body-json + { + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Header_Half_Match"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("Header_Half_Match", false, false, null, false)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse("json", 202, "Header_Half_Match", uploadFileId, null, null)); + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + + //query全匹配 + { + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Query_Full_Match"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("Query_Full_Match", true, true, null, true)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse("raw", 203, "Query_Full_Match", uploadFileId, null, null)); + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + + //query半匹配 + { + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Query_Half_Match"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("Query_Half_Match", true, true, null, false)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse("raw", 204, "Query_Half_Match", uploadFileId, null, null)); + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + + //body-kv 全匹配 + { + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Body-kv_Full_Match"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("Body-kv_Full_Match", false, true, "kv", true)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse("raw", 204, "Body-kv_Full_Match", uploadFileId, null, null)); + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + //body-kv 半匹配 + { + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Body-kv_Half_Match"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("Body-kv_Half_Match", false, true, "kv", false)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse("raw", 204, "Body-kv_Half_Match", uploadFileId, null, null)); + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + //body-kv 半匹配x-www-form-urlencoded + { + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Body-kv-x-www_Half_Match"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("Body-kv-x-www_Half_Match", false, true, "kv", false)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse("raw", 204, "Body-kv-x-www_Half_Match", uploadFileId, null, null)); + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + + //body-json包含匹配 + { + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Body-json_Half_Match"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("Body-json_Half_Match", false, true, "json", false)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse("raw", 204, "Body-json_Half_Match", uploadFileId, null, null)); + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + //body-xml包含匹配 + { + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Body-xml_Half_Match"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("Body-xml_Half_Match", false, true, "xml", false)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse("raw", 204, "Body-xml_Half_Match", uploadFileId, null, null)); + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + //raw包含匹配 + { + ApiDefinitionMockAddRequest mockServerRequest = new ApiDefinitionMockAddRequest(); + mockServerRequest.setName("Mock_" + method + "_Body-raw_Half_Match"); + mockServerRequest.setProjectId(apiDefinition.getProjectId()); + mockServerRequest.setApiDefinitionId(apiDefinition.getId()); + mockServerRequest.setMockMatchRule(mockServerTestService.genMockMatchRule("body-raw_Half_Match", false, true, "raw", false)); + mockServerRequest.setResponse(mockServerTestService.genMockResponse("raw", 204, "body-raw_Half_Match", uploadFileId, null, null)); + MvcResult mockServerResult = this.requestPostWithOkAndReturn(ADD, mockServerRequest); + ApiDefinitionMock definitionMock = getResultData(mockServerResult, ApiDefinitionMock.class); + mockServerTestService.assertAddApiDefinitionMock(mockServerRequest, mockServerRequest.getMockMatchRule(), definitionMock.getId()); + mockList.add(definitionMock); + } + + API_MOCK_MAP.put(apiDefinition, mockList); + } + } + + public static String getFileMD5(byte[] bytes) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + digest.update(bytes, 0, bytes.length); + BigInteger bigInt = new BigInteger(1, digest.digest()); + return bigInt.toString(16); + } catch (Exception e) { + return null; + } + } } diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerTests.java index 857a0adc73..6397e6ad09 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerTests.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerTests.java @@ -6,6 +6,7 @@ import io.metersphere.api.dto.assertion.MsAssertionConfig; import io.metersphere.api.dto.debug.ModuleCreateRequest; import io.metersphere.api.dto.definition.ApiDefinitionAddRequest; import io.metersphere.api.dto.definition.ApiTestCaseAddRequest; +import io.metersphere.api.dto.definition.HttpResponse; import io.metersphere.api.dto.request.http.Header; import io.metersphere.api.dto.request.http.MsHTTPElement; import io.metersphere.api.dto.request.http.QueryParam; @@ -57,7 +58,6 @@ import io.metersphere.system.controller.handler.ResultHolder; import io.metersphere.system.domain.Plugin; import io.metersphere.system.domain.Schedule; import io.metersphere.system.dto.request.PluginUpdateRequest; -import io.metersphere.system.dto.sdk.BaseTreeNode; import io.metersphere.system.dto.sdk.request.PosRequest; import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.mapper.ScheduleMapper; @@ -68,6 +68,7 @@ import io.metersphere.system.utils.CheckLogModel; import io.metersphere.system.utils.Pager; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.*; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @@ -75,7 +76,6 @@ import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; -import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.FileInputStream; @@ -528,7 +528,8 @@ public class ApiScenarioControllerTests extends BaseTest { queryParam2.setValue("bbb2"); msHttpElement.setQuery(List.of(queryParam1, queryParam2)); apiDefinitionAddRequest.setRequest(getMsElementParam(msHttpElement)); - apiDefinitionAddRequest.setResponse("{}"); + HttpResponse httpResponse = new HttpResponse(); + apiDefinitionAddRequest.setResponse(Collections.singletonList(httpResponse)); apiDefinition = apiDefinitionService.create(apiDefinitionAddRequest, "admin"); ApiTestCaseAddRequest apiTestCaseAddRequest = new ApiTestCaseAddRequest(); diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/MsHTTPElementTest.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/MsHTTPElementTest.java index 73824a2567..a14a9e7938 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/MsHTTPElementTest.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/MsHTTPElementTest.java @@ -4,6 +4,7 @@ import io.metersphere.api.dto.ApiFile; import io.metersphere.api.dto.ApiParamConfig; import io.metersphere.api.dto.assertion.MsAssertionConfig; import io.metersphere.api.dto.definition.HttpResponse; +import io.metersphere.api.dto.definition.ResponseBody; import io.metersphere.api.dto.request.MsCommonElement; import io.metersphere.api.dto.request.http.*; import io.metersphere.api.dto.request.http.body.*; @@ -475,24 +476,68 @@ public class MsHTTPElementTest { header.setDescription("desc"); httpResponse.setHeaders(List.of(header)); - FormDataBody formDataBody = new FormDataBody(); - FormDataKV formDataKV = new FormDataKV(); - formDataKV.setEnable(false); - formDataKV.setContentType("text/plain"); - formDataKV.setEncode(true); - formDataKV.setMaxLength(10); - formDataKV.setMinLength(8); - formDataKV.setParamType("text"); - formDataKV.setDescription("test"); - formDataKV.setRequired(true); - formDataKV.setValue("value"); - formDataKV.setKey("key"); - formDataBody.setFormValues(List.of(formDataKV)); - Body body = new Body(); - body.setBodyType(Body.BodyType.FORM_DATA.name()); + ResponseBody body = new ResponseBody(); + body.setBodyType(Body.BodyType.RAW.name()); httpResponse.setBody(body); httpResponses.add(httpResponse); return httpResponses; } + + public static List get2MsHttpResponse(String returnPrefix) { + List httpResponses = new ArrayList<>(); + HttpResponse http1Response = new HttpResponse(); + http1Response.setName("Response1"); + http1Response.setStatusCode("222"); + http1Response.setDefaultFlag(true); + http1Response.setHeaders(new ArrayList<>() {{ + this.add(new Header() {{ + this.setEnable(false); + this.setValue("valueA1"); + this.setKey("keyA1"); + this.setDescription("descA1"); + }}); + this.add(new Header() {{ + this.setEnable(true); + this.setValue("headerDefaultValue"); + this.setKey("headerDefault"); + this.setDescription("headerDefaultDescA2"); + }}); + }}); + ResponseBody body1 = new ResponseBody(); + body1.setBodyType(Body.BodyType.RAW.name()); + body1.setRawBody(new RawBody() {{ + this.setValue(returnPrefix + "___responseDefault"); + }}); + http1Response.setBody(body1); + httpResponses.add(http1Response); + + HttpResponse http2Response = new HttpResponse(); + http2Response.setName("Response2"); + http2Response.setStatusCode("222"); + http2Response.setDefaultFlag(false); + http2Response.setHeaders(new ArrayList<>() {{ + this.add(new Header() {{ + this.setEnable(false); + this.setValue("valueB1"); + this.setKey("keyB1"); + this.setDescription("descB1"); + }}); + this.add(new Header() {{ + this.setEnable(true); + this.setValue("valueB2"); + this.setKey("keyB2"); + this.setDescription("descB2"); + }}); + }}); + ResponseBody body2 = new ResponseBody(); + body2.setBodyType(Body.BodyType.RAW.name()); + body2.setRawBody(new RawBody() {{ + this.setValue(returnPrefix + "___response2"); + }}); + http2Response.setBody(body2); + + httpResponses.add(http2Response); + return httpResponses; + } } diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/service/MockServerTestService.java b/backend/services/api-test/src/test/java/io/metersphere/api/service/MockServerTestService.java new file mode 100644 index 0000000000..cd5acd74ab --- /dev/null +++ b/backend/services/api-test/src/test/java/io/metersphere/api/service/MockServerTestService.java @@ -0,0 +1,360 @@ +package io.metersphere.api.service; + +import io.metersphere.api.domain.ApiDefinitionBlob; +import io.metersphere.api.domain.ApiDefinitionMock; +import io.metersphere.api.domain.ApiDefinitionMockConfig; +import io.metersphere.api.domain.ApiFileResource; +import io.metersphere.api.dto.definition.HttpResponse; +import io.metersphere.api.dto.definition.ResponseBinaryBody; +import io.metersphere.api.dto.definition.ResponseBody; +import io.metersphere.api.dto.mockserver.*; +import io.metersphere.api.dto.request.http.Header; +import io.metersphere.api.dto.request.http.body.Body; +import io.metersphere.api.dto.request.http.body.JsonBody; +import io.metersphere.api.dto.request.http.body.RawBody; +import io.metersphere.api.dto.request.http.body.XmlBody; +import io.metersphere.api.mapper.ApiDefinitionMockConfigMapper; +import io.metersphere.api.mapper.ApiDefinitionMockMapper; +import io.metersphere.api.utils.ApiDataUtils; +import io.metersphere.project.dto.filemanagement.FileInfo; +import io.metersphere.project.service.FileAssociationService; +import io.metersphere.sdk.constants.DefaultRepositoryDir; +import io.metersphere.sdk.file.FileCenter; +import io.metersphere.sdk.file.FileRequest; +import io.metersphere.sdk.util.BeanUtils; +import io.metersphere.sdk.util.CommonBeanFactory; +import io.metersphere.sdk.util.JSON; +import io.metersphere.sdk.util.Translator; +import io.metersphere.system.uid.IDGenerator; +import jakarta.annotation.Resource; +import org.apache.commons.collections.CollectionUtils; +import org.junit.jupiter.api.Assertions; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.stereotype.Service; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static io.metersphere.api.service.BaseResourcePoolTestService.DEFAULT_PROJECT_ID; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Service +public class MockServerTestService { + + @Resource + private ApiDefinitionMockMapper apiDefinitionMockMapper; + @Resource + private ApiDefinitionMockConfigMapper apiDefinitionMockConfigMapper; + + + public static MockMultipartFile getMockMultipartFile(String fileName) { + return new MockMultipartFile( + "file", + fileName, + MediaType.APPLICATION_OCTET_STREAM_VALUE, + "Hello, World!".getBytes() + ); + } + + public static MockMultipartFile getMockMultipartFile(String fileName, String fileContent) { + return new MockMultipartFile( + "file", + fileName, + MediaType.APPLICATION_OCTET_STREAM_VALUE, + fileContent.getBytes() + ); + } + + + /** + * 校验上传的文件 + * + * @param id + * @param fileIds 全部的文件ID + */ + public static void assertUploadFile(String id, List fileIds) throws Exception { + if (fileIds != null) { + ApiFileResourceService apiFileResourceService = CommonBeanFactory.getBean(ApiFileResourceService.class); + // 验证文件的关联关系,以及是否存入对象存储 + List apiFileResources = apiFileResourceService.getByResourceId(id); + Assertions.assertEquals(apiFileResources.size(), fileIds.size()); + + String apiDefinitionDir = DefaultRepositoryDir.getApiDefinitionDir(DEFAULT_PROJECT_ID, id); + FileRequest fileRequest = new FileRequest(); + if (!fileIds.isEmpty()) { + for (ApiFileResource apiFileResource : apiFileResources) { + Assertions.assertEquals(DEFAULT_PROJECT_ID, apiFileResource.getProjectId()); + fileRequest.setFolder(apiDefinitionDir + "/" + apiFileResource.getFileId()); + fileRequest.setFileName(apiFileResource.getFileName()); + Assertions.assertNotNull(FileCenter.getDefaultRepository().getFile(fileRequest)); + } + fileRequest.setFolder(apiDefinitionDir); + } else { + fileRequest.setFolder(apiDefinitionDir); + Assertions.assertTrue(CollectionUtils.isEmpty(FileCenter.getDefaultRepository().getFolderFileNames(fileRequest))); + } + } + } + + /** + * 校验上传的文件 + * + * @param id + * @param fileIds 全部的文件ID + */ + public static void assertLinkFile(String id, List fileIds) { + FileAssociationService fileAssociationService = CommonBeanFactory.getBean(FileAssociationService.class); + List linkFileIds = fileAssociationService.getFiles(id) + .stream() + .map(FileInfo::getFileId) + .toList(); + Assertions.assertEquals(fileIds, linkFileIds); + } + + public ApiDefinitionMock assertAddApiDefinitionMock(Object request, MockMatchRule mockMatchRule, String id) { + ApiDefinitionMock apiDefinitionMock = apiDefinitionMockMapper.selectByPrimaryKey(id); + ApiDefinitionMockConfig apiDefinitionMockConfig = apiDefinitionMockConfigMapper.selectByPrimaryKey(id); + ApiDefinitionMock copyApiDefinitionMock = BeanUtils.copyBean(new ApiDefinitionMock(), apiDefinitionMock); + BeanUtils.copyBean(copyApiDefinitionMock, request); + Assertions.assertEquals(apiDefinitionMock, copyApiDefinitionMock); + if (apiDefinitionMockConfig != null) { + Assertions.assertEquals(mockMatchRule, ApiDataUtils.parseObject(new String(apiDefinitionMockConfig.getMatching()), MockMatchRule.class)); + } + return apiDefinitionMock; + } + + public MockMatchRule genMockMatchRule(String valuePrefix, boolean hasQuery, boolean hasHeader, String bodyParamType, boolean matchAll) { + MockMatchRule mockMatchRule = new MockMatchRule(); + + keyValueMatchRole restMatchRule = new keyValueMatchRole(); + restMatchRule.setMatchAll(matchAll); + restMatchRule.setMatchRules(new ArrayList<>() {{ + this.add(new KeyValueInfo() {{ + this.setKey("param1"); + this.setValue(valuePrefix + "__query-" + hasQuery + "_header-" + hasHeader); + }}); + this.add(new KeyValueInfo() {{ + this.setKey("param2"); + this.setValue(valuePrefix + "-Param2"); + }}); + }}); + mockMatchRule.setRest(restMatchRule); + + if (hasQuery) { + keyValueMatchRole queryMatchRule = new keyValueMatchRole(); + queryMatchRule.setMatchAll(matchAll); + queryMatchRule.setMatchRules(new ArrayList<>() {{ + this.add(new KeyValueInfo() {{ + this.setKey("queryParam1"); + this.setValue(valuePrefix + "_queryParam1Value"); + }}); + this.add(new KeyValueInfo() {{ + this.setKey("queryParam2"); + this.setValue(valuePrefix + "_queryParam2Value"); + }}); + this.add(new KeyValueInfo() {{ + this.setKey("queryParam3"); + this.setValue(valuePrefix + "_queryParam3Value"); + }}); + }}); + mockMatchRule.setQuery(queryMatchRule); + } + + if (hasHeader) { + keyValueMatchRole headerMatchRule = new keyValueMatchRole(); + headerMatchRule.setMatchAll(matchAll); + headerMatchRule.setMatchRules(new ArrayList<>() {{ + this.add(new KeyValueInfo() {{ + this.setKey("headerA"); + this.setValue(valuePrefix + "-header-1"); + }}); + this.add(new KeyValueInfo() {{ + this.setKey("headerB"); + this.setValue(valuePrefix + "-header-2"); + }}); + this.add(new KeyValueInfo() {{ + this.setKey("headerC"); + this.setValue(valuePrefix + "-header-3"); + }}); + }}); + mockMatchRule.setHeader(headerMatchRule); + } + + if (StringUtils.equalsIgnoreCase(bodyParamType, "kv")) { + mockMatchRule.setBody(new BodyParamMatchRole() {{ + this.setParamType(Body.BodyType.FORM_DATA.name()); + this.setFormDataMatch(new keyValueMatchRole() {{ + this.setMatchAll(matchAll); + this.setMatchRules(new ArrayList<>() {{ + this.add(new KeyValueInfo() {{ + this.setKey("bodyKvParam1"); + this.setValue(valuePrefix + "_bodyKvParam1"); + }}); + this.add(new KeyValueInfo() {{ + this.setKey("bodyParam2"); + this.setValue(valuePrefix + "_bodyKvParam2"); + }}); + this.add(new KeyValueInfo() {{ + this.setKey("bodyParam3"); + this.setValue(valuePrefix + "_bodyKvParam3"); + }}); + }}); + }}); + }}); + } else if (StringUtils.equalsIgnoreCase(bodyParamType, "raw")) { + mockMatchRule.setBody(new BodyParamMatchRole() {{ + this.setParamType(Body.BodyType.RAW.name()); + this.setRaw(valuePrefix + "_inputRawBody"); + }}); + } else if (StringUtils.equalsIgnoreCase(bodyParamType, "json")) { + mockMatchRule.setBody(new BodyParamMatchRole() {{ + this.setParamType(Body.BodyType.JSON.name()); + this.setRaw("{\"inputAge\":123}"); + }}); + } else if (StringUtils.equalsIgnoreCase(bodyParamType, "xml")) { + mockMatchRule.setBody(new BodyParamMatchRole() {{ + this.setParamType(Body.BodyType.XML.name()); + this.setRaw("input123"); + }}); + } + return mockMatchRule; + } + + public MockResponse genMockResponse(String returnType, int status, String valueKeyWord, String fileId, String fileName, ApiDefinitionBlob apiDefinitionBlob) { + MockResponse mockResponse = new MockResponse(); + mockResponse.setStatusCode(status); + if (apiDefinitionBlob != null) { + mockResponse.setUseApiResponse(true); + List msHttpResponseList = JSON.parseArray(new String(apiDefinitionBlob.getResponse()), HttpResponse.class); + msHttpResponseList.forEach(item -> { + if (!item.isDefaultFlag()) { + //特意使用非默认的响应 + mockResponse.setApiResponseId(item.getId()); + } + }); + } else { + ResponseBody body = new ResponseBody(); + switch (returnType) { + case "file": + body.setBodyType(Body.BodyType.BINARY.name()); + body.setBinaryBody(new ResponseBinaryBody() {{ + this.setSendAsBody(false); + this.setFileId(fileId); + this.setFileName(fileName); + }}); + break; + case "file-body": + body.setBodyType(Body.BodyType.BINARY.name()); + body.setBinaryBody(new ResponseBinaryBody() {{ + this.setSendAsBody(false); + this.setFileId(fileId); + this.setFileName(fileName); + }}); + break; + case "json": + body.setBodyType(Body.BodyType.JSON.name()); + body.setJsonBody(new JsonBody() {{ + this.setJsonValue("{\"inputAge\":123, \"testKeyWord\":\"" + valueKeyWord + "\"}"); + }}); + break; + case "xml": + body.setBodyType(Body.BodyType.XML.name()); + body.setXmlBody(new XmlBody() {{ + this.setValue("" + valueKeyWord + ""); + }}); + break; + case "raw": + body.setBodyType(Body.BodyType.RAW.name()); + body.setRawBody(new RawBody() {{ + this.setValue("Raw body content:" + valueKeyWord); + }}); + break; + } + mockResponse.setBody(body); + } + + List
headers = new ArrayList<>() {{ + this.add(new Header() {{ + this.setKey("rspHeaderA"); + this.setValue("header-1"); + }}); + this.add(new Header() {{ + this.setKey("rspHeaderB"); + this.setValue("header-2"); + this.setEnable(false); + }}); + this.add(new Header() {{ + this.setKey("rspHeaderC"); + this.setValue("header-3"); + }}); + }}; + mockResponse.setHeaders(headers); + return mockResponse; + } + + public MockHttpServletRequestBuilder getRequestBuilder(String method, String url) { + MockHttpServletRequestBuilder requestBuilder = null; + if (StringUtils.equalsIgnoreCase(method, "get")) { + requestBuilder = MockMvcRequestBuilders.get(url); + } else if (StringUtils.equalsIgnoreCase(method, "post")) { + requestBuilder = MockMvcRequestBuilders.post(url); + } else if (StringUtils.equalsIgnoreCase(method, "put")) { + requestBuilder = MockMvcRequestBuilders.put(url); + } else if (StringUtils.equalsIgnoreCase(method, "delete")) { + requestBuilder = MockMvcRequestBuilders.delete(url); + } else if (StringUtils.equalsIgnoreCase(method, "patch")) { + requestBuilder = MockMvcRequestBuilders.patch(url); + } else if (StringUtils.equalsIgnoreCase(method, "head")) { + requestBuilder = MockMvcRequestBuilders.head(url); + } else if (StringUtils.equalsIgnoreCase(method, "options")) { + requestBuilder = MockMvcRequestBuilders.options(url); + } else if (StringUtils.equalsIgnoreCase(method, "trace")) { + requestBuilder = MockMvcRequestBuilders.request(HttpMethod.TRACE, url); + } + return requestBuilder; + } + + @Resource + private MockMvc mockMvc; + + public void testNoMatchMockConfig(String method, String url, String apiDefinitionPath) throws Exception { + + url = StringUtils.replace(url, "{param1}", "param1"); + url = StringUtils.replace(url, "{param2}", "param2"); + MockHttpServletRequestBuilder requestBuilder; + if (StringUtils.equalsAnyIgnoreCase(method, "get", "delete")) { + requestBuilder = getRequestBuilder(method, url + IDGenerator.nextStr()); + Assertions.assertNotNull(requestBuilder); + } else { + requestBuilder = getRequestBuilder(method, url); + } + ResultActions action = mockMvc.perform(requestBuilder); + MvcResult mockResult = action.andReturn(); + String returnStr = mockResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + Assertions.assertEquals(returnStr, apiDefinitionPath + "___responseDefault"); + Assertions.assertEquals(mockResult.getResponse().getStatus(), 222); + } + + public void testNoMatchApi() throws Exception { + String url = "/mock-server/100001/" + "error" + "/test/error"; + this.testApiNoMockConfigAndNoResponse(url); + } + + public void testApiNoMockConfigAndNoResponse(String url) throws Exception { + MockHttpServletRequestBuilder requestBuilder = getRequestBuilder("get", url); + ResultActions action = mockMvc.perform(requestBuilder); + action.andExpect(status().isNotFound()); + MvcResult mockResult = action.andReturn(); + String returnStr = mockResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + Assertions.assertEquals(returnStr, Translator.get("mock_warning")); + } +} diff --git a/backend/services/project-management/src/test/java/io/metersphere/project/controller/filemanagement/FileRepositoryControllerTest.java b/backend/services/project-management/src/test/java/io/metersphere/project/controller/filemanagement/FileRepositoryControllerTest.java index 524c12a88d..e36ba695b8 100644 --- a/backend/services/project-management/src/test/java/io/metersphere/project/controller/filemanagement/FileRepositoryControllerTest.java +++ b/backend/services/project-management/src/test/java/io/metersphere/project/controller/filemanagement/FileRepositoryControllerTest.java @@ -26,13 +26,13 @@ import io.metersphere.system.utils.CheckLogModel; import io.metersphere.system.utils.Pager; import jakarta.annotation.Resource; 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; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; -import org.apache.commons.lang3.StringUtils; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -203,40 +203,6 @@ public class FileRepositoryControllerTest extends BaseTest { Assertions.assertEquals(response.getToken(), GITEA_TOKEN); Assertions.assertEquals(response.getUrl(), GITEA_URL); - //测试创建gitee的 - String giteeUrl = "https://gitee.com/testformeterspere/gitee-test.git"; - String giteeUserName = "testformetersphere"; - String giteeToken = "4548d369bb595738d726512742e4478f"; - createRequest = new FileRepositoryCreateRequest(); - createRequest.setProjectId(project.getId()); - createRequest.setPlatform(ModuleConstants.NODE_TYPE_GITEE); - createRequest.setUrl(giteeUrl); - createRequest.setUserName(giteeUserName); - createRequest.setToken(giteeToken); - createRequest.setName("GITEE存储库"); - - int tryCount = 0; - while (true) { - //github连接gitee有时会连不到,这里重试10次。 - result = this.requestPost(FileManagementRequestUtils.URL_FILE_REPOSITORY_CREATE, createRequest).andReturn(); - if (result.getResponse().getStatus() == 200) { - break; - } else { - tryCount++; - if (tryCount > 10) { - throw new Exception("无法创建gitee存储库"); - } else { - Thread.sleep(1000); - } - } - } - - returnStr = result.getResponse().getContentAsString(); - rh = JSON.parseObject(returnStr, ResultHolder.class); - this.checkFileRepository(rh.getData().toString(), createRequest.getProjectId(), createRequest.getName(), createRequest.getPlatform(), createRequest.getUrl(), createRequest.getToken(), createRequest.getUserName()); - LOG_CHECK_LIST.add( - new CheckLogModel(rh.getData().toString(), OperationLogType.ADD, FileManagementRequestUtils.URL_FILE_REPOSITORY_CREATE) - ); //参数测试: 没有url createRequest = new FileRepositoryCreateRequest(); @@ -283,12 +249,13 @@ public class FileRepositoryControllerTest extends BaseTest { createRequest.setToken(GITEA_TOKEN); createRequest.setName("GITEA存储库"); this.requestPost(FileManagementRequestUtils.URL_FILE_REPOSITORY_CREATE, createRequest).andExpect(status().is5xxServerError()); + //报错测试: gitee仓库,不填写用户名 createRequest = new FileRepositoryCreateRequest(); createRequest.setProjectId(project.getId()); createRequest.setPlatform(ModuleConstants.NODE_TYPE_GITEE); - createRequest.setUrl(giteeUrl); - createRequest.setToken(giteeToken); + createRequest.setUrl("gitee/test/url"); + createRequest.setToken("gitee-token"); createRequest.setName("Gitee无用户名存储库"); this.requestPost(FileManagementRequestUtils.URL_FILE_REPOSITORY_CREATE, createRequest).andExpect(status().is5xxServerError());