Bläddra i källkod

test: add dimension controller and energy sync tests

Sisyphus 4 månader sedan
förälder
incheckning
429338b23f

+ 218 - 0
cfc-backend/src/test/java/com/etotem/cfc/controller/DimensionAdminControllerTest.java

@@ -0,0 +1,218 @@
+package com.etotem.cfc.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.controller.admin.DimensionAdminController;
+import com.etotem.cfc.entity.HealthDataSourceRecord;
+import com.etotem.cfc.entity.HealthNormReference;
+import com.etotem.cfc.mapper.HealthDataSourceRecordMapper;
+import com.etotem.cfc.mapper.HealthNormReferenceMapper;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * P2-维度常模/数据源 CRUD 管理端
+ * 覆盖 DimensionAdminController 全链路
+ */
+@SpringBootTest
+public class DimensionAdminControllerTest {
+
+    @Resource
+    private DimensionAdminController controller;
+
+    @MockBean
+    private HealthNormReferenceMapper normMapper;
+
+    @MockBean
+    private HealthDataSourceRecordMapper dataSourceRecordMapper;
+
+    // ============= 常模管理 =============
+
+    @Test
+    void listNorms_returnsAll() {
+        List<HealthNormReference> norms = Arrays.asList(
+            norm(1L, "growth", "all", 0, 216),
+            norm(2L, "sleep", "male", 0, 60)
+        );
+        when(normMapper.selectList(any())).thenReturn(norms);
+
+        Result<List<HealthNormReference>> result = controller.listNorms();
+
+        assertEquals(200, result.getCode());
+        assertEquals(2, result.getData().size());
+        assertEquals("growth", result.getData().get(0).getDimension());
+    }
+
+    @Test
+    void listNorms_empty() {
+        when(normMapper.selectList(any())).thenReturn(java.util.Collections.emptyList());
+
+        Result<List<HealthNormReference>> result = controller.listNorms();
+
+        assertEquals(200, result.getCode());
+        assertTrue(result.getData().isEmpty());
+    }
+
+    @Test
+    void saveNorm_insertsWhenIdNull() {
+        HealthNormReference norm = norm(null, "vision", "female", 0, 120);
+
+        Result<String> result = controller.saveNorm(norm);
+
+        assertEquals(200, result.getCode());
+        assertEquals("保存成功", result.getData());
+        verify(normMapper).insert(norm);
+        verify(normMapper, never()).updateById(any());
+    }
+
+    @Test
+    void saveNorm_updatesWhenIdExists() {
+        HealthNormReference norm = norm(5L, "growth", "all", 0, 216);
+        norm.setPercentile50(60);
+
+        Result<String> result = controller.saveNorm(norm);
+
+        assertEquals(200, result.getCode());
+        verify(normMapper).updateById(norm);
+        verify(normMapper, never()).insert(any());
+    }
+
+    @Test
+    void deleteNorm_deletes() {
+        Result<String> result = controller.deleteNorm(1L);
+
+        assertEquals(200, result.getCode());
+        assertEquals("删除成功", result.getData());
+        verify(normMapper).deleteById(1L);
+    }
+
+    @Test
+    void deleteNorm_deletesNonExistent() {
+        doNothing().when(normMapper).deleteById(999L);
+
+        Result<String> result = controller.deleteNorm(999L);
+
+        assertEquals(200, result.getCode());
+        verify(normMapper).deleteById(999L);
+    }
+
+    // ============= 数据源管理 =============
+
+    @Test
+    void listDataSources_returnsPaged() {
+        Page<HealthDataSourceRecord> page = new Page<>(1, 20);
+        page.setRecords(Arrays.asList(
+            dataSource(1L, 10L, "REPORT"),
+            dataSource(2L, 10L, "PHOTO")
+        ));
+        page.setTotal(2);
+        when(dataSourceRecordMapper.selectPage(any(), any())).thenReturn(page);
+
+        Result<Page<HealthDataSourceRecord>> result = controller.listDataSources(1, 20);
+
+        assertEquals(200, result.getCode());
+        assertEquals(2, result.getData().getRecords().size());
+        assertEquals(2, result.getData().getTotal());
+    }
+
+    @Test
+    void listDataSources_defaultPage() {
+        Page<HealthDataSourceRecord> page = new Page<>(1, 20);
+        page.setRecords(java.util.Collections.emptyList());
+        page.setTotal(0);
+        when(dataSourceRecordMapper.selectPage(any(), any())).thenReturn(page);
+
+        Result<Page<HealthDataSourceRecord>> result = controller.listDataSources(1, 20);
+
+        assertEquals(200, result.getCode());
+        assertTrue(result.getData().getRecords().isEmpty());
+    }
+
+    @Test
+    void getDataSource_found() {
+        HealthDataSourceRecord record = dataSource(1L, 10L, "REPORT");
+        when(dataSourceRecordMapper.selectById(1L)).thenReturn(record);
+
+        Result<HealthDataSourceRecord> result = controller.getDataSource(1L);
+
+        assertEquals(200, result.getCode());
+        assertEquals("REPORT", result.getData().getSourceType());
+    }
+
+    @Test
+    void getDataSource_notFound() {
+        when(dataSourceRecordMapper.selectById(999L)).thenReturn(null);
+
+        Result<HealthDataSourceRecord> result = controller.getDataSource(999L);
+
+        assertEquals(500, result.getCode());
+        assertEquals("记录不存在", result.getMessage());
+    }
+
+    @Test
+    void saveDataSource_insertsWhenIdNull() {
+        HealthDataSourceRecord record = dataSource(null, 20L, "MANUAL");
+
+        Result<String> result = controller.saveDataSource(record);
+
+        assertEquals(200, result.getCode());
+        assertNotNull(record.getCreatedAt());
+        verify(dataSourceRecordMapper).insert(record);
+    }
+
+    @Test
+    void saveDataSource_updatesWhenIdExists() {
+        HealthDataSourceRecord record = dataSource(1L, 20L, "VOICE");
+
+        Result<String> result = controller.saveDataSource(record);
+
+        assertEquals(200, result.getCode());
+        verify(dataSourceRecordMapper).updateById(record);
+        verify(dataSourceRecordMapper, never()).insert(any());
+    }
+
+    @Test
+    void deleteDataSource_deletes() {
+        Result<String> result = controller.deleteDataSource(3L);
+
+        assertEquals(200, result.getCode());
+        assertEquals("删除成功", result.getData());
+        verify(dataSourceRecordMapper).deleteById(3L);
+    }
+
+    // ============= helpers =============
+
+    private HealthNormReference norm(Long id, String dimension, String gender,
+                                      Integer ageMin, Integer ageMax) {
+        HealthNormReference n = new HealthNormReference();
+        n.setId(id);
+        n.setDimension(dimension);
+        n.setGender(gender);
+        n.setAgeMin(ageMin);
+        n.setAgeMax(ageMax);
+        n.setPercentile5(10);
+        n.setPercentile50(50);
+        n.setPercentile95(90);
+        return n;
+    }
+
+    private HealthDataSourceRecord dataSource(Long id, Long memberId, String sourceType) {
+        HealthDataSourceRecord r = new HealthDataSourceRecord();
+        r.setId(id);
+        r.setMemberId(memberId);
+        r.setSourceType(sourceType);
+        r.setCreatedAt(new Date());
+        return r;
+    }
+}

+ 246 - 0
cfc-backend/src/test/java/com/etotem/cfc/controller/HealthDimensionControllerTest.java

@@ -0,0 +1,246 @@
+package com.etotem.cfc.controller;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.dto.DimensionUploadVO;
+import com.etotem.cfc.dto.HealthDimensionQuestionnaireVO;
+import com.etotem.cfc.dto.HealthDimensionVO;
+import com.etotem.cfc.service.DimensionScoreService;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.util.*;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+@SpringBootTest
+public class HealthDimensionControllerTest {
+
+    @Resource
+    private HealthDimensionController controller;
+
+    @MockBean
+    private DimensionScoreService dimensionScoreService;
+
+    @Test
+    void overview_success() {
+        Map<String, Object> params = new HashMap<>();
+        params.put("memberId", 10L);
+
+        HealthDimensionVO vo = new HealthDimensionVO();
+        vo.setMemberId(10L);
+        HealthDimensionVO.DimensionItem dim = new HealthDimensionVO.DimensionItem();
+        dim.setDimension("growth");
+        dim.setScore(BigDecimal.valueOf(85));
+        vo.setDimensions(Collections.singletonList(dim));
+
+        when(dimensionScoreService.getHealthDimension(10L)).thenReturn(vo);
+
+        Result<HealthDimensionVO> result = controller.overview(params, 1L);
+
+        assertEquals(200, result.getCode());
+        assertNotNull(result.getData());
+        assertEquals(10L, result.getData().getMemberId());
+        assertEquals(1, result.getData().getDimensions().size());
+        assertEquals("growth", result.getData().getDimensions().get(0).getDimension());
+    }
+
+    @Test
+    void overview_missingMemberId() {
+        Map<String, Object> params = new HashMap<>();
+
+        Result<HealthDimensionVO> result = controller.overview(params, 1L);
+
+        assertEquals(500, result.getCode());
+        assertEquals("memberId不能为空", result.getMessage());
+        verify(dimensionScoreService, never()).getHealthDimension(anyLong());
+    }
+
+    @Test
+    void upload_success() {
+        DimensionUploadVO vo = new DimensionUploadVO();
+        vo.setMemberId(10L);
+        vo.setDimension("growth");
+        vo.setScore(BigDecimal.valueOf(80));
+
+        Result<Void> result = controller.upload(vo, 1L);
+
+        assertEquals(200, result.getCode());
+        verify(dimensionScoreService).uploadScore(vo);
+    }
+
+    @Test
+    void upload_missingMemberId() {
+        DimensionUploadVO vo = new DimensionUploadVO();
+        vo.setDimension("growth");
+        vo.setScore(BigDecimal.valueOf(80));
+
+        Result<Void> result = controller.upload(vo, 1L);
+
+        assertEquals(500, result.getCode());
+        assertEquals("memberId, dimension, score不能为空", result.getMessage());
+        verify(dimensionScoreService, never()).uploadScore(any());
+    }
+
+    @Test
+    void upload_missingDimension() {
+        DimensionUploadVO vo = new DimensionUploadVO();
+        vo.setMemberId(10L);
+        vo.setScore(BigDecimal.valueOf(80));
+
+        Result<Void> result = controller.upload(vo, 1L);
+
+        assertEquals(500, result.getCode());
+        assertEquals("memberId, dimension, score不能为空", result.getMessage());
+    }
+
+    @Test
+    void upload_missingScore() {
+        DimensionUploadVO vo = new DimensionUploadVO();
+        vo.setMemberId(10L);
+        vo.setDimension("sleep");
+
+        Result<Void> result = controller.upload(vo, 1L);
+
+        assertEquals(500, result.getCode());
+        assertEquals("memberId, dimension, score不能为空", result.getMessage());
+    }
+
+    @Test
+    void questionnaire_success() {
+        HealthDimensionQuestionnaireVO vo = new HealthDimensionQuestionnaireVO();
+        vo.setMemberId(10L);
+        vo.setDimension("sleep");
+        HealthDimensionQuestionnaireVO.QuestionAnswer a = new HealthDimensionQuestionnaireVO.QuestionAnswer();
+        a.setQuestionId("q1");
+        a.setAnswer("A");
+        vo.setAnswers(Arrays.asList(a));
+
+        Result<Void> result = controller.questionnaire(vo, 1L);
+
+        assertEquals(200, result.getCode());
+        verify(dimensionScoreService).submitQuestionnaire(vo);
+    }
+
+    @Test
+    void questionnaire_missingMemberId() {
+        HealthDimensionQuestionnaireVO vo = new HealthDimensionQuestionnaireVO();
+        vo.setDimension("sleep");
+        HealthDimensionQuestionnaireVO.QuestionAnswer a = new HealthDimensionQuestionnaireVO.QuestionAnswer();
+        a.setQuestionId("q1");
+        a.setAnswer("A");
+        vo.setAnswers(Arrays.asList(a));
+
+        Result<Void> result = controller.questionnaire(vo, 1L);
+
+        assertEquals(500, result.getCode());
+        assertEquals("memberId, dimension, answers不能为空", result.getMessage());
+        verify(dimensionScoreService, never()).submitQuestionnaire(any());
+    }
+
+    @Test
+    void questionnaire_missingDimension() {
+        HealthDimensionQuestionnaireVO vo = new HealthDimensionQuestionnaireVO();
+        vo.setMemberId(10L);
+        HealthDimensionQuestionnaireVO.QuestionAnswer a = new HealthDimensionQuestionnaireVO.QuestionAnswer();
+        a.setQuestionId("q1");
+        a.setAnswer("A");
+        vo.setAnswers(Arrays.asList(a));
+
+        Result<Void> result = controller.questionnaire(vo, 1L);
+
+        assertEquals(500, result.getCode());
+        assertEquals("memberId, dimension, answers不能为空", result.getMessage());
+    }
+
+    @Test
+    void questionnaire_nullAnswers() {
+        HealthDimensionQuestionnaireVO vo = new HealthDimensionQuestionnaireVO();
+        vo.setMemberId(10L);
+        vo.setDimension("gut");
+        vo.setAnswers(null);
+
+        Result<Void> result = controller.questionnaire(vo, 1L);
+
+        assertEquals(500, result.getCode());
+        assertEquals("memberId, dimension, answers不能为空", result.getMessage());
+    }
+
+    @Test
+    void history_success() {
+        Map<String, Object> params = new HashMap<>();
+        params.put("memberId", 10L);
+        params.put("dimension", "growth");
+
+        HealthDimensionVO.DimensionItem item = new HealthDimensionVO.DimensionItem();
+        item.setDimension("growth");
+        item.setScore(BigDecimal.valueOf(85));
+
+        when(dimensionScoreService.getDimensionHistory(10L, "growth", 10))
+            .thenReturn(Collections.singletonList(item));
+
+        Result<List<HealthDimensionVO.DimensionItem>> result = controller.history(params, 1L);
+
+        assertEquals(200, result.getCode());
+        assertEquals(1, result.getData().size());
+        assertEquals("growth", result.getData().get(0).getDimension());
+    }
+
+    @Test
+    void history_limited() {
+        Map<String, Object> params = new HashMap<>();
+        params.put("memberId", 10L);
+        params.put("dimension", "sleep");
+        params.put("limit", 5);
+
+        when(dimensionScoreService.getDimensionHistory(10L, "sleep", 5))
+            .thenReturn(Collections.emptyList());
+
+        Result<List<HealthDimensionVO.DimensionItem>> result = controller.history(params, 1L);
+
+        assertEquals(200, result.getCode());
+        assertTrue(result.getData().isEmpty());
+        verify(dimensionScoreService).getDimensionHistory(10L, "sleep", 5);
+    }
+
+    @Test
+    void history_missingMemberId() {
+        Map<String, Object> params = new HashMap<>();
+        params.put("dimension", "growth");
+
+        Result<List<HealthDimensionVO.DimensionItem>> result = controller.history(params, 1L);
+
+        assertEquals(500, result.getCode());
+        assertEquals("memberId和dimension不能为空", result.getMessage());
+    }
+
+    @Test
+    void history_missingDimension() {
+        Map<String, Object> params = new HashMap<>();
+        params.put("memberId", 10L);
+
+        Result<List<HealthDimensionVO.DimensionItem>> result = controller.history(params, 1L);
+
+        assertEquals(500, result.getCode());
+        assertEquals("memberId和dimension不能为空", result.getMessage());
+    }
+
+    @Test
+    void history_emptyResults() {
+        Map<String, Object> params = new HashMap<>();
+        params.put("memberId", 99L);
+        params.put("dimension", "vision");
+
+        when(dimensionScoreService.getDimensionHistory(99L, "vision", 10))
+            .thenReturn(Collections.emptyList());
+
+        Result<List<HealthDimensionVO.DimensionItem>> result = controller.history(params, 1L);
+
+        assertEquals(200, result.getCode());
+        assertTrue(result.getData().isEmpty());
+    }
+}

+ 118 - 0
cfc-backend/src/test/java/com/etotem/cfc/service/DimensionEnergySyncServiceTest.java

@@ -0,0 +1,118 @@
+package com.etotem.cfc.service;
+
+import com.etotem.cfc.entity.HealthDimensionScore;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * P2-身能量同步:7维评分均值 x0.5 奖励身能量
+ * 覆盖 DimensionEnergySyncService 全路径
+ */
+@SpringBootTest
+public class DimensionEnergySyncServiceTest {
+
+    @Resource
+    private DimensionEnergySyncService syncService;
+
+    @MockBean
+    private HealthDimensionScoreService scoreService;
+
+    @MockBean
+    private EnergyService energyService;
+
+    // ============= syncBodyEnergy =============
+
+    @Test
+    void syncBodyEnergy_awardsWhenScoresExist() {
+        Long memberId = 1L;
+        List<HealthDimensionScore> scores = Arrays.asList(
+            score(memberId, "growth", 80),
+            score(memberId, "sleep", 70),
+            score(memberId, "vision", 90)
+        );
+        when(scoreService.getLatestScores(memberId)).thenReturn(scores);
+
+        syncService.syncBodyEnergy(memberId);
+
+        int average = (80 + 70 + 90) / 3;
+        int expectedEnergy = (int) (average * 0.5);
+        verify(energyService).awardEnergy(eq(memberId), eq("dimension"), eq(memberId),
+            eq(expectedEnergy), anyString(), eq(30));
+    }
+
+    @Test
+    void syncBodyEnergy_skipsWhenNoScores() {
+        when(scoreService.getLatestScores(1L)).thenReturn(null);
+
+        syncService.syncBodyEnergy(1L);
+
+        verify(energyService, never()).awardEnergy(anyLong(), anyString(), anyLong(),
+            anyInt(), anyString(), anyInt());
+    }
+
+    @Test
+    void syncBodyEnergy_skipsWhenEmptyScores() {
+        when(scoreService.getLatestScores(1L)).thenReturn(Collections.emptyList());
+
+        syncService.syncBodyEnergy(1L);
+
+        verify(energyService, never()).awardEnergy(anyLong(), anyString(), anyLong(),
+            anyInt(), anyString(), anyInt());
+    }
+
+    @Test
+    void syncBodyEnergy_skipsWhenAverageIsZero() {
+        Long memberId = 1L;
+        List<HealthDimensionScore> scores = Collections.singletonList(
+            score(memberId, "growth", 0)
+        );
+        when(scoreService.getLatestScores(memberId)).thenReturn(scores);
+
+        syncService.syncBodyEnergy(memberId);
+
+        int average = 0 / 1;
+        int expectedEnergy = (int) (average * 0.5);
+        verify(energyService, never()).awardEnergy(anyLong(), anyString(), anyLong(),
+            anyInt(), anyString(), anyInt());
+    }
+
+    @Test
+    void syncBodyEnergy_doesNotBlockOnException() {
+        Long memberId = 1L;
+        List<HealthDimensionScore> scores = Collections.singletonList(
+            score(memberId, "growth", 80)
+        );
+        when(scoreService.getLatestScores(memberId)).thenReturn(scores);
+        when(energyService.awardEnergy(anyLong(), anyString(), anyLong(),
+            anyInt(), anyString(), anyInt()))
+            .thenThrow(new RuntimeException("Energy service failed"));
+
+        syncService.syncBodyEnergy(memberId);
+
+        verify(energyService).awardEnergy(anyLong(), anyString(), anyLong(),
+            anyInt(), anyString(), anyInt());
+    }
+
+    // ============= helpers =============
+
+    private HealthDimensionScore score(Long memberId, String dimension, int scoreVal) {
+        HealthDimensionScore s = new HealthDimensionScore();
+        s.setMemberId(memberId);
+        s.setDimension(dimension);
+        s.setScore(scoreVal);
+        s.setPercentile(50);
+        s.setDataSource("MANUAL");
+        s.setTier(3);
+        s.setAssessDate(new java.sql.Date(System.currentTimeMillis()));
+        return s;
+    }
+}