diff --git a/.claude/settings.local.json b/.claude/settings.local.json index c7bae57..8f01af5 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -76,7 +76,11 @@ "Bash(mvn test -Dtest=ActivityServiceCoverageTest -q)", "Bash(mvn clean test jacoco:report -q 2>&1 | tail -100)", "Bash(cat > /tmp/coverage_summary.txt << 'EOF'\n# 测试覆盖率提升报告 - 2026-03-03\n\n## 总体覆盖率\n\n| 指标 | 未覆盖 | 总数 | 覆盖率 | 之前 | 提升 |\n|------|--------|------|--------|------|------|\n| **指令覆盖率** | 1,486 | 10,426 | **85%** | 84% | +1% |\n| **分支覆盖率** | 250 | 646 | **61%** | 57.8% | +3.2% |\n| **行覆盖率** | 184 | 2,327 | **92%** | 90.56% | +1.44% |\n| **方法覆盖率** | 89 | 921 | **90%** | - | - |\n| **类覆盖率** | 4 | 110 | **96%** | - | - |\n\n## 各包覆盖率详情\n\n| 包名 | 指令覆盖率 | 分支覆盖率 | 说明 |\n|------|-----------|-----------|------|\n| **service** | 91% | **83%** | ⬆️ 从74%提升到83% \\(+9%\\) |\n| **controller** | 96% | 67% | ⬆️ 从67%保持稳定 |\n| **domain** | 83% | 91% | ✅ 优秀 |\n| **security** | 91% | 82% | ✅ 优秀 |\n| **web** | 89% | 78% | ✅ 良好 |\n| **sdk** | 93% | 66% | ✅ 良好 |\n| **config** | 96% | 100% | ✅ 完美 |\n| **job** | 100% | 100% | ✅ 完美 |\n| **dto** | 55% | 5% | ⚠️ Lombok代码 |\n\n## 本次工作成果\n\n### 新增测试\n- **ActivityServiceCoverageTest**: +21个测试用例\n- 总测试数: 1360 → 1381 \\(+21\\)\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### 新增测试覆盖的场景\n1. calculateReward边界条件(null/empty tiers, no tier achieved)\n2. calculateMultiLevelReward的null规则\n3. generateLeaderboardCsv的topN边界条件\n4. getActivityGraph的maxDepth和limit边界条件\n5. validateApiKeyByPrefix的异常路径(revoked, invalid hash, missing)\n6. validateAndMarkApiKeyUsed的异常路径\n7. uploadCustomizationImage的null contentType\n8. accessActivity的额外场景\n\n## 距离70%目标\n\n- **当前**: 61% \\(396/646 branches\\)\n- **目标**: 70% \\(451/646 branches\\)\n- **差距**: 55个分支\n- **完成度**: 87%\n\n## 下一步建议\n\n继续提升Service和Controller包的覆盖率,预计再增加30-40个测试用例即可达到70%目标。\n\nEOF\ncat /tmp/coverage_summary.txt)", - "Bash(git add -A && git status)" + "Bash(git add -A && git status)", + "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 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%\")" ] } } diff --git a/src/test/java/com/mosquito/project/service/ShareConfigServiceTest.java b/src/test/java/com/mosquito/project/service/ShareConfigServiceTest.java index ca98a39..d7a257a 100644 --- a/src/test/java/com/mosquito/project/service/ShareConfigServiceTest.java +++ b/src/test/java/com/mosquito/project/service/ShareConfigServiceTest.java @@ -1,26 +1,33 @@ package com.mosquito.project.service; import com.mosquito.project.config.AppConfig; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.HashMap; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; class ShareConfigServiceTest { - @Test - void buildShareUrl_fallsBackToDefaultTemplateAndEncodesExtraParams() { - AppConfig appConfig = new AppConfig(); + private ShareConfigService service; + private AppConfig appConfig; + + @BeforeEach + void setUp() { + appConfig = new AppConfig(); AppConfig.ShortLinkConfig shortLinkConfig = new AppConfig.ShortLinkConfig(); shortLinkConfig.setLandingBaseUrl("https://example.com/landing"); shortLinkConfig.setCdnBaseUrl("https://cdn.example.com"); appConfig.setShortLink(shortLinkConfig); + service = new ShareConfigService(appConfig); + } - ShareConfigService service = new ShareConfigService(appConfig); - + @Test + void buildShareUrl_fallsBackToDefaultTemplateAndEncodesExtraParams() { Map extraParams = new HashMap<>(); extraParams.put("channel", "summer promo"); extraParams.put("source", "email"); @@ -36,13 +43,6 @@ class ShareConfigServiceTest { @Test void getShareMeta_resolvesPlaceholdersAndUsesTemplate() { - AppConfig appConfig = new AppConfig(); - AppConfig.ShortLinkConfig shortLinkConfig = new AppConfig.ShortLinkConfig(); - shortLinkConfig.setLandingBaseUrl("https://example.com/landing"); - shortLinkConfig.setCdnBaseUrl("https://cdn.example.com"); - appConfig.setShortLink(shortLinkConfig); - - ShareConfigService service = new ShareConfigService(appConfig); ShareConfigService.ShareTemplate template = new ShareConfigService.ShareTemplate(); template.setTitle("活动{{activityId}}"); template.setDescription("邀请用户{{userId}}参与"); @@ -67,4 +67,158 @@ class ShareConfigServiceTest { assertTrue(url.contains("utm_source=mosquito")); assertTrue(url.contains("utm_medium=share")); } + + @Test + void buildShareUrl_shouldHandleNullExtraParams() { + ShareConfigService.ShareTemplate template = new ShareConfigService.ShareTemplate(); + template.setLandingPageUrl("https://example.com/landing"); + service.registerTemplate("test", template); + + String url = service.buildShareUrl(1L, 2L, "test", null); + + assertNotNull(url); + assertTrue(url.contains("activityId=1")); + assertTrue(url.contains("inviter=2")); + } + + @Test + void buildShareUrl_shouldHandleNullUtmParams() { + ShareConfigService.ShareTemplate template = new ShareConfigService.ShareTemplate(); + template.setLandingPageUrl("https://example.com/landing"); + template.setUtmParams(null); + service.registerTemplate("test", template); + + String url = service.buildShareUrl(1L, 2L, "test", null); + + assertNotNull(url); + assertTrue(url.contains("activityId=1")); + } + + @Test + void buildShareUrl_shouldSkipNullKeysInExtraParams() { + ShareConfigService.ShareTemplate template = new ShareConfigService.ShareTemplate(); + template.setLandingPageUrl("https://example.com/landing"); + service.registerTemplate("test", template); + + Map extraParams = new HashMap<>(); + extraParams.put(null, "value"); + extraParams.put("key", null); + extraParams.put("valid", "data"); + + String url = service.buildShareUrl(1L, 2L, "test", extraParams); + + assertNotNull(url); + assertTrue(url.contains("valid=data")); + } + + @Test + void getShareMeta_shouldHandleNullTitle() { + ShareConfigService.ShareTemplate template = new ShareConfigService.ShareTemplate(); + template.setTitle(null); + template.setDescription("desc"); + template.setImageUrl("img"); + template.setLandingPageUrl("https://example.com/landing"); + service.registerTemplate("test", template); + + Map meta = service.getShareMeta(1L, 2L, "test"); + + assertEquals("", meta.get("title")); + } + + @Test + void getShareMeta_shouldHandleNullDescription() { + ShareConfigService.ShareTemplate template = new ShareConfigService.ShareTemplate(); + template.setTitle("title"); + template.setDescription(null); + template.setImageUrl("img"); + template.setLandingPageUrl("https://example.com/landing"); + service.registerTemplate("test", template); + + Map meta = service.getShareMeta(1L, 2L, "test"); + + assertEquals("", meta.get("description")); + } + + @Test + void getShareMeta_shouldHandleNullImageUrl() { + ShareConfigService.ShareTemplate template = new ShareConfigService.ShareTemplate(); + template.setTitle("title"); + template.setDescription("desc"); + template.setImageUrl(null); + template.setLandingPageUrl("https://example.com/landing"); + service.registerTemplate("test", template); + + Map meta = service.getShareMeta(1L, 2L, "test"); + + assertEquals("", meta.get("image")); + } + + @Test + void getShareMeta_shouldResolveTimestampPlaceholder() { + ShareConfigService.ShareTemplate template = new ShareConfigService.ShareTemplate(); + template.setTitle("活动{{timestamp}}"); + template.setDescription("desc"); + template.setImageUrl("img"); + template.setLandingPageUrl("https://example.com/landing"); + service.registerTemplate("test", template); + + Map meta = service.getShareMeta(1L, 2L, "test"); + + String title = (String) meta.get("title"); + assertTrue(title.startsWith("活动")); + assertTrue(title.length() > 2); + } + + @Test + void getShareMeta_shouldFallbackToDefaultTemplate() { + Map meta = service.getShareMeta(1L, 2L, "nonexistent"); + + assertEquals("邀请您参与活动", meta.get("title")); + assertEquals("快来加入我们的活动吧!", meta.get("description")); + assertTrue(((String) meta.get("image")).contains("/default-share.png")); + } + + @Test + void registerTemplate_shouldStoreTemplate() { + ShareConfigService.ShareTemplate template = new ShareConfigService.ShareTemplate(); + template.setTitle("test"); + service.registerTemplate("mytemplate", template); + + ShareConfigService.ShareTemplate retrieved = service.getTemplate("mytemplate"); + + assertNotNull(retrieved); + assertEquals("test", retrieved.getTitle()); + } + + @Test + void getTemplate_shouldReturnNullForNonexistent() { + ShareConfigService.ShareTemplate retrieved = service.getTemplate("nonexistent"); + + assertEquals(null, retrieved); + } + + @Test + void buildShareUrl_shouldHandleEmptyUtmParams() { + ShareConfigService.ShareTemplate template = new ShareConfigService.ShareTemplate(); + template.setLandingPageUrl("https://example.com/landing"); + template.setUtmParams(new HashMap<>()); + service.registerTemplate("test", template); + + String url = service.buildShareUrl(1L, 2L, "test", null); + + assertNotNull(url); + assertTrue(url.contains("activityId=1")); + } + + @Test + void buildShareUrl_shouldHandleEmptyExtraParams() { + ShareConfigService.ShareTemplate template = new ShareConfigService.ShareTemplate(); + template.setLandingPageUrl("https://example.com/landing"); + service.registerTemplate("test", template); + + String url = service.buildShareUrl(1L, 2L, "test", new HashMap<>()); + + assertNotNull(url); + assertTrue(url.contains("activityId=1")); + } }