From 5194e587d201356cdb139ec149812b831cf3c43b Mon Sep 17 00:00:00 2001 From: song-cc-rock Date: Tue, 4 Jul 2023 15:52:29 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=B3=BB=E7=BB=9F=E7=AE=A1=E7=90=86):=20?= =?UTF-8?q?=E8=A1=A5=E5=85=85=E7=BB=84=E7=BB=87=E7=AE=A1=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RestControllerExceptionHandler.java | 12 + .../resources/i18n/system_en_US.properties | 4 + .../resources/i18n/system_zh_CN.properties | 6 +- .../resources/i18n/system_zh_TW.properties | 6 +- .../controller/OrganizationController.java | 14 +- .../system/dto/OrganizationDTO.java | 14 +- .../system/job/CleanOrganizationJob.java | 57 +++ .../system/mapper/ExtOrganizationMapper.java | 14 + .../system/mapper/ExtOrganizationMapper.xml | 13 +- .../request/OrganizationEditRequest.java | 28 ++ .../system/service/OrganizationService.java | 6 + .../service/OrganizationServiceImpl.java | 46 ++- .../OrganizationControllerTests.java | 340 ++++++++++++++---- .../system/job/CleanOrganizationJobTests.java | 35 ++ .../resources/dml/init_clean_organization.sql | 5 + .../test/resources/dml/init_organization.sql | 2 +- 16 files changed, 517 insertions(+), 85 deletions(-) create mode 100644 backend/services/system-setting/src/main/java/io/metersphere/system/job/CleanOrganizationJob.java create mode 100644 backend/services/system-setting/src/main/java/io/metersphere/system/request/OrganizationEditRequest.java create mode 100644 backend/services/system-setting/src/test/java/io/metersphere/system/job/CleanOrganizationJobTests.java create mode 100644 backend/services/system-setting/src/test/resources/dml/init_clean_organization.sql diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/handler/RestControllerExceptionHandler.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/handler/RestControllerExceptionHandler.java index 3fe5d93581..074d235f9b 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/handler/RestControllerExceptionHandler.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/handler/RestControllerExceptionHandler.java @@ -10,6 +10,7 @@ import org.apache.shiro.authz.UnauthorizedException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.FieldError; +import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; @@ -43,6 +44,17 @@ public class RestControllerExceptionHandler { MsHttpResultCode.VALIDATE_FAILED.getMessage(), errors); } + /** + * http 状态码返回405 + * @param exception 异常信息 + * @return + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public ResultHolder handleHttpRequestMethodNotSupportedException(HttpServletResponse response, Exception exception) { + response.setStatus(HttpStatus.METHOD_NOT_ALLOWED.value()); + return ResultHolder.error(HttpStatus.METHOD_NOT_ALLOWED.value(), exception.getMessage()); + } + /** * 根据 MSException 中的 errorCode * 设置对应的 Http 状态码,以及业务状态码和错误提示 diff --git a/backend/framework/sdk/src/main/resources/i18n/system_en_US.properties b/backend/framework/sdk/src/main/resources/i18n/system_en_US.properties index ebd8e8930f..accd33b328 100644 --- a/backend/framework/sdk/src/main/resources/i18n/system_en_US.properties +++ b/backend/framework/sdk/src/main/resources/i18n/system_en_US.properties @@ -151,6 +151,10 @@ organization.name.length_range=Organization name must be between {min} and {max} organization.create_user.not_blank=Organization create user must not be blank organization.create_user.length_range=Organization create user must be between {min} and {max} characters long member.id.not_empty=member cannot be empty +and_add_organization_admin=and add organization administrator +organization_add_member_ids_empty=organization add member cannot be empty +organization_not_exist=organization does not exist +organization_member_not_exist=organization member does not exist diff --git a/backend/framework/sdk/src/main/resources/i18n/system_zh_CN.properties b/backend/framework/sdk/src/main/resources/i18n/system_zh_CN.properties index 653c5aa981..22b125541a 100644 --- a/backend/framework/sdk/src/main/resources/i18n/system_zh_CN.properties +++ b/backend/framework/sdk/src/main/resources/i18n/system_zh_CN.properties @@ -149,4 +149,8 @@ organization.id.not_blank=工作空间ID不能为空 organization.name.not_blank=工作空间名称不能为空 organization.name.length_range=工作空间名称长度必须在{min}和{max}之间 organization.create_user.not_blank=工作空间创建人不能为空 -organization.create_user.length_range=工作空间创建人长度必须在{min}和{max}之间 \ No newline at end of file +organization.create_user.length_range=工作空间创建人长度必须在{min}和{max}之间 +and_add_organization_admin=并添加组织管理员 +organization_add_member_ids_empty=组织添加成员不能为空 +organization_not_exist=组织不存在 +organization_member_not_exist=组织成员不存在 \ No newline at end of file diff --git a/backend/framework/sdk/src/main/resources/i18n/system_zh_TW.properties b/backend/framework/sdk/src/main/resources/i18n/system_zh_TW.properties index e017d9dc2e..66570102b9 100644 --- a/backend/framework/sdk/src/main/resources/i18n/system_zh_TW.properties +++ b/backend/framework/sdk/src/main/resources/i18n/system_zh_TW.properties @@ -149,4 +149,8 @@ organization.id.not_blank=工作空間ID不能為空 organization.name.not_blank=工作空間名稱不能為空 organization.name.length_range=工作空間名稱長度必須在{min}和{max}之間 organization.create_user.not_blank=工作空間創建人不能為空 -organization.create_user.length_range=工作空間創建人長度必須在{min}和{max}之間 \ No newline at end of file +organization.create_user.length_range=工作空間創建人長度必須在{min}和{max}之間 +and_add_organization_admin=並添加組織管理員 +organization_add_member_ids_empty=組織添加成員不能爲空 +organization_not_exist=組織不存在 +organization_member_not_exist=組織成員不存在 \ No newline at end of file diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/controller/OrganizationController.java b/backend/services/system-setting/src/main/java/io/metersphere/system/controller/OrganizationController.java index 90d8ff259c..f31f0305c2 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/controller/OrganizationController.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/controller/OrganizationController.java @@ -14,6 +14,8 @@ import io.metersphere.system.request.OrganizationRequest; import io.metersphere.system.request.ProjectRequest; import io.metersphere.system.service.OrganizationService; import io.metersphere.system.service.SystemProjectService; +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; @@ -24,6 +26,7 @@ import java.util.List; /** * @author song-cc-rock */ +@Tag(name = "组织") @RestController @RequestMapping("/organization") public class OrganizationController { @@ -34,6 +37,7 @@ public class OrganizationController { private OrganizationService organizationService; @PostMapping("/list") + @Operation(summary = "获取组织列表") @RequiresPermissions(PermissionConstants.SYSTEM_ORGANIZATION_READ) public Pager> list(@Validated @RequestBody OrganizationRequest organizationRequest) { Page page = PageHelper.startPage(organizationRequest.getCurrent(), organizationRequest.getPageSize()); @@ -41,12 +45,14 @@ public class OrganizationController { } @PostMapping("/list-all") + @Operation(summary = "获取系统所有组织") @RequiresPermissions(PermissionConstants.SYSTEM_ORGANIZATION_READ) - public List listAll(@Validated @RequestBody OrganizationRequest organizationRequest) { - return organizationService.list(organizationRequest); + public List listAll() { + return organizationService.listAll(); } @PostMapping("/list-member") + @Operation(summary = "获取组织成员") @RequiresPermissions(PermissionConstants.SYSTEM_USER_READ) public Pager> listMember(@Validated @RequestBody OrganizationRequest organizationRequest) { Page page = PageHelper.startPage(organizationRequest.getCurrent(), organizationRequest.getPageSize()); @@ -54,6 +60,7 @@ public class OrganizationController { } @PostMapping("/add-member") + @Operation(summary = "添加组织成员") @RequiresPermissions(PermissionConstants.SYSTEM_USER_READ) public void addMember(@Validated @RequestBody OrganizationMemberRequest organizationMemberRequest) { organizationMemberRequest.setCreateUserId(SessionUtils.getUserId()); @@ -61,18 +68,21 @@ public class OrganizationController { } @GetMapping("/remove-member/{organizationId}/{userId}") + @Operation(summary = "删除组织成员") @RequiresPermissions(PermissionConstants.SYSTEM_USER_READ) public void removeMember(@PathVariable String organizationId, @PathVariable String userId) { organizationService.removeMember(organizationId, userId); } @GetMapping("/default") + @Operation(summary = "获取系统默认组织") @RequiresPermissions(PermissionConstants.SYSTEM_ORGANIZATION_READ) public OrganizationDTO getDefault() { return organizationService.getDefault(); } @PostMapping("/list-project") + @Operation(summary = "获取组织下的项目列表") @RequiresPermissions(PermissionConstants.SYSTEM_PROJECT_READ) public Pager> listProject(@Validated @RequestBody ProjectRequest projectRequest) { Page page = PageHelper.startPage(projectRequest.getCurrent(), projectRequest.getPageSize()); diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/OrganizationDTO.java b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/OrganizationDTO.java index d424fa9f43..54ce52afd7 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/OrganizationDTO.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/OrganizationDTO.java @@ -1,7 +1,9 @@ package io.metersphere.system.dto; import io.metersphere.system.domain.Organization; +import io.metersphere.system.domain.User; import io.metersphere.validation.groups.Created; +import io.metersphere.validation.groups.Updated; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import lombok.Data; @@ -30,9 +32,15 @@ public class OrganizationDTO extends Organization { private Integer projectCount; /** - * 成员ID集合 + * 列表组织管理员集合 */ - @Schema(title = "成员ID集合") - @NotEmpty(groups = {Created.class}, message = "{member.id.not_empty}") + @Schema(title = "列表组织管理员集合") + private List orgAdmins; + + /** + * 组织管理员ID集合(新增, 编辑), 必填 + */ + @Schema(title = "组织管理员ID集合") + @NotEmpty(groups = {Created.class, Updated.class}, message = "{member.id.not_empty}") private List memberIds; } diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/job/CleanOrganizationJob.java b/backend/services/system-setting/src/main/java/io/metersphere/system/job/CleanOrganizationJob.java new file mode 100644 index 0000000000..9ac5bcd569 --- /dev/null +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/job/CleanOrganizationJob.java @@ -0,0 +1,57 @@ +package io.metersphere.system.job; + +import com.fit2cloud.quartz.anno.QuartzScheduled; +import io.metersphere.system.domain.Organization; +import io.metersphere.system.domain.OrganizationExample; +import io.metersphere.system.mapper.OrganizationMapper; +import io.metersphere.utils.LoggerUtil; +import jakarta.annotation.Resource; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.List; + +@Component +public class CleanOrganizationJob { + + @Resource + OrganizationMapper organizationMapper; + + /** + * 凌晨3点清理删除的组织 + */ + @QuartzScheduled(cron = "0 0 3 * * ?") + public void cleanOrganization() { + LoggerUtil.info("clean up organization start."); + try { + LocalDate date = LocalDate.now().minusMonths(1); + long timestamp = date.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli(); + this.doCleanupOrganization(timestamp); + } catch (Exception e) { + LoggerUtil.error("clean up organization error.", e); + } + LoggerUtil.info("clean up organization end."); + } + + private void doCleanupOrganization(long timestamp) { + OrganizationExample example = new OrganizationExample(); + example.createCriteria().andDeletedEqualTo(true).andDeleteTimeLessThanOrEqualTo(timestamp); + List organizations = organizationMapper.selectByExample(example); + if (CollectionUtils.isEmpty(organizations)) { + return; + } + + organizations.forEach(organization -> { + // TODO 清理组织下的资源 + // 删除项目 + // 删除用户组, 用户组关系 + // 删除环境组 + // 删除定时任务 + // 操作记录{项目, 组织} + }); + // 删除组织 + organizationMapper.deleteByExample(example); + } +} diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/mapper/ExtOrganizationMapper.java b/backend/services/system-setting/src/main/java/io/metersphere/system/mapper/ExtOrganizationMapper.java index 70d8370ac3..7a47af1233 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/mapper/ExtOrganizationMapper.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/mapper/ExtOrganizationMapper.java @@ -1,5 +1,6 @@ package io.metersphere.system.mapper; +import io.metersphere.system.domain.User; import io.metersphere.system.dto.OrganizationDTO; import io.metersphere.system.dto.UserExtend; import io.metersphere.system.request.OrganizationDeleteRequest; @@ -20,6 +21,12 @@ public interface ExtOrganizationMapper { */ List list(@Param("request") OrganizationRequest organizationRequest); + /** + * 获取系统下所有组织 + * @return 组织列表数据 + */ + List listAll(); + /** * 删除组织 * @param organizationDeleteRequest 组织删除参数 @@ -45,4 +52,11 @@ public interface ExtOrganizationMapper { * @return 组织成员列表数据 */ List listMember(@Param("request") OrganizationRequest organizationRequest); + + /** + * 获取组织管理员 + * @param orgId 组织ID + * @return 组织管理员数据 + */ + List getOrgAdminList(String orgId); } diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/mapper/ExtOrganizationMapper.xml b/backend/services/system-setting/src/main/java/io/metersphere/system/mapper/ExtOrganizationMapper.xml index 0da9db6566..925d8e2278 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/mapper/ExtOrganizationMapper.xml +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/mapper/ExtOrganizationMapper.xml @@ -16,6 +16,10 @@ group by o.id + + @@ -64,7 +68,7 @@ from ( select u.*, urr.role_id from user_role_relation urr - JOIN `user` u ON urr.user_id = u.id + join `user` u on urr.user_id = u.id urr.source_id = #{request.organizationId} @@ -79,4 +83,11 @@ ) temp group by temp.id + + \ No newline at end of file diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/request/OrganizationEditRequest.java b/backend/services/system-setting/src/main/java/io/metersphere/system/request/OrganizationEditRequest.java new file mode 100644 index 0000000000..24b9faf653 --- /dev/null +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/request/OrganizationEditRequest.java @@ -0,0 +1,28 @@ +package io.metersphere.system.request; + +import io.metersphere.validation.groups.Created; +import io.metersphere.validation.groups.Updated; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +public class OrganizationEditRequest implements Serializable { + + @Schema(title = "组织名称", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{organization.name.not_blank}", groups = {Created.class}) + @Size(min = 1, max = 100, message = "{organization.name.length_range}", groups = {Created.class, Updated.class}) + private String name; + + @Schema(title = "描述") + private String description; + + @Schema(title = "成员ID集合") + @NotEmpty(groups = {Created.class}, message = "{member.id.not_empty}") + private List memberIds; +} diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/service/OrganizationService.java b/backend/services/system-setting/src/main/java/io/metersphere/system/service/OrganizationService.java index 315123218b..19fa843acf 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/service/OrganizationService.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/service/OrganizationService.java @@ -20,6 +20,12 @@ public interface OrganizationService { */ List list(OrganizationRequest organizationRequest); + /** + * 获取系统下所有组织 + * @return 列表数据 + */ + List listAll(); + /** * 获取默认组织信息 * @return 默认组织信息 diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/service/OrganizationServiceImpl.java b/backend/services/system-setting/src/main/java/io/metersphere/system/service/OrganizationServiceImpl.java index 0b8aa82004..9c821b807d 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/service/OrganizationServiceImpl.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/service/OrganizationServiceImpl.java @@ -1,15 +1,15 @@ package io.metersphere.system.service; import io.metersphere.sdk.constants.InternalUserRole; +import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.util.BeanUtils; -import io.metersphere.system.domain.Organization; -import io.metersphere.system.domain.OrganizationExample; -import io.metersphere.system.domain.UserRoleRelation; -import io.metersphere.system.domain.UserRoleRelationExample; +import io.metersphere.sdk.util.Translator; +import io.metersphere.system.domain.*; import io.metersphere.system.dto.OrganizationDTO; import io.metersphere.system.dto.UserExtend; import io.metersphere.system.mapper.ExtOrganizationMapper; import io.metersphere.system.mapper.OrganizationMapper; +import io.metersphere.system.mapper.UserMapper; import io.metersphere.system.mapper.UserRoleRelationMapper; import io.metersphere.system.request.OrganizationMemberRequest; import io.metersphere.system.request.OrganizationRequest; @@ -33,11 +33,19 @@ public class OrganizationServiceImpl implements OrganizationService{ ExtOrganizationMapper extOrganizationMapper; @Resource UserRoleRelationMapper userRoleRelationMapper; + @Resource + UserMapper userMapper; @Override public List list(OrganizationRequest organizationRequest) { - return extOrganizationMapper.list(organizationRequest); + List organizationDTOS = extOrganizationMapper.list(organizationRequest); + return buildOrgAdminInfo(organizationDTOS); + } + + @Override + public List listAll() { + return extOrganizationMapper.listAll(); } @Override @@ -58,8 +66,9 @@ public class OrganizationServiceImpl implements OrganizationService{ @Override public void addMember(OrganizationMemberRequest organizationMemberRequest) { - if (CollectionUtils.isEmpty(organizationMemberRequest.getMemberIds())) { - return; + Organization organization = organizationMapper.selectByPrimaryKey(organizationMemberRequest.getOrganizationId()); + if (organization == null) { + throw new MSException(Translator.get("organization_not_exist")); } for (String userId : organizationMemberRequest.getMemberIds()) { UserRoleRelation userRoleRelation = new UserRoleRelation(); @@ -75,8 +84,31 @@ public class OrganizationServiceImpl implements OrganizationService{ @Override public void removeMember(String organizationId, String userId) { + Organization organization = organizationMapper.selectByPrimaryKey(organizationId); + if (organization == null) { + throw new MSException(Translator.get("organization_not_exist")); + } + User user = userMapper.selectByPrimaryKey(userId); + if (user == null) { + throw new MSException(Translator.get("organization_member_not_exist")); + } UserRoleRelationExample example = new UserRoleRelationExample(); example.createCriteria().andUserIdEqualTo(userId).andSourceIdEqualTo(organizationId); + List userRoleRelations = userRoleRelationMapper.selectByExample(example); + if (CollectionUtils.isEmpty(userRoleRelations)) { + throw new MSException(Translator.get("organization_member_not_exist")); + } userRoleRelationMapper.deleteByExample(example); } + + private List buildOrgAdminInfo(List organizationDTOS) { + if (CollectionUtils.isEmpty(organizationDTOS)) { + return organizationDTOS; + } + organizationDTOS.forEach(organizationDTO -> { + List orgAdminList = extOrganizationMapper.getOrgAdminList(organizationDTO.getId()); + organizationDTO.setOrgAdmins(orgAdminList); + }); + return organizationDTOS; + } } diff --git a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/OrganizationControllerTests.java b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/OrganizationControllerTests.java index 9ef8e8241d..7e8419938b 100644 --- a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/OrganizationControllerTests.java +++ b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/OrganizationControllerTests.java @@ -2,27 +2,34 @@ package io.metersphere.system.controller; import base.BaseTest; import io.metersphere.sdk.constants.SessionConstants; +import io.metersphere.sdk.controller.handler.ResultHolder; import io.metersphere.sdk.util.JSON; +import io.metersphere.sdk.util.Pager; +import io.metersphere.system.dto.OrganizationDTO; +import io.metersphere.system.dto.UserExtend; import io.metersphere.system.request.OrganizationMemberRequest; import io.metersphere.system.request.OrganizationRequest; import io.metersphere.system.request.ProjectRequest; +import io.metersphere.utils.JsonUtils; import jakarta.annotation.Resource; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.*; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @@ -33,113 +40,308 @@ public class OrganizationControllerTests extends BaseTest{ @Resource private MockMvc mockMvc; - public static final String REQ_PREFIX = "/organization"; + public static final String ORGANIZATION_LIST = "/organization/list"; + public static final String ORGANIZATION_LIST_ALL = "/organization/list-all"; + public static final String ORGANIZATION_DEFAULT = "/organization/default"; + public static final String ORGANIZATION_LIST_MEMBER = "/organization/list-member"; + public static final String ORGANIZATION_ADD_MEMBER = "/organization/add-member"; + public static final String ORGANIZATION_REMOVE_MEMBER = "/organization/remove-member"; + public static final String ORGANIZATION_LIST_PROJECT = "/organization/list-project"; @Test @Order(0) @Sql(scripts = {"/dml/init_organization.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) - public void testListOrganization() throws Exception { + public void testListOrganizationSuccess() throws Exception { OrganizationRequest organizationRequest = new OrganizationRequest(); organizationRequest.setCurrent(1); organizationRequest.setPageSize(10); organizationRequest.setKeyword("default"); - mockMvc.perform(MockMvcRequestBuilders.post(REQ_PREFIX + "/list") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(organizationRequest)) - .contentType(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultHandlers.print()); + MvcResult mvcResult = this.responsePost(ORGANIZATION_LIST, organizationRequest); + // 获取返回值 + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JsonUtils.parseObject(returnData, ResultHolder.class); + // 返回请求正常 + Assertions.assertNotNull(resultHolder); + Pager pageData = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class); + // 返回值不为空 + Assertions.assertNotNull(pageData); + // 返回值的页码和当前页码相同 + Assertions.assertEquals(pageData.getCurrent(), organizationRequest.getCurrent()); + // 返回的数据量不超过规定要返回的数据量相同 + Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(pageData.getList())).size() <= organizationRequest.getPageSize()); + // 返回值中取出第一条数据, 并判断是否包含关键字default + OrganizationDTO organizationDTO = JSON.parseArray(JSON.toJSONString(pageData.getList()), OrganizationDTO.class).get(0); + Assertions.assertTrue(StringUtils.contains(organizationDTO.getName(), organizationRequest.getKeyword()) + || StringUtils.contains(organizationDTO.getId(), organizationRequest.getKeyword())); } @Test @Order(1) - public void testListAllOrganization() throws Exception { + public void testListOrganizationEmptySuccess() throws Exception { OrganizationRequest organizationRequest = new OrganizationRequest(); organizationRequest.setCurrent(1); organizationRequest.setPageSize(10); - mockMvc.perform(MockMvcRequestBuilders.post(REQ_PREFIX + "/list-all") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(organizationRequest)) - .contentType(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultHandlers.print()); + organizationRequest.setKeyword("default-x"); + MvcResult mvcResult = this.responsePost(ORGANIZATION_LIST, organizationRequest); + // 获取返回值 + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JsonUtils.parseObject(returnData, ResultHolder.class); + // 返回请求正常 + Assertions.assertNotNull(resultHolder); + Pager pageData = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class); + // 返回值不为空 + Assertions.assertNotNull(pageData); + // 返回值的页码和当前页码相同 + Assertions.assertEquals(pageData.getCurrent(), organizationRequest.getCurrent()); + // 返回的数据量为0条 + Assertions.assertEquals(0, pageData.getTotal()); } + @Test + @Order(1) + public void testListOrganizationError() throws Exception { + // 页码有误 + OrganizationRequest organizationRequest = new OrganizationRequest(); + organizationRequest.setCurrent(0); + organizationRequest.setPageSize(10); + this.requestPost(ORGANIZATION_LIST, organizationRequest, status().isBadRequest()); + // 页数有误 + organizationRequest = new OrganizationRequest(); + organizationRequest.setCurrent(1); + organizationRequest.setPageSize(1); + this.requestPost(ORGANIZATION_LIST, organizationRequest, status().isBadRequest()); + } + + @Test @Order(2) - public void testGetDefaultOrganization() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get(REQ_PREFIX + "/default") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken)) - .andDo(MockMvcResultHandlers.print()); + public void testListAllOrganizationSuccess() throws Exception { + MvcResult mvcResult = this.responsePost(ORGANIZATION_LIST_ALL, null); + // 获取返回值 + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JsonUtils.parseObject(returnData, ResultHolder.class); + // 返回值不为空 + Assertions.assertNotNull(resultHolder); + // 返回总条数是否为init_organization.sql中的数据总数 + Assertions.assertEquals(6, JSON.parseArray(JSON.toJSONString(resultHolder.getData())).size()); } @Test @Order(3) - public void testListOrganizationMember() throws Exception { + public void testListAllOrganizationError() throws Exception { + this.requestGet(ORGANIZATION_LIST_ALL, status().isMethodNotAllowed()); + } + + @Test + @Order(4) + public void testListOrganizationMemberSuccess() throws Exception { OrganizationRequest organizationRequest = new OrganizationRequest(); organizationRequest.setCurrent(1); organizationRequest.setPageSize(10); organizationRequest.setKeyword("admin"); organizationRequest.setOrganizationId("default-organization-2"); - mockMvc.perform(MockMvcRequestBuilders.post(REQ_PREFIX + "/list-member") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(organizationRequest)) - .contentType(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultHandlers.print()); - } - - @Test - @Order(4) - public void testAddOrganizationMemberPass() throws Exception { - OrganizationMemberRequest organizationMemberRequest = new OrganizationMemberRequest(); - organizationMemberRequest.setOrganizationId("default-organization-3"); - organizationMemberRequest.setMemberIds(Arrays.asList("admin", "default-admin")); - mockMvc.perform(MockMvcRequestBuilders.post(REQ_PREFIX + "/add-member") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(organizationMemberRequest)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); + MvcResult mvcResult = this.responsePost(ORGANIZATION_LIST_MEMBER, organizationRequest); + // 获取返回值 + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JsonUtils.parseObject(returnData, ResultHolder.class); + // 返回请求正常 + Assertions.assertNotNull(resultHolder); + Pager pageData = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class); + // 返回值不为空 + Assertions.assertNotNull(pageData); + // 返回值的页码和当前页码相同 + Assertions.assertEquals(pageData.getCurrent(), organizationRequest.getCurrent()); + // 返回的数据量不超过规定要返回的数据量相同 + Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(pageData.getList())).size() <= organizationRequest.getPageSize()); + // 返回值中取出第一条数据, 并判断是否包含关键字admin + UserExtend userExtend = JSON.parseArray(JSON.toJSONString(pageData.getList()), UserExtend.class).get(0); + Assertions.assertTrue(StringUtils.contains(userExtend.getName(), organizationRequest.getKeyword()) + || StringUtils.contains(userExtend.getEmail(), organizationRequest.getKeyword()) + || StringUtils.contains(userExtend.getPhone(), organizationRequest.getKeyword())); } @Test @Order(5) - public void testAddOrganizationMemberNotPass() throws Exception { - OrganizationMemberRequest organizationMemberRequest = new OrganizationMemberRequest(); - organizationMemberRequest.setOrganizationId("default-organization-3"); - organizationMemberRequest.setMemberIds(Collections.emptyList()); - mockMvc.perform(MockMvcRequestBuilders.post(REQ_PREFIX + "/add-member") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(organizationMemberRequest)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + public void testListOrganizationMemberError() throws Exception { + // 页码有误 + OrganizationRequest organizationRequest = new OrganizationRequest(); + organizationRequest.setCurrent(0); + organizationRequest.setPageSize(10); + organizationRequest.setKeyword("admin"); + organizationRequest.setOrganizationId("default-organization-2"); + this.requestPost(ORGANIZATION_LIST_MEMBER, organizationRequest, status().isBadRequest()); + // 页数有误 + organizationRequest = new OrganizationRequest(); + organizationRequest.setCurrent(1); + organizationRequest.setPageSize(1); + organizationRequest.setKeyword("admin"); + organizationRequest.setOrganizationId("default-organization-2"); + this.requestPost(ORGANIZATION_LIST_MEMBER, organizationRequest, status().isBadRequest()); } + @Test @Order(6) - public void testRemoveOrganizationMember() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get(REQ_PREFIX + "/remove-member/default-organization-3/admin") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken)) - .andExpect(status().isOk()); + public void testAddOrganizationMemberSuccess() throws Exception { + OrganizationMemberRequest organizationMemberRequest = new OrganizationMemberRequest(); + organizationMemberRequest.setOrganizationId("default-organization-3"); + organizationMemberRequest.setMemberIds(Arrays.asList("admin", "default-admin")); + this.requestPost(ORGANIZATION_ADD_MEMBER, organizationMemberRequest, status().isOk()); + // 批量添加成员成功后, 验证是否添加成功 + OrganizationRequest organizationRequest = new OrganizationRequest(); + organizationRequest.setCurrent(1); + organizationRequest.setPageSize(10); + organizationRequest.setKeyword("admin"); + organizationRequest.setOrganizationId("default-organization-3"); + MvcResult mvcResult = this.responsePost(ORGANIZATION_LIST_MEMBER, organizationRequest); + // 获取返回值 + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JsonUtils.parseObject(returnData, ResultHolder.class); + // 返回请求正常 + Assertions.assertNotNull(resultHolder); + Pager pageData = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class); + // 返回值不为空 + Assertions.assertNotNull(pageData); + // 返回值的页码和当前页码相同 + Assertions.assertEquals(pageData.getCurrent(), organizationRequest.getCurrent()); + // 返回的数据量不超过规定要返回的数据量相同 + Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(pageData.getList())).size() <= organizationRequest.getPageSize()); + // 返回值中取出第一条数据, 并判断是否包含关键字admin + UserExtend userExtend = JSON.parseArray(JSON.toJSONString(pageData.getList()), UserExtend.class).get(0); + Assertions.assertTrue(StringUtils.contains(userExtend.getName(), organizationRequest.getKeyword()) + || StringUtils.contains(userExtend.getEmail(), organizationRequest.getKeyword()) + || StringUtils.contains(userExtend.getPhone(), organizationRequest.getKeyword())); } @Test @Order(7) - public void testGetOrganizationProject() throws Exception { + public void testAddOrganizationMemberError() throws Exception { + // 成员选择为空 + OrganizationMemberRequest organizationMemberRequest = new OrganizationMemberRequest(); + organizationMemberRequest.setOrganizationId("default-organization-3"); + organizationMemberRequest.setMemberIds(Collections.emptyList()); + this.requestPost(ORGANIZATION_ADD_MEMBER, organizationMemberRequest, status().isBadRequest()); + // 组织不存在 + organizationMemberRequest = new OrganizationMemberRequest(); + organizationMemberRequest.setOrganizationId("default-organization-x"); + organizationMemberRequest.setMemberIds(Arrays.asList("admin", "default-admin")); + this.requestPost(ORGANIZATION_ADD_MEMBER, organizationMemberRequest, status().is5xxServerError()); + } + + @Test + @Order(8) + public void testRemoveOrganizationMemberSuccess() throws Exception { + this.requestGet(ORGANIZATION_REMOVE_MEMBER + "/default-organization-3/admin", status().isOk()); + } + + @Test + @Order(9) + public void testRemoveOrganizationMemberError() throws Exception { + // 组织不存在 + this.requestGet(ORGANIZATION_REMOVE_MEMBER + "/default-organization-x/admin", status().is5xxServerError()); + // 用户不存在 + this.requestGet(ORGANIZATION_REMOVE_MEMBER + "/default-organization-3/admin-x", status().is5xxServerError()); + // 用户组织关系不存在 + this.requestGet(ORGANIZATION_REMOVE_MEMBER + "/default-organization-4/default-admin", status().is5xxServerError()); + } + + @Test + @Order(10) + public void testGetOrganizationProjectSuccess() throws Exception { ProjectRequest projectRequest = new ProjectRequest(); projectRequest.setCurrent(1); projectRequest.setPageSize(10); projectRequest.setOrganizationId("default-organization-2"); - mockMvc.perform(MockMvcRequestBuilders.post(REQ_PREFIX + "/list-project") - .header(SessionConstants.HEADER_TOKEN, sessionId) - .header(SessionConstants.CSRF_TOKEN, csrfToken) - .content(JSON.toJSONString(projectRequest)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(MockMvcResultHandlers.print()); + MvcResult mvcResult = this.responsePost(ORGANIZATION_LIST_PROJECT, projectRequest); + // 获取返回值 + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JsonUtils.parseObject(returnData, ResultHolder.class); + // 返回请求正常 + Assertions.assertNotNull(resultHolder); + Pager pageData = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), Pager.class); + // 返回值不为空 + Assertions.assertNotNull(pageData); + // 返回值的页码和当前页码相同 + Assertions.assertEquals(pageData.getCurrent(), projectRequest.getCurrent()); + // 返回的数据量不超过规定要返回的数据量相同 + Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(pageData.getList())).size() <= projectRequest.getPageSize()); + } + + @Test + @Order(11) + public void testGetOrganizationProjectError() throws Exception { + // 页码有误 + ProjectRequest projectRequest = new ProjectRequest(); + projectRequest.setCurrent(0); + projectRequest.setPageSize(10); + projectRequest.setOrganizationId("default-organization-2"); + this.requestPost(ORGANIZATION_LIST_PROJECT, projectRequest, status().isBadRequest()); + // 页数有误 + projectRequest = new ProjectRequest(); + projectRequest.setCurrent(1); + projectRequest.setPageSize(1); + projectRequest.setOrganizationId("default-organization-2"); + this.requestPost(ORGANIZATION_LIST_PROJECT, projectRequest, status().isBadRequest()); + } + + @Test + @Order(12) + public void testGetDefaultOrganizationSuccess() throws Exception { + MvcResult mvcResult = this.responseGet(); + // 获取返回值 + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + ResultHolder resultHolder = JsonUtils.parseObject(returnData, ResultHolder.class); + // 返回请求正常 + Assertions.assertNotNull(resultHolder); + OrganizationDTO defaultOrg = JSON.parseObject(JSON.toJSONString(resultHolder.getData()), OrganizationDTO.class); + // 返回值不为空 + Assertions.assertNotNull(defaultOrg); + // 返回数据NUM是否为默认100001 + Assertions.assertEquals(defaultOrg.getNum(), 100001L); + } + + @Test + @Order(13) + public void testGetDefaultOrganizationError() throws Exception { + this.requestPost(ORGANIZATION_DEFAULT, null, status().isMethodNotAllowed()); + } + + private void requestPost(String url, Object param, ResultMatcher resultMatcher) throws Exception { + mockMvc.perform(MockMvcRequestBuilders.post(url) + .header(SessionConstants.HEADER_TOKEN, sessionId) + .header(SessionConstants.CSRF_TOKEN, csrfToken) + .content(JSON.toJSONString(param)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(resultMatcher).andDo(print()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + } + + private MvcResult responsePost(String url, Object param) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders.post(url) + .header(SessionConstants.HEADER_TOKEN, sessionId) + .header(SessionConstants.CSRF_TOKEN, csrfToken) + .content(JSON.toJSONString(param)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andDo(print()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andReturn(); + } + + private void requestGet(String url, ResultMatcher resultMatcher) throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get(url) + .header(SessionConstants.HEADER_TOKEN, sessionId) + .header(SessionConstants.CSRF_TOKEN, csrfToken) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(resultMatcher).andDo(print()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + } + + private MvcResult responseGet() throws Exception { + return mockMvc.perform(MockMvcRequestBuilders.get(OrganizationControllerTests.ORGANIZATION_DEFAULT) + .header(SessionConstants.HEADER_TOKEN, sessionId) + .header(SessionConstants.CSRF_TOKEN, csrfToken) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andDo(print()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)).andReturn(); } } diff --git a/backend/services/system-setting/src/test/java/io/metersphere/system/job/CleanOrganizationJobTests.java b/backend/services/system-setting/src/test/java/io/metersphere/system/job/CleanOrganizationJobTests.java new file mode 100644 index 0000000000..0bb3959878 --- /dev/null +++ b/backend/services/system-setting/src/test/java/io/metersphere/system/job/CleanOrganizationJobTests.java @@ -0,0 +1,35 @@ +package io.metersphere.system.job; + +import jakarta.annotation.Resource; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; + +@SpringBootTest +@AutoConfigureMockMvc +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class CleanOrganizationJobTests { + + @Resource + CleanOrganizationJob cleanOrganizationJob; + + @Test + @Order(0) + @Sql(scripts = {"/dml/init_clean_organization.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED), executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + public void cleanupProjectSuccess(){ + //TODO + cleanOrganizationJob.cleanOrganization(); + } + + @Test + @Order(1) + public void cleanupProjectError(){ + //TODO + cleanOrganizationJob.cleanOrganization(); + } +} diff --git a/backend/services/system-setting/src/test/resources/dml/init_clean_organization.sql b/backend/services/system-setting/src/test/resources/dml/init_clean_organization.sql new file mode 100644 index 0000000000..cea43d4aaf --- /dev/null +++ b/backend/services/system-setting/src/test/resources/dml/init_clean_organization.sql @@ -0,0 +1,5 @@ +# 定时删除组织列表数据准备 +INSERT INTO organization(id, name, description, create_time, update_time, create_user, update_user, deleted, delete_user, delete_time) VALUE + ('default-organization-delete2', 'default-delete2', 'XXX-delete2', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'admin', 0, null, null); +INSERT INTO organization(id, name, description, create_time, update_time, create_user, update_user, deleted, delete_user, delete_time) VALUE + ('default-organization-delete1', 'default-delete1', 'XXX-delete1', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin', 'admin', 1, 'admin', 1683464436000); \ No newline at end of file diff --git a/backend/services/system-setting/src/test/resources/dml/init_organization.sql b/backend/services/system-setting/src/test/resources/dml/init_organization.sql index fcbcfaf0d8..664b5fa074 100644 --- a/backend/services/system-setting/src/test/resources/dml/init_organization.sql +++ b/backend/services/system-setting/src/test/resources/dml/init_organization.sql @@ -14,6 +14,6 @@ INSERT INTO organization(id, name, description, create_time, update_time, create INSERT INTO user(id, name, email, password, create_time, update_time, language, last_organization_id, phone, source, last_project_id, create_user, update_user) VALUE ('default-admin', 'default-Administrator', 'admin-default@metersphere.io', MD5('metersphere'), UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, NULL, NUll, '', 'LOCAL', NULL, 'admin', 'admin'); INSERT INTO user_role_relation (id, user_id, role_id, source_id, create_time, create_user) VALUE - (UUID(), 'admin', 'admin', 'default-organization-2', UNIX_TIMESTAMP() * 1000, 'admin'); + (UUID(), 'default-admin', 'org_admin', 'default-organization-2', UNIX_TIMESTAMP() * 1000, 'admin'); INSERT INTO project (id, num, organization_id, name, description, create_user, update_user, create_time, update_time) VALUE ('default-project', null, 'default-organization-2', '默认项目', '系统默认创建的项目', 'admin', 'admin', UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000); \ No newline at end of file