Commit 33adcfb8c4f0f57383b12589dbe8281a05f6bf0e

Authored by zichun
1 parent eea41dba

test(usr): user create integration coverage REQ-USR-001

backend/src/main/java/com/xly/erp/common/config/PasswordEncoderConfig.java 0 → 100644
  1 +package com.xly.erp.common.config;
  2 +
  3 +import org.springframework.context.annotation.Bean;
  4 +import org.springframework.context.annotation.Configuration;
  5 +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  6 +
  7 +@Configuration
  8 +public class PasswordEncoderConfig {
  9 +
  10 + @Bean
  11 + public BCryptPasswordEncoder bCryptPasswordEncoder() {
  12 + return new BCryptPasswordEncoder();
  13 + }
  14 +}
backend/src/main/java/com/xly/erp/common/security/SecurityConfig.java
@@ -5,7 +5,6 @@ import org.springframework.context.annotation.Bean; @@ -5,7 +5,6 @@ import org.springframework.context.annotation.Bean;
5 import org.springframework.context.annotation.Configuration; 5 import org.springframework.context.annotation.Configuration;
6 import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
7 import org.springframework.security.config.http.SessionCreationPolicy; 7 import org.springframework.security.config.http.SessionCreationPolicy;
8 -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;  
9 import org.springframework.security.web.SecurityFilterChain; 8 import org.springframework.security.web.SecurityFilterChain;
10 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 9 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
11 10
@@ -32,9 +31,4 @@ public class SecurityConfig { @@ -32,9 +31,4 @@ public class SecurityConfig {
32 .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); 31 .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
33 return http.build(); 32 return http.build();
34 } 33 }
35 -  
36 - @Bean  
37 - public BCryptPasswordEncoder bCryptPasswordEncoder() {  
38 - return new BCryptPasswordEncoder();  
39 - }  
40 } 34 }
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.Result;
  4 +import com.xly.erp.module.usr.dto.CreateUserDTO;
  5 +import com.xly.erp.module.usr.service.UserService;
  6 +import jakarta.validation.Valid;
  7 +import org.springframework.web.bind.annotation.PostMapping;
  8 +import org.springframework.web.bind.annotation.RequestBody;
  9 +import org.springframework.web.bind.annotation.RequestMapping;
  10 +import org.springframework.web.bind.annotation.RestController;
  11 +
  12 +import java.util.Map;
  13 +
  14 +@RestController
  15 +@RequestMapping("/api/usr")
  16 +public class UserController {
  17 +
  18 + private final UserService userService;
  19 +
  20 + public UserController(UserService userService) {
  21 + this.userService = userService;
  22 + }
  23 +
  24 + @PostMapping("/users")
  25 + public Result<Map<String, Object>> create(@Valid @RequestBody CreateUserDTO dto) {
  26 + return Result.ok(userService.create(dto));
  27 + }
  28 +}
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.JsonNode;
  4 +import com.fasterxml.jackson.databind.ObjectMapper;
  5 +import com.xly.erp.common.security.TestJwtHelper;
  6 +import org.junit.jupiter.api.AfterEach;
  7 +import org.junit.jupiter.api.BeforeEach;
  8 +import org.junit.jupiter.api.Test;
  9 +import org.springframework.beans.factory.annotation.Autowired;
  10 +import org.springframework.boot.test.context.SpringBootTest;
  11 +import org.springframework.boot.test.web.client.TestRestTemplate;
  12 +import org.springframework.boot.test.web.server.LocalServerPort;
  13 +import org.springframework.http.HttpEntity;
  14 +import org.springframework.http.HttpHeaders;
  15 +import org.springframework.http.HttpMethod;
  16 +import org.springframework.http.MediaType;
  17 +import org.springframework.http.ResponseEntity;
  18 +import org.springframework.jdbc.core.JdbcTemplate;
  19 +import org.springframework.test.context.ActiveProfiles;
  20 +
  21 +import java.util.HashMap;
  22 +import java.util.List;
  23 +import java.util.Map;
  24 +
  25 +import static org.assertj.core.api.Assertions.assertThat;
  26 +
  27 +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
  28 +@ActiveProfiles("test")
  29 +class UserControllerIT {
  30 +
  31 + @Autowired
  32 + private TestRestTemplate rest;
  33 +
  34 + @Autowired
  35 + private TestJwtHelper testJwtHelper;
  36 +
  37 + @Autowired
  38 + private JdbcTemplate jdbcTemplate;
  39 +
  40 + @Autowired
  41 + private ObjectMapper objectMapper;
  42 +
  43 + @LocalServerPort
  44 + private int port;
  45 +
  46 + @BeforeEach
  47 + @AfterEach
  48 + void cleanup() {
  49 + jdbcTemplate.update("DELETE FROM tUserPermission WHERE iUserId IN "
  50 + + "(SELECT iIncrement FROM tUser WHERE sUserNo LIKE 'sp_test_%')");
  51 + jdbcTemplate.update("DELETE FROM tUser WHERE sUserNo LIKE 'sp_test_%'");
  52 + jdbcTemplate.update("DELETE FROM tStaff WHERE sStaffNo LIKE 'sp_test_%'");
  53 + jdbcTemplate.update("DELETE FROM tPermissionCategory WHERE sCategoryCode LIKE 'sp_test_%'");
  54 + }
  55 +
  56 + @Test
  57 + void postValidBody_with_jwt_returns200_andPersists() throws Exception {
  58 + Integer staffId = insertStaff("sp_test_st1", "员工1");
  59 + Integer cat1 = insertCategory("sp_test_pc1", "权限A");
  60 + Integer cat2 = insertCategory("sp_test_pc2", "权限B");
  61 +
  62 + String token = testJwtHelper.signFor("ADMIN001");
  63 + HttpHeaders headers = jsonHeaders();
  64 + headers.set("Authorization", "Bearer " + token);
  65 + Map<String, Object> body = baseBody("sp_test_u_ok", "正常用户");
  66 + body.put("iStaffId", staffId);
  67 + body.put("permissionCategoryIds", List.of(cat1, cat2));
  68 +
  69 + ResponseEntity<String> resp = rest.exchange(
  70 + url(), HttpMethod.POST, new HttpEntity<>(body, headers), String.class);
  71 +
  72 + JsonNode jb = objectMapper.readTree(resp.getBody());
  73 + assertThat(jb.get("code").asInt()).isZero();
  74 + int newId = jb.get("data").get("iIncrement").asInt();
  75 + assertThat(jb.get("data").get("sUserNo").asText()).isEqualTo("sp_test_u_ok");
  76 +
  77 + Map<String, Object> row = jdbcTemplate.queryForMap(
  78 + "SELECT sBrandsId, sCreatedBy, sUserType FROM tUser WHERE iIncrement = ?", newId);
  79 + assertThat(row.get("sBrandsId")).isEqualTo("XLY");
  80 + assertThat(row.get("sCreatedBy")).isEqualTo("ADMIN001");
  81 + Integer permCount = jdbcTemplate.queryForObject(
  82 + "SELECT COUNT(1) FROM tUserPermission WHERE iUserId = ?", Integer.class, newId);
  83 + assertThat(permCount).isEqualTo(2);
  84 + }
  85 +
  86 + @Test
  87 + void postEmptyBody_returns40001() throws Exception {
  88 + String token = testJwtHelper.signFor("ADMIN001");
  89 + HttpHeaders headers = jsonHeaders();
  90 + headers.set("Authorization", "Bearer " + token);
  91 +
  92 + ResponseEntity<String> resp = rest.exchange(
  93 + url(), HttpMethod.POST, new HttpEntity<>("{}", headers), String.class);
  94 +
  95 + assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40001);
  96 + }
  97 +
  98 + @Test
  99 + void postInvalidUserType_returns40001() throws Exception {
  100 + String token = testJwtHelper.signFor("ADMIN001");
  101 + HttpHeaders headers = jsonHeaders();
  102 + headers.set("Authorization", "Bearer " + token);
  103 + Map<String, Object> body = baseBody("sp_test_u_invtype", "枚举");
  104 + body.put("sUserType", "火星");
  105 +
  106 + ResponseEntity<String> resp = rest.exchange(
  107 + url(), HttpMethod.POST, new HttpEntity<>(body, headers), String.class);
  108 +
  109 + assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40001);
  110 + }
  111 +
  112 + @Test
  113 + void postInvalidLanguage_returns40001() throws Exception {
  114 + String token = testJwtHelper.signFor("ADMIN001");
  115 + HttpHeaders headers = jsonHeaders();
  116 + headers.set("Authorization", "Bearer " + token);
  117 + Map<String, Object> body = baseBody("sp_test_u_invlang", "枚举");
  118 + body.put("sLanguage", "ja");
  119 +
  120 + ResponseEntity<String> resp = rest.exchange(
  121 + url(), HttpMethod.POST, new HttpEntity<>(body, headers), String.class);
  122 +
  123 + assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40001);
  124 + }
  125 +
  126 + @Test
  127 + void postDuplicateUserNo_returns40020() throws Exception {
  128 + String token = testJwtHelper.signFor("ADMIN001");
  129 + HttpHeaders headers = jsonHeaders();
  130 + headers.set("Authorization", "Bearer " + token);
  131 + Map<String, Object> first = baseBody("sp_test_u_dup", "首次");
  132 + ResponseEntity<String> r1 = rest.exchange(url(), HttpMethod.POST, new HttpEntity<>(first, headers), String.class);
  133 + assertThat(objectMapper.readTree(r1.getBody()).get("code").asInt()).isZero();
  134 +
  135 + Map<String, Object> dup = baseBody("sp_test_u_dup", "重复");
  136 + dup.put("sUserName", "sp_test_u_dup_other");
  137 + ResponseEntity<String> r2 = rest.exchange(url(), HttpMethod.POST, new HttpEntity<>(dup, headers), String.class);
  138 + assertThat(objectMapper.readTree(r2.getBody()).get("code").asInt()).isEqualTo(40020);
  139 + }
  140 +
  141 + @Test
  142 + void postStaffNotFound_returns40022() throws Exception {
  143 + String token = testJwtHelper.signFor("ADMIN001");
  144 + HttpHeaders headers = jsonHeaders();
  145 + headers.set("Authorization", "Bearer " + token);
  146 + Map<String, Object> body = baseBody("sp_test_u_nostaff", "缺职员");
  147 + body.put("iStaffId", 99999990);
  148 +
  149 + ResponseEntity<String> resp = rest.exchange(
  150 + url(), HttpMethod.POST, new HttpEntity<>(body, headers), String.class);
  151 +
  152 + assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40022);
  153 + }
  154 +
  155 + @Test
  156 + void postPermissionCategoryNotFound_returns40023() throws Exception {
  157 + String token = testJwtHelper.signFor("ADMIN001");
  158 + HttpHeaders headers = jsonHeaders();
  159 + headers.set("Authorization", "Bearer " + token);
  160 + Map<String, Object> body = baseBody("sp_test_u_nocat", "缺权限");
  161 + body.put("permissionCategoryIds", List.of(99999991));
  162 +
  163 + ResponseEntity<String> resp = rest.exchange(
  164 + url(), HttpMethod.POST, new HttpEntity<>(body, headers), String.class);
  165 +
  166 + assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(40023);
  167 + }
  168 +
  169 + @Test
  170 + void postWithoutJwt_permitAllStub_returns200_andCreatedBySTUBADMIN() throws Exception {
  171 + HttpHeaders headers = jsonHeaders();
  172 + Map<String, Object> body = baseBody("sp_test_u_nojwt", "无JWT");
  173 +
  174 + ResponseEntity<String> resp = rest.exchange(
  175 + url(), HttpMethod.POST, new HttpEntity<>(body, headers), String.class);
  176 +
  177 + JsonNode jb = objectMapper.readTree(resp.getBody());
  178 + assertThat(jb.get("code").asInt()).isZero();
  179 + int newId = jb.get("data").get("iIncrement").asInt();
  180 + String createdBy = jdbcTemplate.queryForObject(
  181 + "SELECT sCreatedBy FROM tUser WHERE iIncrement = ?", String.class, newId);
  182 + assertThat(createdBy).isEqualTo("STUB_ADMIN");
  183 + }
  184 +
  185 + @Test
  186 + void postTamperedJwt_returns20001() throws Exception {
  187 + HttpHeaders headers = jsonHeaders();
  188 + headers.set("Authorization", "Bearer not.a.real.jwt");
  189 + Map<String, Object> body = baseBody("sp_test_u_tamper", "伪JWT");
  190 +
  191 + ResponseEntity<String> resp = rest.exchange(
  192 + url(), HttpMethod.POST, new HttpEntity<>(body, headers), String.class);
  193 +
  194 + assertThat(objectMapper.readTree(resp.getBody()).get("code").asInt()).isEqualTo(20001);
  195 + Integer count = jdbcTemplate.queryForObject(
  196 + "SELECT COUNT(1) FROM tUser WHERE sUserNo = 'sp_test_u_tamper'", Integer.class);
  197 + assertThat(count).isZero();
  198 + }
  199 +
  200 + private Integer insertStaff(String staffNo, String name) {
  201 + jdbcTemplate.update(
  202 + "INSERT INTO tStaff (sBrandsId, sSubsidiaryId, tCreateDate, sStaffNo, sStaffName, sCreatedBy, bDeleted) "
  203 + + "VALUES ('XLY','XLY', NOW(), ?, ?, 'STUB_ADMIN', 0)", staffNo, name);
  204 + return jdbcTemplate.queryForObject(
  205 + "SELECT iIncrement FROM tStaff WHERE sStaffNo = ?", Integer.class, staffNo);
  206 + }
  207 +
  208 + private Integer insertCategory(String code, String name) {
  209 + jdbcTemplate.update(
  210 + "INSERT INTO tPermissionCategory (sBrandsId, sSubsidiaryId, tCreateDate, sCategoryCode, sCategoryName, "
  211 + + "iSortOrder, sCreatedBy, bDeleted) "
  212 + + "VALUES ('XLY','XLY', NOW(), ?, ?, 0, 'STUB_ADMIN', 0)", code, name);
  213 + return jdbcTemplate.queryForObject(
  214 + "SELECT iIncrement FROM tPermissionCategory WHERE sCategoryCode = ?", Integer.class, code);
  215 + }
  216 +
  217 + private static Map<String, Object> baseBody(String userNo, String userName) {
  218 + Map<String, Object> m = new HashMap<>();
  219 + m.put("sUserNo", userNo);
  220 + m.put("sUserName", userName);
  221 + m.put("sUserType", "普通用户");
  222 + m.put("sLanguage", "zh");
  223 + m.put("bCanModifyDocs", false);
  224 + return m;
  225 + }
  226 +
  227 + private static HttpHeaders jsonHeaders() {
  228 + HttpHeaders h = new HttpHeaders();
  229 + h.setContentType(MediaType.APPLICATION_JSON);
  230 + return h;
  231 + }
  232 +
  233 + private String url() {
  234 + return "http://localhost:" + port + "/api/usr/users";
  235 + }
  236 +}