|
|
@@ -0,0 +1,422 @@
|
|
|
+package com.etotem.cfc.unit;
|
|
|
+
|
|
|
+import com.etotem.cfc.common.Result;
|
|
|
+import com.etotem.cfc.controller.admin.AdminEnergyConfigController;
|
|
|
+import com.etotem.cfc.controller.admin.AdminEnergyRuleController;
|
|
|
+import com.etotem.cfc.controller.admin.DimensionWeightController;
|
|
|
+import com.etotem.cfc.controller.admin.AdminEnergyConfigController.DailyLimitSaveRequest;
|
|
|
+import com.etotem.cfc.controller.admin.AdminEnergyConfigController.DimensionRatio;
|
|
|
+import com.etotem.cfc.controller.admin.AdminEnergyConfigController.EnergySourceConfigSaveRequest;
|
|
|
+import com.etotem.cfc.entity.DimensionDailyLimit;
|
|
|
+import com.etotem.cfc.entity.EnergyDimension;
|
|
|
+import com.etotem.cfc.entity.EnergyRule;
|
|
|
+import com.etotem.cfc.entity.EnergySourceConfig;
|
|
|
+import com.etotem.cfc.entity.DimensionWeight;
|
|
|
+import com.etotem.cfc.mapper.DimensionDailyLimitMapper;
|
|
|
+import com.etotem.cfc.mapper.EnergyDimensionMapper;
|
|
|
+import com.etotem.cfc.mapper.EnergyRuleMapper;
|
|
|
+import com.etotem.cfc.mapper.EnergySourceConfigMapper;
|
|
|
+import com.etotem.cfc.service.DimensionWeightService;
|
|
|
+import com.etotem.cfc.service.EnergyService;
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
+import org.junit.jupiter.api.DisplayName;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
+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.assertThrows;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
+import static org.mockito.Mockito.never;
|
|
|
+import static org.mockito.Mockito.times;
|
|
|
+import static org.mockito.Mockito.verify;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+/**
|
|
|
+ * SC-A-25 维度/权重/每日上限配置治理 + SC-A-26 能量来源比例与归因权重治理 契约测试,
|
|
|
+ * 附 SC-P-90(DailyLimit 查看/保存)、SC-P-92(来源比例配置变更后重算生效)、SC-P-19(配置查看)。
|
|
|
+ *
|
|
|
+ * <p>三类流(以 AdminEnergyConfigController / AdminEnergyRuleController / DimensionWeightController
|
|
|
+ * controller 直调 + EnergyService.configureSourceRatios 服务层验证):</p>
|
|
|
+ * <ul>
|
|
|
+ * <li><b>正常流</b>:source-save 拆分多行写入、daily-limit-save 新增/更新幂等、overview 分组汇总、
|
|
|
+ * energy-rule create/toggle、dimension-weight save/list、来源比例变更后 awardEnergy 按新比例发放;</li>
|
|
|
+ * <li><b>异常流</b>:daily-limit-save 空 limits / 缺 code → 业务提示;dimension-weight 缺 targetType/
|
|
|
+ * targetId / weights → 业务提示;</li>
|
|
|
+ * <li><b>权限流</b>:energy-rule toggle/delete 缺 id → 业务提示;rule 不存在 → 业务提示;
|
|
|
+ * source-save 传 null ratios → 观察行为钉住(无显式 400 校验)。</li>
|
|
|
+ * </ul>
|
|
|
+ */
|
|
|
+class EnergyConfigAdminContractTest extends EnergyContractTestBase {
|
|
|
+
|
|
|
+ private AdminEnergyConfigController configController;
|
|
|
+ private EnergySourceConfigMapper sourceConfigMapper;
|
|
|
+ private DimensionDailyLimitMapper dailyLimitMapper;
|
|
|
+
|
|
|
+ private AdminEnergyRuleController ruleController;
|
|
|
+ private EnergyRuleMapper ruleMapper;
|
|
|
+
|
|
|
+ private DimensionWeightController weightController;
|
|
|
+ private DimensionWeightService weightService;
|
|
|
+
|
|
|
+ private EnergyService service;
|
|
|
+ private com.etotem.cfc.mapper.EnergyBehaviorConfigMapper behaviorConfigMapper;
|
|
|
+
|
|
|
+ @BeforeEach
|
|
|
+ void setUp() throws Exception {
|
|
|
+ // --- AdminEnergyConfigController ---
|
|
|
+ configController = new AdminEnergyConfigController();
|
|
|
+ sourceConfigMapper = mock(EnergySourceConfigMapper.class);
|
|
|
+ dailyLimitMapper = mock(DimensionDailyLimitMapper.class);
|
|
|
+ inject(configController, "energySourceConfigMapper", sourceConfigMapper);
|
|
|
+ inject(configController, "dimensionDailyLimitMapper", dailyLimitMapper);
|
|
|
+
|
|
|
+ // --- AdminEnergyRuleController ---
|
|
|
+ ruleController = new AdminEnergyRuleController();
|
|
|
+ ruleMapper = mock(EnergyRuleMapper.class);
|
|
|
+ inject(ruleController, "energyRuleMapper", ruleMapper);
|
|
|
+ inject(ruleController, "energyDimensionMapper", mock(EnergyDimensionMapper.class));
|
|
|
+
|
|
|
+ // --- DimensionWeightController ---
|
|
|
+ weightController = new DimensionWeightController();
|
|
|
+ weightService = mock(DimensionWeightService.class);
|
|
|
+ inject(weightController, "dimensionWeightService", weightService);
|
|
|
+
|
|
|
+ // --- EnergyService(SC-P-92 来源比例重算生效) ---
|
|
|
+ service = new EnergyService();
|
|
|
+ com.etotem.cfc.mapper.EnergyDimensionMapper dimensionMapper = mock(com.etotem.cfc.mapper.EnergyDimensionMapper.class);
|
|
|
+ behaviorConfigMapper = mock(com.etotem.cfc.mapper.EnergyBehaviorConfigMapper.class);
|
|
|
+ com.etotem.cfc.mapper.EnergyLogMapper logMapper = mock(com.etotem.cfc.mapper.EnergyLogMapper.class);
|
|
|
+ com.etotem.cfc.mapper.EnergyBalanceMapper balanceMapper = mock(com.etotem.cfc.mapper.EnergyBalanceMapper.class);
|
|
|
+ com.etotem.cfc.mapper.FamilyMemberMapper familyMemberMapper = mock(com.etotem.cfc.mapper.FamilyMemberMapper.class);
|
|
|
+ inject(service, "energyDimensionMapper", dimensionMapper);
|
|
|
+ inject(service, "energySourceConfigMapper", sourceConfigMapper);
|
|
|
+ inject(service, "energyBehaviorConfigMapper", behaviorConfigMapper);
|
|
|
+ inject(service, "energyLogMapper", logMapper);
|
|
|
+ inject(service, "energyBalanceMapper", balanceMapper);
|
|
|
+ inject(service, "wishMapper", mock(com.etotem.cfc.mapper.WishMapper.class));
|
|
|
+ inject(service, "familyMemberMapper", familyMemberMapper);
|
|
|
+ inject(service, "notificationDispatchService", mock(com.etotem.cfc.service.NotificationDispatchService.class));
|
|
|
+
|
|
|
+ when(dimensionMapper.selectList(any())).thenReturn(Arrays.asList(fiveDims()));
|
|
|
+ when(behaviorConfigMapper.selectOne(any())).thenReturn(null); // 不走行为配置 → 走来源比例
|
|
|
+ when(logMapper.selectList(any())).thenReturn(Collections.emptyList());
|
|
|
+ when(balanceMapper.selectOne(any())).thenReturn(null);
|
|
|
+ com.etotem.cfc.entity.FamilyMember member = new com.etotem.cfc.entity.FamilyMember();
|
|
|
+ member.setId(50L);
|
|
|
+ member.setUserId(12L);
|
|
|
+ when(familyMemberMapper.selectById(any())).thenReturn(member);
|
|
|
+ when(balanceMapper.selectOne(any())).thenReturn(null);
|
|
|
+
|
|
|
+ initTable(EnergySourceConfig.class);
|
|
|
+ initTable(DimensionDailyLimit.class);
|
|
|
+ initTable(EnergyDimension.class);
|
|
|
+ initTable(com.etotem.cfc.entity.EnergyLog.class);
|
|
|
+ initTable(com.etotem.cfc.entity.EnergyBalance.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ private EnergySourceConfig src(Long id, String type, Long sid, String name, Long dimId, Integer ratio) {
|
|
|
+ EnergySourceConfig c = new EnergySourceConfig();
|
|
|
+ c.setId(id);
|
|
|
+ c.setSourceType(type);
|
|
|
+ c.setSourceId(sid);
|
|
|
+ c.setSourceName(name);
|
|
|
+ c.setDimensionId(dimId);
|
|
|
+ c.setRatio(ratio);
|
|
|
+ c.setCreatedAt(new Date());
|
|
|
+ return c;
|
|
|
+ }
|
|
|
+
|
|
|
+ private DimensionDailyLimit limit(Long id, String code, Integer dailyLimit) {
|
|
|
+ DimensionDailyLimit l = new DimensionDailyLimit();
|
|
|
+ l.setId(id);
|
|
|
+ l.setDimensionCode(code);
|
|
|
+ l.setDailyLimit(dailyLimit);
|
|
|
+ Date now = new Date();
|
|
|
+ l.setCreatedAt(now);
|
|
|
+ l.setUpdatedAt(now);
|
|
|
+ return l;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== SC-P-19 / SC-A-26 正常:配置查看 ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-P-19/SC-A-26 正常:source-list 返回来源明细")
|
|
|
+ void 正常流_来源列表() {
|
|
|
+ when(sourceConfigMapper.selectList(any())).thenReturn(Arrays.asList(
|
|
|
+ src(1L, "task", 100L, "任务A", 5L, 60), src(2L, "task", 100L, "任务A", 3L, 40)));
|
|
|
+ Result<List<Map<String, Object>>> r = configController.sourceList(new HashMap<>());
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
+ assertEquals(2, r.getData().size());
|
|
|
+ Map<String, Object> first = r.getData().get(0);
|
|
|
+ assertEquals("task", first.get("sourceType"));
|
|
|
+ assertEquals(100L, first.get("sourceId"));
|
|
|
+ assertEquals(5L, first.get("dimensionId"));
|
|
|
+ assertEquals(60, first.get("ratio"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-25 正常:daily-limits 返回每日上限配置")
|
|
|
+ void 正常流_每日上限列表() {
|
|
|
+ when(dailyLimitMapper.selectList(any())).thenReturn(Arrays.asList(
|
|
|
+ limit(1L, "body", 50), limit(2L, "mind", 40)));
|
|
|
+ Result<List<DimensionDailyLimit>> r = configController.dailyLimits(new HashMap<>());
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
+ assertEquals(2, r.getData().size());
|
|
|
+ assertEquals("body", r.getData().get(0).getDimensionCode());
|
|
|
+ assertEquals(50, r.getData().get(0).getDailyLimit());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-26 正常:overview 按来源分组汇总 + 每日上限")
|
|
|
+ void 正常流_配置总览分组() {
|
|
|
+ when(sourceConfigMapper.selectList(any())).thenReturn(Arrays.asList(
|
|
|
+ src(1L, "task", 100L, "任务A", 5L, 60), src(2L, "task", 100L, "任务A", 3L, 40),
|
|
|
+ src(3L, "game", 200L, "游戏B", 1L, 100)));
|
|
|
+ when(dailyLimitMapper.selectList(any())).thenReturn(Collections.singletonList(limit(1L, "body", 50)));
|
|
|
+ Result<Map<String, Object>> r = configController.overview(new HashMap<>());
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ List<Map<String, Object>> sources = (List<Map<String, Object>>) r.getData().get("sources");
|
|
|
+ assertEquals(2, sources.size(), "应合并为 2 个来源:task/game");
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ List<Map<String, Object>> dailyLimits = (List<Map<String, Object>>) r.getData().get("dailyLimits");
|
|
|
+ assertEquals(1, dailyLimits.size());
|
|
|
+ assertEquals("body", dailyLimits.get(0).get("dimensionCode"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== SC-A-26 正常:来源配置保存 ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-26 正常:source-save 删除旧配置并拆分多行写入")
|
|
|
+ void 正常流_来源保存() {
|
|
|
+ EnergySourceConfigSaveRequest req = new EnergySourceConfigSaveRequest();
|
|
|
+ req.setSourceType("task");
|
|
|
+ req.setSourceId(100L);
|
|
|
+ req.setSourceName("任务A");
|
|
|
+ List<DimensionRatio> ratios = new ArrayList<>();
|
|
|
+ DimensionRatio r1 = new DimensionRatio();
|
|
|
+ r1.setDimensionId(5L);
|
|
|
+ r1.setRatio(60);
|
|
|
+ DimensionRatio r2 = new DimensionRatio();
|
|
|
+ r2.setDimensionId(3L);
|
|
|
+ r2.setRatio(40);
|
|
|
+ ratios.add(r1);
|
|
|
+ ratios.add(r2);
|
|
|
+ req.setRatios(ratios);
|
|
|
+
|
|
|
+ Result<String> r = configController.sourceSave(req);
|
|
|
+
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
+ assertEquals("保存成功", r.getData());
|
|
|
+ verify(sourceConfigMapper).delete(any());
|
|
|
+ verify(sourceConfigMapper, times(2)).insert(any(EnergySourceConfig.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-26 异常:source-save ratios 为 null → NPE(观察:无显式 400 校验,钉住缺口)")
|
|
|
+ void _pinsGap_异常流_来源保存ratios为null() {
|
|
|
+ EnergySourceConfigSaveRequest req = new EnergySourceConfigSaveRequest();
|
|
|
+ req.setSourceType("task");
|
|
|
+ req.setSourceId(100L);
|
|
|
+ req.setRatios(null);
|
|
|
+ // 观察行为:controller 未校验 → 迭代 null 抛 NPE(GlobalExceptionHandler → 500)
|
|
|
+ assertThrows(NullPointerException.class, () -> configController.sourceSave(req));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== SC-A-25 正常/异常:每日上限保存 ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-25 正常:daily-limit-save 新增+更新两条")
|
|
|
+ void 正常流_每日上限保存() {
|
|
|
+ when(dailyLimitMapper.selectOne(any())).thenReturn(null, limit(2L, "body", 50));
|
|
|
+ DailyLimitSaveRequest req = new DailyLimitSaveRequest();
|
|
|
+ List<DimensionDailyLimit> limits = new ArrayList<>();
|
|
|
+ limits.add(limit(null, "wisdom", 80)); // 新增
|
|
|
+ limits.add(limit(null, "body", 100)); // 已存在 → 更新
|
|
|
+ req.setLimits(limits);
|
|
|
+
|
|
|
+ Result<String> r = configController.dailyLimitSave(req);
|
|
|
+
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
+ assertEquals("保存成功", r.getData());
|
|
|
+ verify(dailyLimitMapper).insert(any(DimensionDailyLimit.class));
|
|
|
+ verify(dailyLimitMapper).updateById(any(DimensionDailyLimit.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-25 异常:daily-limit-save 空 limits → 业务提示")
|
|
|
+ void 异常流_每日上限保存空() {
|
|
|
+ DailyLimitSaveRequest req = new DailyLimitSaveRequest();
|
|
|
+ req.setLimits(Collections.emptyList());
|
|
|
+ Result<String> r = configController.dailyLimitSave(req);
|
|
|
+ assertEquals(500, r.getCode());
|
|
|
+ assertTrue(r.getMessage().contains("limits不能为空"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-25 异常:daily-limit-save 缺 dimensionCode/dailyLimit → 业务提示")
|
|
|
+ void 异常流_每日上限缺字段() {
|
|
|
+ DailyLimitSaveRequest req = new DailyLimitSaveRequest();
|
|
|
+ req.setLimits(Collections.singletonList(limit(null, null, null)));
|
|
|
+ Result<String> r = configController.dailyLimitSave(req);
|
|
|
+ assertEquals(500, r.getCode());
|
|
|
+ assertTrue(r.getMessage().contains("dimensionCode和dailyLimit不能为空"));
|
|
|
+ verify(dailyLimitMapper, never()).insert(any());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== AdminEnergyRuleController(SC-A-26 归因规则治理) ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-26 正常:rule create 默认 increase/status=1")
|
|
|
+ void 正常流_规则创建() {
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
+ params.put("ruleName", "文章阅读+行为能量");
|
|
|
+ params.put("eventType", "article_read");
|
|
|
+ params.put("dimensionCode", "action");
|
|
|
+ params.put("energyValue", 20);
|
|
|
+ Result<Void> r = ruleController.create(params);
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
+ verify(ruleMapper).insert(any(EnergyRule.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-26 正常:rule toggle 状态翻转")
|
|
|
+ void 正常流_规则启停() {
|
|
|
+ EnergyRule rule = new EnergyRule();
|
|
|
+ rule.setId(1L);
|
|
|
+ rule.setStatus(1);
|
|
|
+ when(ruleMapper.selectById(1L)).thenReturn(rule);
|
|
|
+ Result<Void> r = ruleController.toggle(Collections.singletonMap("id", "1"));
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
+ assertEquals(0, rule.getStatus(), "启用→禁用");
|
|
|
+ verify(ruleMapper).updateById(rule);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-26 权限:rule toggle 缺 id → 业务提示")
|
|
|
+ void 权限流_规则toggle缺id() {
|
|
|
+ Result<Void> r = ruleController.toggle(new HashMap<>());
|
|
|
+ assertEquals(500, r.getCode());
|
|
|
+ assertTrue(r.getMessage().contains("id不能为空"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-26 权限:rule toggle 规则不存在 → 业务提示")
|
|
|
+ void 权限流_规则不存在() {
|
|
|
+ when(ruleMapper.selectById(1L)).thenReturn(null);
|
|
|
+ Result<Void> r = ruleController.toggle(Collections.singletonMap("id", "1"));
|
|
|
+ assertEquals(500, r.getCode());
|
|
|
+ assertTrue(r.getMessage().contains("规则不存在"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-26 权限:rule delete 缺 id → 业务提示")
|
|
|
+ void 权限流_规则删除缺id() {
|
|
|
+ Result<Void> r = ruleController.delete(new HashMap<>());
|
|
|
+ assertEquals(500, r.getCode());
|
|
|
+ assertTrue(r.getMessage().contains("id不能为空"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== DimensionWeightController(SC-A-25 权重治理) ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-25 正常:dimension-weight save 转维度权重列表")
|
|
|
+ void 正常流_权重保存() {
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
+ params.put("targetType", "member");
|
|
|
+ params.put("targetId", 50L);
|
|
|
+ params.put("weights", Arrays.asList(
|
|
|
+ mapOf("dimension", "body", "weight", 30, "enabled", true),
|
|
|
+ mapOf("dimension", "mind", "weight", 20, "enabled", true)));
|
|
|
+ Result<Void> r = weightController.save(params);
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
+ verify(weightService).saveWeights(org.mockito.ArgumentMatchers.eq("member"),
|
|
|
+ org.mockito.ArgumentMatchers.eq(50L), any(List.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-25 异常:dimension-weight save 缺 targetType/targetId → 业务提示")
|
|
|
+ void 异常流_权重保存缺目标() {
|
|
|
+ Result<Void> r = weightController.save(new HashMap<>());
|
|
|
+ assertEquals(500, r.getCode());
|
|
|
+ assertTrue(r.getMessage().contains("targetType 和 targetId 不能为空"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-25 异常:dimension-weight save weights 为空 → 业务提示")
|
|
|
+ void 异常流_权重保存空weights() {
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
+ params.put("targetType", "member");
|
|
|
+ params.put("targetId", 50L);
|
|
|
+ params.put("weights", Collections.emptyList());
|
|
|
+ Result<Void> r = weightController.save(params);
|
|
|
+ assertEquals(500, r.getCode());
|
|
|
+ assertTrue(r.getMessage().contains("weights 不能为空"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-A-25 正常:dimension-weight list 返回权重")
|
|
|
+ void 正常流_权重列表() {
|
|
|
+ DimensionWeight w = new DimensionWeight();
|
|
|
+ w.setTargetType("member");
|
|
|
+ w.setTargetId(50L);
|
|
|
+ w.setDimension("body");
|
|
|
+ w.setWeight(30);
|
|
|
+ w.setEnabled(true);
|
|
|
+ when(weightService.getByTarget("member", 50L)).thenReturn(Collections.singletonList(w));
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
+ params.put("targetType", "member");
|
|
|
+ params.put("targetId", 50L);
|
|
|
+ Result<List<DimensionWeight>> r = weightController.list(params);
|
|
|
+ assertEquals(200, r.getCode());
|
|
|
+ assertEquals(1, r.getData().size());
|
|
|
+ assertEquals(30, r.getData().get(0).getWeight());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== SC-P-92 正常:来源比例变更后重算生效 ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ @DisplayName("SC-P-92 正常:改配来源比例后 awardEnergy 按新比例发放")
|
|
|
+ void 常规流_来源比例变更后重算() {
|
|
|
+ // 先配置游戏来源比例:body 60 / action 40
|
|
|
+ List<Map<String, Object>> ratios = new ArrayList<>();
|
|
|
+ ratios.add(ratioMap("body", 60));
|
|
|
+ ratios.add(ratioMap("action", 40));
|
|
|
+ service.configureSourceRatios("game", 300L, "游戏", ratios);
|
|
|
+ verify(sourceConfigMapper).delete(any());
|
|
|
+ verify(sourceConfigMapper, times(2)).insert(any(EnergySourceConfig.class));
|
|
|
+
|
|
|
+ // awardEnergy:behavior 配置为空 → 走 energy_source_config(body=5:60%, action=3:40%)
|
|
|
+ when(sourceConfigMapper.selectList(any())).thenReturn(Arrays.asList(
|
|
|
+ src(1L, "game", 300L, "游戏", 5L, 60),
|
|
|
+ src(2L, "game", 300L, "游戏", 3L, 40)));
|
|
|
+ Map<String, Integer> result = service.awardEnergy(50L, "game", 300L, 10, "游戏互动", null);
|
|
|
+ assertNotNull(result);
|
|
|
+ assertEquals(6, result.get("body"), "60% of 10 = 6");
|
|
|
+ assertEquals(4, result.get("action"), "40% of 10 = 4");
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> ratioMap(String code, int ratio) {
|
|
|
+ Map<String, Object> m = new LinkedHashMap<>();
|
|
|
+ m.put("dimensionCode", code);
|
|
|
+ m.put("ratio", ratio);
|
|
|
+ return m;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Map<String, Object> mapOf(Object... kv) {
|
|
|
+ Map<String, Object> m = new LinkedHashMap<>();
|
|
|
+ for (int i = 0; i < kv.length; i += 2) m.put(String.valueOf(kv[i]), kv[i + 1]);
|
|
|
+ return m;
|
|
|
+ }
|
|
|
+}
|