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; /** * 拦截器的「接口 → 权限码」映射。 * *
这层映射出过两次事故:一个接口服务多个页面时,推导出来的权限码在角色管理里根本勾不到,
* 于是配好权限的员工账号也进不去模块;而权限 403 又被各控制器自带的兜底 advice 改写成 500,
* 现场只看到「服务器内部错误」。这里不启动 Spring 上下文,直接盯住这两件事。
*/
class AdminAuthInterceptorMappingTests {
private final AdminAuthService service = mock(AdminAuthService.class);
private final JdbcTemplate jdbc = mock(JdbcTemplate.class);
private final List> 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