From 8c2522ab2675d36d364f750de9646ced649ef2de Mon Sep 17 00:00:00 2001 From: song-tianyang Date: Mon, 8 Jan 2024 19:12:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92):=20?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92=E6=A8=A1=E5=9D=97=E6=A0=91?= =?UTF-8?q?=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/domain/TestPlanModule.java | 129 +++ .../system/domain/TestPlanModuleExample.java | 800 ++++++++++++++++++ .../system/mapper/TestPlanModuleMapper.java | 34 + .../system/mapper/TestPlanModuleMapper.xml | 328 +++++++ .../3.0.0/ddl/V3.0.0_3__test_plan.sql | 192 +---- .../migration/3.0.0/dml/V3.0.0_11_1__data.sql | 15 + .../sdk/constants/PermissionConstants.java | 15 + .../main/resources/i18n/commons.properties | 12 +- .../resources/i18n/commons_en_US.properties | 12 +- .../resources/i18n/commons_zh_CN.properties | 12 +- .../resources/i18n/commons_zh_TW.properties | 12 +- .../project/service/FileModuleLogService.java | 3 - .../log/constants/OperationLogModule.java | 16 +- backend/services/test-plan/pom.xml | 10 +- .../controller/TestPlanModuleController.java | 69 ++ .../request/TestPlanModuleCreateRequest.java | 23 + .../request/TestPlanModuleUpdateRequest.java | 18 + .../plan/mapper/ExtTestPlanModuleMapper.java | 28 + .../plan/mapper/ExtTestPlanModuleMapper.xml | 66 ++ .../service/TestPlanModuleLogService.java | 115 +++ .../plan/service/TestPlanModuleService.java | 250 ++++++ .../src/main/resources/permission.json | 48 ++ .../resources/testPlanGeneratorConfig.xml | 85 ++ .../controller/TestPlanControllerTests.java | 279 ------ .../plan/controller/TestPlanTests.java | 794 +++++++++++++++++ .../metersphere/plan/utils/TestPlanUtils.java | 32 + .../src/test/resources/application.properties | 13 +- .../resources/dml/init_permission_test.sql | 14 + 28 files changed, 2971 insertions(+), 453 deletions(-) create mode 100644 backend/framework/domain/src/main/java/io/metersphere/system/domain/TestPlanModule.java create mode 100644 backend/framework/domain/src/main/java/io/metersphere/system/domain/TestPlanModuleExample.java create mode 100644 backend/framework/domain/src/main/java/io/metersphere/system/mapper/TestPlanModuleMapper.java create mode 100644 backend/framework/domain/src/main/java/io/metersphere/system/mapper/TestPlanModuleMapper.xml create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanModuleController.java create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanModuleCreateRequest.java create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanModuleUpdateRequest.java create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanModuleMapper.java create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanModuleMapper.xml create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanModuleLogService.java create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanModuleService.java create mode 100644 backend/services/test-plan/src/main/resources/permission.json create mode 100644 backend/services/test-plan/src/main/resources/testPlanGeneratorConfig.xml delete mode 100644 backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanControllerTests.java create mode 100644 backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java create mode 100644 backend/services/test-plan/src/test/java/io/metersphere/plan/utils/TestPlanUtils.java create mode 100644 backend/services/test-plan/src/test/resources/dml/init_permission_test.sql diff --git a/backend/framework/domain/src/main/java/io/metersphere/system/domain/TestPlanModule.java b/backend/framework/domain/src/main/java/io/metersphere/system/domain/TestPlanModule.java new file mode 100644 index 0000000000..39fe08e16e --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/system/domain/TestPlanModule.java @@ -0,0 +1,129 @@ +package io.metersphere.system.domain; + +import io.metersphere.validation.groups.*; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import lombok.Data; + +@Data +public class TestPlanModule implements Serializable { + @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{test_plan_module.id.not_blank}", groups = {Updated.class}) + @Size(min = 1, max = 50, message = "{test_plan_module.id.length_range}", groups = {Created.class, Updated.class}) + private String id; + + @Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{test_plan_module.project_id.not_blank}", groups = {Created.class}) + @Size(min = 1, max = 50, message = "{test_plan_module.project_id.length_range}", groups = {Created.class, Updated.class}) + private String projectId; + + @Schema(description = "模块名称", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{test_plan_module.name.not_blank}", groups = {Created.class}) + @Size(min = 1, max = 64, message = "{test_plan_module.name.length_range}", groups = {Created.class, Updated.class}) + private String name; + + @Schema(description = "模块ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{test_plan_module.parent_id.not_blank}", groups = {Created.class}) + @Size(min = 1, max = 50, message = "{test_plan_module.parent_id.length_range}", groups = {Created.class, Updated.class}) + private String parentId; + + @Schema(description = "排序标识", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "{test_plan_module.pos.not_blank}", groups = {Created.class}) + private Long pos; + + @Schema(description = "创建时间") + private Long createTime; + + @Schema(description = "更新时间") + private Long updateTime; + + @Schema(description = "创建人") + private String createUser; + + @Schema(description = "更新人") + private String updateUser; + + private static final long serialVersionUID = 1L; + + public enum Column { + id("id", "id", "VARCHAR", false), + projectId("project_id", "projectId", "VARCHAR", false), + name("name", "name", "VARCHAR", true), + parentId("parent_id", "parentId", "VARCHAR", false), + pos("pos", "pos", "BIGINT", false), + createTime("create_time", "createTime", "BIGINT", false), + updateTime("update_time", "updateTime", "BIGINT", false), + createUser("create_user", "createUser", "VARCHAR", false), + updateUser("update_user", "updateUser", "VARCHAR", false); + + private static final String BEGINNING_DELIMITER = "`"; + + private static final String ENDING_DELIMITER = "`"; + + private final String column; + + private final boolean isColumnNameDelimited; + + private final String javaProperty; + + private final String jdbcType; + + public String value() { + return this.column; + } + + public String getValue() { + return this.column; + } + + public String getJavaProperty() { + return this.javaProperty; + } + + public String getJdbcType() { + return this.jdbcType; + } + + Column(String column, String javaProperty, String jdbcType, boolean isColumnNameDelimited) { + this.column = column; + this.javaProperty = javaProperty; + this.jdbcType = jdbcType; + this.isColumnNameDelimited = isColumnNameDelimited; + } + + public String desc() { + return this.getEscapedColumnName() + " DESC"; + } + + public String asc() { + return this.getEscapedColumnName() + " ASC"; + } + + public static Column[] excludes(Column ... excludes) { + ArrayList columns = new ArrayList<>(Arrays.asList(Column.values())); + if (excludes != null && excludes.length > 0) { + columns.removeAll(new ArrayList<>(Arrays.asList(excludes))); + } + return columns.toArray(new Column[]{}); + } + + public static Column[] all() { + return Column.values(); + } + + public String getEscapedColumnName() { + if (this.isColumnNameDelimited) { + return new StringBuilder().append(BEGINNING_DELIMITER).append(this.column).append(ENDING_DELIMITER).toString(); + } else { + return this.column; + } + } + + public String getAliasedEscapedColumnName() { + return this.getEscapedColumnName(); + } + } +} \ No newline at end of file diff --git a/backend/framework/domain/src/main/java/io/metersphere/system/domain/TestPlanModuleExample.java b/backend/framework/domain/src/main/java/io/metersphere/system/domain/TestPlanModuleExample.java new file mode 100644 index 0000000000..9bd0fa74ce --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/system/domain/TestPlanModuleExample.java @@ -0,0 +1,800 @@ +package io.metersphere.system.domain; + +import java.util.ArrayList; +import java.util.List; + +public class TestPlanModuleExample { + protected String orderByClause; + + protected boolean distinct; + + protected List oredCriteria; + + public TestPlanModuleExample() { + oredCriteria = new ArrayList(); + } + + public void setOrderByClause(String orderByClause) { + this.orderByClause = orderByClause; + } + + public String getOrderByClause() { + return orderByClause; + } + + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + + public boolean isDistinct() { + return distinct; + } + + public List getOredCriteria() { + return oredCriteria; + } + + public void or(Criteria criteria) { + oredCriteria.add(criteria); + } + + public Criteria or() { + Criteria criteria = createCriteriaInternal(); + oredCriteria.add(criteria); + return criteria; + } + + public Criteria createCriteria() { + Criteria criteria = createCriteriaInternal(); + if (oredCriteria.size() == 0) { + oredCriteria.add(criteria); + } + return criteria; + } + + protected Criteria createCriteriaInternal() { + Criteria criteria = new Criteria(); + return criteria; + } + + public void clear() { + oredCriteria.clear(); + orderByClause = null; + distinct = false; + } + + protected abstract static class GeneratedCriteria { + protected List criteria; + + protected GeneratedCriteria() { + super(); + criteria = new ArrayList(); + } + + public boolean isValid() { + return criteria.size() > 0; + } + + public List getAllCriteria() { + return criteria; + } + + public List getCriteria() { + return criteria; + } + + protected void addCriterion(String condition) { + if (condition == null) { + throw new RuntimeException("Value for condition cannot be null"); + } + criteria.add(new Criterion(condition)); + } + + protected void addCriterion(String condition, Object value, String property) { + if (value == null) { + throw new RuntimeException("Value for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value)); + } + + protected void addCriterion(String condition, Object value1, Object value2, String property) { + if (value1 == null || value2 == null) { + throw new RuntimeException("Between values for " + property + " cannot be null"); + } + criteria.add(new Criterion(condition, value1, value2)); + } + + public Criteria andIdIsNull() { + addCriterion("id is null"); + return (Criteria) this; + } + + public Criteria andIdIsNotNull() { + addCriterion("id is not null"); + return (Criteria) this; + } + + public Criteria andIdEqualTo(String value) { + addCriterion("id =", value, "id"); + return (Criteria) this; + } + + public Criteria andIdNotEqualTo(String value) { + addCriterion("id <>", value, "id"); + return (Criteria) this; + } + + public Criteria andIdGreaterThan(String value) { + addCriterion("id >", value, "id"); + return (Criteria) this; + } + + public Criteria andIdGreaterThanOrEqualTo(String value) { + addCriterion("id >=", value, "id"); + return (Criteria) this; + } + + public Criteria andIdLessThan(String value) { + addCriterion("id <", value, "id"); + return (Criteria) this; + } + + public Criteria andIdLessThanOrEqualTo(String value) { + addCriterion("id <=", value, "id"); + return (Criteria) this; + } + + public Criteria andIdLike(String value) { + addCriterion("id like", value, "id"); + return (Criteria) this; + } + + public Criteria andIdNotLike(String value) { + addCriterion("id not like", value, "id"); + return (Criteria) this; + } + + public Criteria andIdIn(List values) { + addCriterion("id in", values, "id"); + return (Criteria) this; + } + + public Criteria andIdNotIn(List values) { + addCriterion("id not in", values, "id"); + return (Criteria) this; + } + + public Criteria andIdBetween(String value1, String value2) { + addCriterion("id between", value1, value2, "id"); + return (Criteria) this; + } + + public Criteria andIdNotBetween(String value1, String value2) { + addCriterion("id not between", value1, value2, "id"); + return (Criteria) this; + } + + public Criteria andProjectIdIsNull() { + addCriterion("project_id is null"); + return (Criteria) this; + } + + public Criteria andProjectIdIsNotNull() { + addCriterion("project_id is not null"); + return (Criteria) this; + } + + public Criteria andProjectIdEqualTo(String value) { + addCriterion("project_id =", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdNotEqualTo(String value) { + addCriterion("project_id <>", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdGreaterThan(String value) { + addCriterion("project_id >", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdGreaterThanOrEqualTo(String value) { + addCriterion("project_id >=", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdLessThan(String value) { + addCriterion("project_id <", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdLessThanOrEqualTo(String value) { + addCriterion("project_id <=", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdLike(String value) { + addCriterion("project_id like", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdNotLike(String value) { + addCriterion("project_id not like", value, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdIn(List values) { + addCriterion("project_id in", values, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdNotIn(List values) { + addCriterion("project_id not in", values, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdBetween(String value1, String value2) { + addCriterion("project_id between", value1, value2, "projectId"); + return (Criteria) this; + } + + public Criteria andProjectIdNotBetween(String value1, String value2) { + addCriterion("project_id not between", value1, value2, "projectId"); + return (Criteria) this; + } + + public Criteria andNameIsNull() { + addCriterion("`name` is null"); + return (Criteria) this; + } + + public Criteria andNameIsNotNull() { + addCriterion("`name` is not null"); + return (Criteria) this; + } + + public Criteria andNameEqualTo(String value) { + addCriterion("`name` =", value, "name"); + return (Criteria) this; + } + + public Criteria andNameNotEqualTo(String value) { + addCriterion("`name` <>", value, "name"); + return (Criteria) this; + } + + public Criteria andNameGreaterThan(String value) { + addCriterion("`name` >", value, "name"); + return (Criteria) this; + } + + public Criteria andNameGreaterThanOrEqualTo(String value) { + addCriterion("`name` >=", value, "name"); + return (Criteria) this; + } + + public Criteria andNameLessThan(String value) { + addCriterion("`name` <", value, "name"); + return (Criteria) this; + } + + public Criteria andNameLessThanOrEqualTo(String value) { + addCriterion("`name` <=", value, "name"); + return (Criteria) this; + } + + public Criteria andNameLike(String value) { + addCriterion("`name` like", value, "name"); + return (Criteria) this; + } + + public Criteria andNameNotLike(String value) { + addCriterion("`name` not like", value, "name"); + return (Criteria) this; + } + + public Criteria andNameIn(List values) { + addCriterion("`name` in", values, "name"); + return (Criteria) this; + } + + public Criteria andNameNotIn(List values) { + addCriterion("`name` not in", values, "name"); + return (Criteria) this; + } + + public Criteria andNameBetween(String value1, String value2) { + addCriterion("`name` between", value1, value2, "name"); + return (Criteria) this; + } + + public Criteria andNameNotBetween(String value1, String value2) { + addCriterion("`name` not between", value1, value2, "name"); + return (Criteria) this; + } + + public Criteria andParentIdIsNull() { + addCriterion("parent_id is null"); + return (Criteria) this; + } + + public Criteria andParentIdIsNotNull() { + addCriterion("parent_id is not null"); + return (Criteria) this; + } + + public Criteria andParentIdEqualTo(String value) { + addCriterion("parent_id =", value, "parentId"); + return (Criteria) this; + } + + public Criteria andParentIdNotEqualTo(String value) { + addCriterion("parent_id <>", value, "parentId"); + return (Criteria) this; + } + + public Criteria andParentIdGreaterThan(String value) { + addCriterion("parent_id >", value, "parentId"); + return (Criteria) this; + } + + public Criteria andParentIdGreaterThanOrEqualTo(String value) { + addCriterion("parent_id >=", value, "parentId"); + return (Criteria) this; + } + + public Criteria andParentIdLessThan(String value) { + addCriterion("parent_id <", value, "parentId"); + return (Criteria) this; + } + + public Criteria andParentIdLessThanOrEqualTo(String value) { + addCriterion("parent_id <=", value, "parentId"); + return (Criteria) this; + } + + public Criteria andParentIdLike(String value) { + addCriterion("parent_id like", value, "parentId"); + return (Criteria) this; + } + + public Criteria andParentIdNotLike(String value) { + addCriterion("parent_id not like", value, "parentId"); + return (Criteria) this; + } + + public Criteria andParentIdIn(List values) { + addCriterion("parent_id in", values, "parentId"); + return (Criteria) this; + } + + public Criteria andParentIdNotIn(List values) { + addCriterion("parent_id not in", values, "parentId"); + return (Criteria) this; + } + + public Criteria andParentIdBetween(String value1, String value2) { + addCriterion("parent_id between", value1, value2, "parentId"); + return (Criteria) this; + } + + public Criteria andParentIdNotBetween(String value1, String value2) { + addCriterion("parent_id not between", value1, value2, "parentId"); + 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 values) { + addCriterion("pos in", values, "pos"); + return (Criteria) this; + } + + public Criteria andPosNotIn(List 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 Criteria andCreateTimeIsNull() { + addCriterion("create_time is null"); + return (Criteria) this; + } + + public Criteria andCreateTimeIsNotNull() { + addCriterion("create_time is not null"); + return (Criteria) this; + } + + public Criteria andCreateTimeEqualTo(Long value) { + addCriterion("create_time =", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeNotEqualTo(Long value) { + addCriterion("create_time <>", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeGreaterThan(Long value) { + addCriterion("create_time >", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeGreaterThanOrEqualTo(Long value) { + addCriterion("create_time >=", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeLessThan(Long value) { + addCriterion("create_time <", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeLessThanOrEqualTo(Long value) { + addCriterion("create_time <=", value, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeIn(List values) { + addCriterion("create_time in", values, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeNotIn(List values) { + addCriterion("create_time not in", values, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeBetween(Long value1, Long value2) { + addCriterion("create_time between", value1, value2, "createTime"); + return (Criteria) this; + } + + public Criteria andCreateTimeNotBetween(Long value1, Long value2) { + addCriterion("create_time not between", value1, value2, "createTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeIsNull() { + addCriterion("update_time is null"); + return (Criteria) this; + } + + public Criteria andUpdateTimeIsNotNull() { + addCriterion("update_time is not null"); + return (Criteria) this; + } + + public Criteria andUpdateTimeEqualTo(Long value) { + addCriterion("update_time =", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeNotEqualTo(Long value) { + addCriterion("update_time <>", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeGreaterThan(Long value) { + addCriterion("update_time >", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeGreaterThanOrEqualTo(Long value) { + addCriterion("update_time >=", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeLessThan(Long value) { + addCriterion("update_time <", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeLessThanOrEqualTo(Long value) { + addCriterion("update_time <=", value, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeIn(List values) { + addCriterion("update_time in", values, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeNotIn(List values) { + addCriterion("update_time not in", values, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeBetween(Long value1, Long value2) { + addCriterion("update_time between", value1, value2, "updateTime"); + return (Criteria) this; + } + + public Criteria andUpdateTimeNotBetween(Long value1, Long value2) { + addCriterion("update_time not between", value1, value2, "updateTime"); + return (Criteria) this; + } + + public Criteria andCreateUserIsNull() { + addCriterion("create_user is null"); + return (Criteria) this; + } + + public Criteria andCreateUserIsNotNull() { + addCriterion("create_user is not null"); + return (Criteria) this; + } + + public Criteria andCreateUserEqualTo(String value) { + addCriterion("create_user =", value, "createUser"); + return (Criteria) this; + } + + public Criteria andCreateUserNotEqualTo(String value) { + addCriterion("create_user <>", value, "createUser"); + return (Criteria) this; + } + + public Criteria andCreateUserGreaterThan(String value) { + addCriterion("create_user >", value, "createUser"); + return (Criteria) this; + } + + public Criteria andCreateUserGreaterThanOrEqualTo(String value) { + addCriterion("create_user >=", value, "createUser"); + return (Criteria) this; + } + + public Criteria andCreateUserLessThan(String value) { + addCriterion("create_user <", value, "createUser"); + return (Criteria) this; + } + + public Criteria andCreateUserLessThanOrEqualTo(String value) { + addCriterion("create_user <=", value, "createUser"); + return (Criteria) this; + } + + public Criteria andCreateUserLike(String value) { + addCriterion("create_user like", value, "createUser"); + return (Criteria) this; + } + + public Criteria andCreateUserNotLike(String value) { + addCriterion("create_user not like", value, "createUser"); + return (Criteria) this; + } + + public Criteria andCreateUserIn(List values) { + addCriterion("create_user in", values, "createUser"); + return (Criteria) this; + } + + public Criteria andCreateUserNotIn(List values) { + addCriterion("create_user not in", values, "createUser"); + return (Criteria) this; + } + + public Criteria andCreateUserBetween(String value1, String value2) { + addCriterion("create_user between", value1, value2, "createUser"); + return (Criteria) this; + } + + public Criteria andCreateUserNotBetween(String value1, String value2) { + addCriterion("create_user not between", value1, value2, "createUser"); + return (Criteria) this; + } + + public Criteria andUpdateUserIsNull() { + addCriterion("update_user is null"); + return (Criteria) this; + } + + public Criteria andUpdateUserIsNotNull() { + addCriterion("update_user is not null"); + return (Criteria) this; + } + + public Criteria andUpdateUserEqualTo(String value) { + addCriterion("update_user =", value, "updateUser"); + return (Criteria) this; + } + + public Criteria andUpdateUserNotEqualTo(String value) { + addCriterion("update_user <>", value, "updateUser"); + return (Criteria) this; + } + + public Criteria andUpdateUserGreaterThan(String value) { + addCriterion("update_user >", value, "updateUser"); + return (Criteria) this; + } + + public Criteria andUpdateUserGreaterThanOrEqualTo(String value) { + addCriterion("update_user >=", value, "updateUser"); + return (Criteria) this; + } + + public Criteria andUpdateUserLessThan(String value) { + addCriterion("update_user <", value, "updateUser"); + return (Criteria) this; + } + + public Criteria andUpdateUserLessThanOrEqualTo(String value) { + addCriterion("update_user <=", value, "updateUser"); + return (Criteria) this; + } + + public Criteria andUpdateUserLike(String value) { + addCriterion("update_user like", value, "updateUser"); + return (Criteria) this; + } + + public Criteria andUpdateUserNotLike(String value) { + addCriterion("update_user not like", value, "updateUser"); + return (Criteria) this; + } + + public Criteria andUpdateUserIn(List values) { + addCriterion("update_user in", values, "updateUser"); + return (Criteria) this; + } + + public Criteria andUpdateUserNotIn(List values) { + addCriterion("update_user not in", values, "updateUser"); + return (Criteria) this; + } + + public Criteria andUpdateUserBetween(String value1, String value2) { + addCriterion("update_user between", value1, value2, "updateUser"); + return (Criteria) this; + } + + public Criteria andUpdateUserNotBetween(String value1, String value2) { + addCriterion("update_user not between", value1, value2, "updateUser"); + return (Criteria) this; + } + } + + public static class Criteria extends GeneratedCriteria { + + protected Criteria() { + super(); + } + } + + public static class Criterion { + private String condition; + + private Object value; + + private Object secondValue; + + private boolean noValue; + + private boolean singleValue; + + private boolean betweenValue; + + private boolean listValue; + + private String typeHandler; + + public String getCondition() { + return condition; + } + + public Object getValue() { + return value; + } + + public Object getSecondValue() { + return secondValue; + } + + public boolean isNoValue() { + return noValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + public boolean isBetweenValue() { + return betweenValue; + } + + public boolean isListValue() { + return listValue; + } + + public String getTypeHandler() { + return typeHandler; + } + + protected Criterion(String condition) { + super(); + this.condition = condition; + this.typeHandler = null; + this.noValue = true; + } + + protected Criterion(String condition, Object value, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.typeHandler = typeHandler; + if (value instanceof List) { + this.listValue = true; + } else { + this.singleValue = true; + } + } + + protected Criterion(String condition, Object value) { + this(condition, value, null); + } + + protected Criterion(String condition, Object value, Object secondValue, String typeHandler) { + super(); + this.condition = condition; + this.value = value; + this.secondValue = secondValue; + this.typeHandler = typeHandler; + this.betweenValue = true; + } + + protected Criterion(String condition, Object value, Object secondValue) { + this(condition, value, secondValue, null); + } + } +} \ No newline at end of file diff --git a/backend/framework/domain/src/main/java/io/metersphere/system/mapper/TestPlanModuleMapper.java b/backend/framework/domain/src/main/java/io/metersphere/system/mapper/TestPlanModuleMapper.java new file mode 100644 index 0000000000..b2e05891ee --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/system/mapper/TestPlanModuleMapper.java @@ -0,0 +1,34 @@ +package io.metersphere.system.mapper; + +import io.metersphere.system.domain.TestPlanModule; +import io.metersphere.system.domain.TestPlanModuleExample; +import java.util.List; +import org.apache.ibatis.annotations.Param; + +public interface TestPlanModuleMapper { + long countByExample(TestPlanModuleExample example); + + int deleteByExample(TestPlanModuleExample example); + + int deleteByPrimaryKey(String id); + + int insert(TestPlanModule record); + + int insertSelective(TestPlanModule record); + + List selectByExample(TestPlanModuleExample example); + + TestPlanModule selectByPrimaryKey(String id); + + int updateByExampleSelective(@Param("record") TestPlanModule record, @Param("example") TestPlanModuleExample example); + + int updateByExample(@Param("record") TestPlanModule record, @Param("example") TestPlanModuleExample example); + + int updateByPrimaryKeySelective(TestPlanModule record); + + int updateByPrimaryKey(TestPlanModule record); + + int batchInsert(@Param("list") List list); + + int batchInsertSelective(@Param("list") List list, @Param("selective") TestPlanModule.Column ... selective); +} \ No newline at end of file diff --git a/backend/framework/domain/src/main/java/io/metersphere/system/mapper/TestPlanModuleMapper.xml b/backend/framework/domain/src/main/java/io/metersphere/system/mapper/TestPlanModuleMapper.xml new file mode 100644 index 0000000000..72819c1c07 --- /dev/null +++ b/backend/framework/domain/src/main/java/io/metersphere/system/mapper/TestPlanModuleMapper.xml @@ -0,0 +1,328 @@ + + + + + + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + + + + + + + + and ${criterion.condition} + + + and ${criterion.condition} #{criterion.value} + + + and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} + + + and ${criterion.condition} + + #{listItem} + + + + + + + + + + + id, project_id, `name`, parent_id, pos, create_time, update_time, create_user, update_user + + + + + delete from test_plan_module + where id = #{id,jdbcType=VARCHAR} + + + delete from test_plan_module + + + + + + insert into test_plan_module (id, project_id, `name`, + parent_id, pos, create_time, + update_time, create_user, update_user + ) + values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, + #{parentId,jdbcType=VARCHAR}, #{pos,jdbcType=BIGINT}, #{createTime,jdbcType=BIGINT}, + #{updateTime,jdbcType=BIGINT}, #{createUser,jdbcType=VARCHAR}, #{updateUser,jdbcType=VARCHAR} + ) + + + insert into test_plan_module + + + id, + + + project_id, + + + `name`, + + + parent_id, + + + pos, + + + create_time, + + + update_time, + + + create_user, + + + update_user, + + + + + #{id,jdbcType=VARCHAR}, + + + #{projectId,jdbcType=VARCHAR}, + + + #{name,jdbcType=VARCHAR}, + + + #{parentId,jdbcType=VARCHAR}, + + + #{pos,jdbcType=BIGINT}, + + + #{createTime,jdbcType=BIGINT}, + + + #{updateTime,jdbcType=BIGINT}, + + + #{createUser,jdbcType=VARCHAR}, + + + #{updateUser,jdbcType=VARCHAR}, + + + + + + update test_plan_module + + + id = #{record.id,jdbcType=VARCHAR}, + + + project_id = #{record.projectId,jdbcType=VARCHAR}, + + + `name` = #{record.name,jdbcType=VARCHAR}, + + + parent_id = #{record.parentId,jdbcType=VARCHAR}, + + + pos = #{record.pos,jdbcType=BIGINT}, + + + create_time = #{record.createTime,jdbcType=BIGINT}, + + + update_time = #{record.updateTime,jdbcType=BIGINT}, + + + create_user = #{record.createUser,jdbcType=VARCHAR}, + + + update_user = #{record.updateUser,jdbcType=VARCHAR}, + + + + + + + + update test_plan_module + set id = #{record.id,jdbcType=VARCHAR}, + project_id = #{record.projectId,jdbcType=VARCHAR}, + `name` = #{record.name,jdbcType=VARCHAR}, + parent_id = #{record.parentId,jdbcType=VARCHAR}, + pos = #{record.pos,jdbcType=BIGINT}, + create_time = #{record.createTime,jdbcType=BIGINT}, + update_time = #{record.updateTime,jdbcType=BIGINT}, + create_user = #{record.createUser,jdbcType=VARCHAR}, + update_user = #{record.updateUser,jdbcType=VARCHAR} + + + + + + update test_plan_module + + + project_id = #{projectId,jdbcType=VARCHAR}, + + + `name` = #{name,jdbcType=VARCHAR}, + + + parent_id = #{parentId,jdbcType=VARCHAR}, + + + pos = #{pos,jdbcType=BIGINT}, + + + create_time = #{createTime,jdbcType=BIGINT}, + + + update_time = #{updateTime,jdbcType=BIGINT}, + + + create_user = #{createUser,jdbcType=VARCHAR}, + + + update_user = #{updateUser,jdbcType=VARCHAR}, + + + where id = #{id,jdbcType=VARCHAR} + + + update test_plan_module + set project_id = #{projectId,jdbcType=VARCHAR}, + `name` = #{name,jdbcType=VARCHAR}, + parent_id = #{parentId,jdbcType=VARCHAR}, + pos = #{pos,jdbcType=BIGINT}, + create_time = #{createTime,jdbcType=BIGINT}, + update_time = #{updateTime,jdbcType=BIGINT}, + create_user = #{createUser,jdbcType=VARCHAR}, + update_user = #{updateUser,jdbcType=VARCHAR} + where id = #{id,jdbcType=VARCHAR} + + + insert into test_plan_module + (id, project_id, `name`, parent_id, pos, create_time, update_time, create_user, update_user + ) + values + + (#{item.id,jdbcType=VARCHAR}, #{item.projectId,jdbcType=VARCHAR}, #{item.name,jdbcType=VARCHAR}, + #{item.parentId,jdbcType=VARCHAR}, #{item.pos,jdbcType=BIGINT}, #{item.createTime,jdbcType=BIGINT}, + #{item.updateTime,jdbcType=BIGINT}, #{item.createUser,jdbcType=VARCHAR}, #{item.updateUser,jdbcType=VARCHAR} + ) + + + + insert into test_plan_module ( + + ${column.escapedColumnName} + + ) + values + + ( + + + #{item.id,jdbcType=VARCHAR} + + + #{item.projectId,jdbcType=VARCHAR} + + + #{item.name,jdbcType=VARCHAR} + + + #{item.parentId,jdbcType=VARCHAR} + + + #{item.pos,jdbcType=BIGINT} + + + #{item.createTime,jdbcType=BIGINT} + + + #{item.updateTime,jdbcType=BIGINT} + + + #{item.createUser,jdbcType=VARCHAR} + + + #{item.updateUser,jdbcType=VARCHAR} + + + ) + + + \ No newline at end of file diff --git a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_3__test_plan.sql b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_3__test_plan.sql index 98e403a3c8..1d9aa3d894 100644 --- a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_3__test_plan.sql +++ b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_3__test_plan.sql @@ -1,157 +1,53 @@ -- set innodb lock wait timeout SET SESSION innodb_lock_wait_timeout = 7200; -CREATE TABLE IF NOT EXISTS test_plan( - `id` VARCHAR(50) NOT NULL COMMENT 'ID' , - `project_id` VARCHAR(50) NOT NULL COMMENT '测试计划所属项目' , - `parent_id` VARCHAR(50) NOT NULL COMMENT '测试计划父ID;测试计划要改为树结构。最上层的为root,其余则是父节点ID' , - `name` VARCHAR(255) NOT NULL COMMENT '测试计划名称' , - `status` VARCHAR(20) NOT NULL COMMENT '测试计划状态;进行中/未开始/已完成/已结束/已归档' , - `stage` VARCHAR(30) NOT NULL COMMENT '测试阶段' , - `tags` VARCHAR(500) COMMENT '标签' , - `create_time` BIGINT NOT NULL COMMENT '创建时间' , - `create_user` VARCHAR(50) NOT NULL COMMENT '创建人' , - `update_time` BIGINT COMMENT '更新时间' , - `update_user` VARCHAR(50) COMMENT '更新人' , - `planned_start_time` BIGINT COMMENT '计划开始时间' , - `planned_end_time` BIGINT COMMENT '计划结束时间' , - `actual_start_time` BIGINT COMMENT '实际开始时间' , - `actual_end_time` BIGINT COMMENT '实际结束时间' , - `description` VARCHAR(2000) COMMENT '描述' , - PRIMARY KEY (id) +CREATE TABLE test_plan_module +( + `id` VARCHAR(50) NOT NULL COMMENT 'ID', + `project_id` VARCHAR(50) NOT NULL COMMENT '项目名称', + `name` VARCHAR(64) NOT NULL COMMENT '模块名称', + `parent_id` VARCHAR(50) NOT NULL COMMENT '模块ID', + `pos` BIGINT NOT NULL COMMENT '排序标识', + `create_time` BIGINT NOT NULL COMMENT '创建时间', + `update_time` BIGINT NOT NULL COMMENT '更新时间', + `create_user` VARCHAR(100) COMMENT '创建人', + `update_user` VARCHAR(100) COMMENT '更新人', + PRIMARY KEY (id) +) COMMENT = '测试计划模块'; + + +CREATE INDEX idx_project_id ON test_plan_module (project_id); +CREATE INDEX idx_name ON test_plan_module (name); +CREATE INDEX idx_pos ON test_plan_module (pos); +CREATE UNIQUE INDEX uq_name_project_parent ON test_plan_module (project_id, name, parent_id); + +CREATE TABLE IF NOT EXISTS test_plan +( + `id` VARCHAR(50) NOT NULL COMMENT 'ID', + `project_id` VARCHAR(50) NOT NULL COMMENT '测试计划所属项目', + `parent_id` VARCHAR(50) NOT NULL COMMENT '测试计划父ID;测试计划要改为树结构。最上层的为root,其余则是父节点ID', + `name` VARCHAR(255) NOT NULL COMMENT '测试计划名称', + `status` VARCHAR(20) NOT NULL COMMENT '测试计划状态;进行中/未开始/已完成/已结束/已归档', + `stage` VARCHAR(30) NOT NULL COMMENT '测试阶段', + `tags` VARCHAR(500) COMMENT '标签', + `create_time` BIGINT NOT NULL COMMENT '创建时间', + `create_user` VARCHAR(50) NOT NULL COMMENT '创建人', + `update_time` BIGINT COMMENT '更新时间', + `update_user` VARCHAR(50) COMMENT '更新人', + `planned_start_time` BIGINT COMMENT '计划开始时间', + `planned_end_time` BIGINT COMMENT '计划结束时间', + `actual_start_time` BIGINT COMMENT '实际开始时间', + `actual_end_time` BIGINT COMMENT '实际结束时间', + `description` VARCHAR(2000) COMMENT '描述', + PRIMARY KEY (id) ) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_general_ci COMMENT = '测试计划'; + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_general_ci COMMENT = '测试计划'; -CREATE INDEX idx_parent_id ON test_plan(project_id); -CREATE INDEX idx_create_user ON test_plan(create_user); -CREATE INDEX idx_status ON test_plan(status); - -CREATE TABLE IF NOT EXISTS test_plan_follower( - `test_plan_id` VARCHAR(50) NOT NULL COMMENT '测试计划ID;联合主键' , - `user_id` VARCHAR(50) NOT NULL COMMENT '用户ID;联合主键' , - PRIMARY KEY (test_plan_id,user_id) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_general_ci COMMENT = '测试计划关注人'; - -CREATE TABLE IF NOT EXISTS test_plan_principal( - `test_plan_id` VARCHAR(50) NOT NULL COMMENT '测试计划ID' , - `user_id` VARCHAR(50) NOT NULL COMMENT '用户ID' , - PRIMARY KEY (test_plan_id,user_id) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_general_ci COMMENT = '测试计划责任人'; - -CREATE TABLE IF NOT EXISTS test_plan_config( - `test_plan_id` VARCHAR(50) NOT NULL COMMENT '测试计划ID' , - `run_mode_config` TEXT COMMENT '运行模式' , - `automatic_status_update` BIT(1) NOT NULL DEFAULT 0 COMMENT '是否自定更新功能用例状态' , - `repeat_case` BIT(1) NOT NULL DEFAULT 0 COMMENT '是否允许重复添加用例' , - `pass_threshold` INT NOT NULL DEFAULT 100 COMMENT '测试计划通过阈值;0-100' , - PRIMARY KEY (test_plan_id) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_general_ci COMMENT = '测试计划配置'; - -CREATE TABLE IF NOT EXISTS test_plan_api_case( - `id` VARCHAR(50) NOT NULL COMMENT 'ID' , - `test_plan_id` VARCHAR(50) NOT NULL COMMENT '测试计划ID' , - `api_case_id` VARCHAR(50) NOT NULL COMMENT '接口用例ID' , - `environment_type` VARCHAR(20) COMMENT '环境类型' , - `environment` LONGTEXT COMMENT '所属环境' , - `environment_group_id` VARCHAR(50) COMMENT '环境组ID' , - `create_time` BIGINT NOT NULL COMMENT '创建时间' , - `create_user` VARCHAR(40) NOT NULL COMMENT '创建人' , - `pos` BIGINT NOT NULL COMMENT '自定义排序,间隔5000' , - PRIMARY KEY (id) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_general_ci COMMENT = '测试计划关联接口用例'; - - -CREATE INDEX idx_api_case_id ON test_plan_api_case(api_case_id); -CREATE INDEX idx_test_plan_id ON test_plan_api_case(test_plan_id); -CREATE INDEX idx_create_user ON test_plan_api_case(create_user); - -CREATE TABLE IF NOT EXISTS test_plan_api_scenario( - `id` VARCHAR(50) NOT NULL COMMENT 'ID' , - `test_plan_id` VARCHAR(50) NOT NULL COMMENT '测试计划ID' , - `api_scenario_id` VARCHAR(255) COMMENT '场景ID' , - `create_time` BIGINT NOT NULL COMMENT '创建时间' , - `create_user` VARCHAR(100) NOT NULL COMMENT '创建人' , - `pos` BIGINT NOT NULL COMMENT '自定义排序,间隔5000' , - `environment_type` VARCHAR(20) COMMENT '环境类型' , - `environment` LONGTEXT COMMENT '所属环境' , - `environment_group_id` VARCHAR(50) COMMENT '环境组ID' , - PRIMARY KEY (id) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_general_ci COMMENT = '测试计划关联场景用例'; - - -CREATE INDEX idx_api_scenario_id ON test_plan_api_scenario(api_scenario_id); -CREATE INDEX idx_test_plan_id ON test_plan_api_scenario(test_plan_id); -CREATE INDEX idx_create_user ON test_plan_api_scenario(create_user); - -CREATE TABLE IF NOT EXISTS test_plan_load_case( - `id` VARCHAR(50) NOT NULL COMMENT 'ID' , - `test_plan_id` VARCHAR(50) NOT NULL COMMENT '测试计划ID' , - `load_case_id` VARCHAR(50) NOT NULL COMMENT '性能用例ID' , - `create_time` BIGINT NOT NULL COMMENT '创建时间' , - `create_user` VARCHAR(50) NOT NULL COMMENT '创建人' , - `test_resource_pool_id` VARCHAR(50) COMMENT '所用测试资源池ID' , - `pos` BIGINT NOT NULL COMMENT '自定义排序,间隔5000' , - `load_configuration` LONGTEXT COMMENT '压力配置' , - `advanced_configuration` TEXT COMMENT '高级配置' , - PRIMARY KEY (id) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_general_ci COMMENT = '测试计划关联性能测试用例'; - - -CREATE INDEX idx_load_case_id ON test_plan_load_case(load_case_id); -CREATE INDEX idx_test_plan_id ON test_plan_load_case(test_plan_id); -CREATE INDEX idx_create_user ON test_plan_load_case(create_user); - -CREATE TABLE IF NOT EXISTS test_plan_function_case( - `id` VARCHAR(50) NOT NULL COMMENT 'ID' , - `test_plan_id` VARCHAR(50) NOT NULL COMMENT '测试计划ID' , - `function_case_id` VARCHAR(50) NOT NULL COMMENT '功能用例ID' , - `create_time` BIGINT NOT NULL COMMENT '创建时间' , - `create_user` VARCHAR(50) NOT NULL COMMENT '创建人' , - `pos` BIGINT NOT NULL COMMENT '自定义排序,间隔5000' , - PRIMARY KEY (id) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_general_ci COMMENT = '测试计划关联功能用例'; - - -CREATE INDEX idx_function_case_id ON test_plan_function_case(function_case_id); -CREATE INDEX idx_test_plan_id ON test_plan_function_case(test_plan_id); -CREATE INDEX idx_create_user ON test_plan_function_case(create_user); - -CREATE TABLE IF NOT EXISTS test_plan_ui_scenario( - `id` VARCHAR(50) NOT NULL COMMENT 'ID' , - `test_plan_id` VARCHAR(50) NOT NULL COMMENT '测试计划ID' , - `ui_scenario_id` VARCHAR(50) NOT NULL COMMENT 'UI场景ID' , - `create_user` VARCHAR(50) NOT NULL COMMENT '创建人' , - `create_time` BIGINT NOT NULL COMMENT '创建时间' , - `pos` BIGINT NOT NULL COMMENT '排序,默认值5000' , - `environment_type` VARCHAR(20) COMMENT '环境类型' , - `environment` LONGTEXT COMMENT '所属环境' , - `environment_group_id` VARCHAR(50) COMMENT '环境组ID' , - PRIMARY KEY (id) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_general_ci COMMENT = '测试计划关联UI场景'; - - -CREATE INDEX idx_ui_scenario_id ON test_plan_ui_scenario(ui_scenario_id); -CREATE INDEX idx_test_plan_id ON test_plan_ui_scenario(test_plan_id); -CREATE INDEX idx_create_user ON test_plan_ui_scenario(create_user); - +CREATE INDEX idx_parent_id ON test_plan (project_id); +CREATE INDEX idx_create_user ON test_plan (create_user); +CREATE INDEX idx_status ON test_plan (status); -- set innodb lock wait timeout to default SET SESSION innodb_lock_wait_timeout = DEFAULT; \ No newline at end of file diff --git a/backend/framework/domain/src/main/resources/migration/3.0.0/dml/V3.0.0_11_1__data.sql b/backend/framework/domain/src/main/resources/migration/3.0.0/dml/V3.0.0_11_1__data.sql index ca4bfe350d..099cdb9640 100644 --- a/backend/framework/domain/src/main/resources/migration/3.0.0/dml/V3.0.0_11_1__data.sql +++ b/backend/framework/domain/src/main/resources/migration/3.0.0/dml/V3.0.0_11_1__data.sql @@ -41,6 +41,8 @@ INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'member', 'SYSTEM_PERSONAL_API_KEY:READ'); INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'member', 'SYSTEM_PERSONAL:READ'); INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'member', 'SYSTEM_PERSONAL:READ+UPDATE'); +INSERT INTO user_role_permission (id, role_id, permission_id) +VALUES (UUID_SHORT(), 'member', 'PROJECT_TEST_PLAN_MODULE:READ'); -- 组织管理员权限 INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'org_admin', 'ORGANIZATION_USER_ROLE:READ'); @@ -188,6 +190,13 @@ INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'CASE_REVIEW:READ+DELETE'); INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'CASE_REVIEW:READ+REVIEW'); INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_admin', 'CASE_REVIEW:READ+RELEVANCE'); +INSERT INTO user_role_permission (id, role_id, permission_id) +VALUES (UUID_SHORT(), 'project_admin', 'PROJECT_TEST_PLAN_MODULE:READ+ADD'); +INSERT INTO user_role_permission (id, role_id, permission_id) +VALUES (UUID_SHORT(), 'project_admin', 'PROJECT_TEST_PLAN_MODULE:READ+UPDATE'); +INSERT INTO user_role_permission (id, role_id, permission_id) +VALUES (UUID_SHORT(), 'project_admin', 'PROJECT_TEST_PLAN_MODULE:READ+DELETE'); + -- 项目成员权限 INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'PROJECT_BASE_INFO:READ'); INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'PROJECT_USER:READ'); @@ -291,6 +300,12 @@ INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'CASE_REVIEW:READ+DELETE'); INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'CASE_REVIEW:READ+REVIEW'); INSERT INTO user_role_permission (id, role_id, permission_id) VALUES (UUID_SHORT(), 'project_member', 'CASE_REVIEW:READ+RELEVANCE'); +INSERT INTO user_role_permission (id, role_id, permission_id) +VALUES (UUID_SHORT(), 'project_member', 'PROJECT_TEST_PLAN_MODULE:READ+ADD'); +INSERT INTO user_role_permission (id, role_id, permission_id) +VALUES (UUID_SHORT(), 'project_member', 'PROJECT_TEST_PLAN_MODULE:READ+UPDATE'); +INSERT INTO user_role_permission (id, role_id, permission_id) +VALUES (UUID_SHORT(), 'project_member', 'PROJECT_TEST_PLAN_MODULE:READ+DELETE'); -- 初始化当前站点配置 INSERT into system_parameter values('base.url', 'http://127.0.0.1:8081', 'text'); -- 初始化prometheus站点配置 diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/PermissionConstants.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/PermissionConstants.java index 06f5055381..d8d2a697bd 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/PermissionConstants.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/PermissionConstants.java @@ -286,4 +286,19 @@ public class PermissionConstants { public static final String SYSTEM_PERSONAL_API_KEY_UPDATE = "SYSTEM_PERSONAL_API_KEY:READ+UPDATE"; public static final String SYSTEM_PERSONAL_READ = "SYSTEM_PERSONAL:READ"; public static final String SYSTEM_PERSONAL_READ_UPDATE = "SYSTEM_PERSONAL:READ+UPDATE"; + /*------ end: PERSONAL_CENTER ------*/ + + //测试计划 + /*------ start: TEST_PLAN ------*/ + public static final String TEST_PLAN_MODULE_READ = "PROJECT_TEST_PLAN_MODULE:READ"; + public static final String TEST_PLAN_MODULE_READ_ADD = "PROJECT_TEST_PLAN_MODULE:READ+ADD"; + public static final String TEST_PLAN_MODULE_READ_UPDATE = "PROJECT_TEST_PLAN_MODULE:READ+UPDATE"; + public static final String TEST_PLAN_MODULE_READ_DELETE = "PROJECT_TEST_PLAN_MODULE:READ+DELETE"; + + public static final String TEST_PLAN_READ = "PROJECT_TEST_PLAN:READ"; + public static final String TEST_PLAN_READ_ADD = "PROJECT_TEST_PLAN:READ+ADD"; + public static final String TEST_PLAN_READ_UPDATE = "PROJECT_TEST_PLAN:READ+UPDATE"; + public static final String TEST_PLAN_READ_DELETE = "PROJECT_TEST_PLAN:READ+DELETE"; + public static final String TEST_PLAN_READ_EXECUTE = "PROJECT_TEST_PLAN:READ+EXECUTE"; + /*------ end: TEST_PLAN ------*/ } diff --git a/backend/framework/sdk/src/main/resources/i18n/commons.properties b/backend/framework/sdk/src/main/resources/i18n/commons.properties index 2fa6379381..9f3a7d29a2 100644 --- a/backend/framework/sdk/src/main/resources/i18n/commons.properties +++ b/backend/framework/sdk/src/main/resources/i18n/commons.properties @@ -395,6 +395,13 @@ name_already_exists_in_module=同层级下已经存在 # bug template copy target_bug_template_not_checked=无法复制,未选中目标项目 source_bug_template_is_empty=复制错误,源项目为空 +#模块相关 +module.id.not_blank=ID不能为空 +module.project_id.not_blank=模块项目ID不能为空 +module.name.not_blank=名称不能为空 +module.not.exist=模块不存在 +module.parent.not.exist=文件模块父节点不存在 +module.root=根目录 #plugin get_plugin_instance_error=获取插件接口实现类错误! @@ -493,4 +500,7 @@ user_key.id.not_blank=ApiKey ID不能为空 expire_time_not_null=过期时间不能为空 permission.organization.name=组织 swagger_parse_error_with_auth=Swagger 解析失败,请确认认证信息是否正确或文件格式是否正确! -swagger_parse_error=Swagger 解析失败,请确认文件格式是否正确! \ No newline at end of file +swagger_parse_error=Swagger 解析失败,请确认文件格式是否正确! +#测试计划 +permission.test_plan.name=测试计划 +permission.test_plan_module.name=测试计划模块 \ No newline at end of file diff --git a/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties b/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties index 7bc3f4fe51..8fb73d3641 100644 --- a/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties +++ b/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties @@ -398,6 +398,13 @@ can_not_move_to_repository_node=Can not move to repository node # bug template copy target_bug_template_not_checked=Cannot copy, target project not checked source_bug_template_is_empty=Copy error, source project is empty +#模块相关 +module.id.not_blank=Id not blank +module.project_id.not_blank=Project Id not blank +module.name.not_blank=Name not blank +module.not.exist=Module not exist +module.parent.not.exist=Parent module not exist +module.root=Root module #plugin get_plugin_instance_error=Get the plugin instance error! @@ -503,4 +510,7 @@ user_key.id.not_blank=User key id can not blank expire_time_not_null=Expire time can not null permission.organization.name=Organization swagger_parse_error_with_auth=Swagger parsing failed, please confirm whether the verification information is correct or the file format is correct! -swagger_parse_error=Swagger parsing failed or file format is incorrect! \ No newline at end of file +swagger_parse_error=Swagger parsing failed or file format is incorrect! +#测试计划 +permission.test_plan.name=Test plan +permission.test_plan_module.name=Test plan module \ No newline at end of file diff --git a/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties b/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties index c9c259dc27..43ede25e3c 100644 --- a/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties +++ b/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties @@ -396,6 +396,13 @@ name_already_exists_in_module=同层级下已经存在 # bug template copy target_bug_template_not_checked=无法复制,未选中目标项目 source_bug_template_is_empty=复制错误,源项目为空 +#模块相关 +module.id.not_blank=ID不能为空 +module.project_id.not_blank=模块项目ID不能为空 +module.name.not_blank=名称不能为空 +module.not.exist=模块不存在 +module.parent.not.exist=文件模块父节点不存在 +module.root=根目录 #plugin get_plugin_instance_error=获取插件接口实现类错误! @@ -499,4 +506,7 @@ user_key.id.not_blank=ApiKey ID不能为空 expire_time_not_null=过期时间不能为空 permission.organization.name=组织 swagger_parse_error_with_auth=Swagger 解析失败,请确认认证信息是否正确或文件格式是否正确! -swagger_parse_error=Swagger 解析失败,请确认文件格式是否正确! \ No newline at end of file +swagger_parse_error=Swagger 解析失败,请确认文件格式是否正确! +#测试计划 +permission.test_plan.name=测试计划 +permission.test_plan_module.name=测试计划模块 \ No newline at end of file diff --git a/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties b/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties index 6b3cd766dd..5a5067eadc 100644 --- a/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties +++ b/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties @@ -395,6 +395,13 @@ name_already_exists_in_module=同層級下已存在 # bug template copy target_bug_template_not_checked=無法複製,未選中目標項目 source_bug_template_is_empty=複製錯誤,源項目為空 +#模块相关 +module.id.not_blank=ID不能為空 +module.project_id.not_blank=模塊項目ID不能為空 +module.name.not_blank=名稱不能為空 +module.not.exist=模塊不存在 +module.parent.not.exist=文件模塊父節點不存在 +module.root=根目錄 #plugin get_plugin_instance_error=獲取插件接口實現類錯誤! @@ -499,4 +506,7 @@ user_key.id.not_blank=ApiKey ID不能为空 expire_time_not_null=過期時間不能為空 permission.organization.name=組織 swagger_parse_error_with_auth=Swagger 解析失敗,請檢查 Swagger 接口是否需要認證 -swagger_parse_error=Swagger 解析失敗 \ No newline at end of file +swagger_parse_error=Swagger 解析失敗 +#測試計劃 +permission.test_plan.name=測試計劃 +permission.test_plan_module.name=測試計劃模塊 \ No newline at end of file diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/service/FileModuleLogService.java b/backend/services/project-management/src/main/java/io/metersphere/project/service/FileModuleLogService.java index d209456cac..93c56097db 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/service/FileModuleLogService.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/service/FileModuleLogService.java @@ -4,7 +4,6 @@ import io.metersphere.project.domain.FileModule; import io.metersphere.project.domain.Project; import io.metersphere.project.dto.NodeSortDTO; import io.metersphere.project.dto.filemanagement.FileRepositoryLog; -import io.metersphere.project.mapper.FileModuleMapper; import io.metersphere.project.mapper.ProjectMapper; import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.util.JSON; @@ -23,8 +22,6 @@ import org.springframework.validation.annotation.Validated; @Service @Transactional(rollbackFor = Exception.class) public class FileModuleLogService { - @Resource - private FileModuleMapper fileModuleMapper; @Resource private ProjectMapper projectMapper; @Resource diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/log/constants/OperationLogModule.java b/backend/services/system-setting/src/main/java/io/metersphere/system/log/constants/OperationLogModule.java index 8500f92471..332abad1ca 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/log/constants/OperationLogModule.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/log/constants/OperationLogModule.java @@ -21,14 +21,7 @@ public class OperationLogModule { public static final String API_DEFINITION = "API_DEFINITION"; public static final String API_DEFINITION_MOCK = "API_DEFINITION_MOCK"; public static final String API_DEFINITION_CASE = "API_DEFINITION_CASE"; - public static final String TRACK_TEST_PLAN = "TRACK_TEST_PLAN"; - public static final String TRACK_TEST_PLAN_SCHEDULE = "TRACK_TEST_PLAN_SCHEDULE"; - public static final String TRACK_BUG = "TRACK_BUG"; - public static final String TRACK_TEST_CASE_REVIEW = "TRACK_TEST_CASE_REVIEW"; - public static final String TRACK_TEST_CASE = "TRACK_TEST_CASE"; - public static final String TRACK_REPORT = "TRACK_REPORT"; public static final String AUTH_TITLE = "AUTH_TITLE"; - public static final String PROJECT_PROJECT_JAR = "PROJECT_PROJECT_JAR"; public static final String PROJECT_ENVIRONMENT_SETTING = "PROJECT_ENVIRONMENT_SETTING"; public static final String PROJECT_PROJECT_MANAGER = "PROJECT_PROJECT_MANAGER"; public static final String PROJECT_FILE_MANAGEMENT = "PROJECT_MANAGEMENT_FILE_MANAGEMENT"; @@ -39,15 +32,9 @@ public class OperationLogModule { public static final String PERSONAL_INFORMATION_LOCAL_CONFIG = "PERSONAL_INFORMATION_LOCAL_CONFIG"; public static final String GROUP_PERMISSION = "GROUP_PERMISSION"; public static final String PERFORMANCE_TEST_REPORT = "PERFORMANCE_TEST_REPORT"; - public static final String PERFORMANCE_TEST = "PERFORMANCE_TEST"; - public static final String ERROR_REPORT_LIBRARY = "ERROR_REPORT_LIBRARY"; public static final String SYSTEM_QUOTA_MANAGEMENT = "SYSTEM_QUOTA_MANAGEMENT"; public static final String ENTERPRISE_TEST_REPORT = "ENTERPRISE_TEST_REPORT"; public static final String SYSTEM_AUTHORIZATION_MANAGEMENT = "SYSTEM_AUTHORIZATION_MANAGEMENT"; - public static final String UI_ELEMENT = "UI_ELEMENT"; - public static final String UI_AUTOMATION = "UI_AUTOMATION"; - public static final String UI_AUTOMATION_REPORT = "UI_AUTOMATION_REPORT"; - public static final String UI_AUTOMATION_SCHEDULE = "UI_AUTOMATION_SCHEDULE"; public static final String SYSTEM_PROJECT = "SYSTEM_PROJECT"; public static final String SYSTEM_PROJECT_MEMBER = "SYSTEM_PROJECT_MEMBER"; public static final String ORGANIZATION_PROJECT = "ORGANIZATION_PROJECT"; @@ -113,4 +100,7 @@ public class OperationLogModule { public static final String API_DEFINITION_ENVIRONMENT = "API_DEFINITION_ENVIRONMENT"; // 缺陷管理 public static final String BUG_MANAGEMENT = "BUG_MANAGEMENT"; + //测试计划 + public static final String TEST_PLAN = "TEST_PLAN"; + public static final String TEST_PLAN_MODULE = "TEST_PLAN_MODULE"; } diff --git a/backend/services/test-plan/pom.xml b/backend/services/test-plan/pom.xml index 5850900c7c..598fb2c3c6 100644 --- a/backend/services/test-plan/pom.xml +++ b/backend/services/test-plan/pom.xml @@ -28,6 +28,14 @@ metersphere-project-management ${revision} + + io.metersphere + metersphere-system-setting + ${revision} + tests + test-jar + test + @@ -40,7 +48,7 @@ true true - src/main/resources/apiGeneratorConfig.xml + src/main/resources/testPlanGeneratorConfig.xml diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanModuleController.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanModuleController.java new file mode 100644 index 0000000000..d2bf8eeea4 --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanModuleController.java @@ -0,0 +1,69 @@ +package io.metersphere.plan.controller; + +import io.metersphere.plan.dto.request.TestPlanModuleCreateRequest; +import io.metersphere.plan.dto.request.TestPlanModuleUpdateRequest; +import io.metersphere.plan.service.TestPlanModuleService; +import io.metersphere.sdk.constants.HttpMethodConstants; +import io.metersphere.sdk.constants.PermissionConstants; +import io.metersphere.system.dto.sdk.BaseTreeNode; +import io.metersphere.system.dto.sdk.request.NodeMoveRequest; +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.*; + +import java.util.List; + +@Tag(name = "测试计划管理-模块树") +@RestController +@RequestMapping("/test-plan/module") +public class TestPlanModuleController { + + @Resource + private TestPlanModuleService testPlanModuleService; + + @GetMapping("/tree/{projectId}") + @Operation(summary = "测试计划管理-模块树-查找模块") + @RequiresPermissions(PermissionConstants.TEST_PLAN_MODULE_READ) + @CheckOwner(resourceId = "#projectId", resourceType = "project") + public List getTree(@PathVariable String projectId) { + return testPlanModuleService.getTree(projectId); + } + + @PostMapping("/add") + @Operation(summary = "测试计划管理-模块树-添加模块") + @RequiresPermissions(PermissionConstants.TEST_PLAN_MODULE_READ_ADD) + @CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project") + public String add(@RequestBody @Validated TestPlanModuleCreateRequest request) { + return testPlanModuleService.add(request, SessionUtils.getUserId(),"/test-plan/module/add", HttpMethodConstants.POST.name()); + } + + @PostMapping("/update") + @Operation(summary = "测试计划管理-模块树-修改模块") + @RequiresPermissions(PermissionConstants.TEST_PLAN_MODULE_READ_UPDATE) + @CheckOwner(resourceId = "#request.getId()", resourceType = "file_module") + public boolean list(@RequestBody @Validated TestPlanModuleUpdateRequest request) { + testPlanModuleService.update(request, SessionUtils.getUserId(),"/test-plan/module/update", HttpMethodConstants.POST.name()); + return true; + } + + @GetMapping("/delete/{deleteId}") + @Operation(summary = "测试计划管理-模块树-删除模块") + @RequiresPermissions(PermissionConstants.TEST_PLAN_MODULE_READ_DELETE) + @CheckOwner(resourceId = "#deleteId", resourceType = "file_module") + public void deleteNode(@PathVariable String deleteId) { + testPlanModuleService.deleteModule(deleteId, SessionUtils.getUserId(),"/test-plan/module/delete",HttpMethodConstants.GET.name()); + } + + @PostMapping("/move") + @Operation(summary = "测试计划管理-模块树-移动模块") + @RequiresPermissions(PermissionConstants.TEST_PLAN_MODULE_READ_UPDATE) + @CheckOwner(resourceId = "#request.getDragNodeId()", resourceType = "test_plan_module") + public void moveNode(@Validated @RequestBody NodeMoveRequest request) { + testPlanModuleService.moveNode(request, SessionUtils.getUserId(),"/test-plan/module/move",HttpMethodConstants.POST.name()); + } +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanModuleCreateRequest.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanModuleCreateRequest.java new file mode 100644 index 0000000000..ef549d7ada --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanModuleCreateRequest.java @@ -0,0 +1,23 @@ +package io.metersphere.plan.dto.request; + +import io.metersphere.sdk.constants.ModuleConstants; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Data +public class TestPlanModuleCreateRequest { + @Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{project.id.not_blank}") + private String projectId; + + @Schema(description = "模块名称", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "{module.name.not_blank}") + private String name; + + @Schema(description = "父模块ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "{parent.node.not_blank}") + private String parentId = ModuleConstants.ROOT_NODE_PARENT_ID; +} + diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanModuleUpdateRequest.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanModuleUpdateRequest.java new file mode 100644 index 0000000000..fc73c9296d --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanModuleUpdateRequest.java @@ -0,0 +1,18 @@ +package io.metersphere.plan.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Data +public class TestPlanModuleUpdateRequest { + @Schema(description = "模块ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{module.id.not_blank}") + private String id; + + @Schema(description = "模块名称", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "{module.name.not_blank}") + private String name; +} + diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanModuleMapper.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanModuleMapper.java new file mode 100644 index 0000000000..d53a83175d --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanModuleMapper.java @@ -0,0 +1,28 @@ +package io.metersphere.plan.mapper; + +import io.metersphere.project.dto.NodeSortQueryParam; +import io.metersphere.system.dto.sdk.BaseModule; +import io.metersphere.system.dto.sdk.BaseTreeNode; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ExtTestPlanModuleMapper { + List selectBaseByProjectId(String projectId); + + List selectIdAndParentIdByProjectId(String projectId); + + Long getMaxPosByParentId(String parentId); + + List selectChildrenIdsByParentIds(@Param("ids") List strings); + + void deleteByIds(@Param("ids") List deleteId); + + BaseModule selectBaseModuleById(String dragNodeId); + + BaseModule selectModuleByParentIdAndPosOperator(NodeSortQueryParam nodeSortQueryParam); + + List selectIdsByProjectId(String projectId); + + List selectChildrenIdsSortByPos(String parentId); +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanModuleMapper.xml b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanModuleMapper.xml new file mode 100644 index 0000000000..0ee559ed4d --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanModuleMapper.xml @@ -0,0 +1,66 @@ + + + + + DELETE FROM test_plan_module WHERE id IN + + #{id} + + + + + + + + + + + \ No newline at end of file diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanModuleLogService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanModuleLogService.java new file mode 100644 index 0000000000..c6229048ae --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanModuleLogService.java @@ -0,0 +1,115 @@ +package io.metersphere.plan.service; + +import io.metersphere.project.domain.Project; +import io.metersphere.project.dto.NodeSortDTO; +import io.metersphere.project.mapper.ProjectMapper; +import io.metersphere.sdk.util.JSON; +import io.metersphere.sdk.util.Translator; +import io.metersphere.system.domain.TestPlanModule; +import io.metersphere.system.dto.builder.LogDTOBuilder; +import io.metersphere.system.dto.sdk.BaseModule; +import io.metersphere.system.log.constants.OperationLogModule; +import io.metersphere.system.log.constants.OperationLogType; +import io.metersphere.system.log.dto.LogDTO; +import io.metersphere.system.log.service.OperationLogService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +@Service +@Transactional(rollbackFor = Exception.class) +public class TestPlanModuleLogService { + + private String logModule = OperationLogModule.TEST_PLAN_MODULE; + + @Resource + private ProjectMapper projectMapper; + @Resource + private OperationLogService operationLogService; + + public void saveAddLog(TestPlanModule module, String operator, String requestUrl, String requestMethod) { + Project project = projectMapper.selectByPrimaryKey(module.getProjectId()); + LogDTO dto = LogDTOBuilder.builder() + .projectId(module.getProjectId()) + .organizationId(project.getOrganizationId()) + .type(OperationLogType.ADD.name()) + .module(logModule) + .method(requestMethod) + .path(requestUrl) + .sourceId(module.getId()) + .content(module.getName()) + .originalValue(JSON.toJSONBytes(module)) + .createUser(operator) + .build().getLogDTO(); + operationLogService.add(dto); + } + + public void saveUpdateLog(TestPlanModule oldModule, TestPlanModule newModule, String projectId, String operator, String requestUrl, String requestMethod) { + Project project = projectMapper.selectByPrimaryKey(projectId); + LogDTO dto = LogDTOBuilder.builder() + .projectId(projectId) + .organizationId(project.getOrganizationId()) + .type(OperationLogType.UPDATE.name()) + .module(logModule) + .method(requestMethod) + .path(requestUrl) + .sourceId(newModule.getId()) + .content(newModule.getName()) + .originalValue(JSON.toJSONBytes(oldModule)) + .modifiedValue(JSON.toJSONBytes(newModule)) + .createUser(operator) + .build().getLogDTO(); + operationLogService.add(dto); + } + + public void saveDeleteLog(TestPlanModule deleteModule, String operator, String requestUrl, String requestMethod) { + Project project = projectMapper.selectByPrimaryKey(deleteModule.getProjectId()); + LogDTO dto = LogDTOBuilder.builder() + .projectId(deleteModule.getProjectId()) + .organizationId(project.getOrganizationId()) + .type(OperationLogType.DELETE.name()) + .module(logModule) + .method(requestMethod) + .path(requestUrl) + .sourceId(deleteModule.getId()) + .content(deleteModule.getName() + " " + Translator.get("file.log.delete_module")) + .originalValue(JSON.toJSONBytes(deleteModule)) + .createUser(operator) + .build().getLogDTO(); + operationLogService.add(dto); + } + + public void saveMoveLog(@Validated NodeSortDTO request, String operator, String requestUrl, String requestMethod) { + BaseModule moveNode = request.getNode(); + BaseModule previousNode = request.getPreviousNode(); + BaseModule nextNode = request.getNextNode(); + BaseModule parentModule = request.getParent(); + + Project project = projectMapper.selectByPrimaryKey(moveNode.getProjectId()); + String logContent; + if (nextNode == null && previousNode == null) { + logContent = moveNode.getName() + " " + Translator.get("file.log.move_to") + parentModule.getName(); + } else if (nextNode == null) { + logContent = moveNode.getName() + " " + Translator.get("file.log.move_to") + parentModule.getName() + " " + previousNode.getName() + Translator.get("file.log.next"); + } else if (previousNode == null) { + logContent = moveNode.getName() + " " + Translator.get("file.log.move_to") + parentModule.getName() + " " + nextNode.getName() + Translator.get("file.log.previous"); + } else { + logContent = moveNode.getName() + " " + Translator.get("file.log.move_to") + parentModule.getName() + " " + + previousNode.getName() + Translator.get("file.log.next") + " " + nextNode.getName() + Translator.get("file.log.previous"); + } + LogDTO dto = LogDTOBuilder.builder() + .projectId(moveNode.getProjectId()) + .organizationId(project.getOrganizationId()) + .type(OperationLogType.UPDATE.name()) + .module(logModule) + .method(requestMethod) + .path(requestUrl) + .sourceId(moveNode.getId()) + .content(logContent) + .createUser(operator) + .build().getLogDTO(); + operationLogService.add(dto); + } + +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanModuleService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanModuleService.java new file mode 100644 index 0000000000..0ccbc0036f --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanModuleService.java @@ -0,0 +1,250 @@ +package io.metersphere.plan.service; + +import io.metersphere.plan.dto.request.TestPlanModuleCreateRequest; +import io.metersphere.plan.dto.request.TestPlanModuleUpdateRequest; +import io.metersphere.plan.mapper.ExtTestPlanModuleMapper; +import io.metersphere.project.dto.ModuleCountDTO; +import io.metersphere.project.dto.NodeSortDTO; +import io.metersphere.project.service.ModuleTreeService; +import io.metersphere.sdk.constants.ModuleConstants; +import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.util.Translator; +import io.metersphere.system.domain.TestPlanModule; +import io.metersphere.system.domain.TestPlanModuleExample; +import io.metersphere.system.dto.sdk.BaseTreeNode; +import io.metersphere.system.dto.sdk.request.NodeMoveRequest; +import io.metersphere.system.mapper.TestPlanModuleMapper; +import io.metersphere.system.service.CleanupProjectResourceService; +import io.metersphere.system.uid.IDGenerator; +import jakarta.annotation.Resource; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@Transactional(rollbackFor = Exception.class) +public class TestPlanModuleService extends ModuleTreeService implements CleanupProjectResourceService { + @Resource + private TestPlanModuleMapper testPlanModuleMapper; + @Resource + private ExtTestPlanModuleMapper extTestPlanModuleMapper; + @Resource + protected SqlSessionFactory sqlSessionFactory; + @Resource + private TestPlanModuleLogService testPlanModuleLogService; + + public List getTree(String projectId) { + List fileModuleList = extTestPlanModuleMapper.selectBaseByProjectId(projectId); + return super.buildTreeAndCountResource(fileModuleList, true, Translator.get("default.module")); + } + + public List getTreeOnlyIdsAndResourceCount(String projectId, List moduleCountDTOList) { + //节点内容只有Id和parentId + List fileModuleList = extTestPlanModuleMapper.selectIdAndParentIdByProjectId(projectId); + return super.buildTreeAndCountResource(fileModuleList, moduleCountDTOList, true, Translator.get("default.module")); + } + + public String add(TestPlanModuleCreateRequest request, String operator, String requestUrl, String requestMethod) { + TestPlanModule testPlanModule = new TestPlanModule(); + testPlanModule.setId(IDGenerator.nextStr()); + testPlanModule.setName(request.getName()); + testPlanModule.setParentId(request.getParentId()); + testPlanModule.setProjectId(request.getProjectId()); + this.checkDataValidity(testPlanModule); + testPlanModule.setCreateTime(System.currentTimeMillis()); + testPlanModule.setUpdateTime(testPlanModule.getCreateTime()); + testPlanModule.setPos(this.countPos(request.getParentId())); + testPlanModule.setCreateUser(operator); + testPlanModule.setUpdateUser(operator); + testPlanModuleMapper.insert(testPlanModule); + //记录日志 + testPlanModuleLogService.saveAddLog(testPlanModule, operator,requestUrl,requestMethod); + return testPlanModule.getId(); + } + + protected Long countPos(String parentId) { + Long maxPos = extTestPlanModuleMapper.getMaxPosByParentId(parentId); + if (maxPos == null) { + return LIMIT_POS; + } else { + return maxPos + LIMIT_POS; + } + } + + /** + * 检查数据的合法性 + */ + protected void checkDataValidity(TestPlanModule module) { + TestPlanModuleExample example = new TestPlanModuleExample(); + if (!StringUtils.equalsIgnoreCase(module.getParentId(), ModuleConstants.ROOT_NODE_PARENT_ID)) { + //检查父ID是否存在 + example.createCriteria().andIdEqualTo(module.getParentId()); + if (testPlanModuleMapper.countByExample(example) == 0) { + throw new MSException(Translator.get("parent.node.not_blank")); + } + example.clear(); + if (StringUtils.isNotBlank(module.getProjectId())) { + //检查项目ID是否和父节点ID一致 + example.createCriteria().andProjectIdEqualTo(module.getProjectId()).andIdEqualTo(module.getParentId()); + if (testPlanModuleMapper.countByExample(example) == 0) { + throw new MSException(Translator.get("project.cannot.match.parent")); + } + example.clear(); + } + } + example.createCriteria().andParentIdEqualTo(module.getParentId()).andNameEqualTo(module.getName()).andIdNotEqualTo(module.getId()); + if (testPlanModuleMapper.countByExample(example) > 0) { + throw new MSException(Translator.get("node.name.repeat")); + } + 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) { + TestPlanModule module = testPlanModuleMapper.selectByPrimaryKey(request.getId()); + if (module == null) { + throw new MSException("file_module.not.exist"); + } + TestPlanModule updateModule = new TestPlanModule(); + updateModule.setId(request.getId()); + updateModule.setName(request.getName().trim()); + updateModule.setParentId(module.getParentId()); + this.checkDataValidity(updateModule); + updateModule.setUpdateTime(System.currentTimeMillis()); + updateModule.setUpdateUser(userId); + testPlanModuleMapper.updateByPrimaryKeySelective(updateModule); + TestPlanModule newModule = testPlanModuleMapper.selectByPrimaryKey(request.getId()); + //记录日志 + testPlanModuleLogService.saveUpdateLog(module, newModule, module.getProjectId(), userId,requestUrl,requestMethod); + } + + + public void deleteModule(String deleteId, String currentUser,String requestUrl,String requestMethod) { + TestPlanModule deleteModule = testPlanModuleMapper.selectByPrimaryKey(deleteId); + if (deleteModule != null) { + this.deleteModule(Collections.singletonList(deleteId)); + //记录日志 + testPlanModuleLogService.saveDeleteLog(deleteModule, currentUser,requestUrl,requestMethod); + } + } + public void deleteModule(List deleteIds) { + if (CollectionUtils.isEmpty(deleteIds)) { + return; + } + extTestPlanModuleMapper.deleteByIds(deleteIds); + + //todo:删除测试计划 + + List childrenIds = extTestPlanModuleMapper.selectChildrenIdsByParentIds(deleteIds); + if (CollectionUtils.isNotEmpty(childrenIds)) { + deleteModule(childrenIds); + } + } + + public void moveNode(NodeMoveRequest request, String currentUser,String requestUrl,String requestMethod) { + + NodeSortDTO nodeSortDTO = super.getNodeSortDTO(request, + extTestPlanModuleMapper::selectBaseModuleById, + extTestPlanModuleMapper::selectModuleByParentIdAndPosOperator); + + TestPlanModuleExample example = new TestPlanModuleExample(); + example.createCriteria().andParentIdEqualTo(nodeSortDTO.getParent().getId()).andIdEqualTo(request.getDragNodeId()); + //节点换到了别的节点下,要先更新parent节点再计算sort + if (testPlanModuleMapper.countByExample(example) == 0) { + TestPlanModule module = new TestPlanModule(); + module.setId(request.getDragNodeId()); + module.setParentId(nodeSortDTO.getParent().getId()); + testPlanModuleMapper.updateByPrimaryKeySelective(module); + } + super.sort(nodeSortDTO); + //记录日志 + testPlanModuleLogService.saveMoveLog(nodeSortDTO, currentUser,requestUrl,requestMethod); + } + + /** + * 查找当前项目下模块每个节点对应的资源统计 + * + */ + public Map getModuleCountMap(String projectId, List moduleCountDTOList) { + + //构建模块树,并计算每个节点下的所有数量(包含子节点) + List treeNodeList = this.getTreeOnlyIdsAndResourceCount(projectId, moduleCountDTOList); + //通过广度遍历的方式构建返回值 + return super.getIdCountMapByBreadth(treeNodeList); + } + + + @Override + public void updatePos(String id, long pos) { + TestPlanModule updateModule = new TestPlanModule(); + updateModule.setPos(pos); + updateModule.setId(id); + testPlanModuleMapper.updateByPrimaryKeySelective(updateModule); + } + + @Override + public void refreshPos(String parentId) { + List childrenIdSortByPos = extTestPlanModuleMapper.selectChildrenIdsSortByPos(parentId); + SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); + TestPlanModuleMapper batchUpdateMapper = sqlSession.getMapper(TestPlanModuleMapper.class); + for (int i = 0; i < childrenIdSortByPos.size(); i++) { + String nodeId = childrenIdSortByPos.get(i); + TestPlanModule updateModule = new TestPlanModule(); + updateModule.setId(nodeId); + updateModule.setPos((i + 1) * LIMIT_POS); + batchUpdateMapper.updateByPrimaryKeySelective(updateModule); + } + sqlSession.flushStatements(); + SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); + } + + @Override + public void deleteResources(String projectId) { + List fileModuleIdList = extTestPlanModuleMapper.selectIdsByProjectId(projectId); + if (CollectionUtils.isNotEmpty(fileModuleIdList)) { + this.deleteModule(fileModuleIdList); + } + } + + @Override + public void cleanReportResources(String projectId) { + // nothing to do + } + + public Map getModuleNameMapByIds(List moduleIds) { + if (CollectionUtils.isEmpty(moduleIds)) { + return new HashMap<>(); + } else { + TestPlanModuleExample example = new TestPlanModuleExample(); + example.createCriteria().andIdIn(moduleIds); + List moduleList = testPlanModuleMapper.selectByExample(example); + return moduleList.stream().collect(Collectors.toMap(TestPlanModule::getId, TestPlanModule::getName)); + } + } + +} diff --git a/backend/services/test-plan/src/main/resources/permission.json b/backend/services/test-plan/src/main/resources/permission.json new file mode 100644 index 0000000000..60a2e6254d --- /dev/null +++ b/backend/services/test-plan/src/main/resources/permission.json @@ -0,0 +1,48 @@ +[ + { + "id": "TEST_PLAN", + "name": "permission.test_plan.name", + "type": "TEST_PLAN", + "children": [ + { + "id": "TEST_PLAN_MODULE", + "name": "permission.test_plan_module.name", + "permissions": [ + { + "id": "PROJECT_TEST_PLAN_MODULE:READ" + }, + { + "id": "PROJECT_TEST_PLAN_MODULE:READ+ADD" + }, + { + "id": "PROJECT_TEST_PLAN_MODULE:READ+UPDATE" + }, + { + "id": "PROJECT_TEST_PLAN_MODULE:READ+DELETE" + } + ] + }, + { + "id": "TEST_PLAN", + "name": "permission.test_plan.name", + "permissions": [ + { + "id": "PROJECT_TEST_PLAN:READ" + }, + { + "id": "PROJECT_TEST_PLAN:READ+ADD" + }, + { + "id": "PROJECT_TEST_PLAN:READ+UPDATE" + }, + { + "id": "PROJECT_TEST_PLAN:READ+DELETE" + }, + { + "id": "PROJECT_TEST_PLAN:READ+EXECUTE" + } + ] + } + ] + } +] diff --git a/backend/services/test-plan/src/main/resources/testPlanGeneratorConfig.xml b/backend/services/test-plan/src/main/resources/testPlanGeneratorConfig.xml new file mode 100644 index 0000000000..87bddf678c --- /dev/null +++ b/backend/services/test-plan/src/main/resources/testPlanGeneratorConfig.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanControllerTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanControllerTests.java deleted file mode 100644 index 66196f65f0..0000000000 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanControllerTests.java +++ /dev/null @@ -1,279 +0,0 @@ -package io.metersphere.plan.controller; - -import com.jayway.jsonpath.JsonPath; -import io.metersphere.plan.domain.TestPlan; -import io.metersphere.plan.dto.TestPlanDTO; -import io.metersphere.sdk.constants.SessionConstants; -import io.metersphere.sdk.util.JSON; -import io.metersphere.system.controller.handler.ResultHolder; -import io.metersphere.system.uid.IDGenerator; -import jakarta.annotation.Resource; -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.*; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import java.util.ArrayList; -import java.util.List; - -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - - -@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class TestPlanControllerTests { - @Resource - private MockMvc mockMvc; - - private static String sessionId; - private static String csrfToken; - - static List SAVED_TEST_PLAN_DTO_LIST = new ArrayList<>(); - - static final String STATIC_UUID = IDGenerator.nextStr(); - - private TestPlanDTO getSimpleTestPlan() { - TestPlanDTO testPlan = new TestPlanDTO(); - testPlan.setName("test"); - testPlan.setProjectId("test-project-id"); - testPlan.setParentId("root"); - testPlan.setCreateUser("JianGuo"); - testPlan.setStage("Smock"); - testPlan.setStatus("PREPARE"); - testPlan.setCreateUser("JianGuo"); - return testPlan; - } - - private void isPreDataOk() throws Exception { - if (SAVED_TEST_PLAN_DTO_LIST.isEmpty()) { - this.testAddSuccess(); - } - } - - private void addTestPlanToSavedList(MockHttpServletResponse mockResponse) throws Exception { - - String returnData = mockResponse.getContentAsString(); - ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class); - - //返回请求正常 - Assertions.assertNotNull(resultHolder); - - TestPlanDTO testPlanDTO = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), TestPlanDTO.class); - - //返回值不为空 - Assertions.assertNotNull(testPlanDTO); - - SAVED_TEST_PLAN_DTO_LIST.add(testPlanDTO); - } - - @BeforeEach - public void login() throws Exception { - if (StringUtils.isAnyBlank(sessionId, csrfToken)) { - MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/login") - .content("{\"username\":\"admin\",\"password\":\"metersphere\"}") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andReturn(); - sessionId = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.sessionId"); - csrfToken = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.data.csrfToken"); - } - } - - @Test - @Order(1) - public void testAddSuccess() throws Exception { - //测试有责任人、关注人 - TestPlanDTO testPlan = this.getSimpleTestPlan(); - - List followerList = new ArrayList<>(); - followerList.add("JianGuo"); - followerList.add("SongGuoyu"); - followerList.add("SongYingyu"); - followerList.add("SongFanti"); - testPlan.setFollowers(followerList); - - List participantList = new ArrayList<>(); - participantList.add("JianGuo"); - participantList.add("SongGuoyu"); - testPlan.setPrincipals(participantList); - - MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(testPlan)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andReturn(); - this.addTestPlanToSavedList(mvcResult.getResponse()); - - //测试自动赋予了UUID - testPlan = this.getSimpleTestPlan(); - testPlan.setId(STATIC_UUID); - mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(testPlan)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andReturn(); - this.addTestPlanToSavedList(mvcResult.getResponse()); - - //测试创建子计划 - testPlan = this.getSimpleTestPlan(); - followerList = new ArrayList<>(); - followerList.add("JianGuo"); - followerList.add("SongGuoyu"); - followerList.add("SongYingyu"); - followerList.add("SongFanti"); - testPlan.setFollowers(followerList); - - participantList = new ArrayList<>(); - participantList.add("JianGuo"); - participantList.add("SongGuoyu"); - testPlan.setPrincipals(participantList); - testPlan.setParentId(SAVED_TEST_PLAN_DTO_LIST.get(0).getId()); - mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(testPlan)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andReturn(); - this.addTestPlanToSavedList(mvcResult.getResponse()); - - //测试只有关注人 - testPlan = this.getSimpleTestPlan(); - followerList = new ArrayList<>(); - followerList.add("JianGuo"); - followerList.add("SongGuoyu"); - followerList.add("SongYingyu"); - followerList.add("SongFanti"); - testPlan.setFollowers(followerList); - mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(testPlan)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andReturn(); - this.addTestPlanToSavedList(mvcResult.getResponse()); - - //测试只有责任人 - testPlan = this.getSimpleTestPlan(); - participantList = new ArrayList<>(); - participantList.add("JianGuo"); - participantList.add("SongGuoyu"); - testPlan.setPrincipals(participantList); - mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(testPlan)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andReturn(); - this.addTestPlanToSavedList(mvcResult.getResponse()); - - //测试没有责任人没有关注人 - testPlan = this.getSimpleTestPlan(); - mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(testPlan)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andReturn(); - this.addTestPlanToSavedList(mvcResult.getResponse()); - } - - @Test - @Order(2) - public void testDeleteSuccess() throws Exception { - this.isPreDataOk(); - - String testPlanId = SAVED_TEST_PLAN_DTO_LIST.get(SAVED_TEST_PLAN_DTO_LIST.size() - 1).getId(); - mockMvc.perform(MockMvcRequestBuilders.get("/test-plan/delete/" + testPlanId) - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)); - } - - @Test - @Order(3) - public void testDeleteBatchSuccess() throws Exception { - this.isPreDataOk(); - - List testPlanIdList = new ArrayList<>(); - testPlanIdList.add(SAVED_TEST_PLAN_DTO_LIST.get(0).getId()); - mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/delete/batch") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(testPlanIdList)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - } - - @Test - public void testBatchDelete_Error_System() throws Exception { - //没有必填项 - List testPlanIdList = new ArrayList<>(); - mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/delete/batch") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(testPlanIdList)) - .contentType(MediaType.APPLICATION_JSON) - ).andExpect(status().is5xxServerError()); - } - - //添加测试计划反例校验:参数不合法 - @Test - public void testAdd_Error_Param() throws Exception { - //没有必填项 - TestPlan testPlan = new TestPlan(); - testPlan.setName("test"); - - mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(testPlan)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)); - - } - - //添加测试计划反例校验:系统错误(例如ID重复、parentId不合法) - @Test - public void testAdd_Error_System() throws Exception { - this.isPreDataOk(); - //测试重复存储UUID不成功 - TestPlanDTO testPlan = this.getSimpleTestPlan(); - testPlan.setId(STATIC_UUID); - mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(testPlan)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().is5xxServerError()); - - //测试parentId和id相同 - testPlan = this.getSimpleTestPlan(); - testPlan.setId(IDGenerator.nextStr()); - testPlan.setParentId(testPlan.getId()); - mockMvc.perform(MockMvcRequestBuilders.post("/test-plan/add") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(testPlan)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().is5xxServerError()); - } -} diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java new file mode 100644 index 0000000000..24a48bfd15 --- /dev/null +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java @@ -0,0 +1,794 @@ +package io.metersphere.plan.controller; + +import io.metersphere.plan.service.TestPlanModuleService; +import io.metersphere.plan.utils.TestPlanUtils; +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.ModuleConstants; +import io.metersphere.sdk.constants.PermissionConstants; +import io.metersphere.sdk.constants.SessionConstants; +import io.metersphere.sdk.util.JSON; +import io.metersphere.system.base.BaseTest; +import io.metersphere.system.controller.handler.ResultHolder; +import io.metersphere.system.domain.TestPlanModule; +import io.metersphere.system.domain.TestPlanModuleExample; +import io.metersphere.system.dto.AddProjectRequest; +import io.metersphere.system.dto.sdk.BaseTreeNode; +import io.metersphere.system.dto.sdk.request.NodeMoveRequest; +import io.metersphere.system.log.constants.OperationLogModule; +import io.metersphere.system.log.constants.OperationLogType; +import io.metersphere.system.mapper.TestPlanModuleMapper; +import io.metersphere.system.service.CommonProjectService; +import io.metersphere.system.uid.IDGenerator; +import jakarta.annotation.Resource; +import lombok.Data; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.*; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@AutoConfigureMockMvc +public class TestPlanTests extends BaseTest { + private static Project project; + + private static List preliminaryTreeNodes = new ArrayList<>(); + + @Resource + private CommonProjectService commonProjectService; + @Resource + private TestPlanModuleMapper testPlanModuleMapper; + @Resource + private TestPlanModuleService testPlanModuleService; + + private static final List LOG_CHECK_LIST = new ArrayList<>(); + + private static final String URL_GET_MODULE_TREE = "/test-plan/module/tree/%s"; + private static final String URL_GET_MODULE_DELETE = "/test-plan/module/delete/%s"; + private static final String URL_POST_MODULE_ADD = "/test-plan/module/add"; + private static final String URL_POST_MODULE_UPDATE = "/test-plan/module/update"; + private static final String URL_POST_MODULE_MOVE = "/test-plan/module/move"; + + @BeforeEach + public void initTestData() { + //文件管理专用项目 + if (project == null) { + AddProjectRequest initProject = new AddProjectRequest(); + initProject.setOrganizationId("100001"); + initProject.setName("文件管理专用项目"); + initProject.setDescription("建国创建的文件管理专用项目"); + initProject.setEnable(true); + project = commonProjectService.add(initProject, "admin", "/organization-project/add", OperationLogModule.SETTING_ORGANIZATION_PROJECT); + } + } + + @Test + @Order(1) + public void emptyDataTest() throws Exception { + //空数据下,检查模块树 + List treeNodes = this.getFileModuleTreeNode(); + //检查有没有默认节点 + boolean hasNode = false; + for (BaseTreeNode baseTreeNode : treeNodes) { + if (StringUtils.equals(baseTreeNode.getId(), ModuleConstants.DEFAULT_NODE_ID)) { + hasNode = true; + } + Assertions.assertNotNull(baseTreeNode.getParentId()); + } + Assertions.assertTrue(hasNode); + + /* + todo 查询测试计划列表 + FileMetadataTableRequest request = new FileMetadataTableRequest() {{ + this.setCurrent(1); + this.setPageSize(10); + this.setProjectId(project.getId()); + }}; + MvcResult pageResult = this.requestPostWithOkAndReturn(FileManagementRequestUtils.URL_FILE_PAGE, request); + String returnData = pageResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class); + Pager> result = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class); + //返回值的页码和当前页码相同 + Assertions.assertEquals(result.getCurrent(), request.getCurrent()); + //返回的数据量不超过规定要返回的数据量相同 + Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(result.getList())).size() <= request.getPageSize()); + */ + //判断权限 + this.requestGetPermissionTest(PermissionConstants.TEST_PLAN_MODULE_READ, String.format(URL_GET_MODULE_TREE, this.DEFAULT_PROJECT_ID)); + } + + @Test + @Order(2) + public void addModuleTest() throws Exception { + //根目录下创建节点(a1) + FileModuleCreateRequest request = new FileModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1"); + MvcResult mvcResult = this.requestPostWithOkAndReturn(URL_POST_MODULE_ADD, request); + String returnId = mvcResult.getResponse().getContentAsString(); + Assertions.assertNotNull(returnId); + List treeNodes = this.getFileModuleTreeNode(); + BaseTreeNode a1Node = null; + for (BaseTreeNode baseTreeNode : treeNodes) { + if (StringUtils.equals(baseTreeNode.getName(), request.getName())) { + a1Node = baseTreeNode; + } + Assertions.assertNotNull(baseTreeNode.getParentId()); + } + Assertions.assertNotNull(a1Node); + LOG_CHECK_LIST.add( + new CheckLogModel(a1Node.getId(), OperationLogType.ADD, URL_POST_MODULE_ADD) + ); + + //根目录下创建节点a2和a3,在a1下创建子节点a1-b1 + request = new FileModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a2"); + this.requestPostWithOkAndReturn(URL_POST_MODULE_ADD, request); + + request.setName("a3"); + this.requestPostWithOkAndReturn(URL_POST_MODULE_ADD, request); + + request = new FileModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1-b1"); + request.setParentId(a1Node.getId()); + this.requestPostWithOkAndReturn(URL_POST_MODULE_ADD, request); + + treeNodes = this.getFileModuleTreeNode(); + BaseTreeNode a1b1Node = null; + BaseTreeNode a2Node = null; + for (BaseTreeNode baseTreeNode : treeNodes) { + Assertions.assertNotNull(baseTreeNode.getParentId()); + if (StringUtils.equals(baseTreeNode.getName(), "a1") && CollectionUtils.isNotEmpty(baseTreeNode.getChildren())) { + for (BaseTreeNode childNode : baseTreeNode.getChildren()) { + if (StringUtils.equals(childNode.getName(), "a1-b1")) { + a1b1Node = childNode; + } + Assertions.assertNotNull(childNode.getParentId()); + } + } else if (StringUtils.equals(baseTreeNode.getName(), "a2")) { + a2Node = baseTreeNode; + } + } + Assertions.assertNotNull(a2Node); + Assertions.assertNotNull(a1b1Node); + + LOG_CHECK_LIST.add( + new CheckLogModel(a2Node.getId(), OperationLogType.ADD, URL_POST_MODULE_ADD) + ); + LOG_CHECK_LIST.add( + new CheckLogModel(a1b1Node.getId(), OperationLogType.ADD, URL_POST_MODULE_ADD) + ); + + //a1节点下可以继续添加a1节点 + request = new FileModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1"); + request.setParentId(a1Node.getId()); + this.requestPostWithOkAndReturn(URL_POST_MODULE_ADD, request); + + //继续创建a1下继续创建a1-a1-b1, + treeNodes = this.getFileModuleTreeNode(); + BaseTreeNode a1ChildNode = null; + for (BaseTreeNode baseTreeNode : treeNodes) { + Assertions.assertNotNull(baseTreeNode.getParentId()); + if (StringUtils.equals(baseTreeNode.getName(), "a1") && CollectionUtils.isNotEmpty(baseTreeNode.getChildren())) { + for (BaseTreeNode childNode : baseTreeNode.getChildren()) { + Assertions.assertNotNull(childNode.getParentId()); + if (StringUtils.equals(childNode.getName(), "a1")) { + a1ChildNode = childNode; + } + } + } + } + Assertions.assertNotNull(a1ChildNode); + LOG_CHECK_LIST.add( + new CheckLogModel(a1ChildNode.getId(), OperationLogType.ADD, URL_POST_MODULE_ADD) + ); + + //a1的子节点a1下继续创建节点a1-a1-c1 + request = new FileModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1-a1-c1"); + request.setParentId(a1ChildNode.getId()); + this.requestPostWithOkAndReturn(URL_POST_MODULE_ADD, request); + treeNodes = this.getFileModuleTreeNode(); + BaseTreeNode a1a1c1Node = null; + for (BaseTreeNode baseTreeNode : treeNodes) { + Assertions.assertNotNull(baseTreeNode.getParentId()); + if (StringUtils.equals(baseTreeNode.getName(), "a1") && CollectionUtils.isNotEmpty(baseTreeNode.getChildren())) { + for (BaseTreeNode secondNode : baseTreeNode.getChildren()) { + Assertions.assertNotNull(secondNode.getParentId()); + if (StringUtils.equals(secondNode.getName(), "a1") && CollectionUtils.isNotEmpty(secondNode.getChildren())) { + for (BaseTreeNode thirdNode : secondNode.getChildren()) { + Assertions.assertNotNull(thirdNode.getParentId()); + if (StringUtils.equals(thirdNode.getName(), "a1-a1-c1")) { + a1a1c1Node = thirdNode; + } + } + } + } + } + } + Assertions.assertNotNull(a1a1c1Node); + LOG_CHECK_LIST.add( + new CheckLogModel(a1a1c1Node.getId(), OperationLogType.ADD, URL_POST_MODULE_ADD) + ); + //子节点a1-b1下继续创建节点a1-b1-c1 + request = new FileModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1-b1-c1"); + request.setParentId(a1b1Node.getId()); + this.requestPostWithOkAndReturn(URL_POST_MODULE_ADD, request); + treeNodes = this.getFileModuleTreeNode(); + BaseTreeNode a1b1c1Node = null; + for (BaseTreeNode baseTreeNode : treeNodes) { + if (StringUtils.equals(baseTreeNode.getName(), "a1") && CollectionUtils.isNotEmpty(baseTreeNode.getChildren())) { + for (BaseTreeNode secondNode : baseTreeNode.getChildren()) { + if (StringUtils.equals(secondNode.getName(), "a1-b1") && CollectionUtils.isNotEmpty(secondNode.getChildren())) { + for (BaseTreeNode thirdNode : secondNode.getChildren()) { + if (StringUtils.equals(thirdNode.getName(), "a1-b1-c1")) { + a1b1c1Node = thirdNode; + } + } + } + } + } + } + Assertions.assertNotNull(a1b1c1Node); + preliminaryTreeNodes = treeNodes; + + LOG_CHECK_LIST.add( + new CheckLogModel(a1b1c1Node.getId(), OperationLogType.ADD, URL_POST_MODULE_ADD) + ); + + + /** + 测试能否正常做200个节点 + */ + String parentId = null; + for (int i = 0; i < 210; i++) { + FileModuleCreateRequest perfRequest = new FileModuleCreateRequest(); + perfRequest.setProjectId(project.getId()); + perfRequest.setName("500-test-root-" + i); + if (StringUtils.isNotEmpty(parentId)) { + perfRequest.setParentId(parentId); + } + if (i < 200) { + MvcResult result = this.requestPostWithOkAndReturn(URL_POST_MODULE_ADD, perfRequest); + ResultHolder holder = JSON.parseObject(result.getResponse().getContentAsString(), ResultHolder.class); + if (i % 50 == 0) { + //到20换下一层级 + parentId = holder.getData().toString(); + } + } else { + //测试超过500会报错 + this.requestPost(URL_POST_MODULE_ADD, perfRequest).andExpect(status().is5xxServerError()); + } + } + treeNodes = this.getFileModuleTreeNode(); + preliminaryTreeNodes = treeNodes; + + a1Node = TestPlanUtils.getNodeByName(preliminaryTreeNodes, "a1"); + assert a1Node != null; + + //参数校验 + request = new FileModuleCreateRequest(); + request.setProjectId(project.getId()); + this.requestPost(URL_POST_MODULE_ADD, request).andExpect(status().isBadRequest()); + request = new FileModuleCreateRequest(); + request.setName("none"); + this.requestPost(URL_POST_MODULE_ADD, request).andExpect(status().isBadRequest()); + request = new FileModuleCreateRequest(); + this.requestPost(URL_POST_MODULE_ADD, request).andExpect(status().isBadRequest()); + request = new FileModuleCreateRequest(); + request.setParentId(null); + this.requestPost(URL_POST_MODULE_ADD, request).andExpect(status().isBadRequest()); + + //父节点ID不存在的 + request = new FileModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("ParentIsUUID"); + request.setParentId(IDGenerator.nextStr()); + this.requestPost(URL_POST_MODULE_ADD, request).andExpect(status().is5xxServerError()); + + //添加重复的a1节点 + request = new FileModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1"); + this.requestPost(URL_POST_MODULE_ADD, request).andExpect(status().is5xxServerError()); + + //a1节点下添加重复的a1-b1节点 + request = new FileModuleCreateRequest(); + request.setProjectId(project.getId()); + request.setName("a1-b1"); + request.setParentId(a1Node.getId()); + this.requestPost(URL_POST_MODULE_ADD, request).andExpect(status().is5xxServerError()); + + //子节点的项目ID和父节点的不匹配 + request = new FileModuleCreateRequest(); + request.setProjectId(IDGenerator.nextStr()); + request.setName("RandomUUID"); + request.setParentId(a1Node.getId()); + this.requestPost(URL_POST_MODULE_ADD, request).andExpect(status().is5xxServerError()); + + //项目ID和父节点的不匹配 + request = new FileModuleCreateRequest(); + request.setProjectId(DEFAULT_PROJECT_ID); + request.setName("RandomUUID"); + request.setParentId(a1Node.getId()); + this.requestPost(URL_POST_MODULE_ADD, request).andExpect(status().is5xxServerError()); + + //判断权限 + this.requestPostPermissionTest(PermissionConstants.TEST_PLAN_MODULE_READ_ADD, URL_POST_MODULE_ADD, request); + } + + @Test + @Order(4) + public void updateModuleTest() throws Exception { + if (CollectionUtils.isEmpty(preliminaryTreeNodes)) { + this.addModuleTest(); + } + //更改名称 + BaseTreeNode a1Node = null; + for (BaseTreeNode node : preliminaryTreeNodes) { + if (StringUtils.equals(node.getName(), "a1")) { + for (BaseTreeNode a1ChildrenNode : node.getChildren()) { + if (StringUtils.equals(a1ChildrenNode.getName(), "a1")) { + a1Node = a1ChildrenNode; + } + } + } + } + assert a1Node != null; + FileModuleUpdateRequest updateRequest = new FileModuleUpdateRequest(); + updateRequest.setId(a1Node.getId()); + updateRequest.setName("a1-a1"); + this.requestPostWithOkAndReturn(URL_POST_MODULE_UPDATE, updateRequest); + + preliminaryTreeNodes = this.getFileModuleTreeNode(); + LOG_CHECK_LIST.add( + new CheckLogModel(a1Node.getId(), OperationLogType.UPDATE, URL_POST_MODULE_UPDATE) + ); + + a1Node = TestPlanUtils.getNodeByName(preliminaryTreeNodes, "a1-a1"); + assert a1Node != null; + //反例-参数校验 + updateRequest = new FileModuleUpdateRequest(); + this.requestPost(URL_POST_MODULE_UPDATE, updateRequest).andExpect(status().isBadRequest()); + + //id不存在 + updateRequest = new FileModuleUpdateRequest(); + updateRequest.setId(IDGenerator.nextStr()); + updateRequest.setName(IDGenerator.nextStr()); + this.requestPost(URL_POST_MODULE_UPDATE, updateRequest).andExpect(status().is5xxServerError()); + + //名称重复 a1-a1改为a1-b1 + updateRequest = new FileModuleUpdateRequest(); + updateRequest.setId(a1Node.getId()); + updateRequest.setName("a1-b1"); + this.requestPost(URL_POST_MODULE_UPDATE, updateRequest).andExpect(status().is5xxServerError()); + + //判断权限 + this.requestPostPermissionTest(PermissionConstants.TEST_PLAN_MODULE_READ_UPDATE, URL_POST_MODULE_UPDATE, updateRequest); + } + + + /* + 80以后是文件、模块的移动和删除 + */ + @Test + @Order(80) + public void moveTest() throws Exception { + this.preliminaryData(); + /* + *默认节点 + | + ·a1 + + | | + | ·a1-b1 + + | | | + | | ·a1-b1-c1 + | | + | *a1-a1 +(创建的时候是a1,通过修改改为a1-a1) + | | + | ·a1-a1-c1 + | + ·a2 + | + ·a3 + */ + BaseTreeNode a1Node = TestPlanUtils.getNodeByName(preliminaryTreeNodes, "a1"); + BaseTreeNode a2Node = TestPlanUtils.getNodeByName(preliminaryTreeNodes, "a2"); + BaseTreeNode a3Node = TestPlanUtils.getNodeByName(preliminaryTreeNodes, "a3"); + BaseTreeNode a1a1Node = TestPlanUtils.getNodeByName(preliminaryTreeNodes, "a1-a1"); + BaseTreeNode a1b1Node = TestPlanUtils.getNodeByName(preliminaryTreeNodes, "a1-b1"); + + //父节点内移动-移动到首位 a1挪到a3后面 + NodeMoveRequest request = new NodeMoveRequest(); + { + request.setDragNodeId(a1Node.getId()); + request.setDropNodeId(a3Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a3Node.getId(), a1Node.getId(), null, false); + } + //父节点内移动-移动到末位 在上面的基础上,a1挪到a2上面 + { + request = new NodeMoveRequest(); + request.setDragNodeId(a1Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(-1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a1Node.getId(), a2Node.getId(), null, false); + } + + //父节点内移动-移动到中位 a1移动到a2-a3中间 + { + request = new NodeMoveRequest(); + request.setDragNodeId(a1Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a2Node.getId(), a1Node.getId(), a3Node.getId(), false); + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a1Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(-1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a1Node.getId(), a2Node.getId(), null, false); + } + + //跨节点移动-移动到首位 a3移动到a1-b1前面,然后移动回来; + { + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1b1Node.getId()); + request.setDropPosition(-1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a3Node.getId(), a1b1Node.getId(), null, false); + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + + //跨节点移动-移动到末尾 a3移动到a1-a1后面,然后移动回来; + { + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1a1Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a1a1Node.getId(), a3Node.getId(), null, false); + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + + //跨节点移动-移动到中位 a3移动到a1-b1和a1-a1中间,然后移动回来; + { + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1b1Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a1b1Node.getId(), a3Node.getId(), a1a1Node.getId(), false); + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + + //父节点内移动-a3移动到首位pos小于2,是否触发计算函数 (先手动更改a1的pos为2,然后移动a3到a1前面) + { + //更改pos + TestPlanModule updateModule = new TestPlanModule(); + updateModule.setId(a1Node.getId()); + updateModule.setPos(2L); + testPlanModuleMapper.updateByPrimaryKeySelective(updateModule); + + //开始移动 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1Node.getId()); + request.setDropPosition(-1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a3Node.getId(), a1Node.getId(), null, true); + + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + + //父节点内移动-移动到中位,前后节点pos差不大于2,是否触发计算函数(在上面的 a3-a1-a2的基础上, 先手动更改a1pos为3*64,a2的pos为3*64+2,然后移动a3到a1和a2中间) + { + //更改pos + TestPlanModule updateModule = new TestPlanModule(); + updateModule.setId(a1Node.getId()); + updateModule.setPos(3 * 64L); + testPlanModuleMapper.updateByPrimaryKeySelective(updateModule); + updateModule.setId(a2Node.getId()); + updateModule.setPos(3 * 64 + 2L); + testPlanModuleMapper.updateByPrimaryKeySelective(updateModule); + + //开始移动 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a1Node.getId(), a3Node.getId(), a2Node.getId(), true); + + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + //跨节点移动-移动到首位pos小于2,是否触发计算函数(先手动更改a1-b1的pos为2,然后移动a3到a1-b1前面,最后再移动回来) + { + //更改pos + TestPlanModule updateModule = new TestPlanModule(); + updateModule.setId(a1b1Node.getId()); + updateModule.setPos(2L); + testPlanModuleMapper.updateByPrimaryKeySelective(updateModule); + + //开始移动 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1b1Node.getId()); + request.setDropPosition(-1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a3Node.getId(), a1b1Node.getId(), null, true); + + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + //跨节点移动-移动到中位,前后节点pos差不大于2,是否触发计算函数先手动更改a1-a1的pos为a1-b1+2,然后移动a3到a1-a1前面,最后再移动回来) + { + //更改pos + TestPlanModule updateModule = new TestPlanModule(); + updateModule.setId(a1b1Node.getId()); + updateModule.setPos(3 * 64L); + testPlanModuleMapper.updateByPrimaryKeySelective(updateModule); + updateModule.setId(a1a1Node.getId()); + updateModule.setPos(3 * 64 + 2L); + testPlanModuleMapper.updateByPrimaryKeySelective(updateModule); + + //开始移动 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a1a1Node.getId()); + request.setDropPosition(-1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a1b1Node.getId(), a3Node.getId(), a1a1Node.getId(), true); + + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + //移动到没有子节点的节点下 a3移动到a2下 + { + //开始移动 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(0); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + TestPlanModule a3Module = testPlanModuleMapper.selectByPrimaryKey(a3Node.getId()); + Assertions.assertEquals(a3Module.getParentId(), a2Node.getId()); + + //移动回去 + request = new NodeMoveRequest(); + request.setDragNodeId(a3Node.getId()); + request.setDropNodeId(a2Node.getId()); + request.setDropPosition(1); + this.requestPostWithOk(URL_POST_MODULE_MOVE, request); + this.checkModulePos(a2Node.getId(), a3Node.getId(), null, false); + } + + LOG_CHECK_LIST.add( + new CheckLogModel(a1Node.getId(), OperationLogType.UPDATE, URL_POST_MODULE_MOVE) + ); + LOG_CHECK_LIST.add( + new CheckLogModel(a3Node.getId(), OperationLogType.UPDATE, URL_POST_MODULE_MOVE) + ); + + //判断权限 + this.requestPostPermissionTest(PermissionConstants.TEST_PLAN_MODULE_READ_UPDATE, URL_POST_MODULE_MOVE, request); + } + + @Test + @Order(90) + public void deleteModuleTestSuccess() throws Exception { + this.preliminaryData(); + + // 删除没有文件的节点a1-b1-c1 检查是否级联删除根节点 + BaseTreeNode a1b1Node = TestPlanUtils.getNodeByName(this.getFileModuleTreeNode(), "a1-b1"); + this.requestGetWithOk(String.format(URL_GET_MODULE_DELETE, a1b1Node.getId())); + this.checkModuleIsEmpty(a1b1Node.getId()); + LOG_CHECK_LIST.add( + new CheckLogModel(a1b1Node.getId(), OperationLogType.DELETE, URL_GET_MODULE_DELETE) + ); + + // 删除有文件的节点 a1-a1 检查是否级联删除根节点 + BaseTreeNode a1a1Node = TestPlanUtils.getNodeByName(this.getFileModuleTreeNode(), "a1-a1"); + this.requestGetWithOk(String.format(URL_GET_MODULE_DELETE, a1a1Node.getId())); + this.checkModuleIsEmpty(a1a1Node.getId()); + LOG_CHECK_LIST.add( + new CheckLogModel(a1a1Node.getId(), OperationLogType.DELETE, URL_GET_MODULE_DELETE) + ); + + //删除不存在的节点 + this.requestGetWithOk(String.format(URL_GET_MODULE_DELETE, IDGenerator.nextNum())); + // 测试删除根节点 + this.requestGetWithOk(String.format(URL_GET_MODULE_DELETE, ModuleConstants.DEFAULT_NODE_ID)); + + //service层判断:测试删除空集合 + testPlanModuleService.deleteModule(new ArrayList<>()); + + //service层判断:测试删除项目 + testPlanModuleService.deleteResources(project.getId()); + + //判断权限 + this.requestGetPermissionTest(PermissionConstants.TEST_PLAN_MODULE_READ_DELETE, (String.format(URL_GET_MODULE_DELETE, IDGenerator.nextNum()))); + } + + private void checkModuleIsEmpty(String id) { + TestPlanModuleExample example = new TestPlanModuleExample(); + example.createCriteria().andParentIdEqualTo(id); + Assertions.assertEquals(testPlanModuleMapper.countByExample(example), 0); + + example = new TestPlanModuleExample(); + example.createCriteria().andIdEqualTo(id); + Assertions.assertEquals(testPlanModuleMapper.countByExample(example), 0); + /* + todo 该模块下已无测试计划 + FileMetadataExample metadataExample = new FileMetadataExample(); + metadataExample.createCriteria().andModuleIdEqualTo(id); + Assertions.assertEquals(testPlanModuleMapper.countByExample(metadataExample), 0); + */ + } + + + public MvcResult responseFile(String url, MockMultipartFile file, Object param) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders.multipart(url) + .file(file) + .content(JSON.toJSONString(param)) + .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) + .header(SessionConstants.HEADER_TOKEN, sessionId) + .header(SessionConstants.CSRF_TOKEN, csrfToken)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andReturn(); + } + + private List getFileModuleTreeNode() throws Exception { + MvcResult result = this.requestGetWithOkAndReturn(String.format(URL_GET_MODULE_TREE, project.getId())); + String returnData = result.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class); + return JSON.parseArray(JSON.toJSONString(resultHolder.getData()), BaseTreeNode.class); + } + + + private void preliminaryData() throws Exception { + if (CollectionUtils.isEmpty(preliminaryTreeNodes)) { + /* + 这里需要获取修改过的树的结构。期望的最终结构是这样的(*为测试用例中挂载文件的节点, · 为空节点): + + *默认节点 + | + ·a1 + + | | + | ·a1-b1 + + | | | + | | ·a1-b1-c1 + | | + | *a1-a1 +(创建的时候是a1,通过修改改为a1-a1) + | | | + | | ·a1-a1-c1(用于测试文件移动) + | + ·a2 + | + ·a3 + */ + this.updateModuleTest(); + } + } + + private void checkModulePos(String firstNode, String secondNode, String thirdNode, boolean isRecalculate) { + TestPlanModule firstModule = testPlanModuleMapper.selectByPrimaryKey(firstNode); + TestPlanModule secondModule = testPlanModuleMapper.selectByPrimaryKey(secondNode); + TestPlanModule thirdModule = null; + Assertions.assertTrue(firstModule.getPos() < secondModule.getPos()); + if (StringUtils.isNotBlank(thirdNode)) { + thirdModule = testPlanModuleMapper.selectByPrimaryKey(thirdNode); + Assertions.assertTrue(secondModule.getPos() < thirdModule.getPos()); + } + if (isRecalculate) { + int limitPos = 64; + Assertions.assertEquals(0, firstModule.getPos() % limitPos); + Assertions.assertEquals(0, secondModule.getPos() % limitPos); + if (thirdModule != null) { + Assertions.assertEquals(0, thirdModule.getPos() % limitPos); + } + } + } + + + @Test + @Order(100) + public void testLog() throws Exception { + Thread.sleep(5000); + for (CheckLogModel checkLogModel : LOG_CHECK_LIST) { + if (StringUtils.isEmpty(checkLogModel.getUrl())) { + this.checkLog(checkLogModel.getResourceId(), checkLogModel.getOperationType()); + } else { + this.checkLog(checkLogModel.getResourceId(), checkLogModel.getOperationType(), checkLogModel.getUrl()); + } + } + } +} + +@Data +class CheckLogModel { + private String resourceId; + private OperationLogType operationType; + private String url; + + public CheckLogModel(String resourceId, OperationLogType operationType, String url) { + this.resourceId = resourceId; + this.operationType = operationType; + this.url = getLogUrl(url); + } + + private String getLogUrl(String url) { + if (StringUtils.endsWith(url, "/%s")) { + return StringUtils.substring(url, 0, url.length() - 3); + } else { + return url; + } + } +} diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/utils/TestPlanUtils.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/utils/TestPlanUtils.java new file mode 100644 index 0000000000..59a4554e52 --- /dev/null +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/utils/TestPlanUtils.java @@ -0,0 +1,32 @@ +package io.metersphere.plan.utils; + +import io.metersphere.system.dto.sdk.BaseTreeNode; +import org.apache.commons.collections4.CollectionUtils; +import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils; + +import java.util.List; + +public class TestPlanUtils { + public static BaseTreeNode getNodeByName(List preliminaryTreeNodes, String nodeName) { + for (BaseTreeNode firstLevelNode : preliminaryTreeNodes) { + if (StringUtils.equals(firstLevelNode.getName(), nodeName)) { + return firstLevelNode; + } + if (CollectionUtils.isNotEmpty(firstLevelNode.getChildren())) { + for (BaseTreeNode secondLevelNode : firstLevelNode.getChildren()) { + if (StringUtils.equals(secondLevelNode.getName(), nodeName)) { + return secondLevelNode; + } + if (CollectionUtils.isNotEmpty(secondLevelNode.getChildren())) { + for (BaseTreeNode thirdLevelNode : secondLevelNode.getChildren()) { + if (StringUtils.equals(thirdLevelNode.getName(), nodeName)) { + return thirdLevelNode; + } + } + } + } + } + } + return null; + } +} diff --git a/backend/services/test-plan/src/test/resources/application.properties b/backend/services/test-plan/src/test/resources/application.properties index b9ee6c2756..ac9c8391ee 100644 --- a/backend/services/test-plan/src/test/resources/application.properties +++ b/backend/services/test-plan/src/test/resources/application.properties @@ -6,14 +6,14 @@ server.compression.enabled=true server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css,text/javascript,image/jpeg server.compression.min-response-size=2048 # -quartz.enabled=false +quartz.enabled=true quartz.scheduler-name=msScheduler quartz.thread-count=10 quartz.properties.org.quartz.jobStore.acquireTriggersWithinLock=true # logging.file.path=/opt/metersphere/logs/metersphere # Hikari -spring.datasource.url=jdbc:mysql://${embedded.mysql.host}:${embedded.mysql.port}/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8 +spring.datasource.url=jdbc:mysql://${embedded.mysql.host}:${embedded.mysql.port}/test?autoReconnect=false&useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull&allowPublicKeyRetrieval=true&useSSL=false&sessionVariables=sql_mode=%27STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION%27 spring.datasource.username=${embedded.mysql.user} spring.datasource.password=${embedded.mysql.password} spring.datasource.type=com.zaxxer.hikari.HikariDataSource @@ -25,8 +25,9 @@ spring.datasource.hikari.pool-name=DatebookHikariCP spring.datasource.hikari.max-lifetime=1800000 spring.datasource.hikari.connection-timeout=30000 spring.datasource.hikari.connection-test-query=SELECT 1 - -# +# 单元测试初始化权限 sql +spring.sql.init.mode=always +spring.sql.init.schema-locations=classpath*:dml/init_permission_test.sql # spring.kafka spring.kafka.bootstrap-servers=${embedded.kafka.brokerList} spring.kafka.consumer.group-id=metersphere_group_id @@ -79,4 +80,6 @@ minio.secret-key=${embedded.minio.secretKey} logging.level.org.springframework.jdbc.core=info logging.level.io.metersphere.sdk.mapper=info -logging.level.io.metersphere.system.mapper=info \ No newline at end of file +logging.level.io.metersphere.system.mapper=info +logging.level.io.metersphere.plan.mapper=info +metersphere.file.batch-download-max=600MB \ No newline at end of file diff --git a/backend/services/test-plan/src/test/resources/dml/init_permission_test.sql b/backend/services/test-plan/src/test/resources/dml/init_permission_test.sql new file mode 100644 index 0000000000..ec90568c9c --- /dev/null +++ b/backend/services/test-plan/src/test/resources/dml/init_permission_test.sql @@ -0,0 +1,14 @@ +-- 初始化用于权限测试的组织用户 +INSERT INTO user(id, name, email, password, create_time, update_time, language, last_organization_id, phone, source, + last_project_id, create_user, update_user, deleted) +VALUES ('PROJECT', 'PROJECT', 'PROJECT@fit2cloud.com', MD5('metersphere'), + UNIX_TIMESTAMP() * 1000, + UNIX_TIMESTAMP() * 1000, NULL, NUll, '', 'LOCAL', NULL, 'admin', 'admin', false); + +-- 初始化一个用于权限测试的用户组,这里默认使用 PROJECT 作为ID,如果是组织和项目级别类似,便于根据权限的前缀找到对应测试的用户组 +INSERT INTO user_role (id, name, description, internal, type, create_time, update_time, create_user, scope_id) +VALUES ('PROJECT', '项目级别权限校验', '', 1, 'PROJECT', 1620674220005, 1620674220000, 'admin', 'global'); + +-- 初始化用户和组的关系 +INSERT INTO user_role_relation (id, user_id, role_id, source_id, organization_id, create_time, create_user) +SELECT 'PROJECT', 'PROJECT', 'PROJECT', id, organization_id, 1684747668375, 'admin' FROM project WHERE num = 100001;