feat(测试计划): 测试计划组后台相关功能开发

This commit is contained in:
Jianguo-Genius 2024-05-27 16:14:15 +08:00 committed by 建国
parent d30cf886f2
commit 0e57b01a8b
42 changed files with 660 additions and 319 deletions

View File

@ -4,13 +4,13 @@ import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Data
public class TestPlan implements Serializable {
@ -53,7 +53,7 @@ public class TestPlan implements Serializable {
private String type;
@Schema(description = "标签")
private List<String> tags;
private java.util.List<String> tags;
@Schema(description = "创建时间")
private Long createTime;
@ -79,9 +79,13 @@ public class TestPlan implements Serializable {
@Schema(description = "实际结束时间")
private Long actualEndTime;
@Schema(description = "描述;描述")
@Schema(description = "描述")
private String description;
@Schema(description = "自定义排序", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{test_plan.pos.not_blank}", groups = {Created.class})
private Long pos;
private static final long serialVersionUID = 1L;
public enum Column {
@ -102,7 +106,8 @@ public class TestPlan implements Serializable {
plannedEndTime("planned_end_time", "plannedEndTime", "BIGINT", false),
actualStartTime("actual_start_time", "actualStartTime", "BIGINT", false),
actualEndTime("actual_end_time", "actualEndTime", "BIGINT", false),
description("description", "description", "VARCHAR", false);
description("description", "description", "VARCHAR", false),
pos("pos", "pos", "BIGINT", false);
private static final String BEGINNING_DELIMITER = "`";

View File

@ -1327,6 +1327,66 @@ public class TestPlanExample {
addCriterion("description not between", value1, value2, "description");
return (Criteria) this;
}
public Criteria andPosIsNull() {
addCriterion("pos is null");
return (Criteria) this;
}
public Criteria andPosIsNotNull() {
addCriterion("pos is not null");
return (Criteria) this;
}
public Criteria andPosEqualTo(Long value) {
addCriterion("pos =", value, "pos");
return (Criteria) this;
}
public Criteria andPosNotEqualTo(Long value) {
addCriterion("pos <>", value, "pos");
return (Criteria) this;
}
public Criteria andPosGreaterThan(Long value) {
addCriterion("pos >", value, "pos");
return (Criteria) this;
}
public Criteria andPosGreaterThanOrEqualTo(Long value) {
addCriterion("pos >=", value, "pos");
return (Criteria) this;
}
public Criteria andPosLessThan(Long value) {
addCriterion("pos <", value, "pos");
return (Criteria) this;
}
public Criteria andPosLessThanOrEqualTo(Long value) {
addCriterion("pos <=", value, "pos");
return (Criteria) this;
}
public Criteria andPosIn(List<Long> values) {
addCriterion("pos in", values, "pos");
return (Criteria) this;
}
public Criteria andPosNotIn(List<Long> values) {
addCriterion("pos not in", values, "pos");
return (Criteria) this;
}
public Criteria andPosBetween(Long value1, Long value2) {
addCriterion("pos between", value1, value2, "pos");
return (Criteria) this;
}
public Criteria andPosNotBetween(Long value1, Long value2) {
addCriterion("pos not between", value1, value2, "pos");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -20,6 +20,7 @@
<result column="actual_start_time" jdbcType="BIGINT" property="actualStartTime" />
<result column="actual_end_time" jdbcType="BIGINT" property="actualEndTime" />
<result column="description" jdbcType="VARCHAR" property="description" />
<result column="pos" jdbcType="BIGINT" property="pos" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -120,7 +121,7 @@
<sql id="Base_Column_List">
id, num, project_id, group_id, module_id, `name`, `status`, `type`, tags, create_time,
create_user, update_time, update_user, planned_start_time, planned_end_time, actual_start_time,
actual_end_time, description
actual_end_time, description, pos
</sql>
<select id="selectByExample" parameterType="io.metersphere.plan.domain.TestPlanExample" resultMap="BaseResultMap">
select
@ -158,15 +159,15 @@
`status`, `type`, tags,
create_time, create_user, update_time,
update_user, planned_start_time, planned_end_time,
actual_start_time, actual_end_time, description
)
actual_start_time, actual_end_time, description,
pos)
values (#{id,jdbcType=VARCHAR}, #{num,jdbcType=BIGINT}, #{projectId,jdbcType=VARCHAR},
#{groupId,jdbcType=VARCHAR}, #{moduleId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
#{status,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR}, #{tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler},
#{createTime,jdbcType=BIGINT}, #{createUser,jdbcType=VARCHAR}, #{updateTime,jdbcType=BIGINT},
#{updateUser,jdbcType=VARCHAR}, #{plannedStartTime,jdbcType=BIGINT}, #{plannedEndTime,jdbcType=BIGINT},
#{actualStartTime,jdbcType=BIGINT}, #{actualEndTime,jdbcType=BIGINT}, #{description,jdbcType=VARCHAR}
)
#{actualStartTime,jdbcType=BIGINT}, #{actualEndTime,jdbcType=BIGINT}, #{description,jdbcType=VARCHAR},
#{pos,jdbcType=BIGINT})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.plan.domain.TestPlan">
insert into test_plan
@ -225,6 +226,9 @@
<if test="description != null">
description,
</if>
<if test="pos != null">
pos,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -281,6 +285,9 @@
<if test="description != null">
#{description,jdbcType=VARCHAR},
</if>
<if test="pos != null">
#{pos,jdbcType=BIGINT},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.plan.domain.TestPlanExample" resultType="java.lang.Long">
@ -346,6 +353,9 @@
<if test="record.description != null">
description = #{record.description,jdbcType=VARCHAR},
</if>
<if test="record.pos != null">
pos = #{record.pos,jdbcType=BIGINT},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -370,7 +380,8 @@
planned_end_time = #{record.plannedEndTime,jdbcType=BIGINT},
actual_start_time = #{record.actualStartTime,jdbcType=BIGINT},
actual_end_time = #{record.actualEndTime,jdbcType=BIGINT},
description = #{record.description,jdbcType=VARCHAR}
description = #{record.description,jdbcType=VARCHAR},
pos = #{record.pos,jdbcType=BIGINT}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -429,6 +440,9 @@
<if test="description != null">
description = #{description,jdbcType=VARCHAR},
</if>
<if test="pos != null">
pos = #{pos,jdbcType=BIGINT},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -450,14 +464,15 @@
planned_end_time = #{plannedEndTime,jdbcType=BIGINT},
actual_start_time = #{actualStartTime,jdbcType=BIGINT},
actual_end_time = #{actualEndTime,jdbcType=BIGINT},
description = #{description,jdbcType=VARCHAR}
description = #{description,jdbcType=VARCHAR},
pos = #{pos,jdbcType=BIGINT}
where id = #{id,jdbcType=VARCHAR}
</update>
<insert id="batchInsert" parameterType="map">
insert into test_plan
(id, num, project_id, group_id, module_id, `name`, `status`, `type`, tags, create_time,
create_user, update_time, update_user, planned_start_time, planned_end_time, actual_start_time,
actual_end_time, description)
actual_end_time, description, pos)
values
<foreach collection="list" item="item" separator=",">
(#{item.id,jdbcType=VARCHAR}, #{item.num,jdbcType=BIGINT}, #{item.projectId,jdbcType=VARCHAR},
@ -466,7 +481,8 @@
#{item.createTime,jdbcType=BIGINT}, #{item.createUser,jdbcType=VARCHAR}, #{item.updateTime,jdbcType=BIGINT},
#{item.updateUser,jdbcType=VARCHAR}, #{item.plannedStartTime,jdbcType=BIGINT},
#{item.plannedEndTime,jdbcType=BIGINT}, #{item.actualStartTime,jdbcType=BIGINT},
#{item.actualEndTime,jdbcType=BIGINT}, #{item.description,jdbcType=VARCHAR})
#{item.actualEndTime,jdbcType=BIGINT}, #{item.description,jdbcType=VARCHAR}, #{item.pos,jdbcType=BIGINT}
)
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
@ -533,6 +549,9 @@
<if test="'description'.toString() == column.value">
#{item.description,jdbcType=VARCHAR}
</if>
<if test="'pos'.toString() == column.value">
#{item.pos,jdbcType=BIGINT}
</if>
</foreach>
)
</foreach>

View File

@ -11,6 +11,10 @@ CREATE TABLE IF NOT EXISTS platform_source(
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT = '平台对接保存参数';
-- 测试计划增加排序字段
alter table test_plan
ADD COLUMN `pos` BIGINT NOT NULL DEFAULT 0 COMMENT '自定义排序';
-- 修改计划配置表
ALTER TABLE test_plan_config DROP `test_planning`;

View File

@ -86,11 +86,25 @@ test_plan_principal.user_id.not_blank=用户id不能为空
test_plan_report_content.id.not_blank=测试计划报告内容id不能为空
test_plan_report_content.test_plan_report_id.length_range=测试计划报告id长度过长
test_plan_report_content.test_plan_report_id.not_blank=测试计划报告id不能为空
log.delete.test_plan=删除测试计划
log.delete.test_plan_group=删除测试计划组
log.test_plan.add=关联了资源
log.test_plan.remove=移除了资源
log.test_plan.move=移动了资源
log.test_plan.move.test_plan=移动了测试计划
log.test_plan.update=修改了资源
log.test_plan.functional_case=功能用例
log.test_plan.api_case=接口用例
log.test_plan.api_scenario=接口场景
test_plan.type.not_blank=测试计划类型不能为空
test_plan.group.not_plan=当前测试计划组没有可归档计划
test_plan.group.error=不是合法的测试计划组
test_plan_group.batch.log={0}测试计划组
test_plan.batch.log={0}测试计划
test_plan_report_not_exist=测试计划报告不存在
test_plan_report_id.not_blank=测试计划报告id不能为空
test_plan_report_name.not_blank=测试计划报告名称不能为空
run_functional_case=执行功能用例
test_plan_not_exist=测试计划不存在
test_plan.report_id.not_blank=测试计划报告ID不能为空
test_plan.report.share_id.not_blank=测试计划报告分享ID不能为空

View File

@ -91,12 +91,14 @@ log.delete.test_plan_group=Test plan group deleted
log.test_plan.add=Association resources
log.test_plan.remove=Remove resource
log.test_plan.move=Move resources
log.test_plan.move.test_plan=Move test plan
log.test_plan.update=Update resources
log.test_plan.functional_case=Functional case
log.test_plan.api_case=Api case
log.test_plan.api_scenario=Api scenario
test_plan.type.not_blank=Test plan type cannot be empty
test_plan.group.not_plan=There are no archived plans in the current testing plan group
test_plan.group.error=Test plan group error
test_plan_group.batch.log={0} test plan group
test_plan.batch.log={0} plan
test_plan_report_not_exist=The test plan report does not exist

View File

@ -91,12 +91,14 @@ log.delete.test_plan_group=删除测试计划组
log.test_plan.add=关联了资源
log.test_plan.remove=移除了资源
log.test_plan.move=移动了资源
log.test_plan.move.test_plan=移动了测试计划
log.test_plan.update=修改了资源
log.test_plan.functional_case=功能用例
log.test_plan.api_case=接口用例
log.test_plan.api_scenario=接口场景
test_plan.type.not_blank=测试计划类型不能为空
test_plan.group.not_plan=当前测试计划组没有可归档计划
test_plan.group.error=不是合法的测试计划组
test_plan_group.batch.log={0}测试计划组
test_plan.batch.log={0}测试计划
test_plan_report_not_exist=测试计划报告不存在

View File

@ -91,12 +91,14 @@ log.delete.test_plan_group=刪除測試計劃組
log.test_plan.add=關聯了資源
log.test_plan.remove=移除了資源
log.test_plan.move=移動了資源
log.test_plan.move.test_plan=移動了測試計劃
log.test_plan.update=修改了資源
log.test_plan.functional_case=功能用例
log.test_plan.api_case=接口用例
log.test_plan.api_scenario=接口場景
test_plan.type.not_blank=測試計劃類型不能為空
test_plan.group.not_plan=當前測試計劃組沒有可歸檔計劃
test_plan.group.error=不是合法的測試計劃組
test_plan_group.batch.log={0}測試計劃組
test_plan.batch.log={0}測試計劃
test_plan_report_not_exist=測試計劃報告不存在

View File

@ -1,6 +1,5 @@
package io.metersphere.api.service.debug;
import io.metersphere.sdk.constants.ApiFileResourceType;
import io.metersphere.api.constants.ApiResourceType;
import io.metersphere.api.domain.ApiDebug;
import io.metersphere.api.domain.ApiDebugBlob;
@ -24,6 +23,7 @@ import io.metersphere.project.domain.FileMetadata;
import io.metersphere.project.dto.MoveNodeSortDTO;
import io.metersphere.project.service.MoveNodeService;
import io.metersphere.project.service.ProjectService;
import io.metersphere.sdk.constants.ApiFileResourceType;
import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.dto.api.task.TaskRequestDTO;
import io.metersphere.sdk.exception.MSException;
@ -265,7 +265,7 @@ public class ApiDebugService extends MoveNodeService {
}
public void moveNode(PosRequest posRequest) {
NodeMoveRequest request = super.getNodeMoveRequest(posRequest);
NodeMoveRequest request = super.getNodeMoveRequest(posRequest, true);
MoveNodeSortDTO sortDTO = super.getNodeSortDTO(
posRequest.getProjectId(),
request,

View File

@ -25,7 +25,10 @@ import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
import io.metersphere.project.service.EnvironmentService;
import io.metersphere.project.service.MoveNodeService;
import io.metersphere.project.service.ProjectService;
import io.metersphere.sdk.constants.*;
import io.metersphere.sdk.constants.ApiFileResourceType;
import io.metersphere.sdk.constants.ApplicationNumScope;
import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.domain.OperationLogBlob;
import io.metersphere.sdk.dto.api.task.TaskRequestDTO;
import io.metersphere.sdk.exception.MSException;
@ -1062,7 +1065,7 @@ public class ApiDefinitionService extends MoveNodeService {
}
public void moveNode(PosRequest posRequest) {
NodeMoveRequest request = super.getNodeMoveRequest(posRequest);
NodeMoveRequest request = super.getNodeMoveRequest(posRequest, true);
MoveNodeSortDTO sortDTO = super.getNodeSortDTO(
posRequest.getProjectId(),
request,

View File

@ -826,7 +826,7 @@ public class ApiTestCaseService extends MoveNodeService {
}
public void moveNode(PosRequest posRequest) {
NodeMoveRequest request = super.getNodeMoveRequest(posRequest);
NodeMoveRequest request = super.getNodeMoveRequest(posRequest,true);
MoveNodeSortDTO sortDTO = super.getNodeSortDTO(
posRequest.getProjectId(),
request,

View File

@ -2997,7 +2997,7 @@ public class ApiScenarioService extends MoveNodeService {
}
public void moveNode(PosRequest posRequest) {
NodeMoveRequest request = super.getNodeMoveRequest(posRequest);
NodeMoveRequest request = super.getNodeMoveRequest(posRequest, true);
MoveNodeSortDTO sortDTO = super.getNodeSortDTO(
posRequest.getProjectId(),
request,

View File

@ -7,8 +7,8 @@ import lombok.Data;
@Data
@AllArgsConstructor
public class MoveNodeSortDTO {
@Schema(description = "项目ID")
private String projectId;
@Schema(description = "排序范围ID")
private String sortRangeId;
@Schema(description = "要排序的节点")
private DropNode sortNode;
@ -18,6 +18,4 @@ public class MoveNodeSortDTO {
@Schema(description = "后一个节点")
private DropNode nextNode;
}
}

View File

@ -273,7 +273,7 @@ public class EnvironmentGroupService extends MoveNodeService{
}
public void moveNode(PosRequest posRequest) {
NodeMoveRequest request = super.getNodeMoveRequest(posRequest);
NodeMoveRequest request = super.getNodeMoveRequest(posRequest, true);
MoveNodeSortDTO sortDTO = super.getNodeSortDTO(
posRequest.getProjectId(),
request,

View File

@ -564,7 +564,7 @@ public class EnvironmentService extends MoveNodeService {
}
public void moveNode(PosRequest posRequest) {
NodeMoveRequest request = super.getNodeMoveRequest(posRequest);
NodeMoveRequest request = super.getNodeMoveRequest(posRequest,true);
MoveNodeSortDTO sortDTO = super.getNodeSortDTO(
posRequest.getProjectId(),
request,

View File

@ -27,9 +27,6 @@ public abstract class ModuleTreeService {
protected static final long LIMIT_POS = NodeSortUtils.DEFAULT_NODE_INTERVAL_POS;
//默认节点所在分支最大数量不超过200 后续会根据需求排期进行调整
protected static final int MAX_BRANCHES_NODE_SIZE = 200;
public BaseTreeNode getDefaultModule(String name) {
//默认模块下不允许创建子模块 它本身也就是叶子节点
return new BaseTreeNode(ModuleConstants.DEFAULT_NODE_ID, name, ModuleConstants.NODE_TYPE_DEFAULT, ModuleConstants.ROOT_NODE_PARENT_ID);

View File

@ -7,7 +7,6 @@ import io.metersphere.project.dto.NodeSortQueryParam;
import io.metersphere.project.utils.NodeSortUtils;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.sdk.enums.MoveTypeEnum;
import io.metersphere.system.dto.sdk.request.NodeMoveRequest;
import io.metersphere.system.dto.sdk.request.PosRequest;
import org.apache.commons.lang3.StringUtils;
@ -28,23 +27,29 @@ public abstract class MoveNodeService {
private static final String MOVE_POS_OPERATOR_MORE = "moreThan";
private static final String DRAG_NODE_NOT_EXIST = "drag_node.not.exist";
public NodeMoveRequest getNodeMoveRequest(PosRequest posRequest) {
/**
* 构建节点移动的请求参数
*
* @param posRequest 拖拽的前端请求参数
* @param isDesc 是否是降序排列
*/
public NodeMoveRequest getNodeMoveRequest(PosRequest posRequest, boolean isDesc) {
NodeMoveRequest request = new NodeMoveRequest();
request.setDragNodeId(posRequest.getMoveId());
request.setDropNodeId(posRequest.getTargetId());
request.setDropPosition(StringUtils.equals(MoveTypeEnum.AFTER.name(), posRequest.getMoveMode()) ? -1 : 1);
request.setAndConvertDropPosition(posRequest.getMoveMode(), isDesc);
return request;
}
/**
* 构建节点排序的参数
*
* @param sortRangeId 排序范围ID
* @param request 拖拽的前端请求参数
* @param selectIdNodeFunc 通过id查询节点的函数
* @param selectPosNodeFunc 通过parentId和pos运算符查询节点的函数
* @return
*/
public MoveNodeSortDTO getNodeSortDTO(String projectId , NodeMoveRequest request, Function<String, DropNode> selectIdNodeFunc, Function<NodeSortQueryParam, DropNode> selectPosNodeFunc) {
public MoveNodeSortDTO getNodeSortDTO(String sortRangeId, NodeMoveRequest request, Function<String, DropNode> selectIdNodeFunc, Function<NodeSortQueryParam, DropNode> selectPosNodeFunc) {
if (StringUtils.equals(request.getDragNodeId(), request.getDropNodeId())) {
//两种节点不能一样
throw new MSException(Translator.get("invalid_parameter") + ": drag node and drop node");
@ -69,7 +74,7 @@ public abstract class MoveNodeService {
NodeSortQueryParam sortParam = new NodeSortQueryParam();
sortParam.setPos(previousNode.getPos());
sortParam.setOperator(MOVE_POS_OPERATOR_MORE);
sortParam.setParentId(projectId);
sortParam.setParentId(sortRangeId);
nextNode = selectPosNodeFunc.apply(sortParam);
} else if (request.getDropPosition() == -1) {
//dropPosition=-1: 放到dropNode节点前原dropNode前面的节点之后
@ -77,29 +82,26 @@ public abstract class MoveNodeService {
NodeSortQueryParam sortParam = new NodeSortQueryParam();
sortParam.setPos(nextNode.getPos());
sortParam.setOperator(MOVE_POS_OPERATOR_LESS);
sortParam.setParentId(projectId);
sortParam.setParentId(sortRangeId);
previousNode = selectPosNodeFunc.apply(sortParam);
} else {
throw new MSException(Translator.get("invalid_parameter") + ": dropPosition");
}
return new MoveNodeSortDTO(projectId, dragNode, previousNode, nextNode);
return new MoveNodeSortDTO(sortRangeId, dragNode, previousNode, nextNode);
}
//排序
public void sort(MoveNodeSortDTO sortDTO) {
// 获取相邻节点
DropNode previousNode = sortDTO.getPreviousNode();
DropNode nextNode = sortDTO.getNextNode();
ModuleSortCountResultDTO countResultDTO = NodeSortUtils.countModuleSort(
previousNode == null ? -1 : previousNode.getPos(),
nextNode == null ? -1 : nextNode.getPos());
updatePos(sortDTO.getSortNode().getId(), countResultDTO.getPos());
if (countResultDTO.isRefreshPos()) {
refreshPos(sortDTO.getProjectId());
refreshPos(sortDTO.getSortRangeId());
}
}

View File

@ -5,7 +5,7 @@ import io.metersphere.project.dto.ModuleSortCountResultDTO;
public class NodeSortUtils {
//默认节点间隔
public static final long DEFAULT_NODE_INTERVAL_POS = 64;
public static final long DEFAULT_NODE_INTERVAL_POS = 4096;
/**
* 计算模块排序

View File

@ -1,8 +1,10 @@
package io.metersphere.system.dto.sdk.request;
import io.metersphere.system.dto.sdk.enums.MoveTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
@Data
public class NodeMoveRequest {
@ -16,5 +18,13 @@ public class NodeMoveRequest {
@Schema(description = "放入的位置(取值:-1,1。 -1dropNodeId节点之前。 1dropNodeId节点后", requiredMode = Schema.RequiredMode.REQUIRED)
private int dropPosition;
public void setAndConvertDropPosition(String position, boolean isSortDesc) {
if (StringUtils.equals(MoveTypeEnum.BEFORE.name(), position)) {
this.dropPosition = isSortDesc ? 1 : -1;
} else {
this.dropPosition = isSortDesc ? -1 : 1;
}
}
}

View File

@ -2,8 +2,10 @@ package io.metersphere.system.dto.sdk.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
@ -13,6 +15,8 @@ import java.io.Serializable;
*/
@Data
@EqualsAndHashCode(callSuper = false)
@NoArgsConstructor
@AllArgsConstructor
public class PosRequest implements Serializable {
@Serial
@ -22,6 +26,10 @@ public class PosRequest implements Serializable {
@NotBlank(message = "{case_review.project_id.not_blank}")
private String projectId;
@Schema(description = "移动用例id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case_relationship_edge.source_id.not_blank}")
private String moveId;
@Schema(description = "目标用例id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case_relationship_edge.target_id.not_blank}")
private String targetId;
@ -29,8 +37,4 @@ public class PosRequest implements Serializable {
@Schema(description = "移动类型", requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"BEFORE", "AFTER", "APPEND"})
@NotBlank(message = "{case_review.moveMode.not_blank}")
private String moveMode;
@Schema(description = "移动用例id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{functional_case_relationship_edge.source_id.not_blank}")
private String moveId;
}

View File

@ -4,11 +4,14 @@ import io.metersphere.plan.constants.TestPlanResourceConfig;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.dto.request.*;
import io.metersphere.plan.dto.response.TestPlanDetailResponse;
import io.metersphere.plan.dto.response.TestPlanResourceSortResponse;
import io.metersphere.plan.dto.response.TestPlanResponse;
import io.metersphere.plan.dto.response.TestPlanStatisticsResponse;
import io.metersphere.plan.service.*;
import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.dto.sdk.request.PosRequest;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.notice.annotation.SendNotice;
@ -193,4 +196,13 @@ public class TestPlanController {
testPlanService.filterArchivedIds(request);
testPlanService.batchEdit(request, SessionUtils.getUserId());
}
@PostMapping(value = "/sort")
@Operation(summary = "测试计划移动(测试计划拖进、拖出到测试计划组、测试计划在测试计划组内的排序")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE)
@CheckOwner(resourceId = "#request.getMoveId()", resourceType = "test_plan")
public TestPlanResourceSortResponse sortTestPlan(@Validated @RequestBody PosRequest request) {
testPlanManagementService.checkModuleIsOpen(request.getMoveId(), TestPlanResourceConfig.CHECK_TYPE_TEST_PLAN, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN));
return testPlanService.sortInGroup(request, new LogInsertModule(SessionUtils.getUserId(), "/test-plan/move", HttpMethodConstants.POST.name()));
}
}

View File

@ -1,11 +0,0 @@
package io.metersphere.plan.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class AssociationNode {
private String id;
private long pos;
}

View File

@ -1,23 +0,0 @@
package io.metersphere.plan.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class AssociationNodeSortDTO {
@Schema(description = "测试计划ID")
private String testPlanId;
@Schema(description = "要排序的节点")
private AssociationNode sortNode;
@Schema(description = "前一个节点")
private AssociationNode previousNode;
@Schema(description = "后一个节点")
private AssociationNode nextNode;
}

View File

@ -1,9 +1,13 @@
package io.metersphere.plan.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TestPlanResourceSortResponse {
@Schema(description = "本次排序的数量")
private long sortNodeNum;

View File

@ -1,10 +1,10 @@
package io.metersphere.plan.mapper;
import io.metersphere.api.dto.definition.ApiDefinitionDTO;
import io.metersphere.plan.dto.AssociationNode;
import io.metersphere.plan.dto.ResourceSelectParam;
import io.metersphere.plan.dto.TestPlanCaseRunResultCount;
import io.metersphere.plan.dto.request.TestPlanApiRequest;
import io.metersphere.project.dto.DropNode;
import io.metersphere.project.dto.NodeSortQueryParam;
import org.apache.ibatis.annotations.Param;
@ -20,9 +20,9 @@ public interface ExtTestPlanApiCaseMapper {
List<String> getIdByParam(ResourceSelectParam resourceSelectParam);
AssociationNode selectDragInfoById(String id);
DropNode selectDragInfoById(String id);
AssociationNode selectNodeByPosOperator(NodeSortQueryParam nodeSortQueryParam);
DropNode selectNodeByPosOperator(NodeSortQueryParam nodeSortQueryParam);
List<TestPlanCaseRunResultCount> selectCaseExecResultCount(String testPlanId);

View File

@ -51,14 +51,14 @@
</if>
</select>
<select id="selectDragInfoById" resultType="io.metersphere.plan.dto.AssociationNode">
<select id="selectDragInfoById" resultType="io.metersphere.project.dto.DropNode">
SELECT id, pos
FROM test_plan_api_case
WHERE id = #{0}
</select>
<select id="selectNodeByPosOperator"
parameterType="io.metersphere.project.dto.NodeSortQueryParam"
resultType="io.metersphere.plan.dto.AssociationNode">
resultType="io.metersphere.project.dto.DropNode">
SELECT id, pos
FROM test_plan_api_case
WHERE test_plan_id = #{parentId}

View File

@ -1,8 +1,8 @@
package io.metersphere.plan.mapper;
import io.metersphere.plan.dto.AssociationNode;
import io.metersphere.plan.dto.ResourceSelectParam;
import io.metersphere.plan.dto.TestPlanCaseRunResultCount;
import io.metersphere.project.dto.DropNode;
import io.metersphere.project.dto.NodeSortQueryParam;
import org.apache.ibatis.annotations.Param;
@ -18,9 +18,9 @@ public interface ExtTestPlanApiScenarioMapper {
List<String> getIdByParam(ResourceSelectParam resourceSelectParam);
AssociationNode selectDragInfoById(String id);
DropNode selectDragInfoById(String id);
AssociationNode selectNodeByPosOperator(NodeSortQueryParam nodeSortQueryParam);
DropNode selectNodeByPosOperator(NodeSortQueryParam nodeSortQueryParam);
List<TestPlanCaseRunResultCount> selectCaseExecResultCount(String testPlanId);
}

View File

@ -44,14 +44,14 @@
ORDER BY #{orderString}
</if>
</select>
<select id="selectDragInfoById" resultType="io.metersphere.plan.dto.AssociationNode">
<select id="selectDragInfoById" resultType="io.metersphere.project.dto.DropNode">
SELECT id, pos
FROM test_plan_api_scenario
WHERE id = #{0}
</select>
<select id="selectNodeByPosOperator"
parameterType="io.metersphere.project.dto.NodeSortQueryParam"
resultType="io.metersphere.plan.dto.AssociationNode">
resultType="io.metersphere.project.dto.DropNode">
SELECT id, pos
FROM test_plan_api_scenario
WHERE test_plan_id = #{parentId}

View File

@ -4,12 +4,12 @@ import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO;
import io.metersphere.functional.dto.FunctionalCaseModuleDTO;
import io.metersphere.functional.dto.ProjectOptionDTO;
import io.metersphere.plan.domain.TestPlanFunctionalCase;
import io.metersphere.plan.dto.AssociationNode;
import io.metersphere.plan.dto.ResourceSelectParam;
import io.metersphere.plan.dto.TestPlanCaseRunResultCount;
import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest;
import io.metersphere.plan.dto.request.TestPlanCaseRequest;
import io.metersphere.plan.dto.response.TestPlanCasePageResponse;
import io.metersphere.project.dto.DropNode;
import io.metersphere.project.dto.NodeSortQueryParam;
import org.apache.ibatis.annotations.Param;
@ -27,9 +27,9 @@ public interface ExtTestPlanFunctionalCaseMapper {
List<String> getIdByParam(ResourceSelectParam resourceSelectParam);
AssociationNode selectDragInfoById(String id);
DropNode selectDragInfoById(String id);
AssociationNode selectNodeByPosOperator(NodeSortQueryParam nodeSortQueryParam);
DropNode selectNodeByPosOperator(NodeSortQueryParam nodeSortQueryParam);
List<TestPlanCasePageResponse> getCasePage(@Param("request") TestPlanCaseRequest request, @Param("deleted") boolean deleted, @Param("sort") String sort);

View File

@ -65,14 +65,14 @@
<include refid="queryWhereConditionByBatchQueryRequest"/>
</select>
<select id="selectDragInfoById" resultType="io.metersphere.plan.dto.AssociationNode">
<select id="selectDragInfoById" resultType="io.metersphere.project.dto.DropNode">
SELECT id, pos
FROM test_plan_functional_case
WHERE id = #{0}
</select>
<select id="selectNodeByPosOperator"
parameterType="io.metersphere.project.dto.NodeSortQueryParam"
resultType="io.metersphere.plan.dto.AssociationNode">
resultType="io.metersphere.project.dto.DropNode">
SELECT id, pos
FROM test_plan_functional_case
WHERE test_plan_id = #{parentId}

View File

@ -5,7 +5,9 @@ import io.metersphere.plan.dto.TestPlanQueryConditions;
import io.metersphere.plan.dto.request.TestPlanBatchProcessRequest;
import io.metersphere.plan.dto.request.TestPlanTableRequest;
import io.metersphere.plan.dto.response.TestPlanResponse;
import io.metersphere.project.dto.DropNode;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.project.dto.NodeSortQueryParam;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -40,4 +42,10 @@ public interface ExtTestPlanMapper {
List<String> selectIdByProjectId(String projectId);
List<String> selectNotArchivedIds(@Param("ids") List<String> selectIds);
DropNode selectDragInfoById(String s);
DropNode selectNodeByPosOperator(NodeSortQueryParam nodeSortQueryParam);
long selectMaxPosByGroupId(String groupId);
}

View File

@ -52,6 +52,7 @@
t.module_id as moduleId,
t.type,
t.description,
t.pos,
t.tags
FROM test_plan t
INNER JOIN user createUser ON t.create_user = createUser.id
@ -63,6 +64,7 @@
</foreach>
</if>
<include refid="queryWhereCondition"/>
ORDER BY t.pos ASC
</select>
<sql id="queryWhereCondition">
@ -90,7 +92,7 @@
</when>
<when test="request.type == 'GROUP'">
and t.group_id = 'NONE'
and t.type = 'GTOUP'
and t.type = 'GROUP'
</when>
</choose>
</if>
@ -408,7 +410,35 @@
</foreach>
AND status != 'ARCHIVED'
</select>
<select id="selectDragInfoById" resultType="io.metersphere.project.dto.DropNode">
SELECT id, pos
FROM test_plan
WHERE id = #{0}
</select>
<select id="selectNodeByPosOperator"
parameterType="io.metersphere.project.dto.NodeSortQueryParam"
resultType="io.metersphere.project.dto.DropNode">
SELECT id, pos
FROM test_plan
WHERE group_id = #{parentId}
<if test="operator == 'moreThan'">
AND pos &gt; #{pos}
</if>
<if test="operator == 'lessThan'">
AND pos &lt; #{pos}
</if>
ORDER BY pos
<if test="operator == 'lessThan' or operator == 'latest'">
DESC
</if>
LIMIT 1
</select>
<select id="selectMaxPosByGroupId" resultType="java.lang.Long">
SELECT IF(MAX(pos) IS NULL, 0, MAX(pos)) AS pos
FROM test_plan
WHERE group_id = #{0}
</select>
<update id="batchUpdate">
update test_plan

View File

@ -18,7 +18,6 @@ import io.metersphere.functional.service.FunctionalCaseAttachmentService;
import io.metersphere.functional.service.FunctionalCaseModuleService;
import io.metersphere.functional.service.FunctionalCaseService;
import io.metersphere.plan.domain.*;
import io.metersphere.plan.dto.AssociationNodeSortDTO;
import io.metersphere.plan.dto.ResourceLogInsertModule;
import io.metersphere.plan.dto.TestPlanCaseRunResultCount;
import io.metersphere.plan.dto.TestPlanResourceAssociationParam;
@ -28,6 +27,7 @@ import io.metersphere.plan.mapper.*;
import io.metersphere.plugin.platform.dto.SelectOption;
import io.metersphere.project.domain.Project;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.project.dto.MoveNodeSortDTO;
import io.metersphere.provider.BaseAssociateBugProvider;
import io.metersphere.request.AssociateBugPageRequest;
import io.metersphere.request.BugPageProviderRequest;
@ -171,9 +171,9 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService {
throw new MSException(Translator.get("test_plan.drag.node.error"));
}
TestPlanResourceSortResponse response = new TestPlanResourceSortResponse();
AssociationNodeSortDTO sortDTO = super.getNodeSortDTO(
super.getNodeMoveRequest(request),
MoveNodeSortDTO sortDTO = super.getNodeSortDTO(
request.getTestPlanId(),
super.getNodeMoveRequest(request, true),
extTestPlanFunctionalCaseMapper::selectDragInfoById,
extTestPlanFunctionalCaseMapper::selectNodeByPosOperator
);

View File

@ -1,8 +1,93 @@
package io.metersphere.plan.service;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.domain.TestPlanConfig;
import io.metersphere.plan.domain.TestPlanExample;
import io.metersphere.plan.mapper.ExtTestPlanMapper;
import io.metersphere.plan.mapper.TestPlanMapper;
import io.metersphere.project.dto.MoveNodeSortDTO;
import io.metersphere.sdk.constants.TestPlanConstants;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.sdk.enums.MoveTypeEnum;
import io.metersphere.system.dto.sdk.request.PosRequest;
import io.metersphere.system.utils.ServiceUtils;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
public interface TestPlanGroupService {
boolean validateGroup(TestPlan testPlan, TestPlanConfig testPlanConfig);
import java.util.List;
@Service
@Transactional(rollbackFor = Exception.class)
public class TestPlanGroupService extends TestPlanSortService {
@Resource
private TestPlanMapper testPlanMapper;
@Resource
private ExtTestPlanMapper extTestPlanMapper;
@Override
public long getNextOrder(String groupId) {
long maxPos = extTestPlanMapper.selectMaxPosByGroupId(groupId);
return maxPos + ServiceUtils.POS_STEP;
}
@Override
public void updatePos(String id, long pos) {
TestPlan testPlan = new TestPlan();
testPlan.setId(id);
testPlan.setPos(pos);
testPlanMapper.updateByPrimaryKeySelective(testPlan);
}
@Override
public void refreshPos(String groupId) {
TestPlanExample testPlanExample = new TestPlanExample();
testPlanExample.createCriteria().andGroupIdEqualTo(groupId);
testPlanExample.setOrderByClause("pos asc");
List<TestPlan> testPlans = testPlanMapper.selectByExample(testPlanExample);
long pos = 1;
for (TestPlan testPlanItem : testPlans) {
this.updatePos(testPlanItem.getId(), pos * ServiceUtils.POS_STEP);
pos++;
}
}
public void sort(PosRequest request) {
TestPlan dropPlan = testPlanMapper.selectByPrimaryKey(request.getMoveId());
TestPlan targetPlan = testPlanMapper.selectByPrimaryKey(request.getTargetId());
// 校验排序的参数 暂时不支持测试计划的移入移出
validateMoveRequest(dropPlan, targetPlan, request.getMoveMode());
MoveNodeSortDTO sortDTO = super.getNodeSortDTO(
targetPlan.getGroupId(),
this.getNodeMoveRequest(request, false),
extTestPlanMapper::selectDragInfoById,
extTestPlanMapper::selectNodeByPosOperator
);
this.sort(sortDTO);
}
private void validateMoveRequest(TestPlan dropPlan, TestPlan targetPlan, String moveType) {
//测试计划组不能进行移动操作
if (dropPlan == null || StringUtils.equalsIgnoreCase(dropPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) {
throw new MSException(Translator.get("test_plan.drag.node.error"));
}
if (targetPlan == null || StringUtils.equalsIgnoreCase(targetPlan.getGroupId(), TestPlanConstants.TEST_PLAN_DEFAULT_GROUP_ID)) {
throw new MSException(Translator.get("test_plan.drag.node.error"));
}
if (StringUtils.equalsIgnoreCase(MoveTypeEnum.APPEND.name(), moveType)) {
if (!StringUtils.equalsIgnoreCase(targetPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) {
throw new MSException(Translator.get("test_plan.drag.node.error"));
}
} else if (StringUtils.equalsAnyIgnoreCase(moveType, MoveTypeEnum.BEFORE.name(), MoveTypeEnum.AFTER.name())) {
if (StringUtils.equalsAny(TestPlanConstants.TEST_PLAN_TYPE_GROUP, dropPlan.getType())) {
throw new MSException(Translator.get("test_plan.drag.node.error"));
}
} else {
throw new MSException(Translator.get("test_plan.drag.position.error"));
}
}
}

View File

@ -11,6 +11,7 @@ import io.metersphere.sdk.constants.HttpMethodConstants;
import io.metersphere.sdk.constants.TestPlanConstants;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.dto.builder.LogDTOBuilder;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.log.constants.OperationLogType;
@ -245,4 +246,22 @@ public class TestPlanLogService {
}
return dtoList;
}
public void saveMoveLog(TestPlan testPlan, String moveId, LogInsertModule logInsertModule) {
Project project = projectMapper.selectByPrimaryKey(testPlan.getProjectId());
LogDTO dto = LogDTOBuilder.builder()
.projectId(testPlan.getProjectId())
.organizationId(project.getOrganizationId())
.type(OperationLogType.UPDATE.name())
.module(logModule)
.method(logInsertModule.getRequestMethod())
.path(logInsertModule.getRequestUrl())
.sourceId(moveId)
.content(Translator.get("log.test_plan.move.test_plan") + ":" + testPlan.getName() + StringUtils.SPACE)
.createUser(logInsertModule.getOperator())
.build().getLogDTO();
operationLogService.add(dto);
}
}

View File

@ -55,9 +55,6 @@ public class TestPlanManagementService {
/**
* 测试计划列表查询
*
* @param request
* @return
*/
public Pager<List<TestPlanResponse>> page(TestPlanTableRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
@ -73,8 +70,6 @@ public class TestPlanManagementService {
/**
* 计划组子节点
*
* @param testPlanResponses
*/
private void handChildren(List<TestPlanResponse> testPlanResponses, String projectId) {
List<String> groupIds = testPlanResponses.stream().filter(item -> StringUtils.equals(item.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)).map(TestPlanResponse::getId).toList();

View File

@ -1,57 +1,33 @@
package io.metersphere.plan.service;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.dto.AssociationNode;
import io.metersphere.plan.dto.AssociationNodeSortDTO;
import io.metersphere.plan.dto.ResourceLogInsertModule;
import io.metersphere.plan.dto.TestPlanResourceAssociationParam;
import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest;
import io.metersphere.plan.dto.response.TestPlanAssociationResponse;
import io.metersphere.plan.mapper.TestPlanMapper;
import io.metersphere.project.dto.ModuleSortCountResultDTO;
import io.metersphere.project.dto.NodeSortQueryParam;
import io.metersphere.project.service.MoveNodeService;
import io.metersphere.project.utils.NodeSortUtils;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.dto.sdk.request.NodeMoveRequest;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
//测试计划关联表 通用方法
@Service
@Transactional(rollbackFor = Exception.class)
public abstract class TestPlanResourceService extends MoveNodeService {
@Resource
private TestPlanMapper testPlanMapper;
@Resource
private TestPlanResourceLogService testPlanResourceLogService;
protected static final long DEFAULT_NODE_INTERVAL_POS = NodeSortUtils.DEFAULT_NODE_INTERVAL_POS;
public abstract void updatePos(String id, long pos);
public abstract void refreshPos(String testPlanId);
public abstract class TestPlanResourceService extends TestPlanSortService {
public abstract void deleteBatchByTestPlanId(List<String> testPlanIdList);
public abstract Map<String, Long> caseExecResultCount(String testPlanId);
private static final String MOVE_POS_OPERATOR_LESS = "lessThan";
private static final String MOVE_POS_OPERATOR_MORE = "moreThan";
private static final String DRAG_NODE_NOT_EXIST = "drag_node.not.exist";
@Resource
private TestPlanMapper testPlanMapper;
@Resource
private TestPlanResourceLogService testPlanResourceLogService;
/**
* 取消关联资源od
@ -75,73 +51,4 @@ public abstract class TestPlanResourceService extends MoveNodeService {
}
return response;
}
/**
* 构建节点排序的参数
*
* @param request 拖拽的前端请求参数
* @param selectIdNodeFunc 通过id查询节点的函数
* @param selectPosNodeFunc 通过parentId和pos运算符查询节点的函数
* @return
*/
public AssociationNodeSortDTO getNodeSortDTO(NodeMoveRequest request, String testPlanId, Function<String, AssociationNode> selectIdNodeFunc, Function<NodeSortQueryParam, AssociationNode> selectPosNodeFunc) {
if (StringUtils.equals(request.getDragNodeId(), request.getDropNodeId())) {
//两种节点不能一样
throw new MSException(Translator.get("invalid_parameter") + ": drag node and drop node");
}
AssociationNode dragNode = selectIdNodeFunc.apply(request.getDragNodeId());
if (dragNode == null) {
throw new MSException(Translator.get(DRAG_NODE_NOT_EXIST) + ":" + request.getDragNodeId());
}
AssociationNode dropNode = selectIdNodeFunc.apply(request.getDropNodeId());
if (dropNode == null) {
throw new MSException(Translator.get(DRAG_NODE_NOT_EXIST) + ":" + request.getDropNodeId());
}
AssociationNode previousNode;
AssociationNode nextNode;
if (request.getDropPosition() == 1) {
//dropPosition=1: 放到dropNode节点后原dropNode后面的节点之前
previousNode = dropNode;
NodeSortQueryParam sortParam = new NodeSortQueryParam();
sortParam.setParentId(testPlanId);
sortParam.setPos(previousNode.getPos());
sortParam.setOperator(MOVE_POS_OPERATOR_MORE);
nextNode = selectPosNodeFunc.apply(sortParam);
} else if (request.getDropPosition() == -1) {
//dropPosition=-1: 放到dropNode节点前原dropNode前面的节点之后
nextNode = dropNode;
NodeSortQueryParam sortParam = new NodeSortQueryParam();
sortParam.setPos(nextNode.getPos());
sortParam.setParentId(testPlanId);
sortParam.setOperator(MOVE_POS_OPERATOR_LESS);
previousNode = selectPosNodeFunc.apply(sortParam);
} else {
throw new MSException(Translator.get("invalid_parameter") + ": dropPosition");
}
return new AssociationNodeSortDTO(testPlanId, dragNode, previousNode, nextNode);
}
//排序
public void sort(AssociationNodeSortDTO sortDTO) {
// 获取相邻节点
AssociationNode previousNode = sortDTO.getPreviousNode();
AssociationNode nextNode = sortDTO.getNextNode();
ModuleSortCountResultDTO countResultDTO = NodeSortUtils.countModuleSort(
previousNode == null ? -1 : previousNode.getPos(),
nextNode == null ? -1 : nextNode.getPos());
updatePos(sortDTO.getSortNode().getId(), countResultDTO.getPos());
if (countResultDTO.isRefreshPos()) {
refreshPos(sortDTO.getTestPlanId());
}
}
}

View File

@ -3,6 +3,7 @@ package io.metersphere.plan.service;
import io.metersphere.plan.domain.*;
import io.metersphere.plan.dto.request.*;
import io.metersphere.plan.dto.response.TestPlanDetailResponse;
import io.metersphere.plan.dto.response.TestPlanResourceSortResponse;
import io.metersphere.plan.mapper.*;
import io.metersphere.sdk.constants.*;
import io.metersphere.sdk.exception.MSException;
@ -13,6 +14,8 @@ import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.ScheduleExample;
import io.metersphere.system.domain.TestPlanModuleExample;
import io.metersphere.system.domain.User;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.dto.sdk.request.PosRequest;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.mapper.ScheduleMapper;
import io.metersphere.system.mapper.TestPlanModuleMapper;
@ -49,6 +52,8 @@ public class TestPlanService extends TestPlanBaseUtilsService {
@Resource
private ExtTestPlanMapper extTestPlanMapper;
@Resource
private TestPlanGroupService testPlanGroupService;
@Resource
private TestPlanConfigMapper testPlanConfigMapper;
@Resource
private TestPlanLogService testPlanLogService;
@ -82,12 +87,6 @@ public class TestPlanService extends TestPlanBaseUtilsService {
/**
* 创建测试计划
*
* @param testPlanCreateRequest
* @param operator
* @param requestUrl
* @param requestMethod
* @return
*/
public TestPlan add(TestPlanCreateRequest testPlanCreateRequest, String operator, String requestUrl, String requestMethod) {
TestPlan testPlan = savePlanDTO(testPlanCreateRequest, operator, null);
@ -108,8 +107,7 @@ public class TestPlanService extends TestPlanBaseUtilsService {
checkModule(createOrCopyRequest.getModuleId());
TestPlan createTestPlan = new TestPlan();
BeanUtils.copyBean(createTestPlan, createOrCopyRequest);
// 5.21查询需求文档测试用例测试计划名称允许重复
// validateTestPlan(createTestPlan);
initTestPlanPos(createTestPlan);
createTestPlan.setId(IDGenerator.nextStr());
long operateTime = System.currentTimeMillis();
@ -138,6 +136,22 @@ public class TestPlanService extends TestPlanBaseUtilsService {
return createTestPlan;
}
//校验测试计划
private void initTestPlanPos(TestPlan createTestPlan) {
if (!StringUtils.equals(createTestPlan.getGroupId(), TestPlanConstants.TEST_PLAN_DEFAULT_GROUP_ID)) {
//如果测试计划组不为NONE判断是否真实存在并未归档
TestPlanExample example = new TestPlanExample();
example.createCriteria().andIdEqualTo(createTestPlan.getGroupId()).andStatusNotEqualTo(TEST_PLAN_STATUS_ARCHIVED);
if (testPlanMapper.countByExample(example) == 0) {
throw new MSException(Translator.get("test_plan.group.error"));
} else {
createTestPlan.setPos(testPlanGroupService.getNextOrder(createTestPlan.getGroupId()));
}
} else {
createTestPlan.setPos(0L);
}
}
/**
* 删除测试计划
@ -179,6 +193,7 @@ public class TestPlanService extends TestPlanBaseUtilsService {
private void deleteGroupByList(List<String> testPlanGroupIds) {
if (CollectionUtils.isNotEmpty(testPlanGroupIds)) {
TestPlanReportService testPlanReportService = CommonBeanFactory.getBean(TestPlanReportService.class);
assert testPlanReportService != null;
BatchProcessUtils.consumerByString(testPlanGroupIds, (deleteGroupIds) -> {
/*
* 计划组删除逻辑{第一版需求: 删除组, 组下的子计划Group置为None}:
@ -189,12 +204,13 @@ public class TestPlanService extends TestPlanBaseUtilsService {
testPlanExample.createCriteria().andGroupIdIn(deleteGroupIds);
List<TestPlan> deleteGroupPlans = testPlanMapper.selectByExample(testPlanExample);
List<String> deleteGroupPlanIds = deleteGroupPlans.stream().map(TestPlan::getId).toList();
if (CollectionUtils.isNotEmpty(deleteGroupPlanIds)) {
// 级联删除子计划关联的资源(计划组不存在关联的资源)
List<String> allDeleteIds = ListUtils.union(deleteGroupIds, deleteGroupPlanIds);
if (CollectionUtils.isNotEmpty(allDeleteIds)) {
// 级联删除子计划关联的资源(计划组不存在关联的资源,但是存在报告)
this.cascadeDeleteTestPlanIds(deleteGroupPlanIds, testPlanReportService);
}
testPlanExample.clear();
testPlanExample.createCriteria().andIdIn(ListUtils.union(deleteGroupIds, deleteGroupPlanIds));
testPlanExample.createCriteria().andIdIn(allDeleteIds);
testPlanMapper.deleteByExample(testPlanExample);
});
}
@ -275,12 +291,6 @@ public class TestPlanService extends TestPlanBaseUtilsService {
/**
* 更新测试计划
*
* @param request
* @param userId
* @param requestUrl
* @param requestMethod
* @return
*/
public TestPlan update(TestPlanUpdateRequest request, String userId, String requestUrl, String requestMethod) {
this.checkTestPlanNotArchived(request.getId());
@ -296,8 +306,6 @@ public class TestPlanService extends TestPlanBaseUtilsService {
if (StringUtils.isNotBlank(request.getName())) {
updateTestPlan.setName(request.getName());
updateTestPlan.setProjectId(testPlan.getProjectId());
// 5.21查询需求文档测试用例测试计划名称允许重复
// validateTestPlan(updateTestPlan);
}
if (CollectionUtils.isNotEmpty(request.getTags())) {
updateTestPlan.setTags(new ArrayList<>(request.getTags()));
@ -706,4 +714,11 @@ public class TestPlanService extends TestPlanBaseUtilsService {
testPlan.setStatus(testPlanFinalStatus);
testPlanMapper.updateByPrimaryKeySelective(testPlan);
}
public TestPlanResourceSortResponse sortInGroup(PosRequest request, LogInsertModule logInsertModule) {
testPlanGroupService.sort(request);
testPlanLogService.saveMoveLog(testPlanMapper.selectByPrimaryKey(request.getMoveId()), request.getMoveId(), logInsertModule);
return new TestPlanResourceSortResponse(1);
}
}

View File

@ -0,0 +1,77 @@
package io.metersphere.plan.service;
import io.metersphere.project.service.MoveNodeService;
import io.metersphere.project.utils.NodeSortUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
//测试计划关联表 通用方法
@Service
@Transactional(rollbackFor = Exception.class)
public abstract class TestPlanSortService extends MoveNodeService {
protected static final long DEFAULT_NODE_INTERVAL_POS = NodeSortUtils.DEFAULT_NODE_INTERVAL_POS;
public abstract void updatePos(String id, long pos);
public abstract void refreshPos(String testPlanId);
private static final String MOVE_POS_OPERATOR_LESS = "lessThan";
private static final String MOVE_POS_OPERATOR_MORE = "moreThan";
private static final String DRAG_NODE_NOT_EXIST = "drag_node.not.exist";
/**
* 构建节点排序的参数
*
* @param request 拖拽的前端请求参数
* @param selectIdNodeFunc 通过id查询节点的函数
* @param selectPosNodeFunc 通过parentId和pos运算符查询节点的函数
* @return
*/
// public MoveNodeSortDTO getNodeSortDTO(NodeMoveRequest request, String testPlanId, Function<String, DropNode> selectIdNodeFunc, Function<NodeSortQueryParam, DropNode> selectPosNodeFunc) {
//
//
// if (StringUtils.equals(request.getDragNodeId(), request.getDropNodeId())) {
// //两种节点不能一样
// throw new MSException(Translator.get("invalid_parameter") + ": drag node and drop node");
// }
//
// DropNode dragNode = selectIdNodeFunc.apply(request.getDragNodeId());
// if (dragNode == null) {
// throw new MSException(Translator.get(DRAG_NODE_NOT_EXIST) + ":" + request.getDragNodeId());
// }
//
// DropNode dropNode = selectIdNodeFunc.apply(request.getDropNodeId());
// if (dropNode == null) {
// throw new MSException(Translator.get(DRAG_NODE_NOT_EXIST) + ":" + request.getDropNodeId());
// }
//
// DropNode previousNode;
// DropNode nextNode;
//
// if (request.getDropPosition() == 1) {
// //dropPosition=1: 放到dropNode节点后原dropNode后面的节点之前
// previousNode = dropNode;
//
// NodeSortQueryParam sortParam = new NodeSortQueryParam();
// sortParam.setParentId(testPlanId);
// sortParam.setPos(previousNode.getPos());
// sortParam.setOperator(MOVE_POS_OPERATOR_MORE);
// nextNode = selectPosNodeFunc.apply(sortParam);
// } else if (request.getDropPosition() == -1) {
// //dropPosition=-1: 放到dropNode节点前原dropNode前面的节点之后
// nextNode = dropNode;
// NodeSortQueryParam sortParam = new NodeSortQueryParam();
// sortParam.setPos(nextNode.getPos());
// sortParam.setParentId(testPlanId);
// sortParam.setOperator(MOVE_POS_OPERATOR_LESS);
// previousNode = selectPosNodeFunc.apply(sortParam);
// } else {
// throw new MSException(Translator.get("invalid_parameter") + ": dropPosition");
// }
// return new MoveNodeSortDTO(testPlanId, dragNode, previousNode, nextNode);
// }
}

View File

@ -16,6 +16,7 @@ import io.metersphere.project.domain.Project;
import io.metersphere.project.dto.filemanagement.request.FileModuleCreateRequest;
import io.metersphere.project.dto.filemanagement.request.FileModuleUpdateRequest;
import io.metersphere.sdk.constants.*;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
@ -26,6 +27,7 @@ import io.metersphere.system.dto.AddProjectRequest;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.system.dto.sdk.enums.MoveTypeEnum;
import io.metersphere.system.dto.sdk.request.NodeMoveRequest;
import io.metersphere.system.dto.sdk.request.PosRequest;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.mapper.TestPlanModuleMapper;
@ -106,6 +108,7 @@ public class TestPlanTests extends BaseTest {
private static final String URL_POST_TEST_PLAN_STATISTICS = "/test-plan/statistics";
private static final String URL_POST_TEST_PLAN_MODULE_COUNT = "/test-plan/module/count";
private static final String URL_POST_TEST_PLAN_ADD = "/test-plan/add";
private static final String URL_POST_TEST_PLAN_SORT = "/test-plan/sort";
private static final String URL_POST_TEST_PLAN_UPDATE = "/test-plan/update";
private static final String URL_POST_TEST_PLAN_BATCH_DELETE = "/test-plan/batch-delete";
@ -136,7 +139,7 @@ public class TestPlanTests extends BaseTest {
@BeforeEach
public void initTestData() {
//文件管理专用项目
//测试计划专用项目
if (project == null) {
AddProjectRequest initProject = new AddProjectRequest();
initProject.setOrganizationId("100001");
@ -607,7 +610,6 @@ public class TestPlanTests extends BaseTest {
request.setType(TestPlanConstants.TEST_PLAN_TYPE_PLAN);
}
/*
抽查
testPlan_13没有设置计划开始时间没有设置重复添加用例和自动更新状态阈值为100描述为空
@ -681,25 +683,133 @@ public class TestPlanTests extends BaseTest {
request.setPassThreshold(100);
this.requestPostPermissionTest(PermissionConstants.TEST_PLAN_READ_ADD, URL_POST_TEST_PLAN_ADD, request);
this.checkTestPlanSortInGroup(groupTestPlanId7);
}
protected void checkTestPlanSortInGroup(String groupTestPlanId7) throws Exception {
/*
排序校验用例设计
1.第一个移动到最后一个
2.最后一个移动到第一个还原为原来的顺序
3.第三个移动到第二个
4.修改第一个和第二个之间的pos差小于2将第三个移动到第二个还原为原来的顺序并检查pos有没有初始化
*/
TestPlanExample example = new TestPlanExample();
example.createCriteria().andGroupIdEqualTo(groupTestPlanId7);
example.setOrderByClause("pos asc");
List<TestPlan> defaultTestPlanInGroup = testPlanMapper.selectByExample(example);
List<TestPlan> lastTestPlanInGroup = defaultTestPlanInGroup;
TestPlan movePlan, targetPlan = null;
PosRequest posRequest = null;
TestPlanResourceSortResponse response = null;
// 第一个移动到最后一个
movePlan = lastTestPlanInGroup.getFirst();
targetPlan = lastTestPlanInGroup.getLast();
posRequest = new PosRequest(project.getId(), movePlan.getId(), targetPlan.getId(), MoveTypeEnum.AFTER.name());
response = JSON.parseObject(
JSON.toJSONString(
JSON.parseObject(
this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_SORT, posRequest)
.getResponse().getContentAsString(), ResultHolder.class).getData()),
TestPlanResourceSortResponse.class);
//位置校验
List<TestPlan> newTestPlanInGroup = testPlanMapper.selectByExample(example);
Assertions.assertEquals(response.getSortNodeNum(), 1);
Assertions.assertEquals(newTestPlanInGroup.size(), lastTestPlanInGroup.size());
for (int newListIndex = 0; newListIndex < newTestPlanInGroup.size(); newListIndex++) {
int oldListIndex = newListIndex == newTestPlanInGroup.size() - 1 ? 0 : newListIndex + 1;
Assertions.assertEquals(newTestPlanInGroup.get(newListIndex).getId(), lastTestPlanInGroup.get(oldListIndex).getId());
}
lastTestPlanInGroup = newTestPlanInGroup;
// 最后一个移动到第一个 (还原为原来的顺序
movePlan = lastTestPlanInGroup.getLast();
targetPlan = lastTestPlanInGroup.getFirst();
posRequest = new PosRequest(project.getId(), movePlan.getId(), targetPlan.getId(), MoveTypeEnum.BEFORE.name());
response = JSON.parseObject(
JSON.toJSONString(
JSON.parseObject(
this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_SORT, posRequest)
.getResponse().getContentAsString(), ResultHolder.class).getData()),
TestPlanResourceSortResponse.class);
//位置校验
newTestPlanInGroup = testPlanMapper.selectByExample(example);
Assertions.assertEquals(response.getSortNodeNum(), 1);
Assertions.assertEquals(newTestPlanInGroup.size(), lastTestPlanInGroup.size());
for (int newListIndex = 0; newListIndex < newTestPlanInGroup.size(); newListIndex++) {
Assertions.assertEquals(newTestPlanInGroup.get(newListIndex).getId(), defaultTestPlanInGroup.get(newListIndex).getId());
}
lastTestPlanInGroup = newTestPlanInGroup;
// 第三个移动到第二个
movePlan = lastTestPlanInGroup.get(2);
targetPlan = lastTestPlanInGroup.get(1);
posRequest = new PosRequest(project.getId(), movePlan.getId(), targetPlan.getId(), MoveTypeEnum.BEFORE.name());
response = JSON.parseObject(
JSON.toJSONString(
JSON.parseObject(
this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_SORT, posRequest)
.getResponse().getContentAsString(), ResultHolder.class).getData()),
TestPlanResourceSortResponse.class);
//位置校验
newTestPlanInGroup = testPlanMapper.selectByExample(example);
Assertions.assertEquals(response.getSortNodeNum(), 1);
Assertions.assertEquals(newTestPlanInGroup.size(), lastTestPlanInGroup.size());
for (int newListIndex = 0; newListIndex < newTestPlanInGroup.size(); newListIndex++) {
int oldListIndex = newListIndex;
if (oldListIndex == 1) {
oldListIndex = 2;
} else if (oldListIndex == 2) {
oldListIndex = 1;
}
Assertions.assertEquals(newTestPlanInGroup.get(newListIndex).getId(), lastTestPlanInGroup.get(oldListIndex).getId());
}
lastTestPlanInGroup = newTestPlanInGroup;
// 修改第一个和第二个之间的pos差为2(拖拽的最小pos差将第三个移动到第二个换回来然后检查pos有没有变化
movePlan = lastTestPlanInGroup.get(2);
targetPlan = lastTestPlanInGroup.get(1);
targetPlan.setPos(lastTestPlanInGroup.get(0).getPos() + 2);
testPlanMapper.updateByPrimaryKey(targetPlan);
posRequest = new PosRequest(project.getId(), movePlan.getId(), targetPlan.getId(), MoveTypeEnum.BEFORE.name());
response = JSON.parseObject(
JSON.toJSONString(
JSON.parseObject(
this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_SORT, posRequest)
.getResponse().getContentAsString(), ResultHolder.class).getData()),
TestPlanResourceSortResponse.class);
//位置校验
newTestPlanInGroup = testPlanMapper.selectByExample(example);
Assertions.assertEquals(response.getSortNodeNum(), 1);
Assertions.assertEquals(newTestPlanInGroup.size(), lastTestPlanInGroup.size());
long lastPos = 0;
for (int newListIndex = 0; newListIndex < newTestPlanInGroup.size(); newListIndex++) {
Assertions.assertEquals(newTestPlanInGroup.get(newListIndex).getId(), defaultTestPlanInGroup.get(newListIndex).getId());
Assertions.assertTrue(newTestPlanInGroup.get(newListIndex).getPos() > (lastPos + 1));
lastPos = newTestPlanInGroup.get(newListIndex).getPos();
}
}
@Test
@Order(12)
public void testPlanPageCountTest() throws Exception {
TestPlanTableRequest testPlanTableRequest = new TestPlanTableRequest();
testPlanTableRequest.setProjectId(project.getId());
testPlanTableRequest.setType("ALL");
testPlanTableRequest.setPageSize(10);
testPlanTableRequest.setCurrent(1);
TestPlanTableRequest dataRequest = new TestPlanTableRequest();
dataRequest.setProjectId(project.getId());
dataRequest.setType("ALL");
dataRequest.setPageSize(10);
dataRequest.setCurrent(1);
//测试项目没有开启测试计划模块时能否使用
testPlanTestService.removeProjectModule(project, PROJECT_MODULE, "testPlan");
this.requestPost(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest).andExpect(status().is5xxServerError());
this.requestPost(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest).andExpect(status().is5xxServerError());
this.requestPost(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest).andExpect(status().is5xxServerError());
this.requestPost(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest).andExpect(status().is5xxServerError());
//恢复
testPlanTestService.resetProjectModule(project, PROJECT_MODULE);
MvcResult moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest);
MvcResult moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest);
String moduleCountReturnData = moduleCountResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
Map<String, Object> moduleCountMap = JSON.parseObject(JSON.toJSONString(JSON.parseObject(moduleCountReturnData, ResultHolder.class).getData()), Map.class);
AtomicBoolean testPlanIsEmpty = new AtomicBoolean(true);
@ -711,11 +821,19 @@ public class TestPlanTests extends BaseTest {
}
if (testPlanIsEmpty.get()) {
//如果没有数据先创建999条再调用这个方法
//如果没有数据先创建再调用这个方法
this.testPlanAddTest();
this.testPlanPageCountTest();
} else {
//this.checkModuleCount(moduleCountMap, a1NodeCount, a2NodeCount, a3NodeCount, a1a1NodeCount, a1b1NodeCount);
//只查询组
TestPlanTableRequest groupRequest = new TestPlanTableRequest();
//查询游离态测试计划
TestPlanTableRequest onlyPlanRequest = new TestPlanTableRequest();
BeanUtils.copyBean(groupRequest, dataRequest);
BeanUtils.copyBean(onlyPlanRequest, dataRequest);
groupRequest.setType(TestPlanConstants.TEST_PLAN_TYPE_GROUP);
onlyPlanRequest.setType(TestPlanConstants.TEST_PLAN_TYPE_PLAN);
BaseTreeNode a1Node = TestPlanTestUtils.getNodeByName(preliminaryTreeNodes, "a1");
BaseTreeNode a2Node = TestPlanTestUtils.getNodeByName(preliminaryTreeNodes, "a2");
@ -724,96 +842,82 @@ public class TestPlanTests extends BaseTest {
BaseTreeNode a1b1Node = TestPlanTestUtils.getNodeByName(preliminaryTreeNodes, "a1-b1");
assert a1Node != null & a2Node != null & a3Node != null & a1a1Node != null & a1b1Node != null;
//查询测试计划列表
MvcResult pageResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_PAGE, testPlanTableRequest);
String returnData = pageResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
Pager<Object> result = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class);
//返回值的页码和当前页码相同
Assertions.assertEquals(result.getCurrent(), testPlanTableRequest.getCurrent());
//返回的数据量不超过规定要返回的数据量相同
Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(result.getList())).size() <= testPlanTableRequest.getPageSize());
Assertions.assertEquals(result.getTotal(), 1010);
testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn(
URL_POST_TEST_PLAN_PAGE, dataRequest).getResponse().getContentAsString(StandardCharsets.UTF_8),
dataRequest.getCurrent(),
dataRequest.getPageSize(),
1010);
testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn(
URL_POST_TEST_PLAN_PAGE, groupRequest).getResponse().getContentAsString(StandardCharsets.UTF_8),
dataRequest.getCurrent(),
dataRequest.getPageSize(),
2);
testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn(
URL_POST_TEST_PLAN_PAGE, onlyPlanRequest).getResponse().getContentAsString(StandardCharsets.UTF_8),
dataRequest.getCurrent(),
dataRequest.getPageSize(),
1008);
//按照名称倒叙
testPlanTableRequest.setSort(new HashMap<>() {{
dataRequest.setSort(new HashMap<>() {{
this.put("name", "desc");
}});
pageResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_PAGE, testPlanTableRequest);
returnData = pageResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
resultHolder = JSON.parseObject(returnData, ResultHolder.class);
result = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class);
//返回值的页码和当前页码相同
Assertions.assertEquals(result.getCurrent(), testPlanTableRequest.getCurrent());
//返回的数据量不超过规定要返回的数据量相同
Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(result.getList())).size() <= testPlanTableRequest.getPageSize());
Assertions.assertEquals(result.getTotal(), 1010);
testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn(
URL_POST_TEST_PLAN_PAGE, dataRequest).getResponse().getContentAsString(StandardCharsets.UTF_8),
dataRequest.getCurrent(),
dataRequest.getPageSize(),
1010);
//指定模块ID查询 (查询count时不会因为选择了模块而更改了总量
testPlanTableRequest.setModuleIds(Arrays.asList(a1Node.getId(), a1a1Node.getId(), a1b1Node.getId()));
moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest);
dataRequest.setModuleIds(Arrays.asList(a1Node.getId(), a1a1Node.getId(), a1b1Node.getId()));
moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest);
moduleCountReturnData = moduleCountResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
moduleCountMap = JSON.parseObject(JSON.toJSONString(JSON.parseObject(moduleCountReturnData, ResultHolder.class).getData()), Map.class);
pageResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_PAGE, testPlanTableRequest);
returnData = pageResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
resultHolder = JSON.parseObject(returnData, ResultHolder.class);
result = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class);
//返回值的页码和当前页码相同
Assertions.assertEquals(result.getCurrent(), testPlanTableRequest.getCurrent());
//返回的数据量不超过规定要返回的数据量相同
Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(result.getList())).size() <= testPlanTableRequest.getPageSize());
testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn(
URL_POST_TEST_PLAN_PAGE, dataRequest).getResponse().getContentAsString(StandardCharsets.UTF_8),
dataRequest.getCurrent(),
dataRequest.getPageSize(),
910);
//测试根据名称模糊查询 Plan_2 预期结果 a1Node下有11条testPlan_2,testPlan_20~testPlan_29), a1b1Node下有100条testPlan_200~testPlan_299
testPlanTableRequest.setModuleIds(null);
testPlanTableRequest.initKeyword("Plan_2");
moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest);
dataRequest.setModuleIds(null);
dataRequest.initKeyword("Plan_2");
moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest);
moduleCountReturnData = moduleCountResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
moduleCountMap = JSON.parseObject(JSON.toJSONString(JSON.parseObject(moduleCountReturnData, ResultHolder.class).getData()), Map.class);
pageResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_PAGE, testPlanTableRequest);
returnData = pageResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
resultHolder = JSON.parseObject(returnData, ResultHolder.class);
result = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class);
//返回值的页码和当前页码相同
Assertions.assertEquals(result.getCurrent(), testPlanTableRequest.getCurrent());
//返回的数据量不超过规定要返回的数据量相同
Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(result.getList())).size() <= testPlanTableRequest.getPageSize());
long allSize = Long.parseLong(String.valueOf(moduleCountMap.get("all")));
testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn(
URL_POST_TEST_PLAN_PAGE, dataRequest).getResponse().getContentAsString(StandardCharsets.UTF_8),
dataRequest.getCurrent(),
dataRequest.getPageSize(),
allSize);
//测试根据名称模糊查询包含测试组的 Plan_7 预期结果 a1Node下有1条testPlan_7), a2Node下有10条testPlan_70~testPlan_79,a1b1Node下有100条testPlan_700~testPlan_799
testPlanTableRequest.initKeyword("Plan_7");
testPlanTableRequest.setSort(new HashMap<>() {{
dataRequest.initKeyword("Plan_7");
dataRequest.setSort(new HashMap<>() {{
this.put("num", "asc");
}});
moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest);
moduleCountResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest);
moduleCountReturnData = moduleCountResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
moduleCountMap = JSON.parseObject(JSON.toJSONString(JSON.parseObject(moduleCountReturnData, ResultHolder.class).getData()), Map.class);
pageResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_PAGE, testPlanTableRequest);
returnData = pageResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
resultHolder = JSON.parseObject(returnData, ResultHolder.class);
result = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class);
//返回值的页码和当前页码相同
Assertions.assertEquals(result.getCurrent(), testPlanTableRequest.getCurrent());
//返回的数据量不超过规定要返回的数据量相同
Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(result.getList())).size() <= testPlanTableRequest.getPageSize());
allSize = Long.parseLong(String.valueOf(moduleCountMap.get("all")));
testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn(
URL_POST_TEST_PLAN_PAGE, dataRequest).getResponse().getContentAsString(StandardCharsets.UTF_8),
dataRequest.getCurrent(),
dataRequest.getPageSize(),
allSize);
//反例参数校验项目ID不存在
testPlanTableRequest.setProjectId(null);
this.requestPost(URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest).andExpect(status().isBadRequest());
this.requestPost(URL_POST_TEST_PLAN_PAGE, testPlanTableRequest).andExpect(status().isBadRequest());
dataRequest.setProjectId(null);
this.requestPost(URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest).andExpect(status().isBadRequest());
this.requestPost(URL_POST_TEST_PLAN_PAGE, dataRequest).andExpect(status().isBadRequest());
//测试权限
testPlanTableRequest.setProjectId(DEFAULT_PROJECT_ID);
this.requestPostPermissionTest(PermissionConstants.TEST_PLAN_READ, URL_POST_TEST_PLAN_MODULE_COUNT, testPlanTableRequest);
this.requestPostPermissionTest(PermissionConstants.TEST_PLAN_READ, URL_POST_TEST_PLAN_PAGE, testPlanTableRequest);
dataRequest.setProjectId(DEFAULT_PROJECT_ID);
this.requestPostPermissionTest(PermissionConstants.TEST_PLAN_READ, URL_POST_TEST_PLAN_MODULE_COUNT, dataRequest);
this.requestPostPermissionTest(PermissionConstants.TEST_PLAN_READ, URL_POST_TEST_PLAN_PAGE, dataRequest);
}
}

View File

@ -16,9 +16,11 @@ import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.sdk.constants.*;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.SubListUtils;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.domain.TestPlanModuleExample;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator;
import io.metersphere.system.utils.Pager;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@ -433,4 +435,16 @@ public class TestPlanTestService {
});
}
public void checkTestPlanPage(String returnData, long current, long pageSize, long allData) {
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
Pager<Object> result = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class);
//返回值的页码和当前页码相同
Assertions.assertEquals(result.getCurrent(), current);
//返回的数据量不超过规定要返回的数据量相同
Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(result.getList())).size() <= pageSize);
if (allData > 0) {
Assertions.assertEquals(result.getTotal(), allData);
}
}
}

View File

@ -1,17 +0,0 @@
package io.metersphere.plan.service.mock;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.domain.TestPlanConfig;
import io.metersphere.plan.service.TestPlanGroupService;
import org.springframework.stereotype.Service;
@Service
public class TestPlanGroupServiceImpl implements TestPlanGroupService {
@Override
public boolean validateGroup(TestPlan testPlan, TestPlanConfig testPlanConfig) {
return true;
}
}