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