feat(接口测试): 接口场景的批量复制功能开发

This commit is contained in:
song-tianyang 2024-01-25 18:27:50 +08:00 committed by Craftsman
parent a7509b47dc
commit 797929cea4
49 changed files with 1038 additions and 125 deletions

View File

@ -381,6 +381,8 @@ message.domain.test_plan_plannedStartTime=计划开始时间
message.domain.test_plan_plannedEndTime=计划结束时间 message.domain.test_plan_plannedEndTime=计划结束时间
message.domain.test_plan_actualStartTime=实际开始时间 message.domain.test_plan_actualStartTime=实际开始时间
message.domain.test_plan_actualEndTime=实际结束时间 message.domain.test_plan_actualEndTime=实际结束时间
message.domain.test_plan_num=编号
message.domain.test_plan_type=类型
# 用例评审 # 用例评审
message.domain.case_review_name=名称 message.domain.case_review_name=名称
message.domain.case_review_status=评审状态 message.domain.case_review_status=评审状态

View File

@ -417,6 +417,8 @@ message.domain.test_plan_plannedStartTime=Planned start time
message.domain.test_plan_plannedEndTime=Planned end time message.domain.test_plan_plannedEndTime=Planned end time
message.domain.test_plan_actualStartTime=Actual start time message.domain.test_plan_actualStartTime=Actual start time
message.domain.test_plan_actualEndTime=Actual end time message.domain.test_plan_actualEndTime=Actual end time
message.domain.test_plan_num=Num
message.domain.test_plan_type=Type
# case Review # case Review
message.domain.case_review_name=Name message.domain.case_review_name=Name
message.domain.case_review_status=Review status message.domain.case_review_status=Review status

View File

@ -416,6 +416,8 @@ message.domain.test_plan_plannedStartTime=计划开始时间
message.domain.test_plan_plannedEndTime=计划结束时间 message.domain.test_plan_plannedEndTime=计划结束时间
message.domain.test_plan_actualStartTime=实际开始时间 message.domain.test_plan_actualStartTime=实际开始时间
message.domain.test_plan_actualEndTime=实际结束时间 message.domain.test_plan_actualEndTime=实际结束时间
message.domain.test_plan_num=编号
message.domain.test_plan_type=类型
# 用例评审 # 用例评审
message.domain.case_review_name=名称 message.domain.case_review_name=名称
message.domain.case_review_status=评审状态 message.domain.case_review_status=评审状态

View File

@ -417,6 +417,8 @@ message.domain.test_plan_plannedStartTime=計劃開始時間
message.domain.test_plan_plannedEndTime=計劃結束時間 message.domain.test_plan_plannedEndTime=計劃結束時間
message.domain.test_plan_actualStartTime=實際開始時間 message.domain.test_plan_actualStartTime=實際開始時間
message.domain.test_plan_actualEndTime=實際結束時間 message.domain.test_plan_actualEndTime=實際結束時間
message.domain.test_plan_num=編號
message.domain.test_plan_type=類型
# 用例評審 # 用例評審
message.domain.case_review_name=名稱 message.domain.case_review_name=名稱
message.domain.case_review_status=評審狀態 message.domain.case_review_status=評審狀態

View File

@ -0,0 +1,8 @@
package io.metersphere.api.constants;
public enum ApiResource {
PROJECT,
API_DEFINITION,
API_TEST_CASE,
API_SCENARIO
}

View File

@ -0,0 +1,100 @@
package io.metersphere.api.controller.scenario;
import io.metersphere.api.constants.ApiResource;
import io.metersphere.api.dto.response.ApiScenarioBatchOperationResponse;
import io.metersphere.api.dto.scenario.ApiScenarioBatchCopyRequest;
import io.metersphere.api.dto.scenario.ApiScenarioBatchEditRequest;
import io.metersphere.api.service.ApiValidateService;
import io.metersphere.api.service.scenario.ApiScenarioService;
import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.utils.SessionUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/api/scenario")
@Tag(name = "接口测试-接口场景批量操作")
public class ApiScenarioBatchOperationController {
@Resource
private ApiValidateService apiValidateService;
@Resource
private ApiScenarioService apiScenarioService;
@PostMapping("/batch-operation/edit")
@Operation(summary = "接口测试-接口场景批量操作-批量编辑")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void batchUpdate(@Validated @RequestBody ApiScenarioBatchEditRequest request) {
apiValidateService.validateApiMenuInProject(request.getProjectId(), ApiResource.PROJECT.name());
apiScenarioService.batchEdit(request, SessionUtils.getUserId());
}
/*
todo:
@PostMapping("/gc-batch-delete")
@Operation(summary = "接口测试-接口场景批量操作-回收站列表-批量删除")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
@SendNotice(taskType = NoticeConstants.TaskType.API_SCENARIO_TASK, event = NoticeConstants.Event.UPDATE, target = "#targetClass.getCaseDTO(#request)", targetClass = ApiTestCaseNoticeService.class)
public void deleteFromGC(@Validated @RequestBody ApiScenarioBatchEditRequest request) {
apiValidateService.validateApiMenuInProject(request.getProjectId(), ApiResource.PROJECT.name());
apiScenarioService.deleteFromGC(request, SessionUtils.getUserId());
}
todo
@PostMapping("/gc-batch-restore")
@Operation(summary = "接口测试-接口场景批量操作-回收站列表-批量恢复")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void restoreFromGC(@Validated @RequestBody ApiScenarioBatchEditRequest request) {
apiValidateService.validateApiMenuInProject(request.getProjectId(), ApiResource.PROJECT.name());
apiScenarioService.restoreFromGC(request, SessionUtils.getUserId());
}
todo
@PostMapping("/batch-delete")
@Operation(summary = "接口测试-接口场景批量操作-场景列表操作-批量删除")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void batchDelete(@Validated @RequestBody ApiScenarioBatchEditRequest request) {
apiValidateService.validateApiMenuInProject(request.getProjectId(), ApiResource.PROJECT.name());
apiScenarioService.batchDelete(request, SessionUtils.getUserId());
}
todo
@PostMapping("/batch-move")
@Operation(summary = "接口测试-接口场景批量操作-场景列表操作-批量移动")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public void batchMove(@Validated @RequestBody ApiScenarioBatchEditRequest request) {
apiValidateService.validateApiMenuInProject(request.getProjectId(), ApiResource.PROJECT.name());
apiScenarioService.batchMove(request, new LogInsertModule(SessionUtils.getUserId(), "/api/scenario/batch-operation/batch-move", HttpMethodConstants.POST.name()));
}
*/
@PostMapping("/batch-operation/copy")
@Operation(summary = "接口测试-接口场景批量操作-场景列表操作-批量复制")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public ApiScenarioBatchOperationResponse batchCopy(@Validated @RequestBody ApiScenarioBatchCopyRequest request) {
apiValidateService.validateApiMenuInProject(request.getProjectId(), ApiResource.PROJECT.name());
return apiScenarioService.batchCopy(request, new LogInsertModule(SessionUtils.getUserId(), "/api/scenario/batch-operation/copy", HttpMethodConstants.POST.name()));
}
}

View File

@ -2,8 +2,10 @@ package io.metersphere.api.controller.scenario;
import com.github.pagehelper.Page; import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import io.metersphere.api.constants.ApiResource;
import io.metersphere.api.domain.ApiScenario; import io.metersphere.api.domain.ApiScenario;
import io.metersphere.api.dto.scenario.*; import io.metersphere.api.dto.scenario.*;
import io.metersphere.api.service.ApiValidateService;
import io.metersphere.api.service.scenario.ApiScenarioLogService; import io.metersphere.api.service.scenario.ApiScenarioLogService;
import io.metersphere.api.service.scenario.ApiScenarioService; import io.metersphere.api.service.scenario.ApiScenarioService;
import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.constants.PermissionConstants;
@ -31,6 +33,8 @@ import java.util.List;
public class ApiScenarioController { public class ApiScenarioController {
@Resource @Resource
private ApiScenarioService apiScenarioService; private ApiScenarioService apiScenarioService;
@Resource
private ApiValidateService apiValidateService;
@PostMapping("/page") @PostMapping("/page")
@Operation(summary = "接口测试-接口场景管理-场景列表(deleted 状态为 1 时为回收站数据)") @Operation(summary = "接口测试-接口场景管理-场景列表(deleted 状态为 1 时为回收站数据)")
@ -53,14 +57,6 @@ public class ApiScenarioController {
return PageUtils.setPageInfo(page, apiScenarioService.getScenarioPage(request)); return PageUtils.setPageInfo(page, apiScenarioService.getScenarioPage(request));
} }
@PostMapping("/batch/edit")
@Operation(summary = "接口测试-接口场景管理-批量编辑")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_UPDATE)
@CheckOwner(resourceId = "#request.getSelectIds()", resourceType = "api_scenario")
public void batchUpdate(@Validated @RequestBody ApiScenarioBatchEditRequest request) {
apiScenarioService.batchEdit(request, SessionUtils.getUserId());
}
@GetMapping("follow/{id}") @GetMapping("follow/{id}")
@Operation(summary = "接口测试-接口场景管理-关注/ 取消关注") @Operation(summary = "接口测试-接口场景管理-关注/ 取消关注")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_UPDATE) @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_UPDATE)
@ -126,6 +122,16 @@ public class ApiScenarioController {
return apiScenarioService.getStepDetail(stepId); return apiScenarioService.getStepDetail(stepId);
} }
@GetMapping("/restore/{id}")
@Operation(summary = "接口测试-接口场景管理-删除场景到回收站")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_DELETE)
@Log(type = OperationLogType.RESTORE, expression = "#msClass.restoreLog(#id)", msClass = ApiScenarioLogService.class)
@CheckOwner(resourceId = "#id", resourceType = "api_scenario")
public void recover(@PathVariable String id) {
apiValidateService.validateApiMenuInProject(id, ApiResource.API_SCENARIO.name());
apiScenarioService.restore(id);
}
@PostMapping("/debug") @PostMapping("/debug")
@Operation(summary = "接口测试-接口场景管理-场景调试") @Operation(summary = "接口测试-接口场景管理-场景调试")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_EXECUTE) @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_EXECUTE)

View File

@ -0,0 +1,48 @@
package io.metersphere.api.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class ApiScenarioBatchOperationResponse {
@Schema(description = "成功的数据")
private long success;
@Schema(description = "失败的数据量")
private long error;
@Schema(description = "成功数据")
List<OperationDataInfo> successData = new ArrayList<>();
@Schema(description = "失败数据")
Map<String, List<OperationDataInfo>> errorInfo = new HashMap<>();
public void addSuccessData(String id, Long num, String name) {
OperationDataInfo operationData = new OperationDataInfo(id, num, name);
successData.add(operationData);
this.success = successData.size();
}
public void addErrorData(String errorMessage, String id, Long num, String name) {
OperationDataInfo errorData = new OperationDataInfo(id, num, name);
if (!errorInfo.containsKey(errorMessage)) {
this.errorInfo.put(errorMessage, new ArrayList<>());
}
this.errorInfo.get(errorMessage).add(errorData);
this.error++;
}
public void merge(ApiScenarioBatchOperationResponse response) {
this.successData.addAll(response.getSuccessData());
this.success = successData.size();
response.getErrorInfo().forEach((errorMessage, errorDataList) -> {
if (!this.errorInfo.containsKey(errorMessage)) {
this.errorInfo.put(errorMessage, new ArrayList<>());
}
this.errorInfo.get(errorMessage).addAll(errorDataList);
this.error += errorDataList.size();
});
}
}

View File

@ -0,0 +1,18 @@
package io.metersphere.api.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OperationDataInfo {
@Schema(description = "数据id")
private String id;
@Schema(description = "数据编号")
private Long num;
@Schema(description = "数据名称")
private String name;
}

View File

@ -0,0 +1,20 @@
package io.metersphere.api.dto.scenario;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = false)
public class ApiScenarioBatchCopyRequest extends ApiScenarioBatchRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "复制的目标模块ID")
@NotBlank
private String targetModuleId;
}

View File

@ -1,7 +1,7 @@
package io.metersphere.api.mapper; package io.metersphere.api.mapper;
import io.metersphere.api.domain.ApiScenario; import io.metersphere.api.domain.ApiScenario;
import io.metersphere.api.dto.scenario.ApiScenarioBatchEditRequest; import io.metersphere.api.dto.scenario.ApiScenarioBatchRequest;
import io.metersphere.api.dto.scenario.ApiScenarioDTO; import io.metersphere.api.dto.scenario.ApiScenarioDTO;
import io.metersphere.api.dto.scenario.ApiScenarioPageRequest; import io.metersphere.api.dto.scenario.ApiScenarioPageRequest;
import io.metersphere.system.dto.taskcenter.TaskCenterDTO; import io.metersphere.system.dto.taskcenter.TaskCenterDTO;
@ -18,7 +18,7 @@ import java.util.List;
public interface ExtApiScenarioMapper { public interface ExtApiScenarioMapper {
List<ApiScenarioDTO> list(@Param("request") ApiScenarioPageRequest request); List<ApiScenarioDTO> list(@Param("request") ApiScenarioPageRequest request);
List<String> getIds(@Param("request") ApiScenarioBatchEditRequest request, @Param("deleted") boolean deleted); List<String> getIds(@Param("request") ApiScenarioBatchRequest request, @Param("deleted") boolean deleted);
List<ApiScenario> getInfoByIds(@Param("ids") List<String> ids, @Param("deleted") boolean deleted); List<ApiScenario> getInfoByIds(@Param("ids") List<String> ids, @Param("deleted") boolean deleted);

View File

@ -98,14 +98,23 @@
<select id="getTestCaseByProvider" resultType="io.metersphere.api.domain.ApiScenario"> <select id="getTestCaseByProvider" resultType="io.metersphere.api.domain.ApiScenario">
SELECT SELECT
t1.id, t1.version_id id, version_id
FROM FROM
api_scenario t1 api_scenario
WHERE t1.deleted =#{deleted} WHERE deleted =#{deleted}
<include refid="queryWhereConditionByBaseQueryRequest"/> <include refid="queryWhereConditionByBaseQueryRequest"/>
</select> </select>
<sql id="queryWhereConditionByBaseQueryRequest"> <sql id="queryWhereConditionByBaseQueryRequest">
<if test="request.projectId != null and request.projectId != ''">
and api_scenario.project_id = #{request.projectId}
</if>
<if test="request.moduleIds != null and request.moduleIds.size() > 0">
and api_scenario.module_id in
<foreach collection="request.moduleIds" item="nodeId" separator="," open="(" close=")">
#{nodeId}
</foreach>
</if>
<if test="request.condition.combine != null"> <if test="request.condition.combine != null">
<include refid="combine"> <include refid="combine">
<property name="condition" value="request.condition.combine"/> <property name="condition" value="request.condition.combine"/>

View File

@ -277,4 +277,14 @@ public class ApiFileResourceService {
apiFileResourceMapper.batchInsert(apiFileResources); apiFileResourceMapper.batchInsert(apiFileResources);
} }
} }
public List<ApiFileResource> selectByApiScenarioId(List<String> scenarioIds) {
ApiFileResourceExample example = new ApiFileResourceExample();
example.createCriteria().andResourceIdIn(scenarioIds);
return apiFileResourceMapper.selectByExample(example);
}
public void batchInsert(List<ApiFileResource> insertApiFileResourceList) {
apiFileResourceMapper.batchInsert(insertApiFileResourceList);
}
} }

View File

@ -0,0 +1,31 @@
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;
@Service
public class ApiValidateService {
@Resource
private CommonProjectService commonProjectService;
//校验接口菜单是否开启
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";
}
commonProjectService.checkProjectHasModuleMenu(Collections.singletonList(ProjectMenuConstants.MODULE_MENU_API_TEST), resourceId, tableName);
}
}

View File

@ -158,10 +158,6 @@ public class ApiDebugModuleService extends ModuleTreeService {
throw new MSException(Translator.get("node.name.repeat")); throw new MSException(Translator.get("node.name.repeat"));
} }
example.clear(); example.clear();
//非默认节点检查该节点所在分支的总长度确保不超过阈值
if (!StringUtils.equals(apiDebugModule.getId(), ModuleConstants.DEFAULT_NODE_ID)) {
this.checkBranchModules(this.getRootNodeId(apiDebugModule), extApiDebugModuleMapper::selectChildrenIdsByParentIds);
}
} }
private String getRootNodeId(ApiDebugModule module) { private String getRootNodeId(ApiDebugModule module) {

View File

@ -124,11 +124,6 @@ public class ApiDefinitionModuleService extends ModuleTreeService {
throw new MSException(Translator.get("node.name.repeat")); throw new MSException(Translator.get("node.name.repeat"));
} }
example.clear(); example.clear();
//非默认节点检查该节点所在分支的总长度确保不超过阈值
if (!StringUtils.equals(module.getId(), ModuleConstants.DEFAULT_NODE_ID)) {
this.checkBranchModules(this.getRootNodeId(module), extApiDefinitionModuleMapper::selectChildrenIdsByParentIds);
}
} }
private String getRootNodeId(ApiDefinitionModule module) { private String getRootNodeId(ApiDefinitionModule module) {

View File

@ -2,6 +2,7 @@ package io.metersphere.api.service.scenario;
import io.metersphere.api.domain.ApiScenario; import io.metersphere.api.domain.ApiScenario;
import io.metersphere.api.domain.ApiScenarioExample; import io.metersphere.api.domain.ApiScenarioExample;
import io.metersphere.api.dto.response.ApiScenarioBatchOperationResponse;
import io.metersphere.api.dto.scenario.ApiScenarioAddRequest; import io.metersphere.api.dto.scenario.ApiScenarioAddRequest;
import io.metersphere.api.dto.scenario.ApiScenarioUpdateRequest; import io.metersphere.api.dto.scenario.ApiScenarioUpdateRequest;
import io.metersphere.api.mapper.ApiScenarioMapper; import io.metersphere.api.mapper.ApiScenarioMapper;
@ -10,6 +11,7 @@ import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.Translator; import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.dto.builder.LogDTOBuilder; import io.metersphere.system.dto.builder.LogDTOBuilder;
import io.metersphere.system.log.constants.OperationLogModule; import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.log.constants.OperationLogType;
@ -160,4 +162,43 @@ public class ApiScenarioLogService {
dto.setOriginalValue(JSON.toJSONBytes(apiScenario)); dto.setOriginalValue(JSON.toJSONBytes(apiScenario));
return dto; return dto;
} }
public LogDTO restoreLog(String id) {
ApiScenario apiScenario = apiScenarioMapper.selectByPrimaryKey(id);
LogDTO dto = new LogDTO(
apiScenario.getProjectId(),
null,
apiScenario.getId(),
null,
OperationLogType.RESTORE.name(),
OperationLogModule.API_SCENARIO,
apiScenario.getName());
dto.setOriginalValue(JSON.toJSONBytes(apiScenario));
return dto;
}
public void saveBatchCopyLog(ApiScenarioBatchOperationResponse response, String projectId, LogInsertModule logInsertModule) {
Project project = projectMapper.selectByPrimaryKey(projectId);
//取出apiTestCases所有的id为新的list
List<LogDTO> logs = new ArrayList<>();
response.getSuccessData().forEach(item -> {
LogDTO dto = LogDTOBuilder.builder()
.projectId(project.getId())
.organizationId(project.getOrganizationId())
.type(OperationLogType.COPY.name())
.module(OperationLogModule.API_SCENARIO)
.method(logInsertModule.getRequestMethod())
.path(logInsertModule.getRequestUrl())
.sourceId(item.getId())
.content(item.getName())
.createUser(logInsertModule.getOperator())
.build().getLogDTO();
dto.setHistory(false);
logs.add(dto);
}
);
operationLogService.batchAdd(logs);
}
} }

View File

@ -111,11 +111,6 @@ public class ApiScenarioModuleService extends ModuleTreeService {
throw new MSException(Translator.get("node.name.repeat")); throw new MSException(Translator.get("node.name.repeat"));
} }
example.clear(); example.clear();
//非默认节点检查该节点所在分支的总长度确保不超过阈值
if (!StringUtils.equals(module.getId(), ModuleConstants.DEFAULT_NODE_ID)) {
this.checkBranchModules(this.getRootNodeId(module), extApiScenarioModuleMapper::selectChildrenIdsByParentIds);
}
} }
private String getRootNodeId(ApiScenarioModule module) { private String getRootNodeId(ApiScenarioModule module) {

View File

@ -7,6 +7,7 @@ import io.metersphere.api.domain.*;
import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest; import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest;
import io.metersphere.api.dto.debug.ApiResourceRunRequest; import io.metersphere.api.dto.debug.ApiResourceRunRequest;
import io.metersphere.api.dto.request.MsScenario; import io.metersphere.api.dto.request.MsScenario;
import io.metersphere.api.dto.response.ApiScenarioBatchOperationResponse;
import io.metersphere.api.dto.scenario.*; import io.metersphere.api.dto.scenario.*;
import io.metersphere.api.mapper.*; import io.metersphere.api.mapper.*;
import io.metersphere.api.parser.step.StepParser; import io.metersphere.api.parser.step.StepParser;
@ -15,6 +16,7 @@ import io.metersphere.api.service.ApiExecuteService;
import io.metersphere.api.service.ApiFileResourceService; import io.metersphere.api.service.ApiFileResourceService;
import io.metersphere.api.service.definition.ApiDefinitionService; import io.metersphere.api.service.definition.ApiDefinitionService;
import io.metersphere.api.service.definition.ApiTestCaseService; import io.metersphere.api.service.definition.ApiTestCaseService;
import io.metersphere.api.utils.ApiScenarioBatchOperationUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.domain.FileMetadata; import io.metersphere.project.domain.FileMetadata;
import io.metersphere.project.mapper.ExtBaseProjectVersionMapper; import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
@ -24,24 +26,29 @@ import io.metersphere.project.service.ProjectService;
import io.metersphere.sdk.constants.ApiExecuteRunMode; import io.metersphere.sdk.constants.ApiExecuteRunMode;
import io.metersphere.sdk.constants.ApplicationNumScope; import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.DefaultRepositoryDir; import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.domain.Environment; import io.metersphere.sdk.domain.Environment;
import io.metersphere.sdk.domain.EnvironmentExample; import io.metersphere.sdk.domain.EnvironmentExample;
import io.metersphere.sdk.domain.EnvironmentGroup; import io.metersphere.sdk.domain.EnvironmentGroup;
import io.metersphere.sdk.domain.EnvironmentGroupExample; import io.metersphere.sdk.domain.EnvironmentGroupExample;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.FileCenter; import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.FileCopyRequest;
import io.metersphere.sdk.file.FileRequest; import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.file.MinioRepository;
import io.metersphere.sdk.mapper.EnvironmentGroupMapper; import io.metersphere.sdk.mapper.EnvironmentGroupMapper;
import io.metersphere.sdk.mapper.EnvironmentMapper; import io.metersphere.sdk.mapper.EnvironmentMapper;
import io.metersphere.sdk.util.*; import io.metersphere.sdk.util.*;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.log.constants.OperationLogModule; import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.service.UserLoginService; import io.metersphere.system.service.UserLoginService;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator; import io.metersphere.system.uid.NumGenerator;
import io.metersphere.system.utils.ServiceUtils; import io.metersphere.system.utils.ServiceUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils; import jakarta.validation.constraints.NotEmpty;
import org.apache.commons.collections.MapUtils; import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils; import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -270,13 +277,16 @@ public class ApiScenarioService {
mapper.updateByExampleSelective(updateScenario, example); mapper.updateByExampleSelective(updateScenario, example);
} }
public List<String> doSelectIds(ApiScenarioBatchEditRequest request, boolean deleted) { public List<String> doSelectIds(ApiScenarioBatchRequest request, boolean deleted) {
if (request.isSelectAll()) { if (request.isSelectAll()) {
List<String> ids = extApiScenarioMapper.getIds(request, deleted); List<String> ids = extApiScenarioMapper.getIds(request, deleted);
if (CollectionUtils.isNotEmpty(request.getSelectIds())) {
ids.addAll(request.getSelectIds());
}
if (CollectionUtils.isNotEmpty(request.getExcludeIds())) { if (CollectionUtils.isNotEmpty(request.getExcludeIds())) {
ids.removeAll(request.getExcludeIds()); ids.removeAll(request.getExcludeIds());
} }
return ids; return new ArrayList<>(ids.stream().distinct().toList());
} else { } else {
return request.getSelectIds(); return request.getSelectIds();
} }
@ -811,6 +821,14 @@ public class ApiScenarioService {
deleteStepDetailByScenarioId(id); deleteStepDetailByScenarioId(id);
} }
public void restore(String id) {
ApiScenario apiScenario = new ApiScenario();
apiScenario.setId(id);
apiScenario.setDeleted(false);
apiScenarioMapper.updateByPrimaryKeySelective(apiScenario);
}
public void deleteToGc(String id) { public void deleteToGc(String id) {
checkResourceExist(id); checkResourceExist(id);
ApiScenario apiScenario = new ApiScenario(); ApiScenario apiScenario = new ApiScenario();
@ -1271,4 +1289,182 @@ public class ApiScenarioService {
private ApiScenarioStep checkStepExist(String id) { private ApiScenarioStep checkStepExist(String id) {
return ServiceUtils.checkResourceExist(apiScenarioStepMapper.selectByPrimaryKey(id), "permission.api_step.name"); return ServiceUtils.checkResourceExist(apiScenarioStepMapper.selectByPrimaryKey(id), "permission.api_step.name");
} }
public ApiScenarioBatchOperationResponse batchCopy(ApiScenarioBatchCopyRequest request, LogInsertModule logInsertModule) {
if (!StringUtils.equals(request.getTargetModuleId(), ModuleConstants.DEFAULT_NODE_ID)) {
ApiScenarioModule module = apiScenarioModuleMapper.selectByPrimaryKey(request.getTargetModuleId());
if (module == null || !StringUtils.equals(module.getProjectId(), request.getProjectId())) {
throw new MSException(Translator.get("module.not.exist"));
}
}
List<String> scenarioIds = doSelectIds(request, false);
if (CollectionUtils.isEmpty(scenarioIds)) {
return new ApiScenarioBatchOperationResponse();
}
request.setSelectIds(scenarioIds);
ApiScenarioBatchOperationResponse response =
ApiScenarioBatchOperationUtils.executeWithBatchOperationResponse(scenarioIds, sublist -> copyAndInsert(sublist, request, logInsertModule.getOperator()));
apiScenarioLogService.saveBatchCopyLog(response, request.getProjectId(), logInsertModule);
return response;
}
private String getScenarioCopyName(String oldName, long oldNum, long newNum, String moduleId) {
String newScenarioName = oldName;
if (!StringUtils.startsWith(newScenarioName, "copy_")) {
newScenarioName = "copy_" + newScenarioName;
}
// 限制名称长度 数据库里最大的长度是255这里判断超过250时截取到200附近
if (newScenarioName.length() > 250) {
newScenarioName = newScenarioName.substring(0, 200) + "...";
}
if (StringUtils.endsWith(newScenarioName, "_" + oldNum)) {
newScenarioName = StringUtils.substringBeforeLast(newScenarioName, "_" + oldNum);
}
newScenarioName = newScenarioName + "_" + newNum;
int indexLength = 1;
while (true) {
ApiScenarioExample example = new ApiScenarioExample();
example.createCriteria().andModuleIdEqualTo(moduleId).andNameEqualTo(newScenarioName);
if (apiScenarioMapper.countByExample(example) == 0) {
break;
} else {
newScenarioName = newScenarioName + "_" + indexLength;
indexLength++;
}
}
return newScenarioName;
}
private ApiScenarioBatchOperationResponse copyAndInsert(List<String> scenarioIds, ApiScenarioBatchCopyRequest request, String operator) {
ApiScenarioBatchOperationResponse response = new ApiScenarioBatchOperationResponse();
ApiScenarioExample example = new ApiScenarioExample();
example.createCriteria().andIdIn(scenarioIds);
List<ApiScenario> operationList = apiScenarioMapper.selectByExample(example);
List<String> operationIdList = operationList.stream().map(ApiScenario::getId).collect(Collectors.toList());
Map<String, ApiScenarioBlob> apiScenarioBlobMap = this.selectBlobByScenarioIds(operationIdList).stream().collect(Collectors.toMap(ApiScenarioBlob::getId, Function.identity()));
Map<String, List<ApiFileResource>> apiFileResourceMap = this.selectApiFileResourceByScenarioIds(operationIdList).stream().collect(Collectors.groupingBy(ApiFileResource::getResourceId));
Map<String, List<ApiScenarioStep>> apiScenarioStepMap = this.selectStepByScenarioIds(operationIdList).stream().collect(Collectors.groupingBy(ApiScenarioStep::getScenarioId));
Map<String, ApiScenarioStepBlob> apiScenarioStepBlobMap = this.selectStepBlobByScenarioIds(operationIdList).stream().collect(Collectors.toMap(ApiScenarioStepBlob::getId, Function.identity()));
List<ApiScenario> insertApiScenarioList = new ArrayList<>();
List<ApiScenarioBlob> insertApiScenarioBlobList = new ArrayList<>();
List<ApiScenarioStep> insertApiScenarioStepList = new ArrayList<>();
List<ApiScenarioStepBlob> insertApiScenarioStepBlobList = new ArrayList<>();
List<ApiFileResource> insertApiFileResourceList = new ArrayList<>();
MinioRepository minioRepository = CommonBeanFactory.getBean(MinioRepository.class);
operationList.forEach(apiScenario -> {
ApiScenario copyScenario = new ApiScenario();
BeanUtils.copyBean(copyScenario, apiScenario);
copyScenario.setId(IDGenerator.nextStr());
copyScenario.setNum(getNextNum(copyScenario.getProjectId()));
copyScenario.setPos(getNextOrder(copyScenario.getProjectId()));
copyScenario.setModuleId(request.getTargetModuleId());
copyScenario.setName(this.getScenarioCopyName(copyScenario.getName(), apiScenario.getNum(), copyScenario.getNum(), request.getTargetModuleId()));
copyScenario.setCreateUser(operator);
copyScenario.setUpdateUser(operator);
copyScenario.setCreateTime(System.currentTimeMillis());
copyScenario.setUpdateTime(System.currentTimeMillis());
copyScenario.setRefId(copyScenario.getId());
copyScenario.setLastReportStatus(StringUtils.EMPTY);
copyScenario.setRequestPassRate("0");
insertApiScenarioList.add(copyScenario);
ApiScenarioBlob apiScenarioBlob = apiScenarioBlobMap.get(apiScenario.getId());
if (apiScenarioBlob != null) {
ApiScenarioBlob copyApiScenarioBlob = new ApiScenarioBlob();
BeanUtils.copyBean(copyApiScenarioBlob, apiScenarioBlob);
copyApiScenarioBlob.setId(copyScenario.getId());
insertApiScenarioBlobList.add(copyApiScenarioBlob);
}
List<ApiFileResource> apiFileResourceList = apiFileResourceMap.get(apiScenario.getId());
if (CollectionUtils.isNotEmpty(apiFileResourceList)) {
apiFileResourceList.forEach(apiFileResource -> {
ApiFileResource copyApiFileResource = new ApiFileResource();
BeanUtils.copyBean(copyApiFileResource, apiFileResource);
copyApiFileResource.setResourceId(copyScenario.getId());
insertApiFileResourceList.add(copyApiFileResource);
try {
FileCopyRequest fileCopyRequest = new FileCopyRequest();
fileCopyRequest.setCopyFolder(DefaultRepositoryDir.getApiScenarioDir(apiScenario.getProjectId(), apiScenario.getId()));
fileCopyRequest.setCopyfileName(copyApiFileResource.getFileId());
fileCopyRequest.setFileName(copyApiFileResource.getFileId());
fileCopyRequest.setFolder(DefaultRepositoryDir.getApiScenarioDir(copyScenario.getProjectId(), copyScenario.getId()));
minioRepository.copyFile(fileCopyRequest);
} catch (Exception ignore) {
}
});
}
List<ApiScenarioStep> stepList = apiScenarioStepMap.get(apiScenario.getId());
if (CollectionUtils.isNotEmpty(stepList)) {
stepList.forEach(step -> {
ApiScenarioStep copyStep = new ApiScenarioStep();
BeanUtils.copyBean(copyStep, step);
copyStep.setId(IDGenerator.nextStr());
copyStep.setScenarioId(copyScenario.getId());
insertApiScenarioStepList.add(copyStep);
//todo 刚の没提交的CSV关联表
ApiScenarioStepBlob stepBlob = apiScenarioStepBlobMap.get(step.getId());
if (stepBlob != null) {
ApiScenarioStepBlob copyStepBlob = new ApiScenarioStepBlob();
BeanUtils.copyBean(copyStepBlob, stepBlob);
copyStepBlob.setId(copyStep.getId());
copyStepBlob.setScenarioId(copyScenario.getId());
insertApiScenarioStepBlobList.add(copyStepBlob);
}
});
}
response.addSuccessData(copyScenario.getId(), copyScenario.getNum(), copyScenario.getName());
});
response.setSuccess(apiScenarioMapper.batchInsert(insertApiScenarioList));
if (CollectionUtils.isNotEmpty(insertApiScenarioBlobList)) {
apiScenarioBlobMapper.batchInsert(insertApiScenarioBlobList);
}
if (CollectionUtils.isNotEmpty(insertApiScenarioStepList)) {
apiScenarioStepMapper.batchInsert(insertApiScenarioStepList);
}
if (CollectionUtils.isNotEmpty(insertApiScenarioStepBlobList)) {
apiScenarioStepBlobMapper.batchInsert(insertApiScenarioStepBlobList);
}
if (CollectionUtils.isNotEmpty(insertApiFileResourceList)) {
apiFileResourceService.batchInsert(insertApiFileResourceList);
}
return response;
}
public List<ApiScenarioBlob> selectBlobByScenarioIds(@NotEmpty List<String> scenarioIds) {
ApiScenarioBlobExample example = new ApiScenarioBlobExample();
example.createCriteria().andIdIn(scenarioIds);
return apiScenarioBlobMapper.selectByExampleWithBLOBs(example);
}
public List<ApiScenarioStep> selectStepByScenarioIds(@NotEmpty List<String> scenarioIds) {
ApiScenarioStepExample example = new ApiScenarioStepExample();
example.createCriteria().andScenarioIdIn(scenarioIds);
return apiScenarioStepMapper.selectByExample(example);
}
public List<ApiFileResource> selectApiFileResourceByScenarioIds(@NotEmpty List<String> scenarioIds) {
return apiFileResourceService.selectByApiScenarioId(scenarioIds);
}
public List<ApiScenarioStepBlob> selectStepBlobByScenarioIds(@NotEmpty List<String> scenarioIds) {
ApiScenarioStepBlobExample example = new ApiScenarioStepBlobExample();
example.createCriteria().andScenarioIdIn(scenarioIds);
return apiScenarioStepBlobMapper.selectByExampleWithBLOBs(example);
}
} }

View File

@ -0,0 +1,29 @@
package io.metersphere.api.utils;
import io.metersphere.api.dto.response.ApiScenarioBatchOperationResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
//场景批量操作工具类
public class ApiScenarioBatchOperationUtils {
private static int MAX_OPERATION_SIZE = 100;
public static <T> ApiScenarioBatchOperationResponse executeWithBatchOperationResponse(List<T> totalList, Function<List<T>, ApiScenarioBatchOperationResponse> subFunc) {
ApiScenarioBatchOperationResponse response = new ApiScenarioBatchOperationResponse();
List<T> operationList = new ArrayList<>(totalList);
while (operationList.size() > 100) {
List<T> subList = operationList.subList(0, MAX_OPERATION_SIZE);
response.merge(
subFunc.apply(subList));
subList.clear();
operationList.removeAll(subList);
}
if (!operationList.isEmpty()) {
response.merge(
subFunc.apply(operationList));
}
return response;
}
}

View File

@ -315,9 +315,6 @@ public class ApiDebugModuleControllerTests extends BaseTest {
//到20换下一层级 //到20换下一层级
parentId = holder.getData().toString(); parentId = holder.getData().toString();
} }
} else {
//测试超过500会报错
this.requestPost(URL_MODULE_ADD, perfRequest).andExpect(status().is5xxServerError());
} }
} }
treeNodes = this.getDebugModuleTreeNode(); treeNodes = this.getDebugModuleTreeNode();

View File

@ -347,9 +347,6 @@ public class ApiDefinitionModuleControllerTests extends BaseTest {
//到20换下一层级 //到20换下一层级
parentId = holder.getData().toString(); parentId = holder.getData().toString();
} }
} else {
//测试超过500会报错
this.requestPost(URL_MODULE_ADD, perfRequest).andExpect(status().is5xxServerError());
} }
} }
treeNodes = this.getModuleTreeNode(); treeNodes = this.getModuleTreeNode();

View File

@ -10,8 +10,11 @@ import io.metersphere.api.dto.definition.ApiTestCaseAddRequest;
import io.metersphere.api.dto.request.assertion.MsAssertionConfig; import io.metersphere.api.dto.request.assertion.MsAssertionConfig;
import io.metersphere.api.dto.request.assertion.MsScriptAssertion; import io.metersphere.api.dto.request.assertion.MsScriptAssertion;
import io.metersphere.api.dto.request.http.MsHTTPElement; import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.dto.response.ApiScenarioBatchOperationResponse;
import io.metersphere.api.dto.response.OperationDataInfo;
import io.metersphere.api.dto.scenario.*; import io.metersphere.api.dto.scenario.*;
import io.metersphere.api.mapper.*; import io.metersphere.api.mapper.*;
import io.metersphere.api.service.ApiScenarioBatchOperationTestService;
import io.metersphere.api.service.BaseResourcePoolTestService; import io.metersphere.api.service.BaseResourcePoolTestService;
import io.metersphere.api.service.definition.ApiDefinitionService; import io.metersphere.api.service.definition.ApiDefinitionService;
import io.metersphere.api.service.definition.ApiTestCaseService; import io.metersphere.api.service.definition.ApiTestCaseService;
@ -22,6 +25,7 @@ import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
import io.metersphere.project.service.FileMetadataService; import io.metersphere.project.service.FileMetadataService;
import io.metersphere.sdk.constants.ApplicationNumScope; import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.DefaultRepositoryDir; import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.domain.Environment; import io.metersphere.sdk.domain.Environment;
import io.metersphere.sdk.domain.EnvironmentExample; import io.metersphere.sdk.domain.EnvironmentExample;
@ -49,6 +53,7 @@ import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.ResultMatcher;
import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils; import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -62,7 +67,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public class ApiScenarioControllerTests extends BaseTest { public class ApiScenarioControllerTests extends BaseTest {
private static final String BASE_PATH = "/api/scenario/"; private static final String BASE_PATH = "/api/scenario/";
private static final String TRASH_PAGE = "trash/page"; private static final String TRASH_PAGE = "trash/page";
private static final String BATCH_EDIT = "batch/edit"; private static final String BATCH_EDIT = "batch-operation/edit";
private static final String FOLLOW = "follow/"; private static final String FOLLOW = "follow/";
protected static final String UPLOAD_TEMP_FILE = "upload/temp/file"; 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 DELETE_TO_GC = "delete-to-gc/{0}";
@ -71,6 +76,9 @@ public class ApiScenarioControllerTests extends BaseTest {
private static final String UPDATE_STATUS = "update-status"; private static final String UPDATE_STATUS = "update-status";
private static final String UPDATE_PRIORITY = "update-priority"; private static final String UPDATE_PRIORITY = "update-priority";
private static final Map<String, String> BATCH_OPERATION_SCENARIO_MODULE_MAP = new HashMap<>();
private static final List<String> BATCH_OPERATION_SCENARIO_ID = new ArrayList<>();
private static final ResultMatcher ERROR_REQUEST_MATCHER = status().is5xxServerError(); private static final ResultMatcher ERROR_REQUEST_MATCHER = status().is5xxServerError();
@Resource @Resource
private ApiScenarioMapper apiScenarioMapper; private ApiScenarioMapper apiScenarioMapper;
@ -97,6 +105,8 @@ public class ApiScenarioControllerTests extends BaseTest {
@Resource @Resource
private ApiTestCaseService apiTestCaseService; private ApiTestCaseService apiTestCaseService;
@Resource @Resource
private ApiScenarioBatchOperationTestService apiScenarioBatchOperationTestService;
@Resource
private BaseResourcePoolTestService baseResourcePoolTestService; private BaseResourcePoolTestService baseResourcePoolTestService;
@Resource @Resource
private FileMetadataService fileMetadataService; private FileMetadataService fileMetadataService;
@ -827,7 +837,7 @@ public class ApiScenarioControllerTests extends BaseTest {
requestPostAndReturn(BATCH_EDIT, request); requestPostAndReturn(BATCH_EDIT, request);
//判断数据的优先级是不是P3 //判断数据的优先级是不是P3
example.clear(); example.clear();
example.createCriteria().andProjectIdEqualTo(DEFAULT_PROJECT_ID).andDeletedEqualTo(false); example.createCriteria().andProjectIdEqualTo(DEFAULT_PROJECT_ID).andDeletedEqualTo(false).andModuleIdEqualTo("scenario-moduleId");
List<ApiScenario> apiScenarios = apiScenarioMapper.selectByExample(example); List<ApiScenario> apiScenarios = apiScenarioMapper.selectByExample(example);
apiScenarios.forEach(apiTestCase -> Assertions.assertEquals(apiTestCase.getPriority(), "P3")); apiScenarios.forEach(apiTestCase -> Assertions.assertEquals(apiTestCase.getPriority(), "P3"));
@ -947,4 +957,302 @@ public class ApiScenarioControllerTests extends BaseTest {
//校验权限 //校验权限
requestPostPermissionTest(PermissionConstants.PROJECT_API_SCENARIO_READ, TRASH_PAGE, pageRequest); requestPostPermissionTest(PermissionConstants.PROJECT_API_SCENARIO_READ, TRASH_PAGE, pageRequest);
} }
/*
要价格与批量测试的数据结构
建国批量测试场景模块_1 ->50条数据 有标签和环境组
|
|
建国批量测试场景模块_51 ->50条数据 有环境
|
|
建国批量测试场景模块_101 ->50条数据 有blob
|
|
建国批量测试场景模块_151 ->50条数据 有step
|
|
建国批量测试场景模块_201 ->50条数据 有step和blob
|
|
建国批量测试场景模块_251 ->250条数据 有文件
*/
@Test
@Order(20)
public void batchCopy() throws Exception {
String testUrl = "/batch-operation/copy";
if (CollectionUtils.isEmpty(BATCH_OPERATION_SCENARIO_ID)) {
this.batchCreateScenarios();
}
/*
正例测试 1.超过300条的批量复制
2.移动到新模块
测试数据使用建国批量测试场景模块_200模块为查询条件和建国批量测试场景模块_250里的所有id复制到 建国批量测试场景模块_1
*/
ApiScenarioBatchCopyRequest request = new ApiScenarioBatchCopyRequest();
request.setSelectIds(BATCH_OPERATION_SCENARIO_ID.subList(250, 500));
request.setModuleIds(List.of(BATCH_OPERATION_SCENARIO_MODULE_MAP.get("建国批量测试场景模块_201")));
request.setTargetModuleId(BATCH_OPERATION_SCENARIO_MODULE_MAP.get("建国批量测试场景模块_1"));
request.setProjectId(DEFAULT_PROJECT_ID);
request.setSelectAll(true);
//先测试一下没有开启模块时接口能否使用
apiScenarioBatchOperationTestService.removeApiModule(DEFAULT_PROJECT_ID);
this.requestPost(testUrl, request).andExpect(status().is5xxServerError());
//恢复
apiScenarioBatchOperationTestService.resetProjectModule(DEFAULT_PROJECT_ID);
MvcResult result = this.requestPostAndReturn(testUrl, request);
ResultHolder resultHolder = JSON.parseObject(result.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class);
ApiScenarioBatchOperationResponse resultResponse = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), ApiScenarioBatchOperationResponse.class);
//检查返回值
Assertions.assertEquals(resultResponse.getSuccess(), 300);
//数据库级别的检查
apiScenarioBatchOperationTestService.checkBatchCopy(BATCH_OPERATION_SCENARIO_ID.subList(200, 500), resultResponse.getSuccessData().stream().map(OperationDataInfo::getId).toList(), 50, request);
// 2.没有数据
request = new ApiScenarioBatchCopyRequest();
request.setTargetModuleId(BATCH_OPERATION_SCENARIO_MODULE_MAP.get("建国批量测试场景模块_1"));
request.setProjectId(DEFAULT_PROJECT_ID);
result = this.requestPostAndReturn(testUrl, request);
resultHolder = JSON.parseObject(result.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class);
resultResponse = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), ApiScenarioBatchOperationResponse.class);
//检查返回值
Assertions.assertEquals(resultResponse.getSuccess(), 0);
/*
正例测试
3.移动到当前模块
4.移动到新模块
5.移动的场景中有名字以copy开头的 不会重复生成copy_copy_
6.要移动的场景中有名字以"_"+自身num结尾的
测试数据建国批量测试场景模块_1中所有的数据350条再复制到当前数据里
*/
ApiScenarioExample example = new ApiScenarioExample();
example.createCriteria().andModuleIdEqualTo(BATCH_OPERATION_SCENARIO_MODULE_MAP.get("建国批量测试场景模块_1"));
List<ApiScenario> reCopyScenarios = apiScenarioMapper.selectByExample(example);
request = new ApiScenarioBatchCopyRequest();
request.setModuleIds(List.of(BATCH_OPERATION_SCENARIO_MODULE_MAP.get("建国批量测试场景模块_1")));
request.setTargetModuleId(BATCH_OPERATION_SCENARIO_MODULE_MAP.get("建国批量测试场景模块_1"));
request.setProjectId(DEFAULT_PROJECT_ID);
request.setSelectAll(true);
result = this.requestPostAndReturn(testUrl, request);
resultHolder = JSON.parseObject(result.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class);
resultResponse = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), ApiScenarioBatchOperationResponse.class);
//检查返回值
Assertions.assertEquals(resultResponse.getSuccess(), reCopyScenarios.size());
//数据库级别的检查
apiScenarioBatchOperationTestService.checkBatchCopy(new ArrayList<>(reCopyScenarios.stream().map(ApiScenario::getId).toList()), resultResponse.getSuccessData().stream().map(OperationDataInfo::getId).toList(), 350, request);
/*
正例测试
5.移动的场景中有名字长度在250以上的
测试数据建国批量测试场景模块_101的用例修改名字长度为255. 然后复制到建国批量测试场景模块_101
*/
apiScenarioBatchOperationTestService.updateNameToTestByModuleId(BATCH_OPERATION_SCENARIO_MODULE_MAP.get("建国批量测试场景模块_101"));
request = new ApiScenarioBatchCopyRequest();
request.setModuleIds(List.of(BATCH_OPERATION_SCENARIO_MODULE_MAP.get("建国批量测试场景模块_101")));
request.setTargetModuleId(BATCH_OPERATION_SCENARIO_MODULE_MAP.get("建国批量测试场景模块_101"));
request.setProjectId(DEFAULT_PROJECT_ID);
request.setSelectAll(true);
result = this.requestPostAndReturn(testUrl, request);
resultHolder = JSON.parseObject(result.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class);
resultResponse = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), ApiScenarioBatchOperationResponse.class);
//检查返回值
Assertions.assertEquals(resultResponse.getSuccess(), 50);
//数据库级别的检查
apiScenarioBatchOperationTestService.checkBatchCopy(BATCH_OPERATION_SCENARIO_ID.subList(100, 150), resultResponse.getSuccessData().stream().map(OperationDataInfo::getId).toList(), 50, request);
//1.反例测试 ->模块不存在
request = new ApiScenarioBatchCopyRequest();
request.setTargetModuleId(IDGenerator.nextStr());
request.setProjectId(DEFAULT_PROJECT_ID);
request.setSelectAll(true);
this.requestPost(testUrl, request).andExpect(status().is5xxServerError());
//2.反例测试 ->要移动的模块是别的项目下的
String otherProjectModuleId = this.createModule(500, IDGenerator.nextStr(), ModuleConstants.ROOT_NODE_PARENT_ID);
request = new ApiScenarioBatchCopyRequest();
request.setTargetModuleId(otherProjectModuleId);
request.setProjectId(DEFAULT_PROJECT_ID);
request.setSelectAll(true);
this.requestPost(testUrl, request).andExpect(status().is5xxServerError());
//3.反例测试 ->参数校验项目ID为空 模块ID为空
request = new ApiScenarioBatchCopyRequest();
request.setTargetModuleId(otherProjectModuleId);
this.requestPost(testUrl, request).andExpect(status().isBadRequest());
request = new ApiScenarioBatchCopyRequest();
request.setProjectId(DEFAULT_PROJECT_ID);
this.requestPost(testUrl, request).andExpect(status().isBadRequest());
}
//30开始是关于删除和恢复的
@Test
@Order(30)
public void restore() throws Exception {
if (CollectionUtils.isEmpty(BATCH_OPERATION_SCENARIO_ID)) {
this.batchCreateScenarios();
}
this.requestGetWithOk("/restore/" + BATCH_OPERATION_SCENARIO_ID.get(0));
}
private String createModule(int i, String projectId, String parentId) {
ApiScenarioModule apiScenarioModule = new ApiScenarioModule();
apiScenarioModule.setId(IDGenerator.nextStr());
apiScenarioModule.setProjectId(projectId);
apiScenarioModule.setName("建国批量测试场景模块_" + i);
apiScenarioModule.setCreateTime(System.currentTimeMillis());
apiScenarioModule.setUpdateTime(System.currentTimeMillis());
apiScenarioModule.setCreateUser("admin");
apiScenarioModule.setUpdateUser("admin");
apiScenarioModule.setParentId(parentId);
apiScenarioModule.setPos(i * 64L);
apiScenarioModuleMapper.insertSelective(apiScenarioModule);
return apiScenarioModule.getId();
}
/*
创建模块树结构
建国批量测试场景模块_1 ->50条数据 有标签和环境组
|
|
建国批量测试场景模块_51 ->50条数据 有环境
|
|
建国批量测试场景模块_101 ->50条数据 有blob
|
|
建国批量测试场景模块_151 ->50条数据 有step
|
|
建国批量测试场景模块_201 ->50条数据 有step和blob
|
|
建国批量测试场景模块_251 ->250条数据 有文件
*/
private void batchCreateScenarios() {
int size = 500;
String moduleId = null;
EnvironmentExample environmentExample = new EnvironmentExample();
environmentExample.createCriteria().andProjectIdEqualTo(DEFAULT_PROJECT_ID).andMockEqualTo(true);
List<Environment> environments = environmentMapper.selectByExample(environmentExample);
String parentModuleId = ModuleConstants.ROOT_NODE_PARENT_ID;
for (int i = 1; i <= size; i++) {
if (moduleId == null) {
moduleId = createModule(i, DEFAULT_PROJECT_ID, parentModuleId);
BATCH_OPERATION_SCENARIO_MODULE_MAP.put("建国批量测试场景模块_" + i, moduleId);
} else if (i == 51) {
moduleId = createModule(i, DEFAULT_PROJECT_ID, parentModuleId);
BATCH_OPERATION_SCENARIO_MODULE_MAP.put("建国批量测试场景模块_" + i, moduleId);
} else if (i == 101) {
parentModuleId = moduleId;
moduleId = createModule(i, DEFAULT_PROJECT_ID, parentModuleId);
BATCH_OPERATION_SCENARIO_MODULE_MAP.put("建国批量测试场景模块_" + i, moduleId);
} else if (i == 151) {
parentModuleId = moduleId;
moduleId = createModule(i, DEFAULT_PROJECT_ID, parentModuleId);
BATCH_OPERATION_SCENARIO_MODULE_MAP.put("建国批量测试场景模块_" + i, moduleId);
} else if (i == 201) {
parentModuleId = moduleId;
moduleId = createModule(i, DEFAULT_PROJECT_ID, parentModuleId);
BATCH_OPERATION_SCENARIO_MODULE_MAP.put("建国批量测试场景模块_" + i, moduleId);
} else if (i == 251) {
//本次创建的模块和 _200 属于同一级
moduleId = createModule(i, DEFAULT_PROJECT_ID, parentModuleId);
BATCH_OPERATION_SCENARIO_MODULE_MAP.put("建国批量测试场景模块_" + i, moduleId);
}
ApiScenario apiScenario = new ApiScenario();
apiScenario.setId(IDGenerator.nextStr());
apiScenario.setProjectId(DEFAULT_PROJECT_ID);
apiScenario.setNum(NumGenerator.nextNum(DEFAULT_PROJECT_ID, ApplicationNumScope.API_SCENARIO));
apiScenario.setName(StringUtils.join("建国批量测试接口场景-", apiScenario.getId()));
apiScenario.setModuleId(moduleId);
apiScenario.setStatus("未规划");
apiScenario.setPos(i * 64L);
apiScenario.setPriority("P0");
apiScenario.setLatest(true);
apiScenario.setVersionId("1.0");
apiScenario.setRefId(apiScenario.getId());
apiScenario.setCreateTime(System.currentTimeMillis());
apiScenario.setUpdateTime(System.currentTimeMillis());
apiScenario.setCreateUser("admin");
apiScenario.setUpdateUser("admin");
if (i <= 50) {
apiScenario.setTags(new ArrayList<>(List.of("tag1", "tag2")));
apiScenario.setGrouped(true);
apiScenario.setEnvironmentId("scenario-environment-group-id");
} else if (i <= 100) {
apiScenario.setGrouped(false);
apiScenario.setEnvironmentId(environments.get(0).getId());
} else if (i <= 150) {
//带blob
ApiScenarioBlob apiScenarioBlob = new ApiScenarioBlob();
apiScenarioBlob.setId(apiScenario.getId());
apiScenarioBlobMapper.insertSelective(apiScenarioBlob);
} else if (i <= 200) {
ApiScenarioStep step1 = new ApiScenarioStep();
step1.setId(IDGenerator.nextStr());
step1.setScenarioId(apiScenario.getId());
step1.setName(apiScenario.getName() + "_" + IDGenerator.nextStr());
step1.setSort(0L);
step1.setEnable(true);
apiScenarioStepMapper.insertSelective(step1);
ApiScenarioStep step2 = new ApiScenarioStep();
step2.setId(IDGenerator.nextStr());
step2.setScenarioId(apiScenario.getId());
step2.setName(apiScenario.getName() + "_" + IDGenerator.nextStr());
step2.setSort(1L);
step1.setEnable(false);
apiScenarioStepMapper.insertSelective(step2);
} else if (i <= 250) {
//带步骤和详情的
ApiScenarioStep step1 = new ApiScenarioStep();
step1.setId(IDGenerator.nextStr());
step1.setScenarioId(apiScenario.getId());
step1.setName(apiScenario.getName() + "_" + IDGenerator.nextStr());
step1.setSort(0L);
step1.setEnable(true);
apiScenarioStepMapper.insertSelective(step1);
ApiScenarioStepBlob step1Blob = new ApiScenarioStepBlob();
step1Blob.setId(step1.getId());
step1Blob.setScenarioId(apiScenario.getId());
apiScenarioStepBlobMapper.insertSelective(step1Blob);
ApiScenarioStep step2 = new ApiScenarioStep();
step2.setId(IDGenerator.nextStr());
step2.setScenarioId(apiScenario.getId());
step2.setName(apiScenario.getName() + "_" + IDGenerator.nextStr());
step2.setSort(1L);
step1.setEnable(false);
apiScenarioStepMapper.insertSelective(step2);
ApiScenarioStepBlob step2Blob = new ApiScenarioStepBlob();
step2Blob.setId(step2.getId());
step2Blob.setScenarioId(apiScenario.getId());
apiScenarioStepBlobMapper.insertSelective(step2Blob);
} else if (i <= 300) {
//带文件的
ApiFileResource apiFileResource = new ApiFileResource();
apiFileResource.setResourceId(apiScenario.getId());
apiFileResource.setFileId(IDGenerator.nextStr());
apiFileResource.setFileName("test");
apiFileResource.setResourceType("API_SCENARIO");
apiFileResource.setCreateTime(System.currentTimeMillis());
apiFileResource.setProjectId(apiScenario.getProjectId());
}
apiScenarioMapper.insertSelective(apiScenario);
BATCH_OPERATION_SCENARIO_ID.add(apiScenario.getId());
}
}
} }

View File

@ -303,9 +303,6 @@ public class ApiScenarioModuleControllerTests extends BaseTest {
//到20换下一层级 //到20换下一层级
parentId = holder.getData().toString(); parentId = holder.getData().toString();
} }
} else {
//测试超过500会报错
this.requestPost(URL_MODULE_ADD, perfRequest).andExpect(status().is5xxServerError());
} }
} }
treeNodes = this.getModuleTreeNode(); treeNodes = this.getModuleTreeNode();

View File

@ -0,0 +1,128 @@
package io.metersphere.api.service;
import io.metersphere.api.domain.*;
import io.metersphere.api.dto.scenario.ApiScenarioBatchCopyRequest;
import io.metersphere.api.mapper.*;
import io.metersphere.project.domain.Project;
import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.sdk.util.JSON;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Assertions;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
@Service
@Transactional
public class ApiScenarioBatchOperationTestService {
@Resource
private ApiScenarioMapper apiScenarioMapper;
@Resource
private ApiScenarioBlobMapper apiScenarioBlobMapper;
@Resource
private ApiScenarioStepMapper apiScenarioStepMapper;
@Resource
private ApiScenarioStepBlobMapper apiScenarioStepBlobMapper;
@Resource
private ApiFileResourceMapper apiFileResourceMapper;
@Resource
private ProjectMapper projectMapper;
public void removeApiModule(String projectId) {
String[] projectModule = new String[]{"workstation", "testPlan", "bugManagement", "caseManagement", "uiTest", "loadTest"};
Project updateProject = new Project();
updateProject.setId(projectId);
updateProject.setModuleSetting(JSON.toJSONString(projectModule));
projectMapper.updateByPrimaryKeySelective(updateProject);
}
public void resetProjectModule(String projectId) {
String[] projectModule = new String[]{"workstation", "testPlan", "bugManagement", "caseManagement", "apiTest", "uiTest", "loadTest"};
Project updateProject = new Project();
updateProject.setId(projectId);
updateProject.setModuleSetting(JSON.toJSONString(projectModule));
projectMapper.updateByPrimaryKeySelective(updateProject);
}
/**
* 检查批量编辑
*
* @param sourceScenarioIds 涉及到的场景id
* @param reCopyCountInModule 关联之前模块里是数据数量
* @param request
*/
public void checkBatchCopy(List<String> sourceScenarioIds, List<String> copyScenarioIdList, int reCopyCountInModule, ApiScenarioBatchCopyRequest request) {
ApiScenarioExample example = new ApiScenarioExample();
example.createCriteria().andModuleIdEqualTo(request.getTargetModuleId()).andIdIn(copyScenarioIdList);
List<ApiScenario> copyScenarios = apiScenarioMapper.selectByExample(example);
Assertions.assertEquals(copyScenarios.size(), sourceScenarioIds.size());
//检查总数据量
example.clear();
example.createCriteria().andModuleIdEqualTo(request.getTargetModuleId());
List<ApiScenario> allScenarioList = apiScenarioMapper.selectByExample(example);
Assertions.assertEquals(allScenarioList.size(), reCopyCountInModule + sourceScenarioIds.size());
//不存在copy_copy_开头的数据复制之前如果存在 -num 会把这个num去掉
for (ApiScenario apiScenario : allScenarioList) {
String name = apiScenario.getName();
Assertions.assertTrue(name.length() <= 255);
Assertions.assertFalse(name.startsWith("copy_copy_"));
if (name.startsWith("copy_")) {
Assertions.assertFalse(name.split("_").length > 3);
}
}
List<String> copyScenarioIds = copyScenarios.stream().map(ApiScenario::getId).toList();
//检查blob
ApiScenarioBlobExample sourceBlobExample = new ApiScenarioBlobExample();
sourceBlobExample.createCriteria().andIdIn(sourceScenarioIds);
ApiScenarioBlobExample copyBlobExample = new ApiScenarioBlobExample();
copyBlobExample.createCriteria().andIdIn(copyScenarioIds);
Assertions.assertEquals(apiScenarioBlobMapper.countByExample(sourceBlobExample), apiScenarioBlobMapper.countByExample(copyBlobExample));
//检查step
ApiScenarioStepExample sourceStepExample = new ApiScenarioStepExample();
sourceStepExample.createCriteria().andScenarioIdIn(sourceScenarioIds);
ApiScenarioStepExample copyStepExample = new ApiScenarioStepExample();
copyStepExample.createCriteria().andScenarioIdIn(copyScenarioIds);
Assertions.assertEquals(apiScenarioStepMapper.countByExample(sourceStepExample), apiScenarioStepMapper.countByExample(copyStepExample));
//检查step_blob
ApiScenarioStepBlobExample sourceStepBlobExample = new ApiScenarioStepBlobExample();
sourceStepBlobExample.createCriteria().andScenarioIdIn(sourceScenarioIds);
ApiScenarioStepBlobExample copyStepBlobExample = new ApiScenarioStepBlobExample();
copyStepBlobExample.createCriteria().andScenarioIdIn(copyScenarioIds);
Assertions.assertEquals(apiScenarioStepBlobMapper.countByExample(sourceStepBlobExample), apiScenarioStepBlobMapper.countByExample(copyStepBlobExample));
//检查fileRequest
ApiFileResourceExample sourceFileExample = new ApiFileResourceExample();
sourceFileExample.createCriteria().andResourceIdIn(sourceScenarioIds);
ApiFileResourceExample copyFileExample = new ApiFileResourceExample();
copyFileExample.createCriteria().andResourceIdIn(copyScenarioIds);
Assertions.assertEquals(apiFileResourceMapper.countByExample(sourceFileExample), apiFileResourceMapper.countByExample(copyFileExample));
}
public void updateNameToTestByModuleId(String moduleId) {
ApiScenarioExample example = new ApiScenarioExample();
example.createCriteria().andModuleIdEqualTo(moduleId);
List<ApiScenario> allScenarioList = apiScenarioMapper.selectByExample(example);
List<ApiScenario> updateList = new ArrayList<>();
for (ApiScenario apiScenario : allScenarioList) {
String name = apiScenario.getName();
while (name.length() < 255) {
name = name + "_";
}
ApiScenario updateScenario = new ApiScenario();
updateScenario.setModuleId(apiScenario.getId());
updateScenario.setName(name);
updateList.add(updateScenario);
}
for (ApiScenario apiScenario : updateList) {
apiScenarioMapper.updateByPrimaryKeySelective(apiScenario);
}
}
}

View File

@ -7,8 +7,12 @@
package io.metersphere.functional.service; package io.metersphere.functional.service;
import io.metersphere.functional.domain.*; import io.metersphere.functional.domain.CaseReview;
import io.metersphere.functional.mapper.*; import io.metersphere.functional.domain.CaseReviewModule;
import io.metersphere.functional.domain.CaseReviewModuleExample;
import io.metersphere.functional.mapper.CaseReviewModuleMapper;
import io.metersphere.functional.mapper.ExtCaseReviewMapper;
import io.metersphere.functional.mapper.ExtCaseReviewModuleMapper;
import io.metersphere.functional.request.CaseReviewModuleCreateRequest; import io.metersphere.functional.request.CaseReviewModuleCreateRequest;
import io.metersphere.functional.request.CaseReviewModuleUpdateRequest; import io.metersphere.functional.request.CaseReviewModuleUpdateRequest;
import io.metersphere.project.dto.ModuleCountDTO; import io.metersphere.project.dto.ModuleCountDTO;
@ -212,11 +216,6 @@ public class CaseReviewModuleService extends ModuleTreeService {
throw new MSException(Translator.get("node.name.repeat")); throw new MSException(Translator.get("node.name.repeat"));
} }
example.clear(); example.clear();
//非默认节点检查该节点所在分支的总长度确保不超过阈值
if (!StringUtils.equals(caseReviewModule.getId(), ModuleConstants.DEFAULT_NODE_ID)) {
this.checkBranchModules(this.getRootNodeId(caseReviewModule), extCaseReviewModuleMapper::selectChildrenIdsByParentIds);
}
} }
private String getRootNodeId(CaseReviewModule caseReviewModule) { private String getRootNodeId(CaseReviewModule caseReviewModule) {

View File

@ -214,11 +214,6 @@ public class FunctionalCaseModuleService extends ModuleTreeService {
throw new MSException(Translator.get("node.name.repeat")); throw new MSException(Translator.get("node.name.repeat"));
} }
example.clear(); example.clear();
//非默认节点检查该节点所在分支的总长度确保不超过阈值
if (!StringUtils.equals(functionalCaseModule.getId(), ModuleConstants.DEFAULT_NODE_ID)) {
this.checkBranchModules(this.getRootNodeId(functionalCaseModule), extFunctionalCaseModuleMapper::selectChildrenIdsByParentIds);
}
} }
private String getRootNodeId(FunctionalCaseModule functionalCaseModule) { private String getRootNodeId(FunctionalCaseModule functionalCaseModule) {

View File

@ -244,9 +244,6 @@ public class CaseReviewModuleControllerTests extends BaseTest {
//到20换下一层级 //到20换下一层级
parentId = holder.getData().toString(); parentId = holder.getData().toString();
} }
} else {
//测试超过500会报错
this.requestPost(URL_MODULE_TREE_ADD, perfRequest).andExpect(status().is5xxServerError());
} }
} }
treeNodes = this.getCaseReviewModuleTreeNode(); treeNodes = this.getCaseReviewModuleTreeNode();

View File

@ -246,9 +246,6 @@ public class FunctionalCaseModuleControllerTests extends BaseTest {
//到20换下一层级 //到20换下一层级
parentId = holder.getData().toString(); parentId = holder.getData().toString();
} }
} else {
//测试超过500会报错
this.requestPost(URL_MODULE_TREE_ADD, perfRequest).andExpect(status().is5xxServerError());
} }
} }
treeNodes = this.getFunctionalCaseModuleTreeNode(); treeNodes = this.getFunctionalCaseModuleTreeNode();

View File

@ -0,0 +1,11 @@
package io.metersphere.project.constants;
public class ProjectMenuConstants {
public static final String MODULE_MENU_WORKSTATION = "workstation";
public static final String MODULE_MENU_TEST_PLAN = "testPlan";
public static final String MODULE_MENU_BUG = "bugManagement";
public static final String MODULE_MENU_FUNCTIONAL_CASE = "caseManagement";
public static final String MODULE_MENU_API_TEST = "apiTest";
public static final String MODULE_MENU_UI = "uiTest";
public static final String MODULE_MENU_LOAD_TEST = "loadTest";
}

View File

@ -12,5 +12,4 @@ public interface ExtProjectMapper {
String getModuleSetting(@Param("projectId") String projectId); String getModuleSetting(@Param("projectId") String projectId);
List<Project> getProject(@Param("userId") String userId); List<Project> getProject(@Param("userId") String userId);
} }

View File

@ -114,11 +114,6 @@ public class FileModuleService extends ModuleTreeService implements CleanupProje
throw new MSException(Translator.get("node.name.repeat")); throw new MSException(Translator.get("node.name.repeat"));
} }
example.clear(); example.clear();
//非默认节点检查该节点所在分支的总长度确保不超过阈值
if (!StringUtils.equals(fileModule.getId(), ModuleConstants.DEFAULT_NODE_ID)) {
this.checkBranchModules(this.getRootNodeId(fileModule), extFileModuleMapper::selectChildrenIdsByParentIds);
}
} }
private String getRootNodeId(FileModule fileModule) { private String getRootNodeId(FileModule fileModule) {

View File

@ -16,7 +16,10 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import java.util.*; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -32,19 +35,6 @@ public abstract class ModuleTreeService {
return new BaseTreeNode(ModuleConstants.DEFAULT_NODE_ID, name, ModuleConstants.NODE_TYPE_DEFAULT, ModuleConstants.ROOT_NODE_PARENT_ID); return new BaseTreeNode(ModuleConstants.DEFAULT_NODE_ID, name, ModuleConstants.NODE_TYPE_DEFAULT, ModuleConstants.ROOT_NODE_PARENT_ID);
} }
public void checkBranchModules(String rootNodeId, Function<List<String>, List<String>> selectIdByParentIdFunc) {
long count = 1;
List<String> child = selectIdByParentIdFunc.apply(Collections.singletonList(rootNodeId));
while (CollectionUtils.isNotEmpty(child)) {
count += child.size();
if (count < MAX_BRANCHES_NODE_SIZE) {
child = selectIdByParentIdFunc.apply(child);
} else {
throw new MSException(Translator.getWithArgs("module.branches.size.limit", MAX_BRANCHES_NODE_SIZE));
}
}
}
//构建树结构并为每个节点计算资源数量 //构建树结构并为每个节点计算资源数量
public List<BaseTreeNode> buildTreeAndCountResource(List<BaseTreeNode> traverseList, @NotNull List<ModuleCountDTO> moduleCountDTOList, boolean haveVirtualRootNode, String virtualRootName) { public List<BaseTreeNode> buildTreeAndCountResource(List<BaseTreeNode> traverseList, @NotNull List<ModuleCountDTO> moduleCountDTOList, boolean haveVirtualRootNode, String virtualRootName) {
//构建模块树 //构建模块树

View File

@ -300,7 +300,7 @@ public class FileManagementControllerTests extends BaseTest {
/** /**
测试能否正常做200个节点 创建210个节点
*/ */
String parentId = null; String parentId = null;
for (int i = 0; i < 210; i++) { for (int i = 0; i < 210; i++) {
@ -317,9 +317,6 @@ public class FileManagementControllerTests extends BaseTest {
//到20换下一层级 //到20换下一层级
parentId = holder.getData().toString(); parentId = holder.getData().toString();
} }
} else {
//测试超过500会报错
this.requestPost(FileManagementRequestUtils.URL_MODULE_ADD, perfRequest).andExpect(status().is5xxServerError());
} }
} }
treeNodes = this.getFileModuleTreeNode(); treeNodes = this.getFileModuleTreeNode();

View File

@ -1,4 +1,4 @@
package io.metersphere.plan.dto; package io.metersphere.system.dto;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;

View File

@ -1,11 +1,11 @@
package io.metersphere.system.mapper; package io.metersphere.system.mapper;
import io.metersphere.system.dto.user.UserExtendDTO;
import io.metersphere.system.dto.OrganizationProjectOptionsDTO; import io.metersphere.system.dto.OrganizationProjectOptionsDTO;
import io.metersphere.system.dto.ProjectDTO; import io.metersphere.system.dto.ProjectDTO;
import io.metersphere.system.dto.ProjectResourcePoolDTO; import io.metersphere.system.dto.ProjectResourcePoolDTO;
import io.metersphere.system.dto.request.ProjectMemberRequest; import io.metersphere.system.dto.request.ProjectMemberRequest;
import io.metersphere.system.dto.request.ProjectRequest; import io.metersphere.system.dto.request.ProjectRequest;
import io.metersphere.system.dto.user.UserExtendDTO;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.List; import java.util.List;
@ -27,4 +27,6 @@ public interface ExtSystemProjectMapper {
List<ProjectDTO> getProjectExtendDTOList(@Param("projectIds") List<String> projectIds); List<ProjectDTO> getProjectExtendDTOList(@Param("projectIds") List<String> projectIds);
List<ProjectResourcePoolDTO> getProjectResourcePoolDTOList(@Param("projectIds") List<String> projectIds); List<ProjectResourcePoolDTO> getProjectResourcePoolDTOList(@Param("projectIds") List<String> projectIds);
String selectModuleSettingsByResourceIdAndTable(@Param("resourceId") String resourceId, @Param("resourceTable") String resourceTable);
} }

View File

@ -181,6 +181,19 @@
</if> </if>
</where> </where>
</select> </select>
<select id="selectModuleSettingsByResourceIdAndTable" resultType="java.lang.String">
SELECT module_setting FROM project
<where>
<if test="resourceTable == 'project' ">
id = #{resourceId}
</if>
<if test="resourceTable != 'project' ">
id IN (
SELECT project_id FROM ${resourceTable} WHERE id = #{resourceId}
)
</if>
</where>
</select>
</mapper> </mapper>

View File

@ -545,5 +545,6 @@ public interface NoticeConstants {
String resourceId = "resourceId"; String resourceId = "resourceId";
String platformBugId = "platformBugId"; String platformBugId = "platformBugId";
String handleUsers = "handleUsers"; String handleUsers = "handleUsers";
String groupId = "groupId";
} }
} }

View File

@ -663,4 +663,16 @@ public class CommonProjectService {
// 校验组织是否有权限 // 校验组织是否有权限
return testResourcePoolService.validateOrgResourcePool(resourcePool, project.getOrganizationId()); return testResourcePoolService.validateOrgResourcePool(resourcePool, project.getOrganizationId());
} }
//检测资源所在的项目是否含有模块菜单
public void checkProjectHasModuleMenu(List<String> moduleMenus, String resourceId, String resourceTable) {
String moduleSettings = extSystemProjectMapper.selectModuleSettingsByResourceIdAndTable(resourceId, resourceTable);
if (StringUtils.isEmpty(moduleSettings)) {
throw new MSException(Translator.get("project.module_menu.check.error"));
}
List<String> projectModuleMenus = JSON.parseArray(moduleSettings, String.class);
if (!projectModuleMenus.containsAll(moduleMenus)) {
throw new MSException(Translator.get("project.module_menu.check.error"));
}
}
} }

View File

@ -1,7 +1,6 @@
package io.metersphere.plan.controller; package io.metersphere.plan.controller;
import io.metersphere.plan.constants.TestPlanResourceConfig; import io.metersphere.plan.constants.TestPlanResourceConfig;
import io.metersphere.plan.dto.LogInsertModule;
import io.metersphere.plan.dto.request.ResourceSortRequest; import io.metersphere.plan.dto.request.ResourceSortRequest;
import io.metersphere.plan.dto.request.TestPlanAssociationRequest; import io.metersphere.plan.dto.request.TestPlanAssociationRequest;
import io.metersphere.plan.dto.response.TestPlanAssociationResponse; import io.metersphere.plan.dto.response.TestPlanAssociationResponse;
@ -10,6 +9,7 @@ import io.metersphere.plan.service.TestPlanApiCaseService;
import io.metersphere.plan.service.TestPlanManagementService; import io.metersphere.plan.service.TestPlanManagementService;
import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.security.CheckOwner; import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.utils.SessionUtils; import io.metersphere.system.utils.SessionUtils;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;

View File

@ -1,7 +1,6 @@
package io.metersphere.plan.controller; package io.metersphere.plan.controller;
import io.metersphere.plan.constants.TestPlanResourceConfig; import io.metersphere.plan.constants.TestPlanResourceConfig;
import io.metersphere.plan.dto.LogInsertModule;
import io.metersphere.plan.dto.request.ResourceSortRequest; import io.metersphere.plan.dto.request.ResourceSortRequest;
import io.metersphere.plan.dto.request.TestPlanAssociationRequest; import io.metersphere.plan.dto.request.TestPlanAssociationRequest;
import io.metersphere.plan.dto.response.TestPlanAssociationResponse; import io.metersphere.plan.dto.response.TestPlanAssociationResponse;
@ -10,6 +9,7 @@ import io.metersphere.plan.service.TestPlanApiScenarioService;
import io.metersphere.plan.service.TestPlanManagementService; import io.metersphere.plan.service.TestPlanManagementService;
import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.security.CheckOwner; import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.utils.SessionUtils; import io.metersphere.system.utils.SessionUtils;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;

View File

@ -1,7 +1,6 @@
package io.metersphere.plan.controller; package io.metersphere.plan.controller;
import io.metersphere.plan.constants.TestPlanResourceConfig; import io.metersphere.plan.constants.TestPlanResourceConfig;
import io.metersphere.plan.dto.LogInsertModule;
import io.metersphere.plan.dto.request.ResourceSortRequest; import io.metersphere.plan.dto.request.ResourceSortRequest;
import io.metersphere.plan.dto.request.TestPlanAssociationRequest; import io.metersphere.plan.dto.request.TestPlanAssociationRequest;
import io.metersphere.plan.dto.response.TestPlanAssociationResponse; import io.metersphere.plan.dto.response.TestPlanAssociationResponse;
@ -10,6 +9,7 @@ import io.metersphere.plan.service.TestPlanFunctionalCaseService;
import io.metersphere.plan.service.TestPlanManagementService; import io.metersphere.plan.service.TestPlanManagementService;
import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.security.CheckOwner; import io.metersphere.system.security.CheckOwner;
import io.metersphere.system.utils.SessionUtils; import io.metersphere.system.utils.SessionUtils;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;

View File

@ -1,5 +1,6 @@
package io.metersphere.plan.dto; package io.metersphere.plan.dto;
import io.metersphere.system.dto.LogInsertModule;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.Data; import lombok.Data;

View File

@ -4,7 +4,6 @@ import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.domain.TestPlanApiCase; import io.metersphere.plan.domain.TestPlanApiCase;
import io.metersphere.plan.domain.TestPlanApiCaseExample; import io.metersphere.plan.domain.TestPlanApiCaseExample;
import io.metersphere.plan.dto.AssociationNodeSortDTO; import io.metersphere.plan.dto.AssociationNodeSortDTO;
import io.metersphere.plan.dto.LogInsertModule;
import io.metersphere.plan.dto.ResourceLogInsertModule; import io.metersphere.plan.dto.ResourceLogInsertModule;
import io.metersphere.plan.dto.TestPlanResourceAssociationParam; import io.metersphere.plan.dto.TestPlanResourceAssociationParam;
import io.metersphere.plan.dto.request.ResourceSortRequest; import io.metersphere.plan.dto.request.ResourceSortRequest;
@ -17,6 +16,7 @@ import io.metersphere.plan.mapper.TestPlanMapper;
import io.metersphere.sdk.constants.ApplicationNumScope; import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.TestPlanResourceConstants; import io.metersphere.sdk.constants.TestPlanResourceConstants;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator; import io.metersphere.system.uid.NumGenerator;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;

View File

@ -4,7 +4,6 @@ import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.domain.TestPlanApiScenario; import io.metersphere.plan.domain.TestPlanApiScenario;
import io.metersphere.plan.domain.TestPlanApiScenarioExample; import io.metersphere.plan.domain.TestPlanApiScenarioExample;
import io.metersphere.plan.dto.AssociationNodeSortDTO; import io.metersphere.plan.dto.AssociationNodeSortDTO;
import io.metersphere.plan.dto.LogInsertModule;
import io.metersphere.plan.dto.ResourceLogInsertModule; import io.metersphere.plan.dto.ResourceLogInsertModule;
import io.metersphere.plan.dto.TestPlanResourceAssociationParam; import io.metersphere.plan.dto.TestPlanResourceAssociationParam;
import io.metersphere.plan.dto.request.ResourceSortRequest; import io.metersphere.plan.dto.request.ResourceSortRequest;
@ -18,6 +17,7 @@ import io.metersphere.plan.mapper.TestPlanMapper;
import io.metersphere.sdk.constants.ApplicationNumScope; import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.TestPlanResourceConstants; import io.metersphere.sdk.constants.TestPlanResourceConstants;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator; import io.metersphere.system.uid.NumGenerator;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;

View File

@ -4,7 +4,6 @@ import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.domain.TestPlanFunctionalCase; import io.metersphere.plan.domain.TestPlanFunctionalCase;
import io.metersphere.plan.domain.TestPlanFunctionalCaseExample; import io.metersphere.plan.domain.TestPlanFunctionalCaseExample;
import io.metersphere.plan.dto.AssociationNodeSortDTO; import io.metersphere.plan.dto.AssociationNodeSortDTO;
import io.metersphere.plan.dto.LogInsertModule;
import io.metersphere.plan.dto.ResourceLogInsertModule; import io.metersphere.plan.dto.ResourceLogInsertModule;
import io.metersphere.plan.dto.TestPlanResourceAssociationParam; import io.metersphere.plan.dto.TestPlanResourceAssociationParam;
import io.metersphere.plan.dto.request.ResourceSortRequest; import io.metersphere.plan.dto.request.ResourceSortRequest;
@ -18,6 +17,7 @@ import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.TestPlanResourceConstants; import io.metersphere.sdk.constants.TestPlanResourceConstants;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.Translator; import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator; import io.metersphere.system.uid.NumGenerator;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;

View File

@ -28,10 +28,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
@Service @Service
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -111,28 +109,10 @@ public class TestPlanModuleService extends ModuleTreeService implements CleanupP
throw new MSException(Translator.get("node.name.repeat")); throw new MSException(Translator.get("node.name.repeat"));
} }
example.clear(); example.clear();
//非默认节点检查该节点所在分支的总长度确保不超过阈值
if (!StringUtils.equals(module.getId(), ModuleConstants.DEFAULT_NODE_ID)) {
this.checkBranchModules(this.getRootNodeId(module), extTestPlanModuleMapper::selectChildrenIdsByParentIds);
}
} }
private String getRootNodeId(TestPlanModule module) {
if (StringUtils.equals(module.getParentId(), ModuleConstants.ROOT_NODE_PARENT_ID)) {
return module.getId();
} else {
TestPlanModule parentModule = testPlanModuleMapper.selectByPrimaryKey(module.getParentId());
return this.getRootNodeId(parentModule);
}
}
public void update(TestPlanModuleUpdateRequest request, String userId,String requestUrl,String requestMethod) { public void update(TestPlanModuleUpdateRequest request, String userId,String requestUrl,String requestMethod) {
TestPlanModule module = testPlanModuleMapper.selectByPrimaryKey(request.getId()); TestPlanModule module = testPlanModuleMapper.selectByPrimaryKey(request.getId());
if (module == null) {
throw new MSException("module.not.exist");
}
TestPlanModule updateModule = new TestPlanModule(); TestPlanModule updateModule = new TestPlanModule();
updateModule.setId(request.getId()); updateModule.setId(request.getId());
updateModule.setName(request.getName().trim()); updateModule.setName(request.getName().trim());
@ -244,17 +224,6 @@ public class TestPlanModuleService extends ModuleTreeService implements CleanupP
// nothing to do // nothing to do
} }
public Map<String, String> getModuleNameMapByIds(List<String> moduleIds) {
if (CollectionUtils.isEmpty(moduleIds)) {
return new HashMap<>();
} else {
TestPlanModuleExample example = new TestPlanModuleExample();
example.createCriteria().andIdIn(moduleIds);
List<TestPlanModule> moduleList = testPlanModuleMapper.selectByExample(example);
return moduleList.stream().collect(Collectors.toMap(TestPlanModule::getId, TestPlanModule::getName));
}
}
public String getNameById(String id) { public String getNameById(String id) {
return extTestPlanModuleMapper.selectNameById(id); return extTestPlanModuleMapper.selectNameById(id);
} }

View File

@ -13,6 +13,7 @@ import io.metersphere.project.dto.NodeSortQueryParam;
import io.metersphere.project.utils.NodeSortUtils; import io.metersphere.project.utils.NodeSortUtils;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.Translator; import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.LogInsertModule;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;

View File

@ -371,9 +371,6 @@ public class TestPlanTests extends BaseTest {
//到20换下一层级 //到20换下一层级
parentId = holder.getData().toString(); parentId = holder.getData().toString();
} }
} else {
//测试超过500会报错
this.requestPost(URL_POST_MODULE_ADD, perfRequest).andExpect(status().is5xxServerError());
} }
} }
treeNodes = this.getFileModuleTreeNode(); treeNodes = this.getFileModuleTreeNode();