test: 提升ActivityController测试覆盖率 - 新增13个API契约测试
- 新增创建/更新/获取活动测试 - 新增活动统计和关系图测试 - 新增排行榜分页测试(topN, page, size边界条件) - 新增排行榜CSV导出测试(带/不带topN) - 新增null/负数/无效参数处理测试 - 新增页码超出范围返回空列表测试 覆盖率提升: - Controller包: 67% → 73% (+6%) - 指令覆盖率: 85% → 86% (+1%) - 总分支覆盖率: 62% (保持) 距离70%目标还需47个分支,完成度90%
This commit is contained in:
@@ -80,7 +80,10 @@
|
|||||||
"Bash(git commit -m \"test: 提升ActivityService测试覆盖率 - 新增21个边界条件和异常处理测试\n\n- 新增calculateReward边界条件测试(null/empty tiers, 无达成层级)\n- 新增calculateMultiLevelReward的null规则测试\n- 新增generateLeaderboardCsv的topN边界条件测试\n- 新增getActivityGraph的maxDepth和limit边界条件测试\n- 新增API密钥验证异常路径测试(revoked, invalid hash, missing)\n- 新增文件上传null contentType测试\n- 新增活动访问权限额外场景测试\n\n覆盖率提升:\n- 分支覆盖率: 57.8% → 61% \\(+3.2%\\)\n- Service包: 74% → 83% \\(+9%\\)\n- 指令覆盖率: 84% → 85% \\(+1%\\)\n- 行覆盖率: 90.56% → 92% \\(+1.44%\\)\n\n距离70%目标还需55个分支,完成度87%\")",
|
"Bash(git commit -m \"test: 提升ActivityService测试覆盖率 - 新增21个边界条件和异常处理测试\n\n- 新增calculateReward边界条件测试(null/empty tiers, 无达成层级)\n- 新增calculateMultiLevelReward的null规则测试\n- 新增generateLeaderboardCsv的topN边界条件测试\n- 新增getActivityGraph的maxDepth和limit边界条件测试\n- 新增API密钥验证异常路径测试(revoked, invalid hash, missing)\n- 新增文件上传null contentType测试\n- 新增活动访问权限额外场景测试\n\n覆盖率提升:\n- 分支覆盖率: 57.8% → 61% \\(+3.2%\\)\n- Service包: 74% → 83% \\(+9%\\)\n- 指令覆盖率: 84% → 85% \\(+1%\\)\n- 行覆盖率: 90.56% → 92% \\(+1.44%\\)\n\n距离70%目标还需55个分支,完成度87%\")",
|
||||||
"Bash(mvn test -Dtest=ShareConfigServiceTest -q)",
|
"Bash(mvn test -Dtest=ShareConfigServiceTest -q)",
|
||||||
"Bash(mvn clean test jacoco:report -q 2>&1 | grep -A 5 \"Tests run:\" | tail -20)",
|
"Bash(mvn clean test jacoco:report -q 2>&1 | grep -A 5 \"Tests run:\" | tail -20)",
|
||||||
"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(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%\")"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
package com.mosquito.project.controller;
|
package com.mosquito.project.controller;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.mosquito.project.domain.Activity;
|
import com.mosquito.project.domain.Activity;
|
||||||
|
import com.mosquito.project.domain.LeaderboardEntry;
|
||||||
|
import com.mosquito.project.dto.ActivityGraphResponse;
|
||||||
|
import com.mosquito.project.dto.ActivityStatsResponse;
|
||||||
|
import com.mosquito.project.dto.CreateActivityRequest;
|
||||||
|
import com.mosquito.project.dto.UpdateActivityRequest;
|
||||||
import com.mosquito.project.service.ActivityService;
|
import com.mosquito.project.service.ActivityService;
|
||||||
import com.mosquito.project.support.TestAuthSupport;
|
import com.mosquito.project.support.TestAuthSupport;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -12,10 +18,16 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyLong;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
|
||||||
|
|
||||||
@WebMvcTest(ActivityController.class)
|
@WebMvcTest(ActivityController.class)
|
||||||
@Import(com.mosquito.project.config.ControllerTestConfig.class)
|
@Import(com.mosquito.project.config.ControllerTestConfig.class)
|
||||||
@@ -29,6 +41,9 @@ class ActivityControllerContractTest {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private MockMvc mockMvc;
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
@MockBean
|
@MockBean
|
||||||
private ActivityService activityService;
|
private ActivityService activityService;
|
||||||
|
|
||||||
@@ -47,4 +62,224 @@ class ActivityControllerContractTest {
|
|||||||
.andExpect(jsonPath("$.code").value(200))
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
.andExpect(jsonPath("$.data.id").value(1));
|
.andExpect(jsonPath("$.data.id").value(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateActivity() throws Exception {
|
||||||
|
CreateActivityRequest request = new CreateActivityRequest();
|
||||||
|
request.setName("新活动");
|
||||||
|
request.setStartTime(ZonedDateTime.now());
|
||||||
|
request.setEndTime(ZonedDateTime.now().plusDays(7));
|
||||||
|
|
||||||
|
Activity activity = new Activity();
|
||||||
|
activity.setId(1L);
|
||||||
|
activity.setName("新活动");
|
||||||
|
when(activityService.createActivity(any(CreateActivityRequest.class))).thenReturn(activity);
|
||||||
|
|
||||||
|
mockMvc.perform(post("/api/v1/activities")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(request))
|
||||||
|
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
|
||||||
|
.header("Authorization", "Bearer test-token"))
|
||||||
|
.andExpect(status().isCreated())
|
||||||
|
.andExpect(jsonPath("$.code").value(201))
|
||||||
|
.andExpect(jsonPath("$.data.id").value(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetActivities() throws Exception {
|
||||||
|
List<Activity> activities = new ArrayList<>();
|
||||||
|
Activity activity = new Activity();
|
||||||
|
activity.setId(1L);
|
||||||
|
activity.setName("活动1");
|
||||||
|
activities.add(activity);
|
||||||
|
when(activityService.getAllActivities()).thenReturn(activities);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v1/activities")
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.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].id").value(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUpdateActivity() throws Exception {
|
||||||
|
UpdateActivityRequest request = new UpdateActivityRequest();
|
||||||
|
request.setName("更新活动");
|
||||||
|
request.setStartTime(ZonedDateTime.now());
|
||||||
|
request.setEndTime(ZonedDateTime.now().plusDays(7));
|
||||||
|
|
||||||
|
Activity activity = new Activity();
|
||||||
|
activity.setId(1L);
|
||||||
|
activity.setName("更新活动");
|
||||||
|
when(activityService.updateActivity(eq(1L), any(UpdateActivityRequest.class))).thenReturn(activity);
|
||||||
|
|
||||||
|
mockMvc.perform(put("/api/v1/activities/1")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(request))
|
||||||
|
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
|
||||||
|
.header("Authorization", "Bearer test-token"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
|
.andExpect(jsonPath("$.data.id").value(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetActivityStats() throws Exception {
|
||||||
|
ActivityStatsResponse stats = new ActivityStatsResponse(100L, 50L, new ArrayList<>());
|
||||||
|
when(activityService.getActivityStats(1L)).thenReturn(stats);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v1/activities/1/stats")
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
|
||||||
|
.header("Authorization", "Bearer test-token"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
|
.andExpect(jsonPath("$.data.totalParticipants").value(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetActivityGraph() throws Exception {
|
||||||
|
ActivityGraphResponse graph = new ActivityGraphResponse(new ArrayList<>(), new ArrayList<>());
|
||||||
|
when(activityService.getActivityGraph(anyLong(), any(), any(), any())).thenReturn(graph);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v1/activities/1/graph")
|
||||||
|
.param("rootUserId", "1")
|
||||||
|
.param("maxDepth", "3")
|
||||||
|
.param("limit", "1000")
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
|
||||||
|
.header("Authorization", "Bearer test-token"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetLeaderboard_withTopN() throws Exception {
|
||||||
|
List<LeaderboardEntry> entries = new ArrayList<>();
|
||||||
|
for (int i = 1; i <= 10; i++) {
|
||||||
|
entries.add(new LeaderboardEntry((long) i, "用户" + i, 100 - i));
|
||||||
|
}
|
||||||
|
when(activityService.getLeaderboard(1L)).thenReturn(entries);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v1/activities/1/leaderboard")
|
||||||
|
.param("topN", "5")
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
|
||||||
|
.header("Authorization", "Bearer test-token"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
|
.andExpect(jsonPath("$.data.length()").value(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetLeaderboard_withPagination() throws Exception {
|
||||||
|
List<LeaderboardEntry> entries = new ArrayList<>();
|
||||||
|
for (int i = 1; i <= 50; i++) {
|
||||||
|
entries.add(new LeaderboardEntry((long) i, "用户" + i, 100 - i));
|
||||||
|
}
|
||||||
|
when(activityService.getLeaderboard(1L)).thenReturn(entries);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v1/activities/1/leaderboard")
|
||||||
|
.param("page", "1")
|
||||||
|
.param("size", "20")
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
|
||||||
|
.header("Authorization", "Bearer test-token"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
|
.andExpect(jsonPath("$.data.length()").value(20))
|
||||||
|
.andExpect(jsonPath("$.meta.pagination.page").value(1))
|
||||||
|
.andExpect(jsonPath("$.meta.pagination.size").value(20))
|
||||||
|
.andExpect(jsonPath("$.meta.pagination.total").value(50));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetLeaderboard_returnEmptyWhenPageExceedsTotal() throws Exception {
|
||||||
|
List<LeaderboardEntry> entries = new ArrayList<>();
|
||||||
|
for (int i = 1; i <= 10; i++) {
|
||||||
|
entries.add(new LeaderboardEntry((long) i, "用户" + i, 100 - i));
|
||||||
|
}
|
||||||
|
when(activityService.getLeaderboard(1L)).thenReturn(entries);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v1/activities/1/leaderboard")
|
||||||
|
.param("page", "10")
|
||||||
|
.param("size", "20")
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
|
||||||
|
.header("Authorization", "Bearer test-token"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
|
.andExpect(jsonPath("$.data.length()").value(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetLeaderboard_handleNullPage() throws Exception {
|
||||||
|
List<LeaderboardEntry> entries = new ArrayList<>();
|
||||||
|
entries.add(new LeaderboardEntry(1L, "用户1", 100));
|
||||||
|
when(activityService.getLeaderboard(1L)).thenReturn(entries);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v1/activities/1/leaderboard")
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
|
||||||
|
.header("Authorization", "Bearer test-token"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
|
.andExpect(jsonPath("$.meta.pagination.page").value(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetLeaderboard_handleNegativePage() throws Exception {
|
||||||
|
List<LeaderboardEntry> entries = new ArrayList<>();
|
||||||
|
entries.add(new LeaderboardEntry(1L, "用户1", 100));
|
||||||
|
when(activityService.getLeaderboard(1L)).thenReturn(entries);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v1/activities/1/leaderboard")
|
||||||
|
.param("page", "-1")
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
|
||||||
|
.header("Authorization", "Bearer test-token"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.meta.pagination.page").value(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetLeaderboard_handleInvalidSize() throws Exception {
|
||||||
|
List<LeaderboardEntry> entries = new ArrayList<>();
|
||||||
|
entries.add(new LeaderboardEntry(1L, "用户1", 100));
|
||||||
|
when(activityService.getLeaderboard(1L)).thenReturn(entries);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v1/activities/1/leaderboard")
|
||||||
|
.param("size", "0")
|
||||||
|
.accept(MediaType.APPLICATION_JSON)
|
||||||
|
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
|
||||||
|
.header("Authorization", "Bearer test-token"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.meta.pagination.size").value(20));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExportLeaderboard_withoutTopN() throws Exception {
|
||||||
|
when(activityService.generateLeaderboardCsv(1L)).thenReturn("userId,userName,score\n1,用户1,100\n");
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v1/activities/1/leaderboard/export")
|
||||||
|
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
|
||||||
|
.header("Authorization", "Bearer test-token"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(header().string("Content-Type", "text/csv;charset=UTF-8"))
|
||||||
|
.andExpect(header().string("Content-Disposition", "attachment; filename=\"leaderboard_1.csv\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExportLeaderboard_withTopN() throws Exception {
|
||||||
|
when(activityService.generateLeaderboardCsv(1L, 10)).thenReturn("userId,userName,score\n1,用户1,100\n");
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/v1/activities/1/leaderboard/export")
|
||||||
|
.param("topN", "10")
|
||||||
|
.header("X-API-Key", TestAuthSupport.RAW_API_KEY)
|
||||||
|
.header("Authorization", "Bearer test-token"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(header().string("Content-Type", "text/csv;charset=UTF-8"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user