package com.talentcard.server.business.admin.auth;
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.anyLong;
|
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.when;
|
|
import java.util.ArrayList;
|
import java.util.List;
|
|
import com.talentcard.server.common.BusinessException;
|
import com.talentcard.server.common.ErrorCode;
|
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.Test;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
import tools.jackson.databind.json.JsonMapper;
|
|
/**
|
* 拦截器的「接口 → 权限码」映射。
|
*
|
* <p>这层映射出过两次事故:一个接口服务多个页面时,推导出来的权限码在角色管理里根本勾不到,
|
* 于是配好权限的员工账号也进不去模块;而权限 403 又被各控制器自带的兜底 advice 改写成 500,
|
* 现场只看到「服务器内部错误」。这里不启动 Spring 上下文,直接盯住这两件事。
|
*/
|
class AdminAuthInterceptorMappingTests {
|
|
private final AdminAuthService service = mock(AdminAuthService.class);
|
private final JdbcTemplate jdbc = mock(JdbcTemplate.class);
|
private final List<List<String>> requested = new ArrayList<>();
|
private AdminAuthInterceptor interceptor;
|
|
@BeforeEach
|
void setUp() {
|
// 运行期注入的是 Jackson 3 的 ObjectMapper(tools.jackson),这里用同一套,别再用 Jackson 2 的。
|
interceptor = new AdminAuthInterceptor(service, jdbc, new JsonMapper());
|
when(service.authenticate(anyString())).thenReturn(1L);
|
when(service.operatorName(anyLong())).thenReturn("测试员工");
|
when(service.hasAnyPermission(anyLong(), any())).thenAnswer(invocation -> {
|
requested.add(invocation.getArgument(1));
|
return true;
|
});
|
}
|
|
@Test
|
void serviceApplicationRequestsAreJudgedByTheirModulePage() {
|
assertThat(pass(get("/api/admin/service-appointment-applications", "applicationType", "MEDICAL_RESERVATION")))
|
.containsExactly("admin.medical-reservations.view");
|
assertThat(pass(get("/api/admin/service-appointment-applications", "applicationType", "FUEL_SERVICE")))
|
.containsExactly("admin.fuel-services.view");
|
assertThat(pass(get("/api/admin/service-appointment-applications/export", "applicationType", "SPOUSE_EMPLOYMENT")))
|
.containsExactly("admin.spouse-employment.export");
|
assertThat(pass(patch("/api/admin/service-appointment-applications", "applicationType", "HOTEL_RESERVATION")))
|
.containsExactly("admin.hotel-reservations.audit");
|
}
|
|
/** 详情页只带 id,得回表查类型,否则又会掉回没人能勾选的 appointment-management。 */
|
@Test
|
void serviceApplicationDetailLooksUpItsTypeFromTheDatabase() {
|
when(jdbc.queryForList(anyString(), eq(String.class), any())).thenReturn(List.of("MEDICAL_APPLICATION"));
|
|
assertThat(pass(get("/api/admin/service-appointment-applications/42")))
|
.containsExactly("admin.medical-applications.view");
|
}
|
|
@Test
|
void unknownServiceApplicationTypeKeepsTheOldPermission() {
|
assertThat(pass(get("/api/admin/service-appointment-applications")))
|
.containsExactly("admin.appointment-management.view");
|
}
|
|
@Test
|
void familyCardRequestsAcceptEitherOfTheTwoPagesThatUseThem() {
|
assertThat(pass(get("/api/admin/dependent-card-applies", "management", "true")))
|
.containsExactly("admin.family-card-applications.view", "admin.family-cards.view");
|
assertThat(pass(patch("/api/admin/dependent-card-applies/15/unbind")))
|
.containsExactly("admin.family-card-applications.audit", "admin.family-cards.disable");
|
assertThat(pass(patch("/api/admin/dependent-card-applies/15/audit")))
|
.containsExactly("admin.family-card-applications.audit");
|
}
|
|
/** 权限不足必须如实回 403,而不是被控制器兜底改写成 500「服务器内部错误」。 */
|
@Test
|
void deniedPermissionIsWrittenAsFourZeroThree() throws Exception {
|
when(service.hasAnyPermission(anyLong(), any())).thenReturn(false);
|
MockHttpServletRequest request = get("/api/admin/service-appointment-applications", "applicationType", "MEDICAL_RESERVATION");
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
assertThat(interceptor.preHandle(request, response, new Object())).isFalse();
|
assertThat(response.getStatus()).isEqualTo(403);
|
assertThat(response.getContentAsString()).contains("\"code\":403").contains("当前账号没有该操作权限");
|
}
|
|
/** 会话失效要回 401,前端才会清会话并跳登录页;以前这批接口会把它写成 500。 */
|
@Test
|
void expiredSessionIsWrittenAsFourZeroOne() throws Exception {
|
when(service.authenticate(anyString()))
|
.thenThrow(new BusinessException(ErrorCode.UNAUTHORIZED, "登录已失效,请重新登录"));
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
assertThat(interceptor.preHandle(get("/api/admin/dependent-card-applies/15"), response, new Object())).isFalse();
|
assertThat(response.getStatus()).isEqualTo(401);
|
assertThat(response.getContentAsString()).contains("\"code\":401").contains("登录已失效");
|
}
|
|
/** 不在这两个特殊前缀里的接口沿用原来的页面映射。 */
|
@Test
|
void otherAdminRequestsKeepTheirPageMapping() {
|
assertThat(pass(get("/api/admin/business/news"))).containsExactly("admin.news.view");
|
assertThat(pass(patch("/api/admin/parking-plate-submissions/9/audit")))
|
.containsExactly("admin.parking-plates.audit");
|
assertThat(pass(get("/api/admin/management/roles"))).containsExactly("admin.roles.view");
|
}
|
|
private List<String> pass(MockHttpServletRequest request) {
|
requested.clear();
|
try {
|
interceptor.preHandle(request, new MockHttpServletResponse(), new Object());
|
} catch (Exception exception) {
|
throw new AssertionError(exception);
|
}
|
assertThat(requested).as("拦截器应该只校验一次权限").hasSize(1);
|
return requested.get(0);
|
}
|
|
private MockHttpServletRequest get(String uri) {
|
return get(uri, new String[0]);
|
}
|
|
private MockHttpServletRequest get(String uri, String... params) {
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
|
request.addHeader("Authorization", "Bearer test-token");
|
for (int index = 0; index + 1 < params.length; index += 2) {
|
request.setParameter(params[index], params[index + 1]);
|
}
|
return request;
|
}
|
|
private MockHttpServletRequest patch(String uri, String... params) {
|
MockHttpServletRequest request = new MockHttpServletRequest("PATCH", uri);
|
request.addHeader("Authorization", "Bearer test-token");
|
for (int index = 0; index + 1 < params.length; index += 2) {
|
request.setParameter(params[index], params[index + 1]);
|
}
|
return request;
|
}
|
}
|