소스 검색

test(tianpan): 补齐 FiveDimensionScoreServiceTest 五维基础分单测

GAP-2026-09-09-tianpan-tradi / sync_gap_tasks: 设计文档
docs/superpowers/specs/2026-09-09-tianpan-tradition-design.md §3.1/§5.1
要求 FiveDimensionScoreServiceTest 单测。实际实现中五维基础分算法落在
FamilyMemberDimensionBaseScoreService(FiveDimensionScoreService 仅承载
五维快照记录),故以实际实现类为被测对象,用例口径对齐设计文档:
全数据示例(§2.6)、wuxing/westernSign/lifeNumber 缺数据兜底、主数化简
(11/22/33 + 非主数 digit sum)、12 星座全覆盖 + 未知星座。12 用例全部通过。
E2E Test Bot 1 주 전
부모
커밋
26c2163427
1개의 변경된 파일과 229개의 추가작업 그리고 0개의 파일을 삭제
  1. 229 0
      cfc-backend/src/test/java/com/etotem/cfc/unit/FiveDimensionScoreServiceTest.java

+ 229 - 0
cfc-backend/src/test/java/com/etotem/cfc/unit/FiveDimensionScoreServiceTest.java

@@ -0,0 +1,229 @@
+package com.etotem.cfc.unit;
+
+import com.etotem.cfc.service.FamilyMemberDimensionBaseScoreService;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * 五维基础能量分算法单测。
+ *
+ * <p>设计文档 docs/superpowers/specs/2026-09-09-tianpan-tradition-design.md §3.1/§5.1
+ * 要求为五维基础分计算(calcBaseScores)提供单测,覆盖:全数据示例(§2.6)、
+ * 各层缺数据兜底(wuxing / westernSign / lifeNumber 为 null)、主数化简(11/22/33)、
+ * 星座边界(12 星座全覆盖 + 未知星座字符串)。</p>
+ *
+ * <p>实现说明:设计文档原定该算法位于 {@code FiveDimensionScoreService},实际实现时
+ * 五维基础分算法落在 {@link FamilyMemberDimensionBaseScoreService}(含 calcBaseScores /
+ * calcAndSave / getScores),而 {@code FiveDimensionScoreService} 仅承担五维快照记录。
+ * 因此本测试以实际实现类 {@link FamilyMemberDimensionBaseScoreService} 为被测对象,
+ * 用例口径严格对齐设计文档。</p>
+ */
+@ExtendWith(MockitoExtension.class)
+class FiveDimensionScoreServiceTest {
+
+    @InjectMocks
+    private FamilyMemberDimensionBaseScoreService service;
+
+    /** 构造完整五行 map(量纲 0-100)。 */
+    private Map<String, Integer> wuxing(int wood, int fire, int earth, int metal, int water) {
+        Map<String, Integer> m = new HashMap<>();
+        m.put("wood", wood);
+        m.put("fire", fire);
+        m.put("earth", earth);
+        m.put("metal", metal);
+        m.put("water", water);
+        return m;
+    }
+
+    /** 构造五行值全 50 的 map。 */
+    private Map<String, Integer> wuxingNeutral() {
+        return wuxing(50, 50, 50, 50, 50);
+    }
+
+    /**
+     * 全数据用例:严格对照设计文档 §2.6 计算示例。
+     *
+     * <p>输入:八字五行 {wood:40, fire:60, earth:50, metal:30, water:20}、
+     * 星座狮子座(火象→心 mind)、灵数 7(→智 wisdom)。</p>
+     */
+    @Test
+    void calcBaseScores_fullData_matchesDocExample26() {
+        Map<String, Integer> result = service.calcBaseScores(
+                wuxing(40, 60, 50, 30, 20), "狮子座", 7);
+
+        assertEquals(30, result.get("body"));      // earth 50×0.6=30
+        assertEquals(56, result.get("mind"));      // fire 60×0.6=36 + 狮子座(火→心)20 = 56
+        assertEquals(38, result.get("wisdom"));    // metal 30×0.6=18 + 灵数7(→智)20 = 38
+        assertEquals(24, result.get("action"));    // wood 40×0.6=24
+        assertEquals(12, result.get("wealth"));    // water 20×0.6=12
+    }
+
+    /**
+     * 全部为 null:wuxing / westernSign / lifeNumber 均为 null 时,
+     * 五维全部取中性值 50(设计文档 §2.3 兜底口径;§5.1 中「50×0.6=30」为笔误)。
+     */
+    @Test
+    void calcBaseScores_allNull_returnsNeutral50() {
+        Map<String, Integer> result = service.calcBaseScores(null, null, null);
+
+        assertNotNull(result);
+        assertEquals(5, result.size());
+        assertEquals(50, result.get("body"));
+        assertEquals(50, result.get("mind"));
+        assertEquals(50, result.get("wisdom"));
+        assertEquals(50, result.get("action"));
+        assertEquals(50, result.get("wealth"));
+    }
+
+    /** 兜底:wuxing=null 但星座/灵数有值 —— 基础 50 上叠加星座/灵数命中。 */
+    @Test
+    void calcBaseScores_nullWuxing_withZodiacAndLifeNumber() {
+        Map<String, Integer> result = service.calcBaseScores(null, "狮子座", 7);
+
+        assertEquals(50, result.get("body"));
+        assertEquals(70, result.get("mind"));      // 50 + 狮子座(→心)20
+        assertEquals(70, result.get("wisdom"));    // 50 + 灵数7(→智)20
+        assertEquals(50, result.get("action"));
+        assertEquals(50, result.get("wealth"));
+    }
+
+    /** 兜底:westernSign=null 且 lifeNumber=null,仅八字层有效。 */
+    @Test
+    void calcBaseScores_nullWesternSignAndLifeNumber_onlyWuxing() {
+        Map<String, Integer> result = service.calcBaseScores(
+                wuxing(40, 60, 50, 30, 20), null, null);
+
+        assertEquals(30, result.get("body"));
+        assertEquals(36, result.get("mind"));
+        assertEquals(18, result.get("wisdom"));
+        assertEquals(24, result.get("action"));
+        assertEquals(12, result.get("wealth"));
+    }
+
+    /** 兜底:lifeNumber=null,仅八字层 + 星座层有效。 */
+    @Test
+    void calcBaseScores_nullLifeNumber_onlyWuxingAndZodiac() {
+        Map<String, Integer> result = service.calcBaseScores(
+                wuxing(40, 60, 50, 30, 20), "狮子座", null);
+
+        assertEquals(30, result.get("body"));
+        assertEquals(56, result.get("mind"));      // 36 + 狮子座20
+        assertEquals(18, result.get("wisdom"));
+        assertEquals(24, result.get("action"));
+        assertEquals(12, result.get("wealth"));
+    }
+
+    /** 兜底:wuxing map 缺失某五行键时,该维度取中性值 50×0.6=30。 */
+    @Test
+    void calcBaseScores_missingWuxingElement_usesNeutral50() {
+        Map<String, Integer> partial = new HashMap<>();
+        partial.put("fire", 60);
+        partial.put("earth", 50);
+        partial.put("metal", 30);
+        partial.put("water", 20);
+        // 缺 wood → action 取中性 50×0.6=30
+
+        Map<String, Integer> result = service.calcBaseScores(partial, null, null);
+
+        assertEquals(30, result.get("action"));    // wood 缺失 → 50×0.6=30
+        assertEquals(36, result.get("mind"));
+        assertEquals(30, result.get("body"));
+        assertEquals(18, result.get("wisdom"));
+        assertEquals(12, result.get("wealth"));
+    }
+
+    /** 主数化简:11→2→心(mind)。 */
+    @Test
+    void calcBaseScores_masterNumber_11MapsTo2() {
+        Map<String, Integer> result = service.calcBaseScores(wuxingNeutral(), null, 11);
+
+        assertEquals(50, result.get("mind"));      // 30 + 灵数11(→2→心)20
+        assertEquals(30, result.get("body"));
+        assertEquals(30, result.get("wisdom"));
+        assertEquals(30, result.get("action"));
+        assertEquals(30, result.get("wealth"));
+    }
+
+    /** 主数化简:22→4→身(body)。 */
+    @Test
+    void calcBaseScores_masterNumber_22MapsTo4() {
+        Map<String, Integer> result = service.calcBaseScores(wuxingNeutral(), null, 22);
+
+        assertEquals(50, result.get("body"));      // 30 + 灵数22(→4→身)20
+        assertEquals(30, result.get("mind"));
+        assertEquals(30, result.get("wisdom"));
+        assertEquals(30, result.get("action"));
+        assertEquals(30, result.get("wealth"));
+    }
+
+    /** 主数化简:33→6→心(mind)。 */
+    @Test
+    void calcBaseScores_masterNumber_33MapsTo6() {
+        Map<String, Integer> result = service.calcBaseScores(wuxingNeutral(), null, 33);
+
+        assertEquals(50, result.get("mind"));      // 30 + 灵数33(→6→心)20
+        assertEquals(30, result.get("body"));
+        assertEquals(30, result.get("wisdom"));
+        assertEquals(30, result.get("action"));
+        assertEquals(30, result.get("wealth"));
+    }
+
+    /** 非主数 >9 按 digit sum 递归化简(如 19→1→行 action)。 */
+    @Test
+    void calcBaseScores_nonMasterDigitSum_reduces() {
+        Map<String, Integer> result = service.calcBaseScores(wuxingNeutral(), null, 19);
+
+        // 19 → 1+9=10 → 1+0=1 → action
+        assertEquals(50, result.get("action"));    // 30 + 灵数19(→1→行)20
+        assertEquals(30, result.get("mind"));
+        assertEquals(30, result.get("body"));
+        assertEquals(30, result.get("wisdom"));
+        assertEquals(30, result.get("wealth"));
+    }
+
+    /** 星座边界:12 星座全部覆盖且不抛异常,并按四元素正确命中维度。 */
+    @Test
+    void calcBaseScores_all12ZodiacSigns() {
+        Map<String, Integer> w = wuxing(40, 60, 50, 30, 20);
+
+        // 火象(白羊/狮子/射手)→ 心 mind:36+20=56
+        assertEquals(56, service.calcBaseScores(w, "白羊座", null).get("mind"));
+        assertEquals(56, service.calcBaseScores(w, "狮子座", null).get("mind"));
+        assertEquals(56, service.calcBaseScores(w, "射手座", null).get("mind"));
+
+        // 土象(金牛/处女/摩羯)→ 身 body:30+20=50
+        assertEquals(50, service.calcBaseScores(w, "金牛座", null).get("body"));
+        assertEquals(50, service.calcBaseScores(w, "处女座", null).get("body"));
+        assertEquals(50, service.calcBaseScores(w, "摩羯座", null).get("body"));
+
+        // 风象(双子/天秤/水瓶)→ 行 action:24+20=44
+        assertEquals(44, service.calcBaseScores(w, "双子座", null).get("action"));
+        assertEquals(44, service.calcBaseScores(w, "天秤座", null).get("action"));
+        assertEquals(44, service.calcBaseScores(w, "水瓶座", null).get("action"));
+
+        // 水象(巨蟹/天蝎/双鱼)→ 富 wealth:12+20=32
+        assertEquals(32, service.calcBaseScores(w, "巨蟹座", null).get("wealth"));
+        assertEquals(32, service.calcBaseScores(w, "天蝎座", null).get("wealth"));
+        assertEquals(32, service.calcBaseScores(w, "双鱼座", null).get("wealth"));
+    }
+
+    /** 星座边界:未知星座字符串不命中任何维度(不抛异常、不加分)。 */
+    @Test
+    void calcBaseScores_unknownZodiacNoBonus() {
+        Map<String, Integer> result = service.calcBaseScores(wuxingNeutral(), "未知星座", null);
+
+        assertEquals(30, result.get("body"));
+        assertEquals(30, result.get("mind"));
+        assertEquals(30, result.get("wisdom"));
+        assertEquals(30, result.get("action"));
+        assertEquals(30, result.get("wealth"));
+    }
+}