test: 提升Controller测试覆盖率 - 新增IP提取和分页边界测试

- ShortLinkController: 新增3个测试覆盖IP地址提取逻辑
  * X-Forwarded-For头部处理
  * RemoteAddr回退逻辑
  * 空白X-Forwarded-For处理

- UserExperienceController: 新增4个测试覆盖分页边界条件
  * size=0时返回空列表
  * 负数page处理
  * Math.max边界逻辑

覆盖率提升:
- 总体分支覆盖率: 62% → 63%
- Controller包: 73% → 80% (+7%)
- 新增测试用例: 7个
- 距离70%目标: 还需44个分支
This commit is contained in:
Your Name
2026-03-03 11:51:55 +08:00
parent f815fdf5b8
commit 4f5060724b
3 changed files with 119 additions and 1 deletions

View File

@@ -83,7 +83,14 @@
"Bash(git add -A && git commit -m \"test: 提升ShareConfigService测试覆盖率 - 新增12个边界条件测试\n\n- 新增null参数处理测试extraParams, utmParams, title, description, imageUrl\n- 新增空集合处理测试empty utmParams, empty extraParams\n- 新增null key/value过滤测试\n- 新增占位符解析测试timestamp\n- 新增默认模板回退测试\n- 新增模板注册和获取测试\n\n覆盖率提升:\n- 分支覆盖率: 61% → 62% \\(+1%\\)\n- Service包: 83% → 85% \\(+2%\\)\n\n距离70%目标还需50个分支完成度89%\")",
"Bash(mvn test -Dtest=ActivityControllerContractTest -q)",
"Bash(mvn clean test jacoco:report -q 2>&1 | tail -20)",
"Bash(git add -A && git commit -m \"test: 提升ActivityController测试覆盖率 - 新增13个API契约测试\n\n- 新增创建/更新/获取活动测试\n- 新增活动统计和关系图测试\n- 新增排行榜分页测试topN, page, size边界条件\n- 新增排行榜CSV导出测试带/不带topN\n- 新增null/负数/无效参数处理测试\n- 新增页码超出范围返回空列表测试\n\n覆盖率提升:\n- Controller包: 67% → 73% \\(+6%\\)\n- 指令覆盖率: 85% → 86% \\(+1%\\)\n- 总分支覆盖率: 62% \\(保持\\)\n\n距离70%目标还需47个分支完成度90%\")"
"Bash(git add -A && git commit -m \"test: 提升ActivityController测试覆盖率 - 新增13个API契约测试\n\n- 新增创建/更新/获取活动测试\n- 新增活动统计和关系图测试\n- 新增排行榜分页测试topN, page, size边界条件\n- 新增排行榜CSV导出测试带/不带topN\n- 新增null/负数/无效参数处理测试\n- 新增页码超出范围返回空列表测试\n\n覆盖率提升:\n- Controller包: 67% → 73% \\(+6%\\)\n- 指令覆盖率: 85% → 86% \\(+1%\\)\n- 总分支覆盖率: 62% \\(保持\\)\n\n距离70%目标还需47个分支完成度90%\")",
"Bash(find src/test/java/com/mosquito/project/controller -name \"*Test.java\" -type f -exec basename {} \\\\; | sort)",
"Bash(find src/main/java/com/mosquito/project/controller -name \"*.java\" -type f -exec basename {} \\\\; | sort)",
"Bash(mvn clean test jacoco:report -q && echo \"=== Coverage Report Generated ===\" && ls -lh target/site/jacoco/)",
"Bash(mvn clean test jacoco:report -DskipTests=false 2>&1 | tail -100)",
"Bash(mvn test -Dtest=ShortLinkControllerTest 2>&1 | tail -50)",
"Bash(mvn clean test jacoco:report 2>&1 | grep -A 20 \"Results:\" | head -25)",
"Bash(mvn clean test jacoco:report 2>&1 | tail -100)"
]
}
}

View File

@@ -130,4 +130,45 @@ class ShortLinkControllerTest {
mockMvc.perform(get("/r/mal12345"))
.andExpect(status().isBadRequest());
}
@Test
void shouldExtractIpFromXForwardedFor() throws Exception {
ShortLinkEntity e = new ShortLinkEntity();
e.setCode("ip12345");
e.setOriginalUrl("https://example.com/page");
when(shortLinkService.findByCode("ip12345")).thenReturn(Optional.of(e));
when(urlValidator.isAllowedUrl("https://example.com/page")).thenReturn(true);
mockMvc.perform(get("/r/ip12345")
.header("X-Forwarded-For", "203.0.113.1, 198.51.100.1"))
.andExpect(status().isFound())
.andExpect(header().string("Location", "https://example.com/page"));
}
@Test
void shouldUseRemoteAddrWhenNoXForwardedFor() throws Exception {
ShortLinkEntity e = new ShortLinkEntity();
e.setCode("remote123");
e.setOriginalUrl("https://example.com/page");
when(shortLinkService.findByCode("remote123")).thenReturn(Optional.of(e));
when(urlValidator.isAllowedUrl("https://example.com/page")).thenReturn(true);
mockMvc.perform(get("/r/remote123"))
.andExpect(status().isFound())
.andExpect(header().string("Location", "https://example.com/page"));
}
@Test
void shouldHandleBlankXForwardedFor() throws Exception {
ShortLinkEntity e = new ShortLinkEntity();
e.setCode("blank123");
e.setOriginalUrl("https://example.com/page");
when(shortLinkService.findByCode("blank123")).thenReturn(Optional.of(e));
when(urlValidator.isAllowedUrl("https://example.com/page")).thenReturn(true);
mockMvc.perform(get("/r/blank123")
.header("X-Forwarded-For", " "))
.andExpect(status().isFound())
.andExpect(header().string("Location", "https://example.com/page"));
}
}

View File

@@ -240,4 +240,74 @@ class UserExperienceControllerTest {
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.title").value("自定义模板"));
}
@Test
void shouldHandleZeroOrNegativeSize_inInvitedFriends() throws Exception {
UserInviteEntity a = new UserInviteEntity(); a.setInviteeUserId(10L); a.setStatus("clicked");
UserInviteEntity b = new UserInviteEntity(); b.setInviteeUserId(11L); b.setStatus("registered");
when(userInviteRepository.findByActivityIdAndInviterUserId(anyLong(), anyLong())).thenReturn(List.of(a, b));
mockMvc.perform(get("/api/v1/me/invited-friends")
.param("activityId", "1")
.param("userId", "2")
.param("page", "0")
.param("size", "0")
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
.header("Authorization", "Bearer test-token"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").isEmpty());
}
@Test
void shouldHandleZeroOrNegativeSize_inRewards() throws Exception {
var r1 = new com.mosquito.project.persistence.entity.UserRewardEntity(); r1.setType("points"); r1.setPoints(100); r1.setCreatedAt(java.time.OffsetDateTime.now());
var r2 = new com.mosquito.project.persistence.entity.UserRewardEntity(); r2.setType("coupon"); r2.setPoints(0); r2.setCreatedAt(java.time.OffsetDateTime.now().minusDays(1));
when(userRewardRepository.findByActivityIdAndUserIdOrderByCreatedAtDesc(anyLong(), anyLong())).thenReturn(java.util.List.of(r1, r2));
mockMvc.perform(get("/api/v1/me/rewards")
.param("activityId", "1")
.param("userId", "2")
.param("page", "0")
.param("size", "0")
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
.header("Authorization", "Bearer test-token"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").isEmpty());
}
@Test
void shouldHandleNegativePage_inInvitedFriends() throws Exception {
UserInviteEntity a = new UserInviteEntity(); a.setInviteeUserId(10L); a.setStatus("clicked");
when(userInviteRepository.findByActivityIdAndInviterUserId(anyLong(), anyLong())).thenReturn(List.of(a));
mockMvc.perform(get("/api/v1/me/invited-friends")
.param("activityId", "1")
.param("userId", "2")
.param("page", "-1")
.param("size", "20")
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
.header("Authorization", "Bearer test-token"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data[0].status").value("clicked"));
}
@Test
void shouldHandleNegativePage_inRewards() throws Exception {
var r1 = new com.mosquito.project.persistence.entity.UserRewardEntity(); r1.setType("points"); r1.setPoints(100); r1.setCreatedAt(java.time.OffsetDateTime.now());
when(userRewardRepository.findByActivityIdAndUserIdOrderByCreatedAtDesc(anyLong(), anyLong())).thenReturn(java.util.List.of(r1));
mockMvc.perform(get("/api/v1/me/rewards")
.param("activityId", "1")
.param("userId", "2")
.param("page", "-1")
.param("size", "20")
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
.header("Authorization", "Bearer test-token"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data[0].type").value("points"));
}
}