package com.xly.erp.modules.usr; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.fasterxml.jackson.databind.ObjectMapper; import com.xly.erp.common.security.JwtUtil; import com.xly.erp.modules.usr.entity.UsrPermission; import com.xly.erp.modules.usr.entity.UsrUser; import com.xly.erp.modules.usr.entity.UsrUserPermission; import com.xly.erp.modules.usr.mapper.UsrPermissionMapper; import com.xly.erp.modules.usr.mapper.UsrUserMapper; import com.xly.erp.modules.usr.mapper.UsrUserPermissionMapper; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; /** * REQ-USR-001 T8:新增用户端到端验收回归(spec § 7)。 * *

@SpringBootTest + 真实 MockMvc 安全链 + test profile 连测试库(Flyway 已 apply V1)。 * 认证态通过真实 JwtUtil 签发 token 走 JwtAuthenticationFilter 注入; * 真实 BCrypt 哈希 + 真实库写入做端到端确认。每个用例自管理 fixture 清理。

*/ @SpringBootTest @AutoConfigureMockMvc @ActiveProfiles("test") class UsrUserCreateIT { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @Autowired private JwtUtil jwtUtil; @Autowired private PasswordEncoder passwordEncoder; @Autowired private UsrUserMapper usrUserMapper; @Autowired private UsrUserPermissionMapper usrUserPermissionMapper; @Autowired private UsrPermissionMapper usrPermissionMapper; private static final String CALLER = "it_admin"; @AfterEach void cleanup() { // 删除本测试创建的用户(及其权限授权由外键 CASCADE 清理)与 fixture 权限。 usrUserMapper.delete(Wrappers.lambdaQuery() .likeRight(UsrUser::getSUserName, "it_user_")); usrPermissionMapper.delete(Wrappers.lambdaQuery() .likeRight(UsrPermission::getSPermissionCode, "IT_PERM_")); } private String adminToken() { return "Bearer " + jwtUtil.generateToken(CALLER, "超级管理员"); } private String normalToken() { return "Bearer " + jwtUtil.generateToken("it_normal", "普通用户"); } private Map baseBody(String userName) { Map body = new HashMap<>(); body.put("sUserName", userName); body.put("sLanguage", "中文"); return body; } private int insertPermissionFixture(String codeSuffix) { UsrPermission perm = new UsrPermission(); perm.setSPermissionName("IT权限" + codeSuffix); perm.setSPermissionCode("IT_PERM_" + codeSuffix); usrPermissionMapper.insert(perm); return perm.getIIncrement(); } @Test void ac1NormalCreatePersistsHashedPassword() throws Exception { String userName = "it_user_ac1"; MvcResult result = mockMvc.perform(post("/api/usr/users") .header("Authorization", adminToken()) .contentType("application/json") .content(objectMapper.writeValueAsString(baseBody(userName)))) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(0)) .andExpect(jsonPath("$.data.id").isNumber()) .andReturn(); Map resp = objectMapper.readValue(result.getResponse().getContentAsString(), Map.class); Map data = (Map) resp.get("data"); Integer newId = (Integer) data.get("id"); UsrUser saved = usrUserMapper.selectById(newId); assertThat(saved).isNotNull(); assertThat(saved.getSPassword()).isNotEqualTo("666666"); assertThat(passwordEncoder.matches("666666", saved.getSPassword())).isTrue(); assertThat(saved.getIIsVoid()).isZero(); assertThat(saved.getSCreator()).isEqualTo(CALLER); assertThat(saved.getTCreateDate()).isNotNull(); } @Test void ac2DuplicateUserNameRollsBack() throws Exception { String userName = "it_user_ac2"; mockMvc.perform(post("/api/usr/users") .header("Authorization", adminToken()) .contentType("application/json") .content(objectMapper.writeValueAsString(baseBody(userName)))) .andExpect(jsonPath("$.code").value(0)); long before = usrUserMapper.selectCount(Wrappers.lambdaQuery() .eq(UsrUser::getSUserName, userName)); mockMvc.perform(post("/api/usr/users") .header("Authorization", adminToken()) .contentType("application/json") .content(objectMapper.writeValueAsString(baseBody(userName)))) .andExpect(jsonPath("$.code").value(40901)); long after = usrUserMapper.selectCount(Wrappers.lambdaQuery() .eq(UsrUser::getSUserName, userName)); assertThat(after).isEqualTo(before).isEqualTo(1L); } @Test void ac4PermissionGrantWritesRows() throws Exception { int permA = insertPermissionFixture("A"); int permB = insertPermissionFixture("B"); String userName = "it_user_ac4"; Map body = baseBody(userName); body.put("permissionIds", List.of(permA, permB, permA)); MvcResult result = mockMvc.perform(post("/api/usr/users") .header("Authorization", adminToken()) .contentType("application/json") .content(objectMapper.writeValueAsString(body))) .andExpect(jsonPath("$.code").value(0)) .andReturn(); Map resp = objectMapper.readValue(result.getResponse().getContentAsString(), Map.class); Integer newId = (Integer) ((Map) resp.get("data")).get("id"); long grants = usrUserPermissionMapper.selectCount(Wrappers.lambdaQuery() .eq(UsrUserPermission::getIUserId, newId)); assertThat(grants).isEqualTo(2L); } @Test void ac5NonAdminForbidden() throws Exception { String userName = "it_user_ac5"; // 普通用户 token → 40301 mockMvc.perform(post("/api/usr/users") .header("Authorization", normalToken()) .contentType("application/json") .content(objectMapper.writeValueAsString(baseBody(userName)))) .andExpect(jsonPath("$.code").value(40301)); // 无 token → 401(安全链拦截,未认证) mockMvc.perform(post("/api/usr/users") .contentType("application/json") .content(objectMapper.writeValueAsString(baseBody(userName)))) .andExpect(status().isUnauthorized()); long created = usrUserMapper.selectCount(Wrappers.lambdaQuery() .eq(UsrUser::getSUserName, userName)); assertThat(created).isZero(); } @Test void ac7ResponseHasNoPassword() throws Exception { String userName = "it_user_ac7"; MvcResult result = mockMvc.perform(post("/api/usr/users") .header("Authorization", adminToken()) .contentType("application/json") .content(objectMapper.writeValueAsString(baseBody(userName)))) .andExpect(jsonPath("$.code").value(0)) .andExpect(jsonPath("$.data.sPassword").doesNotExist()) .andExpect(jsonPath("$.data.password").doesNotExist()) .andReturn(); String responseBody = result.getResponse().getContentAsString(); assertThat(responseBody).doesNotContain("666666"); assertThat(responseBody.toLowerCase()).doesNotContain("password"); } }