|
|
@@ -0,0 +1,155 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.etotem.cfc.entity.HealthReport;
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.mockito.Mock;
|
|
|
+import org.mockito.MockitoAnnotations;
|
|
|
+import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
+
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.*;
|
|
|
+import static org.mockito.ArgumentMatchers.*;
|
|
|
+import static org.mockito.Mockito.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * HealthReportService 单元测试(纯单元测试,无 SpringBootTest,避免 MySQL 连接)。
|
|
|
+ *
|
|
|
+ * 测试范围:
|
|
|
+ * - reassignReport: 重新分配报告归属(无实际变化快路径、权限检查、SQL 更新、维度重算)
|
|
|
+ * - getUnlinkedReports: 获取未绑定归属的报告列表(查询条件修复:familyId → family_id)
|
|
|
+ */
|
|
|
+public class HealthReportServiceTest {
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private com.etotem.cfc.mapper.HealthReportMapper healthReportMapper;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private com.etotem.cfc.mapper.HealthReportPermissionMapper healthReportPermissionMapper;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private DimensionScoreService dimensionScoreService;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private FoodRecommendService foodRecommendService;
|
|
|
+
|
|
|
+ @Mock
|
|
|
+ private JdbcTemplate jdbcTemplate;
|
|
|
+
|
|
|
+ private HealthReportService service;
|
|
|
+
|
|
|
+ @BeforeEach
|
|
|
+ public void setUp() {
|
|
|
+ MockitoAnnotations.openMocks(this);
|
|
|
+ service = new HealthReportService();
|
|
|
+ setField(service, "healthReportMapper", healthReportMapper);
|
|
|
+ setField(service, "healthReportPermissionMapper", healthReportPermissionMapper);
|
|
|
+ setField(service, "dimensionScoreService", dimensionScoreService);
|
|
|
+ setField(service, "foodRecommendService", foodRecommendService);
|
|
|
+ setField(service, "jdbcTemplate", jdbcTemplate);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void setField(Object target, String fieldName, Object value) {
|
|
|
+ try {
|
|
|
+ java.lang.reflect.Field f = target.getClass().getDeclaredField(fieldName);
|
|
|
+ f.setAccessible(true);
|
|
|
+ f.set(target, value);
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private HealthReport report(Long id, Long familyId, Long subjectId, String status) {
|
|
|
+ HealthReport r = new HealthReport();
|
|
|
+ r.setId(id);
|
|
|
+ r.setFamilyId(familyId);
|
|
|
+ r.setSubjectId(subjectId);
|
|
|
+ r.setStatus(status);
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── reassignReport ──────────────────────────────────────────────────
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void reassignReport_noActualChange_returnsEarly() {
|
|
|
+ when(healthReportMapper.selectById(1L)).thenReturn(report(1L, 10L, 20L, "active"));
|
|
|
+
|
|
|
+ service.reassignReport(1L, 20L, 10L);
|
|
|
+
|
|
|
+ verify(jdbcTemplate, never()).update(anyString(), any(), anyLong());
|
|
|
+ verify(dimensionScoreService, never()).refreshFromGutReport(anyLong(), anyLong());
|
|
|
+ verify(foodRecommendService, never()).recalculateForUser(anyLong());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void reassignReport_unbind_subjectIdNull() {
|
|
|
+ when(healthReportMapper.selectById(1L)).thenReturn(report(1L, 10L, 20L, "active"));
|
|
|
+
|
|
|
+ service.reassignReport(1L, null, 10L);
|
|
|
+
|
|
|
+ verify(jdbcTemplate).update(eq("UPDATE health_reports SET subject_id = ? WHERE id = ?"),
|
|
|
+ eq(null), eq(1L));
|
|
|
+ verify(dimensionScoreService).refreshFromGutReport(eq(20L), eq(1L));
|
|
|
+ verify(foodRecommendService).recalculateForUser(eq(20L));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void reassignReport_bind_newMember() {
|
|
|
+ when(healthReportMapper.selectById(1L)).thenReturn(report(1L, 10L, null, "active"));
|
|
|
+
|
|
|
+ service.reassignReport(1L, 30L, 10L);
|
|
|
+
|
|
|
+ verify(jdbcTemplate).update(eq("UPDATE health_reports SET subject_id = ? WHERE id = ?"),
|
|
|
+ eq(30L), eq(1L));
|
|
|
+ verify(dimensionScoreService).refreshFromGutReport(eq(30L), eq(1L));
|
|
|
+ verify(foodRecommendService).recalculateForUser(eq(30L));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void reassignReport_switchMember_recalcBoth() {
|
|
|
+ when(healthReportMapper.selectById(1L)).thenReturn(report(1L, 10L, 20L, "active"));
|
|
|
+
|
|
|
+ service.reassignReport(1L, 30L, 10L);
|
|
|
+
|
|
|
+ verify(jdbcTemplate).update(eq("UPDATE health_reports SET subject_id = ? WHERE id = ?"),
|
|
|
+ eq(30L), eq(1L));
|
|
|
+ verify(dimensionScoreService).refreshFromGutReport(eq(20L), eq(1L));
|
|
|
+ verify(dimensionScoreService).refreshFromGutReport(eq(30L), eq(1L));
|
|
|
+ verify(foodRecommendService).recalculateForUser(eq(20L));
|
|
|
+ verify(foodRecommendService).recalculateForUser(eq(30L));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void reassignReport_wrongFamilyId_throws() {
|
|
|
+ when(healthReportMapper.selectById(1L)).thenReturn(report(1L, 99L, 20L, "active"));
|
|
|
+
|
|
|
+ assertThrows(RuntimeException.class, () -> service.reassignReport(1L, 30L, 10L));
|
|
|
+
|
|
|
+ verify(jdbcTemplate, never()).update(anyString(), any(), anyLong());
|
|
|
+ verify(dimensionScoreService, never()).refreshFromGutReport(anyLong(), anyLong());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void reassignReport_reportNotFound_throws() {
|
|
|
+ when(healthReportMapper.selectById(1L)).thenReturn(null);
|
|
|
+
|
|
|
+ assertThrows(RuntimeException.class, () -> service.reassignReport(1L, 30L, 10L));
|
|
|
+
|
|
|
+ verify(jdbcTemplate, never()).update(anyString(), any(), anyLong());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── getUnlinkedReports ──────────────────────────────────────────────
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void getUnlinkedReports_returnsReports() {
|
|
|
+ List<HealthReport> reports = Collections.singletonList(report(1L, 10L, null, "active"));
|
|
|
+ when(healthReportMapper.selectList(any())).thenReturn(reports);
|
|
|
+
|
|
|
+ List<HealthReport> result = service.getUnlinkedReports(10L);
|
|
|
+
|
|
|
+ assertEquals(1, result.size());
|
|
|
+ verify(healthReportMapper).selectList(any());
|
|
|
+ }
|
|
|
+}
|