Просмотр исходного кода

fix(cf): CfCommissionServiceTest 改调生产 CfReferralService.matchRateTier(注入 mock Mapper,消除 test-the-copy 反模式)

Sisyphus 2 недель назад
Родитель
Сommit
6d902fa127

+ 84 - 91
cfc-backend/src/test/java/com/etotem/cfc/service/CfCommissionServiceTest.java

@@ -1,126 +1,119 @@
 package com.etotem.cfc.service;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.entity.CfRateTier;
+import com.etotem.cfc.mapper.CfRateTierMapper;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
 
 /**
  * CF 值分佣阶梯匹配边界测试。
  *
- * 复刻 CfReferralService.matchRateTier 的匹配语义:
- *   WHERE enabled = 1 AND min_team_size <= teamSize
- *   ORDER BY min_team_size DESC LIMIT 1
+ * 通过 Mockito 注入 mock 的 CfRateTierMapper 到生产方法所在的 CfReferralService,
+ * 调用真实的 CfReferralService.matchRateTier(int) 验证匹配语义,而非复刻一份逻辑。
  *
- * 种子数据(DatabaseInitializer 迁移279):
+ * 生产方法 matchRateTier 内部用 MyBatis-Plus 查询:
+ *   WHERE enabled=1 AND min_team_size <= teamSize ORDER BY min_team_size DESC LIMIT 1
+ * 该查询在数据库侧执行;单元测试中由 mock 用 thenAnswer 模拟 DB 侧语义(过滤 + 降序),
+ * 再由生产方法自行取第一条。这样生产方法 get(0)/empty 的真实 Java 逻辑仍被覆盖。
+ *
+ * 种子档位:
  *   铜牌 min=0  5% / 银牌 min=3  10% / 金牌 min=10 15%
  *   铂金 min=30 20% / 钻石 min=100 25%
- *
- * 预期边界映射:
- *   0/1/2  → 铜牌 5
- *   3~9    → 银牌 10
- *   10~29  → 金牌 15
- *   30~99  → 铂金 20
- *   100+   → 钻石 25
+ * 预期边界:
+ *   0/1/2  → 铜牌 5;3~9 → 银牌 10;10~29 → 金牌 15
+ *   30~99  → 铂金 20;100+ → 钻石 25
  */
 class CfCommissionServiceTest {
 
-    private static final List<CfRateTier> SEED_TIERS = buildSeedTiers();
+    @Mock
+    private CfRateTierMapper cfRateTierMapper;
+
+    @InjectMocks
+    private CfReferralService cfReferralService;
+
+    private List<CfRateTier> seedTiers;
+
+    @BeforeEach
+    void setUp() {
+        MockitoAnnotations.openMocks(this);
+        seedTiers = new ArrayList<>();
+        seedTiers.add(tier("铜牌", 0, 5));
+        seedTiers.add(tier("银牌", 3, 10));
+        seedTiers.add(tier("金牌", 10, 15));
+        seedTiers.add(tier("铂金", 30, 20));
+        seedTiers.add(tier("钻石", 100, 25));
+
+        // 模拟 DB 侧语义:WHERE enabled=1 AND min_team_size <= teamSize ORDER BY min_team_size DESC
+        when(cfRateTierMapper.selectList(any(LambdaQueryWrapper.class))).thenAnswer(inv -> {
+            LambdaQueryWrapper<CfRateTier> wrapper = inv.getArgument(0);
+            int teamSize = extractTeamSize(wrapper);
+            return seedTiers.stream()
+                    .filter(t -> t.getEnabled() != null && t.getEnabled() == 1
+                            && t.getMinTeamSize() != null && t.getMinTeamSize() <= teamSize)
+                    .sorted(Comparator.comparing(CfRateTier::getMinTeamSize).reversed())
+                    .collect(Collectors.toList());
+        });
+    }
 
-    private static List<CfRateTier> buildSeedTiers() {
-        List<CfRateTier> list = new ArrayList<>();
-        list.add(tier("铜牌", 0, 5, 1));
-        list.add(tier("银牌", 3, 10, 2));
-        list.add(tier("金牌", 10, 15, 3));
-        list.add(tier("铂金", 30, 20, 4));
-        list.add(tier("钻石", 100, 25, 5));
-        return list;
+    /**
+     * 从 wrapper 的参数取值里取出 le 条件的 teamSize。
+     * wrapper.getParamNameValuePairs() 记录了 eq(enabled,1) 与 le(min_team_size, teamSize) 的值,
+     * enabled 恒为 1,取其余整型即 teamSize。
+     */
+    private int extractTeamSize(LambdaQueryWrapper<CfRateTier> wrapper) {
+        Map<String, Object> params = wrapper.getParamNameValuePairs();
+        return params.values().stream()
+                .filter(v -> v instanceof Integer && !v.equals(1))
+                .map(v -> (Integer) v)
+                .findFirst()
+                .orElse(0);
     }
 
-    private static CfRateTier tier(String name, int minTeamSize, int ratePercent, int sortOrder) {
+    private CfRateTier tier(String name, int min, int rate) {
         CfRateTier t = new CfRateTier();
         t.setTierName(name);
-        t.setMinTeamSize(minTeamSize);
-        t.setRatePercent(ratePercent);
-        t.setSortOrder(sortOrder);
+        t.setMinTeamSize(min);
+        t.setRatePercent(rate);
         t.setEnabled(1);
         return t;
     }
 
-    /**
-     * 等价于 CfReferralService.matchRateTier 的查询语义。
-     * 在内存中对启用的档位按 min_team_size <= teamSize 过滤后取最大 min_team_size 档位。
-     */
-    private CfRateTier matchRateTier(int teamSize) {
-        CfRateTier best = null;
-        for (CfRateTier t : SEED_TIERS) {
-            if (t.getEnabled() != 1 || t.getMinTeamSize() > teamSize) {
-                continue;
-            }
-            if (best == null || t.getMinTeamSize() > best.getMinTeamSize()) {
-                best = t;
-            }
-        }
-        return best;
-    }
-
-    @Test
-    void teamSize0_shouldMatchBronze5() {
-        assertTier(0, 5, "铜牌");
-    }
-
-    @Test
-    void teamSize2_shouldMatchBronze5() {
-        assertTier(2, 5, "铜牌");
-    }
-
-    @Test
-    void teamSize3_shouldMatchSilver10() {
-        assertTier(3, 10, "银牌");
-    }
-
-    @Test
-    void teamSize9_shouldMatchSilver10() {
-        assertTier(9, 10, "银牌");
-    }
-
-    @Test
-    void teamSize10_shouldMatchGold15() {
-        assertTier(10, 15, "金牌");
-    }
-
-    @Test
-    void teamSize29_shouldMatchGold15() {
-        assertTier(29, 15, "金牌");
-    }
-
-    @Test
-    void teamSize30_shouldMatchPlatinum20() {
-        assertTier(30, 20, "铂金");
-    }
-
-    @Test
-    void teamSize99_shouldMatchPlatinum20() {
-        assertTier(99, 20, "铂金");
-    }
-
     @Test
-    void teamSize100_shouldMatchDiamond25() {
-        assertTier(100, 25, "钻石");
+    void matchRateTier_边界档位() {
+        assertTier(0, "铜牌", 5);
+        assertTier(3, "银牌", 10);
+        assertTier(10, "金牌", 15);
+        assertTier(30, "铂金", 20);
+        assertTier(100, "钻石", 25);
     }
 
     @Test
-    void teamSize100plus_shouldMatchDiamond25() {
-        assertTier(1000, 25, "钻石");
+    void matchRateTier_临界相邻值() {
+        assertTier(2, "铜牌", 5);
+        assertTier(9, "银牌", 10);
+        assertTier(29, "金牌", 15);
+        assertTier(99, "铂金", 20);
+        assertTier(1000, "钻石", 25);
     }
 
-    private void assertTier(int teamSize, int expectedRate, String expectedName) {
-        CfRateTier tier = matchRateTier(teamSize);
-        assertNotNull(tier, "teamSize=" + teamSize + " 应匹配到档位");
-        assertEquals(expectedRate, tier.getRatePercent(), "teamSize=" + teamSize + " 返佣比例");
-        assertEquals(expectedName, tier.getTierName(), "teamSize=" + teamSize + " 档位名");
+    private void assertTier(int teamSize, String name, int rate) {
+        CfRateTier result = cfReferralService.matchRateTier(teamSize);
+        assertEquals(name, result.getTierName(), "teamSize=" + teamSize);
+        assertEquals(rate, result.getRatePercent(), "teamSize=" + teamSize);
     }
 }