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
This commit is contained in:
Your Name
2026-03-04 09:58:38 +08:00
parent c50e32d9e5
commit 0b9d82c8d3
4 changed files with 101 additions and 2 deletions

View File

@@ -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)"
]
}
}

View File

@@ -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());
}
}

View File

@@ -231,7 +231,7 @@ class RateLimitInterceptorTest {
Environment environment = mock(Environment.class);
StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);
ValueOperations<String, String> 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<String, String> 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");
}
}

View File

@@ -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());
}
}