From 26f9f6726e56980ae5866b431eb1ff14781679d5 Mon Sep 17 00:00:00 2001 From: AgAngle <1323481023@qq.com> Date: Mon, 22 Jan 2024 18:49:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95):=20?= =?UTF-8?q?=E5=9C=BA=E6=99=AF=E6=89=A7=E8=A1=8C=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../3.0.0/ddl/V3.0.0_5__api_test.sql | 2 +- .../api/constants/ApiScenarioStepRefType.java | 6 +- .../scenario/ApiScenarioController.java | 17 +- .../api/dto/debug/ApiDebugRunRequest.java | 2 +- .../api/dto/debug/ApiResourceRunRequest.java | 11 +- .../api/dto/request/MsScenario.java | 2 + .../dto/scenario/ApiScenarioDebugRequest.java | 48 +++ .../api/dto/scenario/ApiScenarioDetail.java | 13 + .../dto/scenario/ApiScenarioStepRequest.java | 3 +- .../dto/scenario/PartialRefStepDetail.java | 20 ++ .../api/parser/step/ApiCaseStepParser.java | 24 ++ .../parser/step/ApiScenarioStepParser.java | 29 ++ .../api/parser/step/ApiStepParser.java | 89 +++++ .../api/parser/step/StepParser.java | 24 ++ .../api/parser/step/StepParserFactory.java | 25 ++ .../api/service/ApiExecuteService.java | 9 +- .../api/service/debug/ApiDebugService.java | 3 + .../definition/ApiDefinitionService.java | 9 + .../definition/ApiTestCaseService.java | 10 + .../service/scenario/ApiScenarioService.java | 315 ++++++++++++++++-- .../utils/JmeterElementConverterRegister.java | 2 + .../controller/ApiDebugControllerTests.java | 24 +- .../ApiScenarioControllerTests.java | 101 ++++-- 23 files changed, 702 insertions(+), 86 deletions(-) create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioDebugRequest.java create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioDetail.java create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/PartialRefStepDetail.java create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/parser/step/ApiCaseStepParser.java create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/parser/step/ApiScenarioStepParser.java create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/parser/step/ApiStepParser.java create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/parser/step/StepParser.java create mode 100644 backend/services/api-test/src/main/java/io/metersphere/api/parser/step/StepParserFactory.java diff --git a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_5__api_test.sql b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_5__api_test.sql index 4d7266a054..6693cb406f 100644 --- a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_5__api_test.sql +++ b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_5__api_test.sql @@ -283,7 +283,7 @@ CREATE TABLE IF NOT EXISTS api_scenario_step( `project_id` VARCHAR(50) COMMENT '项目fk' , `parent_id` VARCHAR(50) DEFAULT 'NONE' COMMENT '父级fk' , `version_id` VARCHAR(50) COMMENT '版本号' , - `ref_type` VARCHAR(10) COMMENT '引用/复制/自定义' , + `ref_type` VARCHAR(20) COMMENT '引用/复制/自定义' , `config` VARCHAR(500) COMMENT '循环等组件基础数据' , PRIMARY KEY (id) ) ENGINE = InnoDB diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiScenarioStepRefType.java b/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiScenarioStepRefType.java index 63d910c0e2..3e4c175199 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiScenarioStepRefType.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/constants/ApiScenarioStepRefType.java @@ -11,13 +11,13 @@ public enum ApiScenarioStepRefType { */ DIRECT, /** - * 引用 + * 完全引用 */ REF, /** - * 步骤引用 + * 部分引用 */ - STEP_REF, + PARTIAL_REF, /** * 复制 */ diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioController.java b/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioController.java index 75a66253c0..7038c76e84 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioController.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/controller/scenario/ApiScenarioController.java @@ -70,7 +70,7 @@ public class ApiScenarioController { } @PostMapping("/add") - @Operation(summary = "创建场景") + @Operation(summary = "接口测试-接口场景管理-创建场景") @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_ADD) @Log(type = OperationLogType.ADD, expression = "#msClass.addLog(#request)", msClass = ApiScenarioLogService.class) public ApiScenario add(@Validated @RequestBody ApiScenarioAddRequest request) { @@ -78,14 +78,14 @@ public class ApiScenarioController { } @PostMapping("/upload/temp/file") - @Operation(summary = "上传场景所需的文件资源,并返回文件ID") + @Operation(summary = "接口测试-接口场景管理-上传场景所需的文件资源,并返回文件ID") @RequiresPermissions(logical = Logical.OR, value = {PermissionConstants.PROJECT_API_SCENARIO_ADD, PermissionConstants.PROJECT_API_SCENARIO_UPDATE}) public String uploadTempFile(@RequestParam("file") MultipartFile file) { return apiScenarioService.uploadTempFile(file); } @PostMapping("/update") - @Operation(summary = "更新场景") + @Operation(summary = "接口测试-接口场景管理-更新场景") @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_UPDATE) @Log(type = OperationLogType.UPDATE, expression = "#msClass.updateLog(#request)", msClass = ApiScenarioLogService.class) public ApiScenario update(@Validated @RequestBody ApiScenarioUpdateRequest request) { @@ -93,7 +93,7 @@ public class ApiScenarioController { } @GetMapping("/delete/{id}") - @Operation(summary = "删除场景") + @Operation(summary = "接口测试-接口场景管理-删除场景") @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_DELETE) @Log(type = OperationLogType.DELETE, expression = "#msClass.deleteLog(#id)", msClass = ApiScenarioLogService.class) public void delete(@PathVariable String id) { @@ -101,10 +101,17 @@ public class ApiScenarioController { } @GetMapping("/delete-to-gc/{id}") - @Operation(summary = "删除场景到回收站") + @Operation(summary = "接口测试-接口场景管理-删除场景到回收站") @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_DELETE) @Log(type = OperationLogType.DELETE, expression = "#msClass.deleteLog(#id)", msClass = ApiScenarioLogService.class) public void deleteToGc(@PathVariable String id) { apiScenarioService.deleteToGc(id); } + + @PostMapping("/debug") + @Operation(summary = "接口测试-接口场景管理-场景调试") + @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_EXECUTE) + public String debug(@RequestBody ApiScenarioDebugRequest request) { + return apiScenarioService.debug(request); + } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/debug/ApiDebugRunRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/debug/ApiDebugRunRequest.java index a0ca5d1459..10dca58479 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/debug/ApiDebugRunRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/debug/ApiDebugRunRequest.java @@ -18,5 +18,5 @@ public class ApiDebugRunRequest { private List tempFileIds; @NotNull @Schema(description = "请求内容") - private String request; + private Object request; } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/debug/ApiResourceRunRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/debug/ApiResourceRunRequest.java index 914fa0d1bc..f23e5782f5 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/debug/ApiResourceRunRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/debug/ApiResourceRunRequest.java @@ -1,5 +1,6 @@ package io.metersphere.api.dto.debug; +import io.metersphere.plugin.api.spi.AbstractMsTestElement; import lombok.Data; import java.util.List; @@ -20,7 +21,11 @@ public class ApiResourceRunRequest { */ private String reportId; /** - * 环境ID + * 是否为环境组 + */ + private Boolean grouped = false; + /** + * 环境或者环境组ID */ private String environmentId; /** @@ -33,9 +38,9 @@ public class ApiResourceRunRequest { */ private String resourceType; /** - * 请求内容 + * 执行组件 */ - private String request; + private AbstractMsTestElement testElement; /** * 点击调试时尚未保存的文件列表 */ diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/request/MsScenario.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/request/MsScenario.java index 37db039227..82e97c054c 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/request/MsScenario.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/request/MsScenario.java @@ -1,5 +1,6 @@ package io.metersphere.api.dto.request; +import io.metersphere.api.dto.scenario.ScenarioConfig; import io.metersphere.plugin.api.spi.AbstractMsTestElement; import lombok.Data; import lombok.EqualsAndHashCode; @@ -7,4 +8,5 @@ import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = true) public class MsScenario extends AbstractMsTestElement { + private ScenarioConfig scenarioConfig; } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioDebugRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioDebugRequest.java new file mode 100644 index 0000000000..a794dcd5df --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioDebugRequest.java @@ -0,0 +1,48 @@ +package io.metersphere.api.dto.scenario; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * @Author: jianxing + * @CreateTime: 2024-01-10 11:24 + */ +@Data +public class ApiScenarioDebugRequest { + @Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{api_scenario.id.not_blank}") + @Size(max = 50, message = "{api_scenario.id.length_range}") + private String id; + + @Schema(description = "是否为环境组") + private Boolean grouped = false; + + @Schema(description = "环境或者环境组ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank + private String environmentId; + + @Schema(description = "场景的通用配置") + private ScenarioConfig scenarioConfig; + + @Schema(description = "步骤集合") + private List steps; + + /** + * 步骤详情 + * key 为步骤ID + * 值 为详情 + */ + @Schema(description = "步骤详情") + private Map stepDetails; + + @Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank + private String projectId; + @Schema(description = "点击调试时尚未保存的文件ID列表") + private List tempFileIds; +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioDetail.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioDetail.java new file mode 100644 index 0000000000..4cab52611e --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioDetail.java @@ -0,0 +1,13 @@ +package io.metersphere.api.dto.scenario; + +import io.metersphere.api.domain.ApiScenario; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class ApiScenarioDetail extends ApiScenario { + @Schema(description = "场景的通用配置") + private ScenarioConfig scenarioConfig; +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioStepRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioStepRequest.java index a7641ba03b..8a3464bfc4 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioStepRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/ApiScenarioStepRequest.java @@ -50,7 +50,8 @@ public class ApiScenarioStepRequest { /** * 引用模式:默认完全引用 * - 完全引用:步骤状态不可调整 - * - 步骤引用:步骤状态可调整 + * - 部分引用:步骤状态可调整 + * @see io.metersphere.api.constants.ApiScenarioStepRefType */ @Schema(description = "引用/复制/自定义") private String refType; diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/PartialRefStepDetail.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/PartialRefStepDetail.java new file mode 100644 index 0000000000..5c3c5bb2bf --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/scenario/PartialRefStepDetail.java @@ -0,0 +1,20 @@ +package io.metersphere.api.dto.scenario; + +import lombok.Data; + +import java.util.Set; + +/** + * @Author: jianxing + * @CreateTime: 2024-01-22 15:17 + */ +@Data +public class PartialRefStepDetail { + /** + * 记录子步骤中启用的步骤ID + * 不包含,部分引用的子步骤 + * 部分引用的子步骤也会自行保存子步骤的启用状态 + */ + private Set enableStepIds; + // 预留保存其他信息 +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/ApiCaseStepParser.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/ApiCaseStepParser.java new file mode 100644 index 0000000000..7b7c8ac6f0 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/ApiCaseStepParser.java @@ -0,0 +1,24 @@ +package io.metersphere.api.parser.step; + +import io.metersphere.api.dto.scenario.ApiScenarioStepRequest; +import io.metersphere.api.utils.ApiDataUtils; +import io.metersphere.plugin.api.spi.AbstractMsTestElement; +import org.apache.commons.lang3.StringUtils; + +/** + * @Author: jianxing + * @CreateTime: 2024-01-20 15:43 + */ +public class ApiCaseStepParser extends StepParser { + @Override + public AbstractMsTestElement parse(ApiScenarioStepRequest step, String resourceBlob, String stepDetail) { + if (isRef(step.getRefType())) { + return StringUtils.isBlank(resourceBlob) ? null : parse2MsTestElement(resourceBlob); + } else { + if (StringUtils.isBlank(stepDetail)) { + return null; + } + return StringUtils.isBlank(stepDetail) ? null : ApiDataUtils.parseObject(stepDetail, AbstractMsTestElement.class); + } + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/ApiScenarioStepParser.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/ApiScenarioStepParser.java new file mode 100644 index 0000000000..ccb68556ad --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/ApiScenarioStepParser.java @@ -0,0 +1,29 @@ +package io.metersphere.api.parser.step; + +import io.metersphere.api.dto.request.MsScenario; +import io.metersphere.api.dto.scenario.ApiScenarioStepRequest; +import io.metersphere.api.dto.scenario.ScenarioConfig; +import io.metersphere.plugin.api.spi.AbstractMsTestElement; +import io.metersphere.sdk.util.JSON; +import org.apache.commons.lang3.StringUtils; + +/** + * @Author: jianxing + * @CreateTime: 2024-01-20 15:43 + */ +public class ApiScenarioStepParser extends StepParser { + @Override + public AbstractMsTestElement parse(ApiScenarioStepRequest step, String resourceBlob, String stepDetail) { + MsScenario msScenario = new MsScenario(); + if (isRef(step.getRefType())) { + if (StringUtils.isNotBlank(resourceBlob)) { + msScenario.setScenarioConfig(JSON.parseObject(resourceBlob, ScenarioConfig.class)); + } + } else { + if (StringUtils.isNotBlank(stepDetail)) { + msScenario.setScenarioConfig(JSON.parseObject(stepDetail, ScenarioConfig.class)); + } + } + return msScenario; + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/ApiStepParser.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/ApiStepParser.java new file mode 100644 index 0000000000..c9b46f825e --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/ApiStepParser.java @@ -0,0 +1,89 @@ +package io.metersphere.api.parser.step; + +import io.metersphere.api.dto.request.http.KeyValueParam; +import io.metersphere.api.dto.request.http.MsHTTPElement; +import io.metersphere.api.dto.request.http.body.Body; +import io.metersphere.api.dto.scenario.ApiScenarioStepRequest; +import io.metersphere.api.utils.ApiDataUtils; +import io.metersphere.plugin.api.spi.AbstractMsTestElement; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; + +/** + * @Author: jianxing + * @CreateTime: 2024-01-20 15:43 + */ +public class ApiStepParser extends StepParser { + @Override + public AbstractMsTestElement parse(ApiScenarioStepRequest step, String resourceBlob, String stepDetail) { + if (isRef(step.getRefType())) { + if (StringUtils.isBlank(resourceBlob)) { + return null; + } + AbstractMsTestElement refResourceElement = parse2MsTestElement(resourceBlob); + if (refResourceElement instanceof MsHTTPElement && StringUtils.isNotBlank(stepDetail)) { + // 如果是 http 并且有修改请求参数,则替换请求参数 + AbstractMsTestElement msTestElement = ApiDataUtils.parseObject(stepDetail, AbstractMsTestElement.class); + return replaceParams((MsHTTPElement) msTestElement, (MsHTTPElement) refResourceElement); + } else { + return refResourceElement; + } + } else { + return StringUtils.isBlank(stepDetail) ? null : ApiDataUtils.parseObject(stepDetail, AbstractMsTestElement.class); + } + } + + + private AbstractMsTestElement replaceParams(MsHTTPElement msTestElement, MsHTTPElement refResourceElement) { + replaceKvParam(msTestElement.getHeaders(), refResourceElement.getHeaders()); + replaceKvParam(msTestElement.getQuery(), refResourceElement.getQuery()); + replaceKvParam(msTestElement.getRest(), refResourceElement.getRest()); + replaceBodyParams(msTestElement.getBody(), refResourceElement.getBody()); + return refResourceElement; + } + + /** + * 替换请求体中的参数 + * + * @param valueBody + * @param refBody + */ + private void replaceBodyParams(Body valueBody, Body refBody) { + if (refBody == null || valueBody == null) { + return; + } + if (StringUtils.equals(refBody.getBodyType(), Body.BodyType.FORM_DATA.name()) && + valueBody.getFormDataBody() != null && refBody.getFormDataBody() != null) { + replaceKvParam(valueBody.getFormDataBody().getFromValues(), valueBody.getFormDataBody().getFromValues()); + } + if (StringUtils.equals(refBody.getBodyType(), Body.BodyType.WWW_FORM.name()) && + valueBody.getWwwFormBody() != null && refBody.getWwwFormBody() != null) { + replaceKvParam(valueBody.getWwwFormBody().getFromValues(), valueBody.getWwwFormBody().getFromValues()); + } + // todo JsonSchema body + } + + /** + * 替换参数 + * + * @param valueList + * @param refList + */ + private void replaceKvParam(List valueList, List refList) { + if (CollectionUtils.isEmpty(refList) || CollectionUtils.isEmpty(valueList)) { + return; + } + refList.forEach(item -> { + KeyValueParam keyValueParam = (KeyValueParam) item; + for (Object valueItem : valueList) { + KeyValueParam valueParam = (KeyValueParam) valueItem; + if (StringUtils.equals(keyValueParam.getKey(), valueParam.getKey())) { + keyValueParam.setValue(valueParam.getValue()); + break; + } + } + }); + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/StepParser.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/StepParser.java new file mode 100644 index 0000000000..89868e6967 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/StepParser.java @@ -0,0 +1,24 @@ +package io.metersphere.api.parser.step; + +import io.metersphere.api.constants.ApiScenarioStepRefType; +import io.metersphere.api.dto.scenario.ApiScenarioStepRequest; +import io.metersphere.api.utils.ApiDataUtils; +import io.metersphere.plugin.api.spi.AbstractMsTestElement; +import org.apache.commons.lang3.StringUtils; + +/** + * @Author: jianxing + * @CreateTime: 2024-01-20 15:43 + */ +public abstract class StepParser { + + public abstract AbstractMsTestElement parse(ApiScenarioStepRequest step, String resourceBlob, String stepDetail); + + protected boolean isRef(String refType) { + return StringUtils.equalsAny(refType, ApiScenarioStepRefType.REF.name(), ApiScenarioStepRefType.PARTIAL_REF.name()); + } + + protected static AbstractMsTestElement parse2MsTestElement(String blobContent) { + return ApiDataUtils.parseObject(blobContent, AbstractMsTestElement.class); + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/StepParserFactory.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/StepParserFactory.java new file mode 100644 index 0000000000..f0f9904a2c --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/step/StepParserFactory.java @@ -0,0 +1,25 @@ +package io.metersphere.api.parser.step; + +import io.metersphere.api.constants.ApiScenarioStepType; + +import java.util.HashMap; +import java.util.Map; + +/** + * @Author: jianxing + * @CreateTime: 2024-01-20 15:43 + */ +public abstract class StepParserFactory { + + private static Map stepParserMap = new HashMap<>(); + + static { + stepParserMap.put(ApiScenarioStepType.API.name(), new ApiStepParser()); + stepParserMap.put(ApiScenarioStepType.API_CASE.name(), new ApiCaseStepParser()); + stepParserMap.put(ApiScenarioStepType.API_SCENARIO.name(), new ApiScenarioStepParser()); + } + + public static StepParser getStepParser(String stepType) { + return stepParserMap.get(stepType); + } +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java index edbd4c5517..c6f85e75a7 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java @@ -131,7 +131,7 @@ public class ApiExecuteService { // todo 接口用例 method 获取定义中的数据库字段 ParameterConfig parameterConfig = new ParameterConfig(); parameterConfig.setReportId(reportId); - String executeScript = parseExecuteScript(request.getRequest(), parameterConfig); + String executeScript = parseExecuteScript(request.getTestElement(), parameterConfig); TestResourceNodeDTO testResourceNodeDTO = getProjectExecuteNode(request.getProjectId()); @@ -306,15 +306,10 @@ public class ApiExecuteService { /** * 生成执行脚本 * - * @param testElementStr + * @param msTestElement * @param config * @return */ - private static String parseExecuteScript(String testElementStr, ParameterConfig config) { - // 解析生成脚本 - return parseExecuteScript(ApiDataUtils.parseObject(testElementStr, AbstractMsTestElement.class), config); - } - private static String parseExecuteScript(AbstractMsTestElement msTestElement, ParameterConfig config) { // 解析生成脚本 TestElementParser defaultParser = TestElementParserFactory.getDefaultParser(); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/debug/ApiDebugService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/debug/ApiDebugService.java index 0b80a42dad..9c7e0b1baa 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/debug/ApiDebugService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/debug/ApiDebugService.java @@ -18,6 +18,7 @@ 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.utils.ServiceUtils; @@ -187,6 +188,8 @@ public class ApiDebugService { runRequest.setReportId(id); runRequest.setResourceType(ApiResourceType.API_DEBUG.name()); runRequest.setRunMode(ApiExecuteRunMode.BACKEND_DEBUG.name()); + runRequest.setEnvironmentId(request.getEnvironmentId()); + runRequest.setTestElement(ApiDataUtils.parseObject(JSON.toJSONString(request.getRequest()), AbstractMsTestElement.class)); apiExecuteService.debug(runRequest); 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 e81f37d91c..b2415c9ecb 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 @@ -1011,4 +1011,13 @@ public class ApiDefinitionService { // 记录操作日志 return apiDefinitionLogService.recoverOperationHistoryLog(apiDefinitionDTO, apiDefinition.getCreateUser(), apiDefinition.getProjectId()); } + + public List getBlobByIds(List apiIds) { + if (CollectionUtils.isEmpty(apiIds)) { + return Collections.emptyList(); + } + ApiDefinitionBlobExample apiDefinitionBlobExample = new ApiDefinitionBlobExample(); + apiDefinitionBlobExample.createCriteria().andIdIn(apiIds); + return apiDefinitionBlobMapper.selectByExampleWithBLOBs(apiDefinitionBlobExample); + } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseService.java index ef2fcea439..85089743bb 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseService.java @@ -40,6 +40,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -529,4 +530,13 @@ public class ApiTestCaseService { } } } + + public List getBlobByIds(List apiCaseIds) { + if (CollectionUtils.isEmpty(apiCaseIds)) { + return Collections.emptyList(); + } + ApiTestCaseBlobExample example = new ApiTestCaseBlobExample(); + example.createCriteria().andIdIn(apiCaseIds); + return apiTestCaseBlobMapper.selectByExampleWithBLOBs(example); + } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java index a7b5f2f29e..0f1f7a1acd 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioService.java @@ -5,11 +5,20 @@ import io.metersphere.api.constants.ApiScenarioStepRefType; import io.metersphere.api.constants.ApiScenarioStepType; import io.metersphere.api.domain.*; import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest; +import io.metersphere.api.dto.debug.ApiResourceRunRequest; +import io.metersphere.api.dto.request.MsScenario; import io.metersphere.api.dto.scenario.*; import io.metersphere.api.mapper.*; +import io.metersphere.api.parser.step.StepParser; +import io.metersphere.api.parser.step.StepParserFactory; +import io.metersphere.api.service.ApiExecuteService; import io.metersphere.api.service.ApiFileResourceService; +import io.metersphere.api.service.definition.ApiDefinitionService; +import io.metersphere.api.service.definition.ApiTestCaseService; +import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.project.mapper.ExtBaseProjectVersionMapper; import io.metersphere.project.service.ProjectService; +import io.metersphere.sdk.constants.ApiExecuteRunMode; import io.metersphere.sdk.constants.ApplicationNumScope; import io.metersphere.sdk.constants.DefaultRepositoryDir; import io.metersphere.sdk.domain.Environment; @@ -45,6 +54,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import static io.metersphere.api.controller.result.ApiResultCode.API_SCENARIO_EXIST; + @Service @Transactional(rollbackFor = Exception.class) public class ApiScenarioService { @@ -84,6 +94,12 @@ public class ApiScenarioService { private ApiScenarioStepBlobMapper apiScenarioStepBlobMapper; @Resource private ApiScenarioBlobMapper apiScenarioBlobMapper; + @Resource + private ApiExecuteService apiExecuteService; + @Resource + private ApiDefinitionService apiDefinitionService; + @Resource + private ApiTestCaseService apiTestCaseService; public static final String PRIORITY = "Priority"; public static final String STATUS = "Status"; public static final String TAGS = "Tags"; @@ -284,13 +300,16 @@ public class ApiScenarioService { // 插入步骤 if (CollectionUtils.isNotEmpty(request.getSteps())) { - List apiScenarioStepsDetails = new ArrayList<>(); - List apiScenarioSteps = getUpdateApiScenarioSteps(null, request.getSteps(), apiScenarioStepsDetails); - apiScenarioStepsDetails.addAll(getUpdateStepDetails(apiScenarioSteps, request.getStepDetails())); - apiScenarioSteps.forEach(step -> step.setScenarioId(scenario.getId())); + // 获取待添加的步骤 + List steps = getApiScenarioSteps(null, request.getSteps()); + steps.forEach(step -> step.setScenarioId(scenario.getId())); + // 获取待添加的步骤详情 + List apiScenarioStepsDetails = getPartialRefStepDetails(request.getSteps()); + apiScenarioStepsDetails.addAll(getUpdateStepDetails(steps, request.getStepDetails())); apiScenarioStepsDetails.forEach(step -> step.setScenarioId(scenario.getId())); - if (CollectionUtils.isNotEmpty(apiScenarioSteps)) { - apiScenarioStepMapper.batchInsert(apiScenarioSteps); + + if (CollectionUtils.isNotEmpty(steps)) { + apiScenarioStepMapper.batchInsert(steps); } if (CollectionUtils.isNotEmpty(apiScenarioStepsDetails)) { apiScenarioStepBlobMapper.batchInsert(apiScenarioStepsDetails); @@ -359,6 +378,7 @@ public class ApiScenarioService { /** * 更新场景步骤 + * * @param request * @param scenario */ @@ -372,10 +392,13 @@ public class ApiScenarioService { return; } - List apiScenarioStepsDetails = new ArrayList<>(); - List apiScenarioSteps = getUpdateApiScenarioSteps(null, request.getSteps(), apiScenarioStepsDetails); - apiScenarioStepsDetails.addAll(getUpdateStepDetails(apiScenarioSteps, request.getStepDetails())); + // 获取待更新的步骤 + List apiScenarioSteps = getApiScenarioSteps(null, request.getSteps()); apiScenarioSteps.forEach(step -> step.setScenarioId(scenario.getId())); + + // 获取待更新的步骤详情 + List apiScenarioStepsDetails = getPartialRefStepDetails(request.getSteps()); + apiScenarioStepsDetails.addAll(getUpdateStepDetails(apiScenarioSteps, request.getStepDetails())); apiScenarioStepsDetails.forEach(step -> step.setScenarioId(scenario.getId())); List stepIds = apiScenarioSteps.stream().map(ApiScenarioStep::getId).collect(Collectors.toList()); @@ -439,6 +462,7 @@ public class ApiScenarioService { /** * 获取待更新的 ApiScenarioStepBlob 列表 + * * @param apiScenarioSteps * @param stepDetails * @return @@ -457,8 +481,7 @@ public class ApiScenarioService { if (step == null) { return; } - boolean isRef = StringUtils.equalsAny(step.getRefType(), ApiScenarioStepRefType.REF.name(), ApiScenarioStepRefType.STEP_REF.name()); - if (!isRef || StringUtils.equals(step.getRefType(), ApiScenarioStepType.API.name())) { + if (!isRef(step.getRefType()) || StringUtils.equals(step.getRefType(), ApiScenarioStepType.API.name())) { // 非引用的步骤,如果有编辑内容,保存到blob表 // 如果引用的是接口定义,也保存详情,因为应用接口定义允许修改参数值 ApiScenarioStepBlob apiScenarioStepBlob = new ApiScenarioStepBlob(); @@ -470,17 +493,20 @@ public class ApiScenarioService { return apiScenarioStepsDetails; } + private boolean isRef(String refType) { + return StringUtils.equalsAny(refType, ApiScenarioStepRefType.REF.name(), ApiScenarioStepRefType.PARTIAL_REF.name()); + } + /** * 解析步骤树结构 * 获取待更新的 ApiScenarioStep 列表 + * * @param parent * @param steps - * @param apiScenarioStepsDetails * @return */ - private List getUpdateApiScenarioSteps(ApiScenarioStepRequest parent, - List steps, - List apiScenarioStepsDetails) { + private List getApiScenarioSteps(ApiScenarioStepRequest parent, + List steps) { if (CollectionUtils.isEmpty(steps)) { return Collections.emptyList(); @@ -499,25 +525,49 @@ public class ApiScenarioService { } apiScenarioSteps.add(apiScenarioStep); - if (StringUtils.equals(step.getRefType(), ApiScenarioStepRefType.STEP_REF.name())) { - // 如果是步骤引用,blob表保存启用的子步骤ID - Set enableStepSet = getEnableStepSet(step.getChildren()); - ApiScenarioStepBlob apiScenarioStepBlob = new ApiScenarioStepBlob(); - apiScenarioStepBlob.setId(apiScenarioStep.getId()); - apiScenarioStepBlob.setContent(JSON.toJSONString(enableStepSet).getBytes()); - apiScenarioStepsDetails.add(apiScenarioStepBlob); - } - - if (StringUtils.equalsAny(step.getRefType(), ApiScenarioStepRefType.REF.name(), ApiScenarioStepRefType.STEP_REF.name())) { + if (StringUtils.equalsAny(step.getRefType(), ApiScenarioStepRefType.REF.name(), ApiScenarioStepRefType.PARTIAL_REF.name())) { // 引用的步骤不解析子步骤 continue; } // 解析子步骤 - apiScenarioSteps.addAll(getUpdateApiScenarioSteps(step, step.getChildren(), apiScenarioStepsDetails)); + apiScenarioSteps.addAll(getApiScenarioSteps(step, step.getChildren())); } return apiScenarioSteps; } + /** + * 解析步骤树结构 + * 获取待更新的 ApiScenarioStep 列表 + * + * @param steps + * @return + */ + private List getPartialRefStepDetails(List steps) { + if (CollectionUtils.isEmpty(steps)) { + return Collections.emptyList(); + } + List apiScenarioStepsDetails = new ArrayList<>(); + + for (ApiScenarioStepRequest step : steps) { + if (StringUtils.equals(step.getRefType(), ApiScenarioStepRefType.REF.name())) { + // 引用的步骤不解析子步骤 + continue; + } + if (StringUtils.equals(step.getRefType(), ApiScenarioStepRefType.PARTIAL_REF.name())) { + // 如果是部分引用,blob表保存启用的子步骤ID + Set enableStepSet = getEnableStepSet(step.getChildren()); + PartialRefStepDetail stepDetail = new PartialRefStepDetail(); + stepDetail.setEnableStepIds(enableStepSet); + ApiScenarioStepBlob apiScenarioStepBlob = new ApiScenarioStepBlob(); + apiScenarioStepBlob.setId(step.getId()); + apiScenarioStepBlob.setContent(JSON.toJSONString(stepDetail).getBytes()); + apiScenarioStepsDetails.add(apiScenarioStepBlob); + } + apiScenarioStepsDetails.addAll(getPartialRefStepDetails(step.getChildren())); + } + return apiScenarioStepsDetails; + } + /** * 获取步骤及子步骤中 enable 的步骤ID * @@ -533,8 +583,11 @@ public class ApiScenarioService { if (BooleanUtils.isTrue(step.getEnable())) { enableSteps.add(step.getId()); } - // 获取子步骤中 enable = true 的步骤 - enableSteps.addAll(getEnableStepSet(step.getChildren())); + // 完全引用和部分引用不解析子步骤 + if (!isRef(step.getRefType())) { + // 获取子步骤中 enable 的步骤 + enableSteps.addAll(getEnableStepSet(step.getChildren())); + } } return enableSteps; } @@ -609,4 +662,210 @@ public class ApiScenarioService { public String uploadTempFile(MultipartFile file) { return apiFileResourceService.uploadTempFile(file); } + + public String debug(ApiScenarioDebugRequest request) { + ApiScenario apiScenario = apiScenarioMapper.selectByPrimaryKey(request.getId()); + boolean hasSave = apiScenario != null; + String reportId = IDGenerator.nextStr(); + + List steps = request.getSteps(); + + // 记录引用的资源ID + Map> refResourceMap = new HashMap<>(); + buildRefResourceIdMap(steps, refResourceMap); + + // 查询引用的资源详情 + Map resourceBlobMap = getResourceBlobMap(refResourceMap); + + // 查询复制的步骤详情 + Map detailMap = getStepDetailMap(steps, request.getStepDetails()); + + // 解析生成待执行的场景树 + MsScenario msScenario = new MsScenario(); + msScenario.setScenarioConfig(getScenarioConfig(request, hasSave)); + parseStep2MsElement(msScenario, steps, resourceBlobMap, detailMap); + + ApiResourceRunRequest runRequest = BeanUtils.copyBean(new ApiResourceRunRequest(), request); + runRequest.setProjectId(request.getProjectId()); + runRequest.setTestId(request.getId()); + runRequest.setReportId(reportId); + runRequest.setResourceType(ApiResourceType.API_SCENARIO.name()); + runRequest.setRunMode(ApiExecuteRunMode.BACKEND_DEBUG.name()); + runRequest.setTempFileIds(request.getTempFileIds()); + runRequest.setGrouped(request.getGrouped()); + runRequest.setEnvironmentId(request.getEnvironmentId()); + runRequest.setTestElement(msScenario); + + apiExecuteService.debug(runRequest); + + return reportId; + } + + /** + * 将步骤解析成 MsTestElement 树结构 + * + * @param parentElement + * @param steps + * @param resourceBlobMap + * @param stepDetailMap + */ + private void parseStep2MsElement(AbstractMsTestElement parentElement, + List steps, + Map resourceBlobMap, + Map stepDetailMap) { + if (CollectionUtils.isNotEmpty(steps)) { + parentElement.setChildren(new LinkedList<>()); + } + for (ApiScenarioStepRequest step : steps) { + StepParser stepParser = StepParserFactory.getStepParser(step.getStepType()); + if (stepParser == null || BooleanUtils.isFalse(step.getEnable())) { + continue; + } + if (StringUtils.equals(step.getStepType(), ApiScenarioStepRefType.PARTIAL_REF.name())) { + setPartialRefStepEnable(step, stepDetailMap); + } + // 将步骤详情解析生成对应的MsTestElement + AbstractMsTestElement msTestElement = stepParser.parse(step, resourceBlobMap.get(step.getResourceId()), stepDetailMap.get(step.getId())); + if (msTestElement != null) { + parentElement.getChildren().add(msTestElement); + } + if (CollectionUtils.isNotEmpty(step.getChildren())) { + parseStep2MsElement(msTestElement, step.getChildren(), resourceBlobMap, stepDetailMap); + } + } + } + + /** + * 设置部分引用的步骤的启用状态 + * @param step + * @param stepDetailMap + */ + private void setPartialRefStepEnable(ApiScenarioStepRequest step, Map stepDetailMap) { + String stepDetail = stepDetailMap.get(step.getId()); + if (StringUtils.isBlank(stepDetail)) { + return; + } + PartialRefStepDetail partialRefStepDetail = JSON.parseObject(stepDetail, PartialRefStepDetail.class); + setChildPartialRefEnable(step.getChildren(), partialRefStepDetail.getEnableStepIds(), stepDetailMap); + } + + /** + * 设置部分引用的步骤的启用状态 + * @param steps + * @param enableStepIds + * @param stepDetailMap + */ + private void setChildPartialRefEnable(List steps, Set enableStepIds, Map stepDetailMap) { + for (ApiScenarioStepRequest step : steps) { + if (StringUtils.equals(step.getRefType(), ApiScenarioStepRefType.REF.name())) { + // 引用的启用不修改 + continue; + } + // 非完全引用的步骤,使用当前场景配置的启用状态 + step.setEnable(enableStepIds.contains(step.getId())); + if (StringUtils.equals(step.getRefType(), ApiScenarioStepRefType.PARTIAL_REF.name())) { + // 如果是部分引用的场景,重新获取详情再解析 + setPartialRefStepEnable(step, stepDetailMap); + continue; + } + // 非完全引用和部分引用的步骤,递归设置子步骤 + if (CollectionUtils.isNotEmpty(step.getChildren())) { + setChildPartialRefEnable(step.getChildren(), enableStepIds, stepDetailMap); + } + } + } + + private Map getStepDetailMap(List steps, Map stepDetailsParam) { + List needBlobStepIds = new ArrayList<>(); + for (ApiScenarioStepRequest step : steps) { + if (BooleanUtils.isFalse(step.getEnable())) { + continue; + } + if (StringUtils.equalsAny(step.getStepType(), ApiScenarioStepRefType.REF.name()) + && !StringUtils.equals(step.getRefType(), ApiScenarioStepType.API.name())) { + // 非完全引用的步骤和接口定义的步骤,才需要查blob + continue; + } + if (stepDetailsParam != null && stepDetailsParam.keySet().contains(step.getId())) { + // 前端传了blob,不需要再查 + continue; + } + needBlobStepIds.add(step.getId()); + } + + Map stepDetails = getStepBlobByIds(needBlobStepIds).stream() + .collect(Collectors.toMap(ApiScenarioStepBlob::getId, blob -> new String(blob.getContent()))); + // 前端有传,就用前端传的 + if (stepDetailsParam != null) { + stepDetailsParam.forEach((stepId, detail) -> stepDetails.put(stepId, JSON.toJSONString(detail))); + } + return stepDetails; + } + + private Map getResourceBlobMap(Map> refResourceMap) { + Map resourceBlobMap = new HashMap<>(); + List apiIds = refResourceMap.get(ApiScenarioStepType.API.name()); + List apiDefinitionBlobs = apiDefinitionService.getBlobByIds(apiIds); + apiDefinitionBlobs.forEach(blob -> resourceBlobMap.put(blob.getId(), new String(blob.getRequest()))); + + List apiCaseIds = refResourceMap.get(ApiScenarioStepType.API_CASE.name()); + List apiTestCaseBlobs = apiTestCaseService.getBlobByIds(apiCaseIds); + apiTestCaseBlobs.forEach(blob -> resourceBlobMap.put(blob.getId(), new String(blob.getRequest()))); + + List apiScenarioIds = refResourceMap.get(ApiScenarioStepType.API_SCENARIO.name()); + List apiScenarioBlobs = getBlobByIds(apiScenarioIds); + apiScenarioBlobs.forEach(blob -> resourceBlobMap.put(blob.getId(), new String(blob.getConfig()))); + return resourceBlobMap; + } + + private List getStepBlobByIds(List stepIds) { + if (CollectionUtils.isEmpty(stepIds)) { + return Collections.emptyList(); + } + ApiScenarioStepBlobExample example = new ApiScenarioStepBlobExample(); + example.createCriteria().andIdIn(stepIds); + return apiScenarioStepBlobMapper.selectByExampleWithBLOBs(example); + } + + private List getBlobByIds(List apiScenarioIds) { + if (CollectionUtils.isEmpty(apiScenarioIds)) { + return Collections.emptyList(); + } + ApiScenarioBlobExample example = new ApiScenarioBlobExample(); + example.createCriteria().andIdIn(apiScenarioIds); + return apiScenarioBlobMapper.selectByExampleWithBLOBs(example); + } + + private void buildRefResourceIdMap(List steps, Map> refResourceIdMap) { + for (ApiScenarioStepRequest step : steps) { + if (isRef(step.getRefType()) && BooleanUtils.isTrue(step.getEnable())) { + // 记录引用的步骤ID + List resourceIds = refResourceIdMap.get(step.getStepType()); + if (resourceIds == null) { + resourceIds = new ArrayList<>(); + refResourceIdMap.put(step.getStepType(), resourceIds); + } + resourceIds.add(step.getResourceId()); + } + + if (CollectionUtils.isNotEmpty(step.getChildren())) { + buildRefResourceIdMap(step.getChildren(), refResourceIdMap); + } + } + } + + private ScenarioConfig getScenarioConfig(ApiScenarioDebugRequest request, boolean hasSave) { + ScenarioConfig scenarioConfig = null; + if (request.getScenarioConfig() != null) { + // 优先使用前端传的配置 + scenarioConfig = request.getScenarioConfig(); + } else if (hasSave) { + // 没传并且保存过,则从数据库获取 + ApiScenarioBlob apiScenarioBlob = apiScenarioBlobMapper.selectByPrimaryKey(request.getId()); + if (apiScenarioBlob != null) { + scenarioConfig = JSON.parseObject(new String(apiScenarioBlob.getConfig()), ScenarioConfig.class); + } + } + return scenarioConfig; + } } \ No newline at end of file diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/utils/JmeterElementConverterRegister.java b/backend/services/api-test/src/main/java/io/metersphere/api/utils/JmeterElementConverterRegister.java index 4b844f8a5d..b2548b7e00 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/utils/JmeterElementConverterRegister.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/utils/JmeterElementConverterRegister.java @@ -3,6 +3,7 @@ package io.metersphere.api.utils; import io.metersphere.api.parser.jmeter.MsCommentScriptElementConverter; import io.metersphere.api.parser.jmeter.MsCommonElementConverter; import io.metersphere.api.parser.jmeter.MsHTTPElementConverter; +import io.metersphere.api.parser.jmeter.MsScenarioConverter; import io.metersphere.plugin.api.spi.AbstractJmeterElementConverter; import io.metersphere.plugin.api.spi.MsTestElement; import io.metersphere.plugin.sdk.util.PluginLogUtils; @@ -30,6 +31,7 @@ public class JmeterElementConverterRegister { register(MsHTTPElementConverter.class); register(MsCommonElementConverter.class); register(MsCommentScriptElementConverter.class); + register(MsScenarioConverter.class); } /** diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDebugControllerTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDebugControllerTests.java index 17d8970bc9..9b99e1b12f 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDebugControllerTests.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDebugControllerTests.java @@ -363,7 +363,7 @@ public class ApiDebugControllerTests extends BaseTest { MsHTTPElement msHTTPElement = new MsHTTPElement(); msHTTPElement.setPath("/test"); msHTTPElement.setMethod("GET"); - request.setRequest(ApiDataUtils.toJSONString(msHTTPElement)); + request.setRequest(JSON.parseObject(ApiDataUtils.toJSONString(msHTTPElement))); // @校验组织没有资源池权限异常 assertErrorCode(this.requestPost(DEBUG, request), ApiResultCode.EXECUTE_RESOURCE_POOL_NOT_CONFIG); @@ -392,7 +392,7 @@ public class ApiDebugControllerTests extends BaseTest { msHTTPElement = MsHTTPElementTest.getMsHttpElement(); msHTTPElement.setChildren(linkedList); msHTTPElement.setEnable(true); - request.setRequest(ApiDataUtils.toJSONString(msHTTPElement)); + request.setRequest(getMsElementParam(msHTTPElement)); this.requestPostWithOk(DEBUG, request); // 测试请求体 @@ -407,10 +407,10 @@ public class ApiDebugControllerTests extends BaseTest { // 增加覆盖率 msHTTPElement.setMethod("GET"); - request.setRequest(ApiDataUtils.toJSONString(msHTTPElement)); + request.setRequest(getMsElementParam(msHTTPElement)); this.requestPostWithOk(DEBUG, request); msHTTPElement.setEnable(false); - request.setRequest(ApiDataUtils.toJSONString(msHTTPElement)); + request.setRequest(getMsElementParam(msHTTPElement)); this.requestPostWithOk(DEBUG, request); // 增加覆盖率 @@ -426,35 +426,39 @@ public class ApiDebugControllerTests extends BaseTest { private void testBodyParse(ApiDebugRunRequest request, MsHTTPElement msHTTPElement, Body generalBody) throws Exception { // 测试 FORM_DATA generalBody.setBodyType(Body.BodyType.FORM_DATA.name()); - request.setRequest(ApiDataUtils.toJSONString(msHTTPElement)); + request.setRequest(getMsElementParam(msHTTPElement)); this.requestPostWithOk(DEBUG, request); // 测试 WWW_FORM generalBody.setBodyType(Body.BodyType.WWW_FORM.name()); - request.setRequest(ApiDataUtils.toJSONString(msHTTPElement)); + request.setRequest(getMsElementParam(msHTTPElement)); this.requestPostWithOk(DEBUG, request); // 测试 BINARY generalBody.setBodyType(Body.BodyType.BINARY.name()); - request.setRequest(ApiDataUtils.toJSONString(msHTTPElement)); + request.setRequest(getMsElementParam(msHTTPElement)); this.requestPostWithOk(DEBUG, request); // 测试 JSON generalBody.setBodyType(Body.BodyType.JSON.name()); - request.setRequest(ApiDataUtils.toJSONString(msHTTPElement)); + request.setRequest(getMsElementParam(msHTTPElement)); this.requestPostWithOk(DEBUG, request); // 测试 XML generalBody.setBodyType(Body.BodyType.XML.name()); - request.setRequest(ApiDataUtils.toJSONString(msHTTPElement)); + request.setRequest(getMsElementParam(msHTTPElement)); this.requestPostWithOk(DEBUG, request); // 测试 RAW generalBody.setBodyType(Body.BodyType.RAW.name()); - request.setRequest(ApiDataUtils.toJSONString(msHTTPElement)); + request.setRequest(getMsElementParam(msHTTPElement)); this.requestPostWithOk(DEBUG, request); } + private Object getMsElementParam(MsHTTPElement msHTTPElement) { + return JSON.parseObject(ApiDataUtils.toJSONString(msHTTPElement)); + } + @Test @Order(7) public void delete() throws Exception { 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 d833812351..cb0fa16555 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 @@ -3,6 +3,7 @@ package io.metersphere.api.controller; import io.metersphere.api.constants.ApiDefinitionStatus; import io.metersphere.api.constants.ApiScenarioStatus; import io.metersphere.api.constants.ApiScenarioStepRefType; +import io.metersphere.api.constants.ApiScenarioStepType; import io.metersphere.api.domain.*; import io.metersphere.api.dto.definition.ApiDefinitionAddRequest; import io.metersphere.api.dto.definition.ApiTestCaseAddRequest; @@ -11,6 +12,7 @@ import io.metersphere.api.dto.request.assertion.MsScriptAssertion; import io.metersphere.api.dto.request.http.MsHTTPElement; import io.metersphere.api.dto.scenario.*; import io.metersphere.api.mapper.*; +import io.metersphere.api.service.BaseResourcePoolTestService; import io.metersphere.api.service.definition.ApiDefinitionService; import io.metersphere.api.service.definition.ApiTestCaseService; import io.metersphere.api.utils.ApiDataUtils; @@ -60,6 +62,7 @@ public class ApiScenarioControllerTests extends BaseTest { private static final String FOLLOW = "follow/"; protected static final String UPLOAD_TEMP_FILE = "upload/temp/file"; protected static final String DELETE_TO_GC = "delete-to-gc/{0}"; + protected static final String DEBUG = "debug"; private static final ResultMatcher ERROR_REQUEST_MATCHER = status().is5xxServerError(); @Resource @@ -86,6 +89,8 @@ public class ApiScenarioControllerTests extends BaseTest { private ApiDefinitionService apiDefinitionService; @Resource private ApiTestCaseService apiTestCaseService; + @Resource + private BaseResourcePoolTestService baseResourcePoolTestService; private static ApiScenario addApiScenario; private static ApiScenario anOtherAddApiScenario; private static ApiDefinition apiDefinition; @@ -227,7 +232,7 @@ public class ApiScenarioControllerTests extends BaseTest { stepRequest2.setId(IDGenerator.nextStr()); stepRequest2.setName(addApiScenario.getName()); stepRequest2.setResourceId(addApiScenario.getId()); - stepRequest2.setRefType(ApiScenarioStepRefType.STEP_REF.name()); + stepRequest2.setRefType(ApiScenarioStepRefType.PARTIAL_REF.name()); stepRequest2.setChildren(List.of(stepRequest)); steps = List.of(stepRequest, stepRequest2); request.setSteps(steps); @@ -326,8 +331,11 @@ public class ApiScenarioControllerTests extends BaseTest { stepRequest3.setResourceId(apiDefinition.getId()); stepRequest.setName(apiDefinition.getName() + "3"); stepRequest3.setRefType(ApiScenarioStepRefType.REF.name()); - - return List.of(stepRequest, stepRequest2); + return new ArrayList<>() {{ + add(stepRequest); + add(stepRequest2); + add(stepRequest3); + }}; } private void initTestData() { @@ -409,30 +417,6 @@ public class ApiScenarioControllerTests extends BaseTest { requestPostPermissionTest(PermissionConstants.PROJECT_API_SCENARIO_UPDATE, DEFAULT_UPDATE, request); } - @Test - @Order(3) - public void deleteToGc() throws Exception { - // @@请求成功 - this.requestGetWithOk(DELETE_TO_GC, addApiScenario.getId()); - // todo 校验请求成功数据 - // @@校验日志 - checkLog(addApiScenario.getId(), OperationLogType.DELETE); - // @@校验权限 - requestGetPermissionTest(PermissionConstants.PROJECT_API_SCENARIO_DELETE, DELETE_TO_GC, addApiScenario.getId()); - } - - @Test - @Order(4) - public void delete() throws Exception { - // @@请求成功 - this.requestGetWithOk(DEFAULT_DELETE, addApiScenario.getId()); - // todo 校验请求成功数据 - // @@校验日志 - checkLog(addApiScenario.getId(), OperationLogType.DELETE); - // @@校验权限 - requestGetPermissionTest(PermissionConstants.PROJECT_API_SCENARIO_DELETE, DEFAULT_DELETE, addApiScenario.getId()); - } - @Test @Order(5) public void uploadTempFile() throws Exception { @@ -450,6 +434,45 @@ public class ApiScenarioControllerTests extends BaseTest { requestUploadPermissionTest(PermissionConstants.PROJECT_API_SCENARIO_UPDATE, UPLOAD_TEMP_FILE, file); } + @Test + @Order(6) + public void debug() throws Exception { + mockPost("/api/debug", ""); + baseResourcePoolTestService.initProjectResourcePool(); + // @@请求成功 + ApiScenarioDebugRequest request = new ApiScenarioDebugRequest(); + request.setProjectId(DEFAULT_PROJECT_ID); + request.setScenarioConfig(new ScenarioConfig()); + request.setEnvironmentId("environmentId"); + + List steps = getApiScenarioStepRequests(); + ApiScenarioStepRequest stepRequest = new ApiScenarioStepRequest(); + stepRequest.setName("test step"); + stepRequest.setId(IDGenerator.nextStr()); + stepRequest.setRefType(ApiScenarioStepRefType.REF.name()); + stepRequest.setResourceId(addApiScenario.getId()); + stepRequest.setResourceNum(addApiScenario.getNum().toString()); + stepRequest.setName(addApiScenario.getName()); + stepRequest.setStepType(ApiScenarioStepType.API_SCENARIO.name()); + stepRequest.setChildren(getApiScenarioStepRequests()); + steps.add(stepRequest); + ApiScenarioStepRequest copyScenarioStep = BeanUtils.copyBean(new ApiScenarioStepRequest(), stepRequest); + copyScenarioStep.setRefType(ApiScenarioStepRefType.COPY.name()); + copyScenarioStep.setChildren(getApiScenarioStepRequests()); + steps.add(stepRequest); + ApiScenarioStepRequest partialScenarioStep = BeanUtils.copyBean(new ApiScenarioStepRequest(), stepRequest); + partialScenarioStep.setRefType(ApiScenarioStepRefType.PARTIAL_REF.name()); + partialScenarioStep.setChildren(getApiScenarioStepRequests()); + steps.add(partialScenarioStep); + request.setId(addApiScenario.getId()); + request.setSteps(steps); + request.setStepDetails(new HashMap<>()); + this.requestPostWithOk(DEBUG, request); + + // @@校验权限 + requestPostPermissionTest(PermissionConstants.PROJECT_API_SCENARIO_EXECUTE, DEBUG, request); + } + private String doUploadTempFile(MockMultipartFile file) throws Exception { return JSON.parseObject(requestUploadFileWithOkAndReturn(UPLOAD_TEMP_FILE, file) .getResponse() @@ -467,6 +490,30 @@ public class ApiScenarioControllerTests extends BaseTest { return file; } + @Test + @Order(9) + public void deleteToGc() throws Exception { + // @@请求成功 + this.requestGetWithOk(DELETE_TO_GC, addApiScenario.getId()); + // todo 校验请求成功数据 + // @@校验日志 + checkLog(addApiScenario.getId(), OperationLogType.DELETE); + // @@校验权限 + requestGetPermissionTest(PermissionConstants.PROJECT_API_SCENARIO_DELETE, DELETE_TO_GC, addApiScenario.getId()); + } + + @Test + @Order(10) + public void delete() throws Exception { + // @@请求成功 + this.requestGetWithOk(DEFAULT_DELETE, addApiScenario.getId()); + // todo 校验请求成功数据 + // @@校验日志 + checkLog(addApiScenario.getId(), OperationLogType.DELETE); + // @@校验权限 + requestGetPermissionTest(PermissionConstants.PROJECT_API_SCENARIO_DELETE, DEFAULT_DELETE, addApiScenario.getId()); + } + @Test @Order(11) public void page() throws Exception {