Commit 5ebb96abf86dbdd817361525db999509e724bddd

Authored by zichun
1 parent 9e549f65

feat(usr): 修改用户 Controller 与管理员权限前置 REQ-USR-002

backend/src/main/java/com/xly/erp/modules/usr/controller/UsrUserController.java
@@ -5,10 +5,13 @@ import com.xly.erp.common.response.Result; @@ -5,10 +5,13 @@ import com.xly.erp.common.response.Result;
5 import com.xly.erp.common.response.ResultCode; 5 import com.xly.erp.common.response.ResultCode;
6 import com.xly.erp.common.security.SecurityUtil; 6 import com.xly.erp.common.security.SecurityUtil;
7 import com.xly.erp.modules.usr.dto.CreateUserDTO; 7 import com.xly.erp.modules.usr.dto.CreateUserDTO;
  8 +import com.xly.erp.modules.usr.dto.UpdateUserDTO;
8 import com.xly.erp.modules.usr.service.UsrUserService; 9 import com.xly.erp.modules.usr.service.UsrUserService;
9 import jakarta.validation.Valid; 10 import jakarta.validation.Valid;
10 import java.util.Map; 11 import java.util.Map;
  12 +import org.springframework.web.bind.annotation.PathVariable;
11 import org.springframework.web.bind.annotation.PostMapping; 13 import org.springframework.web.bind.annotation.PostMapping;
  14 +import org.springframework.web.bind.annotation.PutMapping;
12 import org.springframework.web.bind.annotation.RequestBody; 15 import org.springframework.web.bind.annotation.RequestBody;
13 import org.springframework.web.bind.annotation.RequestMapping; 16 import org.springframework.web.bind.annotation.RequestMapping;
14 import org.springframework.web.bind.annotation.RestController; 17 import org.springframework.web.bind.annotation.RestController;
@@ -42,4 +45,17 @@ public class UsrUserController { @@ -42,4 +45,17 @@ public class UsrUserController {
42 Integer newId = usrUserService.createUser(dto); 45 Integer newId = usrUserService.createUser(dto);
43 return Result.success(Map.of("id", newId)); 46 return Result.success(Map.of("id", newId));
44 } 47 }
  48 +
  49 + /**
  50 + * 修改用户:仅超级管理员可调用(spec § 3.9,先于业务校验)。REQ-USR-002。
  51 + */
  52 + @PutMapping("/users/{id}")
  53 + public Result<Map<String, Object>> updateUser(@PathVariable Integer id,
  54 + @Valid @RequestBody UpdateUserDTO dto) {
  55 + if (!ADMIN_USER_TYPE.equals(SecurityUtil.currentUserType())) {
  56 + throw new BusinessException(ResultCode.FORBIDDEN);
  57 + }
  58 + Integer updatedId = usrUserService.updateUser(id, dto);
  59 + return Result.success(Map.of("id", updatedId));
  60 + }
45 } 61 }
backend/src/test/java/com/xly/erp/modules/usr/controller/UsrUserControllerTest.java
@@ -4,14 +4,19 @@ import static org.mockito.ArgumentMatchers.any; @@ -4,14 +4,19 @@ import static org.mockito.ArgumentMatchers.any;
4 import static org.mockito.Mockito.never; 4 import static org.mockito.Mockito.never;
5 import static org.mockito.Mockito.verify; 5 import static org.mockito.Mockito.verify;
6 import static org.mockito.Mockito.when; 6 import static org.mockito.Mockito.when;
  7 +import static org.mockito.ArgumentMatchers.eq;
7 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 8 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
  9 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
8 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 10 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
9 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 11 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
10 12
11 import com.fasterxml.jackson.databind.ObjectMapper; 13 import com.fasterxml.jackson.databind.ObjectMapper;
  14 +import com.xly.erp.common.exception.BusinessException;
12 import com.xly.erp.common.exception.GlobalExceptionHandler; 15 import com.xly.erp.common.exception.GlobalExceptionHandler;
  16 +import com.xly.erp.common.response.ResultCode;
13 import com.xly.erp.common.security.SecurityUtil; 17 import com.xly.erp.common.security.SecurityUtil;
14 import com.xly.erp.modules.usr.dto.CreateUserDTO; 18 import com.xly.erp.modules.usr.dto.CreateUserDTO;
  19 +import com.xly.erp.modules.usr.dto.UpdateUserDTO;
15 import com.xly.erp.modules.usr.service.UsrUserService; 20 import com.xly.erp.modules.usr.service.UsrUserService;
16 import org.junit.jupiter.api.AfterEach; 21 import org.junit.jupiter.api.AfterEach;
17 import org.junit.jupiter.api.BeforeEach; 22 import org.junit.jupiter.api.BeforeEach;
@@ -99,4 +104,68 @@ class UsrUserControllerTest { @@ -99,4 +104,68 @@ class UsrUserControllerTest {
99 .andExpect(jsonPath("$.code").value(40001)); 104 .andExpect(jsonPath("$.code").value(40001));
100 verify(usrUserService, never()).createUser(any(CreateUserDTO.class)); 105 verify(usrUserService, never()).createUser(any(CreateUserDTO.class));
101 } 106 }
  107 +
  108 + // ---------------- REQ-USR-002 T4:修改用户 Controller + 管理员前置 ----------------
  109 +
  110 + private String validUpdateBody() throws Exception {
  111 + UpdateUserDTO dto = new UpdateUserDTO();
  112 + dto.setSUserType("普通用户");
  113 + dto.setSLanguage("中文");
  114 + return objectMapper.writeValueAsString(dto);
  115 + }
  116 +
  117 + @Test
  118 + void adminUpdateReturnsCodeZeroWithId() throws Exception {
  119 + securityUtilMock.when(SecurityUtil::currentUserType).thenReturn("超级管理员");
  120 + when(usrUserService.updateUser(eq(55), any(UpdateUserDTO.class))).thenReturn(55);
  121 +
  122 + mockMvc.perform(put("/api/usr/users/55")
  123 + .contentType("application/json")
  124 + .content(validUpdateBody()))
  125 + .andExpect(status().isOk())
  126 + .andExpect(jsonPath("$.code").value(0))
  127 + .andExpect(jsonPath("$.data.id").value(55))
  128 + .andExpect(jsonPath("$.data.sPassword").doesNotExist())
  129 + .andExpect(jsonPath("$.data.password").doesNotExist());
  130 + }
  131 +
  132 + @Test
  133 + void nonAdminUpdateReturns40301() throws Exception {
  134 + securityUtilMock.when(SecurityUtil::currentUserType).thenReturn("普通用户");
  135 +
  136 + mockMvc.perform(put("/api/usr/users/55")
  137 + .contentType("application/json")
  138 + .content(validUpdateBody()))
  139 + .andExpect(status().isOk())
  140 + .andExpect(jsonPath("$.code").value(40301));
  141 + verify(usrUserService, never()).updateUser(any(), any(UpdateUserDTO.class));
  142 + }
  143 +
  144 + @Test
  145 + void invalidBodyUpdateReturns40001() throws Exception {
  146 + securityUtilMock.when(SecurityUtil::currentUserType).thenReturn("超级管理员");
  147 + UpdateUserDTO dto = new UpdateUserDTO();
  148 + dto.setSLanguage("中文"); // 缺 sUserType → @NotBlank 失败
  149 + String body = objectMapper.writeValueAsString(dto);
  150 +
  151 + mockMvc.perform(put("/api/usr/users/55")
  152 + .contentType("application/json")
  153 + .content(body))
  154 + .andExpect(status().isOk())
  155 + .andExpect(jsonPath("$.code").value(40001));
  156 + verify(usrUserService, never()).updateUser(any(), any(UpdateUserDTO.class));
  157 + }
  158 +
  159 + @Test
  160 + void userNotFoundReturns40401() throws Exception {
  161 + securityUtilMock.when(SecurityUtil::currentUserType).thenReturn("超级管理员");
  162 + when(usrUserService.updateUser(eq(404), any(UpdateUserDTO.class)))
  163 + .thenThrow(new BusinessException(ResultCode.NOT_FOUND));
  164 +
  165 + mockMvc.perform(put("/api/usr/users/404")
  166 + .contentType("application/json")
  167 + .content(validUpdateBody()))
  168 + .andExpect(status().isOk())
  169 + .andExpect(jsonPath("$.code").value(40401));
  170 + }
102 } 171 }