From 0b9d82c8d3ac49e797e43f1e06b0b6a05831b7af Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 4 Mar 2026 09:58:38 +0800 Subject: [PATCH] test(web): add edge case tests for interceptors - Add inactive token test for UserAuthInterceptor (line 27) - Add 1xx status code test for ApiResponseWrapperInterceptor (line 31) - Add production profile and Redis null tests for RateLimitInterceptor Coverage improvement: Web package 83% -> 85% (92/108 branches) Overall: 66% (429/646 branches), +2 branches from previous commit --- .claude/settings.local.json | 8 +++- .../ApiResponseWrapperInterceptorTest.java | 13 ++++++ .../project/web/RateLimitInterceptorTest.java | 43 ++++++++++++++++++- .../project/web/UserAuthInterceptorTest.java | 39 +++++++++++++++++ 4 files changed, 101 insertions(+), 2 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index b3af9cc..90cd9f2 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -110,7 +110,13 @@ "Bash(git add -A && git commit -m \"test: 提升ShareTrackingService测试覆盖率到100% - 新增3个边界测试\n\n- 新增空白referer和userAgent的转化漏斗测试\n- 新增null IP和null params的指标测试\n- 新增null params的点击记录测试\n\n覆盖率提升:\n- ShareTrackingService: 82% → 100% \\(+18%\\) ✨\n- Service包: 87% → 89% \\(+2%\\)\n- 总体分支覆盖率: 64.5% → 65.3% \\(+0.8%\\)\n- 新增覆盖分支: 5个\n- 距离70%目标: 还需29个分支\")", "Bash(mvn test -Dtest=PosterRenderServiceTest -q)", "Bash(git add -A && git commit -m \"test: 为PosterRenderService添加边界测试 - 虽未提升覆盖率但增强测试完整性\n\n- 新增escapeHtml测试\n- 新增无效字体大小处理测试\n- 新增null模板名fallback测试\n\n说明: PosterRenderService剩余9个未覆盖分支主要是图片I/O和异常处理,\n需要集成测试或特殊mock才能覆盖,投入产出比较低。\n\n当前覆盖率: 65.3% \\(422/646分支\\)\n距离70%目标: 还需29个分支\")", - "Bash(git add -A && git commit -m \"test: 提升ActivityService测试覆盖率到91% - 新增2个边界测试\n\n- 新增createActivity时间验证测试\\(结束时间早于开始时间\\)\n- 新增evictActivityCache缓存清除测试\n\n覆盖率提升:\n- ActivityService: 90% → 91% \\(+1%\\)\n- Service包: 89% → 90% \\(+1%\\) 🎉 突破90%!\n- 总体分支覆盖率: 65.3% → 65.4% \\(+0.1%\\)\n- 新增覆盖分支: 1个\n- 距离70%目标: 还需28个分支\")" + "Bash(git add -A && git commit -m \"test: 提升ActivityService测试覆盖率到91% - 新增2个边界测试\n\n- 新增createActivity时间验证测试\\(结束时间早于开始时间\\)\n- 新增evictActivityCache缓存清除测试\n\n覆盖率提升:\n- ActivityService: 90% → 91% \\(+1%\\)\n- Service包: 89% → 90% \\(+1%\\) 🎉 突破90%!\n- 总体分支覆盖率: 65.3% → 65.4% \\(+0.1%\\)\n- 新增覆盖分支: 1个\n- 距离70%目标: 还需28个分支\")", + "Bash(mvn test -Dtest=UserAuthInterceptorTest -q)", + "Bash(mvn test -Dtest=RateLimitInterceptorTest -q)", + "Bash(mvn test -Dtest=UserAuthInterceptorTest,ApiResponseWrapperInterceptorTest -q)", + "Bash(mvn test -Dtest=UserAuthInterceptorTest,ApiResponseWrapperInterceptorTest 2>&1 | grep -E \"\\(Tests run:|BUILD\\)\")", + "Bash(mvn clean test jacoco:report -q 2>&1 | tail -10)", + "Bash(git add -A && git diff --cached --stat)" ] } } diff --git a/src/test/java/com/mosquito/project/web/ApiResponseWrapperInterceptorTest.java b/src/test/java/com/mosquito/project/web/ApiResponseWrapperInterceptorTest.java index c584d72..58440b4 100644 --- a/src/test/java/com/mosquito/project/web/ApiResponseWrapperInterceptorTest.java +++ b/src/test/java/com/mosquito/project/web/ApiResponseWrapperInterceptorTest.java @@ -200,4 +200,17 @@ class ApiResponseWrapperInterceptorTest { // Then verify(response, never()).setHeader(anyString(), anyString()); } + + @Test + @DisplayName("postHandle不应该为1xx信息响应设置版本头") + void shouldNotSetVersionHeader_whenResponseIsInformational() { + // Given - 100 Continue + when(response.getStatus()).thenReturn(100); + + // When + interceptor.postHandle(request, response, handler, modelAndView); + + // Then + verify(response, never()).setHeader(anyString(), anyString()); + } } diff --git a/src/test/java/com/mosquito/project/web/RateLimitInterceptorTest.java b/src/test/java/com/mosquito/project/web/RateLimitInterceptorTest.java index 6fbbe26..062ef1a 100644 --- a/src/test/java/com/mosquito/project/web/RateLimitInterceptorTest.java +++ b/src/test/java/com/mosquito/project/web/RateLimitInterceptorTest.java @@ -231,7 +231,7 @@ class RateLimitInterceptorTest { Environment environment = mock(Environment.class); StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class); ValueOperations valueOperations = mock(ValueOperations.class); - + given(environment.getActiveProfiles()).willReturn(new String[]{"dev"}); given(environment.getProperty("app.rate-limit.per-minute", "100")).willReturn("100"); given(redisTemplate.opsForValue()).willReturn(valueOperations); @@ -250,4 +250,45 @@ class RateLimitInterceptorTest { // Then assertThat(result).isTrue(); } + + @Test + @DisplayName("应识别production配置文件为生产模式") + void shouldRecognizeProductionProfile() { + // Given + Environment environment = mock(Environment.class); + StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class); + + given(environment.getActiveProfiles()).willReturn(new String[]{"production"}); + given(environment.getProperty("app.rate-limit.per-minute", "100")).willReturn("100"); + + // When & Then - 不应抛出异常 + RateLimitInterceptor interceptor = new RateLimitInterceptor(environment, redisTemplate); + assertThat(interceptor).isNotNull(); + } + + @Test + @DisplayName("Redis返回null时应使用默认值1") + void shouldUseDefaultValue_whenRedisReturnsNull() { + // Given + Environment environment = mock(Environment.class); + StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class); + ValueOperations valueOperations = mock(ValueOperations.class); + + given(environment.getActiveProfiles()).willReturn(new String[]{"dev"}); + given(environment.getProperty("app.rate-limit.per-minute", "100")).willReturn("100"); + given(redisTemplate.opsForValue()).willReturn(valueOperations); + given(valueOperations.increment(anyString())).willReturn(null); // Redis返回null + + RateLimitInterceptor interceptor = new RateLimitInterceptor(environment, redisTemplate); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("X-API-Key", API_KEY); + MockHttpServletResponse response = new MockHttpServletResponse(); + + // When + boolean result = interceptor.preHandle(request, response, new Object()); + + // Then + assertThat(result).isTrue(); + assertThat(response.getHeader("X-RateLimit-Remaining")).isEqualTo("99"); + } } diff --git a/src/test/java/com/mosquito/project/web/UserAuthInterceptorTest.java b/src/test/java/com/mosquito/project/web/UserAuthInterceptorTest.java index 72af9b3..039fb2d 100644 --- a/src/test/java/com/mosquito/project/web/UserAuthInterceptorTest.java +++ b/src/test/java/com/mosquito/project/web/UserAuthInterceptorTest.java @@ -1,6 +1,7 @@ package com.mosquito.project.web; import com.mosquito.project.config.AppConfig; +import com.mosquito.project.security.IntrospectionResponse; import com.mosquito.project.security.UserIntrospectionService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -12,6 +13,9 @@ import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class UserAuthInterceptorTest { @@ -28,4 +32,39 @@ class UserAuthInterceptorTest { assertFalse(result); assertEquals(401, response.getStatus()); } + + @Test + @DisplayName("空白Authorization应拒绝") + void shouldRejectRequest_whenBlankAuthorization() { + UserIntrospectionService service = new UserIntrospectionService(new RestTemplateBuilder(), new AppConfig(), Optional.empty()); + UserAuthInterceptor interceptor = new UserAuthInterceptor(service); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", " "); // 空白 + MockHttpServletResponse response = new MockHttpServletResponse(); + + boolean result = interceptor.preHandle(request, response, new Object()); + + assertFalse(result); + assertEquals(401, response.getStatus()); + } + + @Test + @DisplayName("不活跃的token应拒绝") + void shouldRejectRequest_whenTokenIsInactive() { + // Given + UserIntrospectionService service = mock(UserIntrospectionService.class); + when(service.introspect(anyString())).thenReturn(IntrospectionResponse.inactive()); + + UserAuthInterceptor interceptor = new UserAuthInterceptor(service); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer expired-token"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + // When + boolean result = interceptor.preHandle(request, response, new Object()); + + // Then + assertFalse(result); + assertEquals(401, response.getStatus()); + } }