Commit 8e0ddfdc46bbf3ac0773d33e05df3ac25878f294

Authored by zichun
1 parent 323b1ef4

feat(usr): POST /api/users controller REQ-USR-001

backend/src/main/java/com/xly/erp/module/usr/controller/UserController.java 0 → 100644
  1 +package com.xly.erp.module.usr.controller;
  2 +
  3 +import com.xly.erp.common.response.ApiResponse;
  4 +import com.xly.erp.module.usr.dto.UserCreateDTO;
  5 +import com.xly.erp.module.usr.service.UserService;
  6 +import com.xly.erp.module.usr.vo.UserVO;
  7 +import jakarta.validation.Valid;
  8 +import lombok.RequiredArgsConstructor;
  9 +import org.springframework.web.bind.annotation.PostMapping;
  10 +import org.springframework.web.bind.annotation.RequestBody;
  11 +import org.springframework.web.bind.annotation.RequestMapping;
  12 +import org.springframework.web.bind.annotation.RestController;
  13 +
  14 +@RestController
  15 +@RequestMapping("/api/users")
  16 +@RequiredArgsConstructor
  17 +public class UserController {
  18 +
  19 + private final UserService userService;
  20 +
  21 + /** REQ-USR-001 用户新增 — REQ-USR-004 完成后追加 @PreAuthorize("hasAuthority('USR:CREATE')") */
  22 + @PostMapping
  23 + public ApiResponse<UserVO> create(@Valid @RequestBody UserCreateDTO dto) {
  24 + return ApiResponse.ok(userService.create(dto));
  25 + }
  26 +}
... ...
backend/src/test/java/com/xly/erp/module/usr/controller/UserControllerIT.java 0 → 100644
  1 +package com.xly.erp.module.usr.controller;
  2 +
  3 +import com.fasterxml.jackson.databind.ObjectMapper;
  4 +import com.xly.erp.module.usr.dto.UserCreateDTO;
  5 +import com.xly.erp.module.usr.entity.PermissionCategoryEntity;
  6 +import com.xly.erp.module.usr.entity.StaffEntity;
  7 +import com.xly.erp.module.usr.entity.UserEntity;
  8 +import com.xly.erp.module.usr.entity.UserPermissionEntity;
  9 +import com.xly.erp.module.usr.mapper.PermissionCategoryMapper;
  10 +import com.xly.erp.module.usr.mapper.StaffMapper;
  11 +import com.xly.erp.module.usr.mapper.UserMapper;
  12 +import com.xly.erp.module.usr.mapper.UserPermissionMapper;
  13 +import org.junit.jupiter.api.Test;
  14 +import org.springframework.beans.factory.annotation.Autowired;
  15 +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
  16 +import org.springframework.boot.test.context.SpringBootTest;
  17 +import org.springframework.http.MediaType;
  18 +import org.springframework.test.annotation.Rollback;
  19 +import org.springframework.test.context.ActiveProfiles;
  20 +import org.springframework.test.web.servlet.MockMvc;
  21 +import org.springframework.transaction.annotation.Transactional;
  22 +
  23 +import java.time.LocalDateTime;
  24 +import java.util.List;
  25 +
  26 +import static com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery;
  27 +import static org.assertj.core.api.Assertions.assertThat;
  28 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
  29 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
  30 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  31 +
  32 +@SpringBootTest
  33 +@AutoConfigureMockMvc
  34 +@ActiveProfiles("test")
  35 +@Transactional
  36 +@Rollback
  37 +class UserControllerIT {
  38 +
  39 + @Autowired MockMvc mockMvc;
  40 + @Autowired ObjectMapper objectMapper;
  41 + @Autowired UserMapper userMapper;
  42 + @Autowired StaffMapper staffMapper;
  43 + @Autowired PermissionCategoryMapper permissionCategoryMapper;
  44 + @Autowired UserPermissionMapper userPermissionMapper;
  45 +
  46 + private UserCreateDTO baseDto(String userName) {
  47 + UserCreateDTO d = new UserCreateDTO();
  48 + d.setSUserNo("uno_" + System.nanoTime());
  49 + d.setSUserName(userName);
  50 + d.setSUserType("普通用户");
  51 + d.setSLanguage("zh");
  52 + return d;
  53 + }
  54 +
  55 + private String json(Object o) throws Exception { return objectMapper.writeValueAsString(o); }
  56 +
  57 + private Integer insertStaff() {
  58 + StaffEntity s = new StaffEntity();
  59 + s.setSStaffNo("st_" + System.nanoTime());
  60 + s.setSStaffName("员工A");
  61 + s.setBDeleted(false);
  62 + s.setTCreateDate(LocalDateTime.now());
  63 + staffMapper.insert(s);
  64 + return s.getIIncrement();
  65 + }
  66 +
  67 + private Integer insertCategory() {
  68 + PermissionCategoryEntity p = new PermissionCategoryEntity();
  69 + p.setSCategoryCode("c_" + System.nanoTime());
  70 + p.setSCategoryName("分类");
  71 + p.setISortOrder(0);
  72 + p.setBDeleted(false);
  73 + p.setTCreateDate(LocalDateTime.now());
  74 + permissionCategoryMapper.insert(p);
  75 + return p.getIIncrement();
  76 + }
  77 +
  78 + @Test
  79 + void post_minimalFields_returns200() throws Exception {
  80 + UserCreateDTO dto = baseDto("alice_" + System.nanoTime());
  81 +
  82 + mockMvc.perform(post("/api/users")
  83 + .contentType(MediaType.APPLICATION_JSON)
  84 + .content(json(dto)))
  85 + .andExpect(status().isOk())
  86 + .andExpect(jsonPath("$.code").value(200))
  87 + .andExpect(jsonPath("$.data.iIncrement").isNumber())
  88 + .andExpect(jsonPath("$.data.sUserName").value(dto.getSUserName()))
  89 + .andExpect(jsonPath("$.data.bCanModifyDocs").value(false))
  90 + .andExpect(jsonPath("$.data.permissionCategoryIds").isArray())
  91 + .andExpect(jsonPath("$.data.permissionCategoryIds.length()").value(0));
  92 + }
  93 +
  94 + @Test
  95 + void post_withStaffAndPermissions_returns200_andDbAssociated() throws Exception {
  96 + Integer staffId = insertStaff();
  97 + Integer cat1 = insertCategory();
  98 + Integer cat2 = insertCategory();
  99 + Integer cat3 = insertCategory();
  100 +
  101 + UserCreateDTO dto = baseDto("bob_" + System.nanoTime());
  102 + dto.setIStaffId(staffId);
  103 + dto.setPermissionCategoryIds(List.of(cat1, cat2, cat3));
  104 +
  105 + mockMvc.perform(post("/api/users")
  106 + .contentType(MediaType.APPLICATION_JSON)
  107 + .content(json(dto)))
  108 + .andExpect(status().isOk())
  109 + .andExpect(jsonPath("$.code").value(200))
  110 + .andExpect(jsonPath("$.data.iStaffId").value(staffId));
  111 +
  112 + UserEntity u = userMapper.selectOne(lambdaQuery(UserEntity.class)
  113 + .eq(UserEntity::getSUserName, dto.getSUserName()));
  114 + assertThat(u).isNotNull();
  115 + Long upCount = userPermissionMapper.selectCount(lambdaQuery(UserPermissionEntity.class)
  116 + .eq(UserPermissionEntity::getIUserId, u.getIIncrement()));
  117 + assertThat(upCount).isEqualTo(3L);
  118 + }
  119 +
  120 + @Test
  121 + void post_duplicateUserName_returns40921() throws Exception {
  122 + String userName = "dup_" + System.nanoTime();
  123 + UserCreateDTO first = baseDto(userName);
  124 + mockMvc.perform(post("/api/users").contentType(MediaType.APPLICATION_JSON).content(json(first)))
  125 + .andExpect(status().isOk());
  126 +
  127 + UserCreateDTO second = baseDto(userName);
  128 + mockMvc.perform(post("/api/users").contentType(MediaType.APPLICATION_JSON).content(json(second)))
  129 + .andExpect(status().isOk())
  130 + .andExpect(jsonPath("$.code").value(40921));
  131 + }
  132 +
  133 + @Test
  134 + void post_staffNotFound_returns40421() throws Exception {
  135 + UserCreateDTO dto = baseDto("noStaff_" + System.nanoTime());
  136 + dto.setIStaffId(999999);
  137 + mockMvc.perform(post("/api/users")
  138 + .contentType(MediaType.APPLICATION_JSON)
  139 + .content(json(dto)))
  140 + .andExpect(status().isOk())
  141 + .andExpect(jsonPath("$.code").value(40421));
  142 + }
  143 +
  144 + @Test
  145 + void post_permissionCategoryNotFound_returns40422() throws Exception {
  146 + UserCreateDTO dto = baseDto("noCat_" + System.nanoTime());
  147 + dto.setPermissionCategoryIds(List.of(999999));
  148 + mockMvc.perform(post("/api/users")
  149 + .contentType(MediaType.APPLICATION_JSON)
  150 + .content(json(dto)))
  151 + .andExpect(status().isOk())
  152 + .andExpect(jsonPath("$.code").value(40422));
  153 + }
  154 +
  155 + @Test
  156 + void post_passwordHashedInDb_notPlaintext() throws Exception {
  157 + UserCreateDTO dto = baseDto("pw_" + System.nanoTime());
  158 +
  159 + mockMvc.perform(post("/api/users")
  160 + .contentType(MediaType.APPLICATION_JSON)
  161 + .content(json(dto)))
  162 + .andExpect(status().isOk());
  163 +
  164 + UserEntity u = userMapper.selectOne(lambdaQuery(UserEntity.class)
  165 + .eq(UserEntity::getSUserName, dto.getSUserName()));
  166 + assertThat(u.getSPasswordHash())
  167 + .satisfiesAnyOf(
  168 + h -> assertThat(h).startsWith("$2a$"),
  169 + h -> assertThat(h).startsWith("$2b$"),
  170 + h -> assertThat(h).startsWith("$2y$"))
  171 + .doesNotContain("666666");
  172 + }
  173 +
  174 + @Test
  175 + void post_responseExcludesSPasswordHash() throws Exception {
  176 + UserCreateDTO dto = baseDto("priv_" + System.nanoTime());
  177 +
  178 + mockMvc.perform(post("/api/users")
  179 + .contentType(MediaType.APPLICATION_JSON)
  180 + .content(json(dto)))
  181 + .andExpect(status().isOk())
  182 + .andExpect(jsonPath("$.data.sPasswordHash").doesNotExist());
  183 + }
  184 +}
... ...