Commit 3a64246d76e6df790fee4ac3353c65f5bf771de3

Authored by zichun
1 parent 3f1b9e89

feat(usr): PUT /api/users/{id} controller REQ-USR-002

backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java
... ... @@ -2,11 +2,14 @@ package com.xly.erp.module.usr.controller;
2 2  
3 3 import com.xly.erp.common.response.ApiResponse;
4 4 import com.xly.erp.module.usr.dto.UserCreateDTO;
  5 +import com.xly.erp.module.usr.dto.UserUpdateDTO;
5 6 import com.xly.erp.module.usr.service.UserService;
6 7 import com.xly.erp.module.usr.vo.UserVO;
7 8 import jakarta.validation.Valid;
8 9 import lombok.RequiredArgsConstructor;
  10 +import org.springframework.web.bind.annotation.PathVariable;
9 11 import org.springframework.web.bind.annotation.PostMapping;
  12 +import org.springframework.web.bind.annotation.PutMapping;
10 13 import org.springframework.web.bind.annotation.RequestBody;
11 14 import org.springframework.web.bind.annotation.RequestMapping;
12 15 import org.springframework.web.bind.annotation.RestController;
... ... @@ -23,4 +26,10 @@ public class UserController {
23 26 public ApiResponse<UserVO> create(@Valid @RequestBody UserCreateDTO dto) {
24 27 return ApiResponse.ok(userService.create(dto));
25 28 }
  29 +
  30 + /** REQ-USR-002 用户修改 — REQ-USR-004 完成后追加 @PreAuthorize("hasAuthority('USR:UPDATE')") */
  31 + @PutMapping("/{id}")
  32 + public ApiResponse<UserVO> update(@PathVariable Integer id, @Valid @RequestBody UserUpdateDTO dto) {
  33 + return ApiResponse.ok(userService.update(id, dto));
  34 + }
26 35 }
... ...
backend/src/test/java/com/xly/erp/module/usr/controller/UserControllerIT.java
... ... @@ -2,6 +2,7 @@ package com.xly.erp.module.usr.controller;
2 2  
3 3 import com.fasterxml.jackson.databind.ObjectMapper;
4 4 import com.xly.erp.module.usr.dto.UserCreateDTO;
  5 +import com.xly.erp.module.usr.dto.UserUpdateDTO;
5 6 import com.xly.erp.module.usr.entity.PermissionCategoryEntity;
6 7 import com.xly.erp.module.usr.entity.StaffEntity;
7 8 import com.xly.erp.module.usr.entity.UserEntity;
... ... @@ -26,6 +27,7 @@ import java.util.List;
26 27 import static com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery;
27 28 import static org.assertj.core.api.Assertions.assertThat;
28 29 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
  30 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
29 31 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
30 32 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
31 33  
... ... @@ -181,4 +183,186 @@ class UserControllerIT {
181 183 .andExpect(status().isOk())
182 184 .andExpect(jsonPath("$.data.sPasswordHash").doesNotExist());
183 185 }
  186 +
  187 + // ============================================================
  188 + // REQ-USR-002 PUT 系列
  189 + // ============================================================
  190 +
  191 + private Integer insertUser(String userName, Integer staffId, List<Integer> categoryIds) {
  192 + UserEntity u = new UserEntity();
  193 + u.setSUserNo("uno_" + System.nanoTime());
  194 + u.setSUserName(userName);
  195 + u.setIStaffId(staffId);
  196 + u.setSUserType("普通用户");
  197 + u.setSLanguage("zh");
  198 + u.setBCanModifyDocs(false);
  199 + u.setSPasswordHash("$2a$10$origUser");
  200 + u.setBDeleted(false);
  201 + u.setTCreateDate(LocalDateTime.now());
  202 + userMapper.insert(u);
  203 + for (Integer cid : categoryIds) {
  204 + UserPermissionEntity up = new UserPermissionEntity();
  205 + up.setIUserId(u.getIIncrement());
  206 + up.setICategoryId(cid);
  207 + up.setTCreateDate(LocalDateTime.now());
  208 + userPermissionMapper.insert(up);
  209 + }
  210 + return u.getIIncrement();
  211 + }
  212 +
  213 + private UserUpdateDTO updateDto(Integer staffId, List<Integer> permissionIds) {
  214 + UserUpdateDTO d = new UserUpdateDTO();
  215 + d.setIStaffId(staffId);
  216 + d.setSUserType("超级管理员");
  217 + d.setSLanguage("en");
  218 + d.setBCanModifyDocs(true);
  219 + d.setPermissionCategoryIds(permissionIds);
  220 + return d;
  221 + }
  222 +
  223 + @Test
  224 + void put_validUpdate_returns200_andDbReflects() throws Exception {
  225 + Integer staff1 = insertStaff();
  226 + Integer staff2 = insertStaff();
  227 + Integer cat1 = insertCategory();
  228 + Integer cat2 = insertCategory();
  229 + Integer cat3 = insertCategory();
  230 + Integer userId = insertUser("upd_" + System.nanoTime(), staff1, List.of(cat1, cat2, cat3));
  231 +
  232 + Integer catNew1 = insertCategory();
  233 + Integer catNew2 = insertCategory();
  234 +
  235 + UserUpdateDTO dto = updateDto(staff2, List.of(catNew1, catNew2));
  236 +
  237 + mockMvc.perform(put("/api/users/" + userId)
  238 + .contentType(MediaType.APPLICATION_JSON)
  239 + .content(json(dto)))
  240 + .andExpect(status().isOk())
  241 + .andExpect(jsonPath("$.code").value(200))
  242 + .andExpect(jsonPath("$.data.iStaffId").value(staff2))
  243 + .andExpect(jsonPath("$.data.sUserType").value("超级管理员"))
  244 + .andExpect(jsonPath("$.data.sLanguage").value("en"));
  245 +
  246 + UserEntity reloaded = userMapper.selectById(userId);
  247 + assertThat(reloaded.getIStaffId()).isEqualTo(staff2);
  248 + assertThat(reloaded.getSUserType()).isEqualTo("超级管理员");
  249 + assertThat(reloaded.getBCanModifyDocs()).isTrue();
  250 + Long upCount = userPermissionMapper.selectCount(lambdaQuery(UserPermissionEntity.class)
  251 + .eq(UserPermissionEntity::getIUserId, userId));
  252 + assertThat(upCount).isEqualTo(2L); // 原 3 删 + 新 2 插
  253 + }
  254 +
  255 + @Test
  256 + void put_clearStaffId_setsNull() throws Exception {
  257 + Integer staffId = insertStaff();
  258 + Integer userId = insertUser("clr_" + System.nanoTime(), staffId, List.of());
  259 +
  260 + UserUpdateDTO dto = updateDto(null, List.of());
  261 +
  262 + mockMvc.perform(put("/api/users/" + userId)
  263 + .contentType(MediaType.APPLICATION_JSON)
  264 + .content(json(dto)))
  265 + .andExpect(status().isOk())
  266 + .andExpect(jsonPath("$.code").value(200));
  267 +
  268 + assertThat(userMapper.selectById(userId).getIStaffId()).isNull();
  269 + }
  270 +
  271 + @Test
  272 + void put_emptyPermissionCategoryIds_clearsAssociations() throws Exception {
  273 + Integer cat1 = insertCategory();
  274 + Integer cat2 = insertCategory();
  275 + Integer userId = insertUser("emp_" + System.nanoTime(), null, List.of(cat1, cat2));
  276 +
  277 + UserUpdateDTO dto = updateDto(null, List.of());
  278 +
  279 + mockMvc.perform(put("/api/users/" + userId)
  280 + .contentType(MediaType.APPLICATION_JSON)
  281 + .content(json(dto)))
  282 + .andExpect(status().isOk())
  283 + .andExpect(jsonPath("$.code").value(200));
  284 +
  285 + Long upCount = userPermissionMapper.selectCount(lambdaQuery(UserPermissionEntity.class)
  286 + .eq(UserPermissionEntity::getIUserId, userId));
  287 + assertThat(upCount).isZero();
  288 + }
  289 +
  290 + @Test
  291 + void put_targetNotFound_returns40431() throws Exception {
  292 + UserUpdateDTO dto = updateDto(null, List.of());
  293 + mockMvc.perform(put("/api/users/999999")
  294 + .contentType(MediaType.APPLICATION_JSON)
  295 + .content(json(dto)))
  296 + .andExpect(status().isOk())
  297 + .andExpect(jsonPath("$.code").value(40431));
  298 + }
  299 +
  300 + @Test
  301 + void put_staffNotFound_returns40421() throws Exception {
  302 + Integer userId = insertUser("nost_" + System.nanoTime(), null, List.of());
  303 + UserUpdateDTO dto = updateDto(999999, List.of());
  304 + mockMvc.perform(put("/api/users/" + userId)
  305 + .contentType(MediaType.APPLICATION_JSON)
  306 + .content(json(dto)))
  307 + .andExpect(status().isOk())
  308 + .andExpect(jsonPath("$.code").value(40421));
  309 + }
  310 +
  311 + @Test
  312 + void put_permissionCategoryNotFound_returns40422() throws Exception {
  313 + Integer userId = insertUser("noc_" + System.nanoTime(), null, List.of());
  314 + UserUpdateDTO dto = updateDto(null, List.of(999999));
  315 + mockMvc.perform(put("/api/users/" + userId)
  316 + .contentType(MediaType.APPLICATION_JSON)
  317 + .content(json(dto)))
  318 + .andExpect(status().isOk())
  319 + .andExpect(jsonPath("$.code").value(40422));
  320 + }
  321 +
  322 + @Test
  323 + void put_missingRequired_returns40010() throws Exception {
  324 + Integer userId = insertUser("miss_" + System.nanoTime(), null, List.of());
  325 + UserUpdateDTO dto = updateDto(null, List.of());
  326 + dto.setSUserType(null); // 必填缺失
  327 + mockMvc.perform(put("/api/users/" + userId)
  328 + .contentType(MediaType.APPLICATION_JSON)
  329 + .content(json(dto)))
  330 + .andExpect(status().isOk())
  331 + .andExpect(jsonPath("$.code").value(40010));
  332 + }
  333 +
  334 + @Test
  335 + void put_ignoresProtectedFields_doesNotChangeUserNoOrName() throws Exception {
  336 + String origName = "prot_" + System.nanoTime();
  337 + Integer userId = insertUser(origName, null, List.of());
  338 + String origNo = userMapper.selectById(userId).getSUserNo();
  339 + String origHash = userMapper.selectById(userId).getSPasswordHash();
  340 +
  341 + // 手工拼 body 含保护字段
  342 + String body = """
  343 + {
  344 + "sUserNo": "hijack",
  345 + "sUserName": "hijack",
  346 + "sPasswordHash": "$2a$10$hijacked",
  347 + "iStaffId": null,
  348 + "sUserType": "超级管理员",
  349 + "sLanguage": "zh-TW",
  350 + "bCanModifyDocs": true,
  351 + "permissionCategoryIds": []
  352 + }
  353 + """;
  354 + mockMvc.perform(put("/api/users/" + userId)
  355 + .contentType(MediaType.APPLICATION_JSON)
  356 + .content(body))
  357 + .andExpect(status().isOk())
  358 + .andExpect(jsonPath("$.code").value(200));
  359 +
  360 + UserEntity reloaded = userMapper.selectById(userId);
  361 + assertThat(reloaded.getSUserNo()).isEqualTo(origNo);
  362 + assertThat(reloaded.getSUserName()).isEqualTo(origName);
  363 + assertThat(reloaded.getSPasswordHash()).isEqualTo(origHash);
  364 + // 但其他字段已修改
  365 + assertThat(reloaded.getSUserType()).isEqualTo("超级管理员");
  366 + assertThat(reloaded.getSLanguage()).isEqualTo("zh-TW");
  367 + }
184 368 }
... ...