|
|
@@ -0,0 +1,288 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.etotem.cfc.entity.HealthIndicator;
|
|
|
+import com.etotem.cfc.entity.HealthReport;
|
|
|
+import com.etotem.cfc.entity.NutritionDeficiencyRecord;
|
|
|
+import com.etotem.cfc.entity.NutritionIndicatorMapping;
|
|
|
+import com.etotem.cfc.mapper.NutritionDeficiencyRecordMapper;
|
|
|
+import com.etotem.cfc.mapper.NutritionIndicatorMappingMapper;
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.mockito.Mock;
|
|
|
+import org.mockito.MockitoAnnotations;
|
|
|
+
|
|
|
+import java.lang.reflect.Field;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.*;
|
|
|
+import static org.mockito.ArgumentMatchers.*;
|
|
|
+import static org.mockito.Mockito.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * NutritionDeficiencyService 纯单元测试
|
|
|
+ * 使用 MockitoExtension(无 Spring 上下文,无 MySQL 连接)
|
|
|
+ */
|
|
|
+public class NutritionDeficiencyServiceTest {
|
|
|
+
|
|
|
+ private NutritionDeficiencyService nutritionDeficiencyService;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private NutritionIndicatorMappingMapper mappingMapper;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private NutritionDeficiencyRecordMapper recordMapper;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private HealthReportService healthReportService;
|
|
|
+
|
|
|
+ @BeforeEach
|
|
|
+ public void setup() throws Exception {
|
|
|
+ MockitoAnnotations.openMocks(this);
|
|
|
+ nutritionDeficiencyService = new NutritionDeficiencyService();
|
|
|
+ setField(nutritionDeficiencyService, "mappingMapper", mappingMapper);
|
|
|
+ setField(nutritionDeficiencyService, "recordMapper", recordMapper);
|
|
|
+ setField(nutritionDeficiencyService, "healthReportService", healthReportService);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void setField(Object target, String fieldName, Object value) throws Exception {
|
|
|
+ Field field = target.getClass().getDeclaredField(fieldName);
|
|
|
+ field.setAccessible(true);
|
|
|
+ field.set(target, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ private NutritionIndicatorMapping mockMapping(Long id, String nutrient, String pattern, Integer sortOrder) {
|
|
|
+ NutritionIndicatorMapping m = new NutritionIndicatorMapping();
|
|
|
+ m.setId(id);
|
|
|
+ m.setNutrient(nutrient);
|
|
|
+ m.setIndicatorNamePattern(pattern);
|
|
|
+ m.setSortOrder(sortOrder);
|
|
|
+ m.setDeficiencySymptoms("缺乏" + nutrient);
|
|
|
+ m.setExcessSymptoms("过量" + nutrient);
|
|
|
+ m.setFoodSuggestions("建议食用含" + nutrient + "的食物");
|
|
|
+ m.setSeverityWeight(BigDecimal.ONE);
|
|
|
+ return m;
|
|
|
+ }
|
|
|
+
|
|
|
+ private HealthReport mockReport(Long id, Long userId) {
|
|
|
+ HealthReport r = new HealthReport();
|
|
|
+ r.setId(id);
|
|
|
+ r.setUserId(userId);
|
|
|
+ r.setReportDate(new Date());
|
|
|
+ r.setNutritionScore(55);
|
|
|
+ r.setGutHealthScore(60);
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+
|
|
|
+ private HealthIndicator mockIndicator(Long id, Long reportId, String name, String value, String status) {
|
|
|
+ HealthIndicator i = new HealthIndicator();
|
|
|
+ i.setId(id);
|
|
|
+ i.setReportId(reportId);
|
|
|
+ i.setIndicatorName(name);
|
|
|
+ i.setIndicatorValue(value);
|
|
|
+ i.setStatus(status);
|
|
|
+ i.setRefRange("20-100");
|
|
|
+ return i;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== getAllMappings ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void getAllMappings_returnsAllMappingsOrdered() {
|
|
|
+ List<NutritionIndicatorMapping> mappings = Arrays.asList(
|
|
|
+ mockMapping(1L, "铁", "铁蛋白", 2),
|
|
|
+ mockMapping(2L, "维生素D", "维生素D", 1)
|
|
|
+ );
|
|
|
+ when(mappingMapper.selectList(any())).thenReturn(mappings);
|
|
|
+
|
|
|
+ List<NutritionIndicatorMapping> result = nutritionDeficiencyService.getAllMappings();
|
|
|
+
|
|
|
+ assertEquals(2, result.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== saveMapping ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void saveMapping_insert_whenIdNull() {
|
|
|
+ NutritionIndicatorMapping m = mockMapping(null, "钙", "钙", 1);
|
|
|
+ when(mappingMapper.insert(any())).thenReturn(1);
|
|
|
+
|
|
|
+ nutritionDeficiencyService.saveMapping(m);
|
|
|
+
|
|
|
+ verify(mappingMapper).insert(m);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void saveMapping_update_whenIdNotNull() {
|
|
|
+ NutritionIndicatorMapping m = mockMapping(5L, "锌", "锌", 1);
|
|
|
+ when(mappingMapper.updateById(any())).thenReturn(1);
|
|
|
+
|
|
|
+ nutritionDeficiencyService.saveMapping(m);
|
|
|
+
|
|
|
+ verify(mappingMapper).updateById(m);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== deleteMapping ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void deleteMapping_callsDeleteById() {
|
|
|
+ when(mappingMapper.deleteById(3L)).thenReturn(1);
|
|
|
+
|
|
|
+ nutritionDeficiencyService.deleteMapping(3L);
|
|
|
+
|
|
|
+ verify(mappingMapper).deleteById(3L);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== getDeficiencyRecords ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void getDeficiencyRecords_withChildIdOnly() {
|
|
|
+ List<NutritionDeficiencyRecord> records = Collections.singletonList(new NutritionDeficiencyRecord());
|
|
|
+ when(recordMapper.selectList(any())).thenReturn(records);
|
|
|
+
|
|
|
+ List<NutritionDeficiencyRecord> result = nutritionDeficiencyService.getDeficiencyRecords(5L, null);
|
|
|
+
|
|
|
+ assertEquals(1, result.size());
|
|
|
+ verify(recordMapper).selectList(any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void getDeficiencyRecords_withChildIdAndReportId() {
|
|
|
+ when(recordMapper.selectList(any())).thenReturn(Collections.emptyList());
|
|
|
+
|
|
|
+ List<NutritionDeficiencyRecord> result = nutritionDeficiencyService.getDeficiencyRecords(5L, 10L);
|
|
|
+
|
|
|
+ assertNotNull(result);
|
|
|
+ assertTrue(result.isEmpty());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== analyzeDeficiency ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void analyzeDeficiency_noReport_returnsEmpty() {
|
|
|
+ when(healthReportService.getLatestReport(5L)).thenReturn(null);
|
|
|
+
|
|
|
+ List<NutritionDeficiencyRecord> result = nutritionDeficiencyService.analyzeDeficiency(5L, null);
|
|
|
+
|
|
|
+ assertNotNull(result);
|
|
|
+ assertTrue(result.isEmpty());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void analyzeDeficiency_noIndicators_returnsEmpty() {
|
|
|
+ when(healthReportService.getLatestReport(5L)).thenReturn(mockReport(1L, 5L));
|
|
|
+ when(healthReportService.getReportIndicators(1L)).thenReturn(Collections.emptyList());
|
|
|
+
|
|
|
+ List<NutritionDeficiencyRecord> result = nutritionDeficiencyService.analyzeDeficiency(5L, null);
|
|
|
+
|
|
|
+ assertNotNull(result);
|
|
|
+ assertTrue(result.isEmpty());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void analyzeDeficiency_withMatchingIndicator_createsRecord() {
|
|
|
+ HealthReport report = mockReport(2L, 5L);
|
|
|
+ HealthIndicator indicator = mockIndicator(1L, 2L, "铁蛋白", "15", "偏低");
|
|
|
+ NutritionIndicatorMapping mapping = mockMapping(1L, "铁", "铁蛋白", 1);
|
|
|
+
|
|
|
+ when(healthReportService.getLatestReport(5L)).thenReturn(report);
|
|
|
+ when(healthReportService.getReportIndicators(2L)).thenReturn(Collections.singletonList(indicator));
|
|
|
+ when(mappingMapper.selectList(any())).thenReturn(Collections.singletonList(mapping));
|
|
|
+ when(recordMapper.delete(any())).thenReturn(0);
|
|
|
+ when(recordMapper.insert(any())).thenReturn(1);
|
|
|
+
|
|
|
+ List<NutritionDeficiencyRecord> result = nutritionDeficiencyService.analyzeDeficiency(5L, null);
|
|
|
+
|
|
|
+ assertEquals(1, result.size());
|
|
|
+ assertEquals("铁", result.get(0).getNutrient());
|
|
|
+ assertEquals("deficiency", result.get(0).getDeficiencyType());
|
|
|
+ assertEquals("铁蛋白", result.get(0).getIndicatorName());
|
|
|
+ assertEquals("15", result.get(0).getIndicatorValue());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void analyzeDeficiency_excessStatus_mapsToExcess() {
|
|
|
+ HealthReport report = mockReport(3L, 5L);
|
|
|
+ HealthIndicator indicator = mockIndicator(2L, 3L, "胆固醇", "250", "偏高");
|
|
|
+ NutritionIndicatorMapping mapping = mockMapping(2L, "胆固醇", "胆固醇", 1);
|
|
|
+
|
|
|
+ when(healthReportService.getLatestReport(5L)).thenReturn(report);
|
|
|
+ when(healthReportService.getReportIndicators(3L)).thenReturn(Collections.singletonList(indicator));
|
|
|
+ when(mappingMapper.selectList(any())).thenReturn(Collections.singletonList(mapping));
|
|
|
+ when(recordMapper.delete(any())).thenReturn(0);
|
|
|
+ when(recordMapper.insert(any())).thenReturn(1);
|
|
|
+
|
|
|
+ List<NutritionDeficiencyRecord> result = nutritionDeficiencyService.analyzeDeficiency(5L, null);
|
|
|
+
|
|
|
+ assertEquals(1, result.size());
|
|
|
+ assertEquals("excess", result.get(0).getDeficiencyType());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void analyzeDeficiency_borderlineStatus_mapsToBorderline() {
|
|
|
+ HealthReport report = mockReport(4L, 5L);
|
|
|
+ HealthIndicator indicator = mockIndicator(3L, 4L, "血糖", "6.5", "临界");
|
|
|
+ NutritionIndicatorMapping mapping = mockMapping(3L, "血糖", "血糖", 1);
|
|
|
+
|
|
|
+ when(healthReportService.getLatestReport(5L)).thenReturn(report);
|
|
|
+ when(healthReportService.getReportIndicators(4L)).thenReturn(Collections.singletonList(indicator));
|
|
|
+ when(mappingMapper.selectList(any())).thenReturn(Collections.singletonList(mapping));
|
|
|
+ when(recordMapper.delete(any())).thenReturn(0);
|
|
|
+ when(recordMapper.insert(any())).thenReturn(1);
|
|
|
+
|
|
|
+ List<NutritionDeficiencyRecord> result = nutritionDeficiencyService.analyzeDeficiency(5L, null);
|
|
|
+
|
|
|
+ assertEquals(1, result.size());
|
|
|
+ assertEquals("borderline", result.get(0).getDeficiencyType());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void analyzeDeficiency_noMatchingMapping_skipsIndicator() {
|
|
|
+ HealthReport report = mockReport(5L, 5L);
|
|
|
+ HealthIndicator indicator = mockIndicator(4L, 5L, "未知指标", "50", "偏低");
|
|
|
+
|
|
|
+ when(healthReportService.getLatestReport(5L)).thenReturn(report);
|
|
|
+ when(healthReportService.getReportIndicators(5L)).thenReturn(Collections.singletonList(indicator));
|
|
|
+ when(mappingMapper.selectList(any())).thenReturn(Collections.emptyList());
|
|
|
+ when(recordMapper.delete(any())).thenReturn(0);
|
|
|
+
|
|
|
+ List<NutritionDeficiencyRecord> result = nutritionDeficiencyService.analyzeDeficiency(5L, null);
|
|
|
+
|
|
|
+ assertTrue(result.isEmpty());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void analyzeDeficiency_usesReportIdFromParam_whenProvided() {
|
|
|
+ HealthReport report = mockReport(6L, 5L);
|
|
|
+ HealthIndicator indicator = mockIndicator(5L, 6L, "铁蛋白", "12", "偏低");
|
|
|
+ NutritionIndicatorMapping mapping = mockMapping(1L, "铁", "铁蛋白", 1);
|
|
|
+
|
|
|
+ when(healthReportService.getLatestReport(5L)).thenReturn(report);
|
|
|
+ when(healthReportService.getReportIndicators(6L)).thenReturn(Collections.singletonList(indicator));
|
|
|
+ when(mappingMapper.selectList(any())).thenReturn(Collections.singletonList(mapping));
|
|
|
+ when(recordMapper.delete(any())).thenReturn(0);
|
|
|
+ when(recordMapper.insert(any())).thenReturn(1);
|
|
|
+
|
|
|
+ List<NutritionDeficiencyRecord> result = nutritionDeficiencyService.analyzeDeficiency(5L, 6L);
|
|
|
+
|
|
|
+ assertEquals(1, result.size());
|
|
|
+ verify(healthReportService).getReportIndicators(6L);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void analyzeDeficiency_deletesExistingRecordsBeforeInsert() {
|
|
|
+ HealthReport report = mockReport(7L, 5L);
|
|
|
+ HealthIndicator indicator = mockIndicator(6L, 7L, "维生素D", "15", "偏低");
|
|
|
+ NutritionIndicatorMapping mapping = mockMapping(2L, "维生素D", "维生素D", 1);
|
|
|
+
|
|
|
+ when(healthReportService.getLatestReport(5L)).thenReturn(report);
|
|
|
+ when(healthReportService.getReportIndicators(7L)).thenReturn(Collections.singletonList(indicator));
|
|
|
+ when(mappingMapper.selectList(any())).thenReturn(Collections.singletonList(mapping));
|
|
|
+ when(recordMapper.delete(any())).thenReturn(1);
|
|
|
+ when(recordMapper.insert(any())).thenReturn(1);
|
|
|
+
|
|
|
+ nutritionDeficiencyService.analyzeDeficiency(5L, 7L);
|
|
|
+
|
|
|
+ verify(recordMapper).delete(any());
|
|
|
+ }
|
|
|
+}
|