feat(系统管理): 补充组织管理功能

This commit is contained in:
song-cc-rock 2023-07-04 15:52:29 +08:00 committed by fit2-zhao
parent 0c1cde2a65
commit 5194e587d2
16 changed files with 517 additions and 85 deletions

View File

@ -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 状态码以及业务状态码和错误提示

View File

@ -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

View File

@ -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}之间
organization.create_user.length_range=工作空间创建人长度必须在{min}和{max}之间
and_add_organization_admin=并添加组织管理员
organization_add_member_ids_empty=组织添加成员不能为空
organization_not_exist=组织不存在
organization_member_not_exist=组织成员不存在

View File

@ -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}之間
organization.create_user.length_range=工作空間創建人長度必須在{min}和{max}之間
and_add_organization_admin=並添加組織管理員
organization_add_member_ids_empty=組織添加成員不能爲空
organization_not_exist=組織不存在
organization_member_not_exist=組織成員不存在

View File

@ -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<OrganizationDTO>> list(@Validated @RequestBody OrganizationRequest organizationRequest) {
Page<Object> 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<OrganizationDTO> listAll(@Validated @RequestBody OrganizationRequest organizationRequest) {
return organizationService.list(organizationRequest);
public List<OrganizationDTO> listAll() {
return organizationService.listAll();
}
@PostMapping("/list-member")
@Operation(summary = "获取组织成员")
@RequiresPermissions(PermissionConstants.SYSTEM_USER_READ)
public Pager<List<UserExtend>> listMember(@Validated @RequestBody OrganizationRequest organizationRequest) {
Page<Object> 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<List<ProjectDTO>> listProject(@Validated @RequestBody ProjectRequest projectRequest) {
Page<Object> page = PageHelper.startPage(projectRequest.getCurrent(), projectRequest.getPageSize());

View File

@ -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<User> orgAdmins;
/**
* 组织管理员ID集合(新增, 编辑), 必填
*/
@Schema(title = "组织管理员ID集合")
@NotEmpty(groups = {Created.class, Updated.class}, message = "{member.id.not_empty}")
private List<String> memberIds;
}

View File

@ -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<Organization> organizations = organizationMapper.selectByExample(example);
if (CollectionUtils.isEmpty(organizations)) {
return;
}
organizations.forEach(organization -> {
// TODO 清理组织下的资源
// 删除项目
// 删除用户组, 用户组关系
// 删除环境组
// 删除定时任务
// 操作记录{项目, 组织}
});
// 删除组织
organizationMapper.deleteByExample(example);
}
}

View File

@ -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<OrganizationDTO> list(@Param("request") OrganizationRequest organizationRequest);
/**
* 获取系统下所有组织
* @return 组织列表数据
*/
List<OrganizationDTO> listAll();
/**
* 删除组织
* @param organizationDeleteRequest 组织删除参数
@ -45,4 +52,11 @@ public interface ExtOrganizationMapper {
* @return 组织成员列表数据
*/
List<UserExtend> listMember(@Param("request") OrganizationRequest organizationRequest);
/**
* 获取组织管理员
* @param orgId 组织ID
* @return 组织管理员数据
*/
List<User> getOrgAdminList(String orgId);
}

View File

@ -16,6 +16,10 @@
group by o.id
</select>
<select id="listAll" resultType="io.metersphere.system.dto.OrganizationDTO">
select * from organization
</select>
<sql id="queryWhereCondition">
<where>
<if test="request.keyword != null">
@ -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
<where>
<if test="request.organizationId != null">
urr.source_id = #{request.organizationId}
@ -79,4 +83,11 @@
) temp
group by temp.id
</select>
<select id="getOrgAdminList" resultType="io.metersphere.system.domain.User">
select u.*
from user_role_relation urr
join `user` u on urr.user_id = u.id
where urr.role_id = 'org_admin'and urr.source_id = #{orgId}
</select>
</mapper>

View File

@ -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<String> memberIds;
}

View File

@ -20,6 +20,12 @@ public interface OrganizationService {
*/
List<OrganizationDTO> list(OrganizationRequest organizationRequest);
/**
* 获取系统下所有组织
* @return 列表数据
*/
List<OrganizationDTO> listAll();
/**
* 获取默认组织信息
* @return 默认组织信息

View File

@ -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<OrganizationDTO> list(OrganizationRequest organizationRequest) {
return extOrganizationMapper.list(organizationRequest);
List<OrganizationDTO> organizationDTOS = extOrganizationMapper.list(organizationRequest);
return buildOrgAdminInfo(organizationDTOS);
}
@Override
public List<OrganizationDTO> 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<UserRoleRelation> userRoleRelations = userRoleRelationMapper.selectByExample(example);
if (CollectionUtils.isEmpty(userRoleRelations)) {
throw new MSException(Translator.get("organization_member_not_exist"));
}
userRoleRelationMapper.deleteByExample(example);
}
private List<OrganizationDTO> buildOrgAdminInfo(List<OrganizationDTO> organizationDTOS) {
if (CollectionUtils.isEmpty(organizationDTOS)) {
return organizationDTOS;
}
organizationDTOS.forEach(organizationDTO -> {
List<User> orgAdminList = extOrganizationMapper.getOrgAdminList(organizationDTO.getId());
organizationDTO.setOrgAdmins(orgAdminList);
});
return organizationDTOS;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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);