实现登录权限底座

This commit is contained in:
andy
2026-07-09 19:31:20 +08:00
parent a5a251a3f3
commit 666fb41328
77 changed files with 3946 additions and 19 deletions

View File

@@ -0,0 +1,180 @@
package cn.nianxx.thhotel.platform.identity.control;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import cn.nianxx.thhotel.ThHotelApplication;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@SpringBootTest(
classes = ThHotelApplication.class,
properties = {
"auth.bootstrap.admin.username=m003-admin",
"auth.bootstrap.admin.password=Admin@123456",
"auth.bootstrap.admin.display-name=系统管理员",
"auth.bootstrap.default-hotel-id=HOTEL-TEST",
"auth.bootstrap.default-hotel-name=测试酒店",
"auth.bootstrap.default-hotel-time-zone=Asia/Bangkok",
"auth.session.ttl-minutes=720"
})
@AutoConfigureMockMvc
@ActiveProfiles("test")
class AuthControllerTest {
private static final String UTC_INSTANT_PATTERN = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$";
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
void shouldLoginBootstrapAdminAndReturnCurrentUserContext() throws Exception {
MvcResult loginResult = mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"username": "m003-admin",
"password": "Admin@123456"
}
"""))
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").value(notNullValue()))
.andExpect(jsonPath("$.token_type").value("Bearer"))
.andExpect(jsonPath("$.expires_at").value(matchesPattern(UTC_INSTANT_PATTERN)))
.andExpect(jsonPath("$.user.username").value("m003-admin"))
.andExpect(jsonPath("$.user.display_name").value("系统管理员"))
.andExpect(jsonPath("$.user.super_admin").value(true))
.andExpect(jsonPath("$.default_hotel_id").value("HOTEL-TEST"))
.andExpect(jsonPath("$.hotels[0].hotel_id").value("HOTEL-TEST"))
.andExpect(jsonPath("$.hotels[0].hotel_name").value("测试酒店"))
.andExpect(jsonPath("$.hotels[0].time_zone").value("Asia/Bangkok"))
.andExpect(jsonPath("$.permissions").value(contains(
"SOURCE_MESSAGE_READ",
"SOURCE_MESSAGE_ORIGINAL_READ",
"RESERVATION_ORDER_READ",
"RESERVATION_TASK_READ",
"RESERVATION_TASK_EDIT",
"RESERVATION_TASK_CONFIRM",
"RESERVATION_OPERA_SIM_EXECUTE",
"RESERVATION_AUDIT_READ",
"HOTEL_SWITCH",
"SYSTEM_AUTH_READ",
"SYSTEM_USER_MANAGE",
"SYSTEM_ROLE_MANAGE",
"SYSTEM_MENU_MANAGE",
"HOTEL_MANAGE",
"SYSTEM_DEBUG_EML_RUN")))
.andExpect(jsonPath("$.menus[0].menu_code").value("RESERVATION_ORDERS"))
.andExpect(jsonPath("$.menus[0].route_path").value("/reservation/orders"))
.andExpect(jsonPath("$.menus[0].icon_key").value("pi pi-list"))
.andExpect(jsonPath("$.menus[1].menu_code").value("RESERVATION_TASKS"))
.andExpect(jsonPath("$.menus[1].route_path").value("/reservation/tasks"))
.andExpect(jsonPath("$.menus[1].icon_key").value("pi pi-check-square"))
.andExpect(jsonPath("$.menus[2].menu_code").value("DEBUG_EML_SUPERAGENT"))
.andExpect(jsonPath("$.menus[2].route_path").value("/debug/eml-superagent"))
.andExpect(jsonPath("$.menus[2].icon_key").value("pi pi-upload"))
.andReturn();
String token = tokenFrom(loginResult);
mockMvc.perform(get("/api/auth/me")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk())
.andExpect(jsonPath("$.user.username").value("m003-admin"))
.andExpect(jsonPath("$.default_hotel_id").value("HOTEL-TEST"))
.andExpect(jsonPath("$.menus[2].menu_code").value("DEBUG_EML_SUPERAGENT"));
}
@Test
void shouldRejectInvalidLoginWithoutLeakingAccountState() throws Exception {
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"username": "m003-admin",
"password": "wrong-password"
}
"""))
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.error_code").value("AUTH_INVALID_CREDENTIALS"))
.andExpect(jsonPath("$.message").value("用户名或密码错误。"))
.andExpect(content().string(not("DISABLED")))
.andExpect(content().string(not("not found")));
}
@Test
void shouldRequireTokenForAuthMeAndRevokeSessionOnLogout() throws Exception {
mockMvc.perform(get("/api/auth/me"))
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.error_code").value("AUTH_TOKEN_REQUIRED"));
String token = tokenFrom(login());
mockMvc.perform(post("/api/auth/logout")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true));
mockMvc.perform(post("/api/auth/logout")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true));
mockMvc.perform(get("/api/auth/me")
.header("Authorization", "Bearer " + token))
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.error_code").value("AUTH_SESSION_INVALID"));
}
@Test
void shouldKeepExistingBusinessEndpointsCompatibleWhenTokenIsMissingOrInvalid() throws Exception {
mockMvc.perform(get("/api/reservation/orders")
.param("hotel_id", "HOTEL-TEST")
.param("page_num", "1")
.param("page_size", "1"))
.andExpect(status().isOk());
mockMvc.perform(get("/api/reservation/orders")
.header("Authorization", "Bearer invalid-token")
.param("hotel_id", "HOTEL-TEST")
.param("page_num", "1")
.param("page_size", "1"))
.andExpect(status().isOk());
}
private MvcResult login() throws Exception {
return mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"username": "m003-admin",
"password": "Admin@123456"
}
"""))
.andExpect(status().isOk())
.andReturn();
}
private String tokenFrom(MvcResult result) throws Exception {
JsonNode json = objectMapper.readTree(result.getResponse().getContentAsString());
return json.path("access_token").asText();
}
}