|
|
@@ -0,0 +1,103 @@
|
|
|
+package com.etotem.cfc.config;
|
|
|
+
|
|
|
+import java.lang.reflect.Field;
|
|
|
+import java.lang.reflect.Method;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.List;
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.mockito.InjectMocks;
|
|
|
+import org.mockito.Mock;
|
|
|
+import org.mockito.MockitoAnnotations;
|
|
|
+import org.springframework.web.servlet.HandlerInterceptor;
|
|
|
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
|
|
+import org.springframework.web.servlet.handler.MappedInterceptor;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
+
|
|
|
+/**
|
|
|
+ * WebConfig 注册链契约(回归 2026-11-12):/api/growth/** 不得再出现在
|
|
|
+ * jwtInterceptor 的 excludePathPatterns 中 —— 否则拦截器对 growth 整族不执行,
|
|
|
+ * GrowthRecordController.record/** 的 @RequestAttribute("userId") 永不注入,
|
|
|
+ * 无 token(甚至带 token)一律 500 "Missing request attribute 'userId'"。
|
|
|
+ *
|
|
|
+ * <p>纯单元(无 DB/Spring 上下文),任何环境可跑;与端到端
|
|
|
+ * {@link com.etotem.cfc.controller.growth.GrowthAuthContractTest} 互补。
|
|
|
+ */
|
|
|
+class WebConfigGrowthExcludeRemovedTest {
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private JwtInterceptor jwtInterceptor;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private FamilyAccessInterceptor familyAccessInterceptor;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private OperationLogInterceptor operationLogInterceptor;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private RateLimitInterceptor rateLimitInterceptor;
|
|
|
+
|
|
|
+ @InjectMocks
|
|
|
+ private WebConfig webConfig;
|
|
|
+
|
|
|
+ private List<MappedInterceptor> mapped;
|
|
|
+
|
|
|
+ @BeforeEach
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ void setUp() throws Exception {
|
|
|
+ MockitoAnnotations.openMocks(this);
|
|
|
+ InterceptorRegistry registry = new InterceptorRegistry();
|
|
|
+ webConfig.addInterceptors(registry);
|
|
|
+ Method getInterceptors = InterceptorRegistry.class.getDeclaredMethod("getInterceptors");
|
|
|
+ getInterceptors.setAccessible(true);
|
|
|
+ List<Object> raw = (List<Object>) getInterceptors.invoke(registry);
|
|
|
+ assertNotNull(raw, "注册列表不应为空");
|
|
|
+ mapped = new ArrayList<>();
|
|
|
+ for (Object o : raw) {
|
|
|
+ mapped.add((MappedInterceptor) o);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private MappedInterceptor findMapped(HandlerInterceptor ic) {
|
|
|
+ return mapped.stream()
|
|
|
+ .filter(m -> m.getInterceptor() == ic)
|
|
|
+ .findFirst()
|
|
|
+ .orElseThrow(() -> new AssertionError("拦截器未注册: " + ic));
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private List<String> excludePatterns(MappedInterceptor mi) throws Exception {
|
|
|
+ Field f = MappedInterceptor.class.getDeclaredField("excludePatterns");
|
|
|
+ f.setAccessible(true);
|
|
|
+ Object[] arr = (Object[]) f.get(mi);
|
|
|
+ List<String> res = new ArrayList<>();
|
|
|
+ if (arr != null) {
|
|
|
+ for (Object pa : arr) {
|
|
|
+ Method g = pa.getClass().getMethod("getPatternString");
|
|
|
+ g.setAccessible(true); // 类包私有(org.springframework.web.servlet.handler),方法虽 public 亦需放行
|
|
|
+ res.add((String) g.invoke(pa));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void jwtInterceptor_不得再将growth_排除出拦截链() throws Exception {
|
|
|
+ MappedInterceptor m = findMapped(jwtInterceptor);
|
|
|
+ List<String> excludes = excludePatterns(m);
|
|
|
+ assertFalse(excludes.contains("/api/growth/**"),
|
|
|
+ "jwtInterceptor excludes 不得包含 /api/growth/**(否则 growth 无 userId 注入,恒 500)。当前=" + excludes);
|
|
|
+ assertTrue(Arrays.asList(m.getPathPatterns()).contains("/api/**"), "jwtInterceptor 应覆盖 /api/**");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void familyAccessInterceptor_保持原有growth_排除不变() throws Exception {
|
|
|
+ MappedInterceptor m = findMapped(familyAccessInterceptor);
|
|
|
+ assertTrue(excludePatterns(m).contains("/api/growth/**"),
|
|
|
+ "familyAccessInterceptor excludes 应保持 /api/growth/**(本次只动 jwtInterceptor)");
|
|
|
+ }
|
|
|
+}
|