|
|
@@ -0,0 +1,322 @@
|
|
|
+package com.etotem.cfc.integration;
|
|
|
+
|
|
|
+import com.etotem.cfc.common.Result;
|
|
|
+import com.etotem.cfc.controller.DailyCheckinController;
|
|
|
+import com.etotem.cfc.controller.StreakController;
|
|
|
+import com.etotem.cfc.entity.DailyHealthCheckin;
|
|
|
+import com.etotem.cfc.entity.User;
|
|
|
+import com.etotem.cfc.entity.Child;
|
|
|
+import com.etotem.cfc.service.DailyCheckinService;
|
|
|
+import com.etotem.cfc.service.StreakService;
|
|
|
+import com.etotem.cfc.mapper.UserMapper;
|
|
|
+import com.etotem.cfc.mapper.ChildMapper;
|
|
|
+import org.junit.jupiter.api.*;
|
|
|
+import org.springframework.boot.test.context.SpringBootTest;
|
|
|
+import org.springframework.boot.test.mock.mockito.MockBean;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.*;
|
|
|
+import static org.mockito.ArgumentMatchers.*;
|
|
|
+import static org.mockito.Mockito.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * ================================================================
|
|
|
+ * 场景:连续签到奖励全流程
|
|
|
+ * ================================================================
|
|
|
+ * 用户故事:作为孩子,我希望每天完成健康打卡,
|
|
|
+ * 保持连续签到记录以获得更多奖励。
|
|
|
+ *
|
|
|
+ * 流程步骤:
|
|
|
+ * 1. 孩子每天完成健康打卡(饮食、运动、睡眠、饮水、心情)
|
|
|
+ * 2. 系统记录打卡并更新连续签到天数
|
|
|
+ * 3. 达到特定天数阈值时发放奖励(积分或能量)
|
|
|
+ * 4. 第二天继续打卡以保持连续
|
|
|
+ * 5. 如果中断,系统重置连续天数为0
|
|
|
+ *
|
|
|
+ * 关键里程碑:
|
|
|
+ * - [Milestone-1] 打卡记录创建成功
|
|
|
+ * - [Milestone-2] 连续签到天数正确累计
|
|
|
+ * - [Milestone-3] 达到奖励阈值,奖励发放
|
|
|
+ * - [Milestone-4] 中断后重置连续天数
|
|
|
+ * ================================================================
|
|
|
+ */
|
|
|
+@SpringBootTest
|
|
|
+public class DailyCheckinStreakFlowTest {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private DailyCheckinController checkinController;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private StreakController streakController;
|
|
|
+
|
|
|
+ @MockBean
|
|
|
+ private DailyCheckinService dailyCheckinService;
|
|
|
+
|
|
|
+ @MockBean
|
|
|
+ private StreakService streakService;
|
|
|
+
|
|
|
+ @MockBean
|
|
|
+ private UserMapper userMapper;
|
|
|
+
|
|
|
+ @MockBean
|
|
|
+ private ChildMapper childMapper;
|
|
|
+
|
|
|
+ // ========== 测试数据 ==========
|
|
|
+
|
|
|
+ private static final Long USER_ID = 1001L;
|
|
|
+ private static final Long CHILD_ID = 2001L;
|
|
|
+ private static final Long FAMILY_ID = 3001L;
|
|
|
+ private static final Long CHECKIN_ID = 1L;
|
|
|
+
|
|
|
+ // ========== 场景1:每日健康打卡 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:孩子完成每日健康打卡
|
|
|
+ * 角色:孩子
|
|
|
+ * 触发条件:每天进入打卡页面填写健康数据
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景1] 孩子完成每日打卡 - 期望打卡成功")
|
|
|
+ void childDailyCheckin_Success() {
|
|
|
+ // === Given:孩子填写了打卡数据 ===
|
|
|
+ DailyHealthCheckin checkin = new DailyHealthCheckin();
|
|
|
+ checkin.setMemberId(CHILD_ID);
|
|
|
+ checkin.setDietScore(85);
|
|
|
+ checkin.setExerciseScore(90);
|
|
|
+ checkin.setSleepScore(80);
|
|
|
+ checkin.setWaterIntake(2000);
|
|
|
+ checkin.setMoodScore(88);
|
|
|
+
|
|
|
+ DailyHealthCheckin savedCheckin = new DailyHealthCheckin();
|
|
|
+ savedCheckin.setId(CHECKIN_ID);
|
|
|
+ savedCheckin.setMemberId(CHILD_ID);
|
|
|
+ savedCheckin.setDietScore(85);
|
|
|
+
|
|
|
+ when(dailyCheckinService.createCheckin(any(DailyHealthCheckin.class)))
|
|
|
+ .thenReturn(savedCheckin);
|
|
|
+
|
|
|
+ // === When:提交打卡 ===
|
|
|
+ Result<DailyHealthCheckin> result = checkinController.create(checkin);
|
|
|
+
|
|
|
+ // === Then:Milestone-1 - 打卡成功 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertNotNull(result.getData());
|
|
|
+ assertEquals(CHECKIN_ID, result.getData().getId());
|
|
|
+ verify(dailyCheckinService).createCheckin(any(DailyHealthCheckin.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景2:查看打卡列表 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:查看历史打卡记录
|
|
|
+ * 角色:孩子或家长
|
|
|
+ * 触发条件:进入打卡历史页面
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景2] 查看打卡记录列表 - 期望返回历史记录")
|
|
|
+ void parentViewsCheckinList_Success() {
|
|
|
+ // === Given:有多条打卡记录 ===
|
|
|
+ Map<String, Object> listRequest = new HashMap<>();
|
|
|
+ listRequest.put("memberId", CHILD_ID);
|
|
|
+
|
|
|
+ List<DailyHealthCheckin> mockList = Arrays.asList(
|
|
|
+ createCheckin(1L, CHILD_ID, "2026-07-01", 85),
|
|
|
+ createCheckin(2L, CHILD_ID, "2026-06-30", 82),
|
|
|
+ createCheckin(3L, CHILD_ID, "2026-06-29", 88)
|
|
|
+ );
|
|
|
+
|
|
|
+ when(dailyCheckinService.getCheckinsByMember(CHILD_ID)).thenReturn(mockList);
|
|
|
+
|
|
|
+ // === When:请求打卡列表 ===
|
|
|
+ Result<List<DailyHealthCheckin>> result = checkinController.list(listRequest);
|
|
|
+
|
|
|
+ // === Then:返回历史记录 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertNotNull(result.getData());
|
|
|
+ assertEquals(3, result.getData().size());
|
|
|
+ verify(dailyCheckinService).getCheckinsByMember(CHILD_ID);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景3:获取连续签到进度 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:孩子查看自己的连续签到进度
|
|
|
+ * 角色:孩子
|
|
|
+ * 触发条件:查看签到激励页面
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景3] 获取连续签到进度 - 期望返回当前连续天数和奖励状态")
|
|
|
+ void childGetsStreakProgress_Success() {
|
|
|
+ // === Given:孩子已连续打卡多天 ===
|
|
|
+ User user = new User();
|
|
|
+ user.setId(USER_ID);
|
|
|
+ user.setFamilyId(FAMILY_ID);
|
|
|
+
|
|
|
+ Child child = new Child();
|
|
|
+ child.setId(CHILD_ID);
|
|
|
+ child.setFamilyId(FAMILY_ID);
|
|
|
+
|
|
|
+ Map<String, Object> mockProgress = new HashMap<>();
|
|
|
+ mockProgress.put("childId", CHILD_ID);
|
|
|
+ mockProgress.put("currentStreak", 7);
|
|
|
+ mockProgress.put("longestStreak", 15);
|
|
|
+ mockProgress.put("todayChecked", true);
|
|
|
+ mockProgress.put("nextRewardDays", 3); // 再连续3天可获奖励
|
|
|
+ mockProgress.put("pendingReward", true); // 有待领取奖励
|
|
|
+
|
|
|
+ when(userMapper.selectById(USER_ID)).thenReturn(user);
|
|
|
+ when(childMapper.selectById(CHILD_ID)).thenReturn(child);
|
|
|
+ when(streakService.getStreakProgress(CHILD_ID)).thenReturn(mockProgress);
|
|
|
+
|
|
|
+ // === When:请求签到进度 ===
|
|
|
+ Result<Map<String, Object>> result = streakController.getStreakProgress(USER_ID, CHILD_ID);
|
|
|
+
|
|
|
+ // === Then:Milestone-2 - 返回连续天数等信息 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertNotNull(result.getData());
|
|
|
+ assertEquals(7, result.getData().get("currentStreak"));
|
|
|
+ assertEquals(15, result.getData().get("longestStreak"));
|
|
|
+ assertTrue((Boolean) result.getData().get("todayChecked"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景4:达到奖励阈值发放奖励 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:孩子连续签到达到7天阈值,系统发放奖励
|
|
|
+ * 角色:系统(自动触发)
|
|
|
+ * 触发条件:连续签到天数达到特定阈值
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景4] 连续7天打卡达成 - 期望奖励发放")
|
|
|
+ void childReaches7DayStreak_RewardGranted() {
|
|
|
+ // === Given:连续打卡达到7天 ===
|
|
|
+ when(streakService.checkAndGrantReward(eq(CHILD_ID), eq(7)))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ // === When:系统检查并发放奖励 ===
|
|
|
+ Result<Boolean> result = streakController.checkAndGrantReward(CHILD_ID, 7);
|
|
|
+
|
|
|
+ // === Then:Milestone-3 - 奖励发放成功 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertTrue(result.getData());
|
|
|
+ verify(streakService).checkAndGrantReward(eq(CHILD_ID), eq(7));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:孩子连续签到达到30天,获得大奖励
|
|
|
+ * 角色:系统
|
|
|
+ * 触发条件:连续签到达到30天
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景4b] 连续30天打卡达成 - 期望获得大额奖励")
|
|
|
+ void childReaches30DayStreak_GrandRewardGranted() {
|
|
|
+ // === Given:连续打卡达到30天 ===
|
|
|
+ when(streakService.checkAndGrantReward(eq(CHILD_ID), eq(30)))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ // === When:系统发放大奖励 ===
|
|
|
+ Result<Boolean> result = streakController.checkAndGrantReward(CHILD_ID, 30);
|
|
|
+
|
|
|
+ // === Then:奖励发放 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertTrue(result.getData());
|
|
|
+ verify(streakService).checkAndGrantReward(eq(CHILD_ID), eq(30));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景5:打卡中断重置 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:孩子忘记打卡,连续签到中断
|
|
|
+ * 角色:系统(定时任务触发)
|
|
|
+ * 触发条件:凌晨检查发现昨天未打卡
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景5] 打卡中断 - 期望连续天数重置")
|
|
|
+ void childMissesCheckin_StreakReset() {
|
|
|
+ // === Given:昨天未打卡 ===
|
|
|
+ when(streakService.resetStreakIfNeeded()).thenReturn(true);
|
|
|
+
|
|
|
+ // === When:系统重置中断的签到 ===
|
|
|
+ Result<Boolean> result = streakController.resetStreak();
|
|
|
+
|
|
|
+ // === Then:Milestone-4 - 连续天数重置 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertTrue(result.getData());
|
|
|
+ verify(streakService).resetStreakIfNeeded();
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景6:更新打卡记录 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:孩子补充或修改今天的打卡数据
|
|
|
+ * 角色:孩子
|
|
|
+ * 触发条件:发现打卡数据填错需要修改
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景6] 更新打卡记录 - 期望更新成功")
|
|
|
+ void childUpdatesCheckin_Success() {
|
|
|
+ // === Given:需要修改打卡数据 ===
|
|
|
+ Map<String, Object> updateRequest = new HashMap<>();
|
|
|
+ updateRequest.put("id", CHECKIN_ID);
|
|
|
+ updateRequest.put("dietScore", 90); // 原本85,修改为90
|
|
|
+ updateRequest.put("moodScore", 95);
|
|
|
+
|
|
|
+ DailyHealthCheckin updatedCheckin = new DailyHealthCheckin();
|
|
|
+ updatedCheckin.setId(CHECKIN_ID);
|
|
|
+ updatedCheckin.setDietScore(90);
|
|
|
+ updatedCheckin.setMoodScore(95);
|
|
|
+
|
|
|
+ when(dailyCheckinService.updateCheckin(eq(CHECKIN_ID), any(DailyHealthCheckin.class)))
|
|
|
+ .thenReturn(updatedCheckin);
|
|
|
+
|
|
|
+ // === When:更新打卡记录 ===
|
|
|
+ Result<DailyHealthCheckin> result = checkinController.update(updateRequest);
|
|
|
+
|
|
|
+ // === Then:更新成功 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertNotNull(result.getData());
|
|
|
+ assertEquals(90, result.getData().getDietScore());
|
|
|
+ assertEquals(95, result.getData().getMoodScore());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景7:删除打卡记录 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:孩子删除一条错误的打卡记录
|
|
|
+ * 角色:孩子
|
|
|
+ * 触发条件:发现打卡记录有误需要删除
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景7] 删除打卡记录 - 期望删除成功")
|
|
|
+ void childDeletesCheckin_Success() {
|
|
|
+ // === Given:需要删除记录 ===
|
|
|
+ Map<String, Object> deleteRequest = new HashMap<>();
|
|
|
+ deleteRequest.put("id", CHECKIN_ID);
|
|
|
+
|
|
|
+ doNothing().when(dailyCheckinService).deleteCheckin(CHECKIN_ID);
|
|
|
+
|
|
|
+ // === When:删除打卡记录 ===
|
|
|
+ Result<String> result = checkinController.delete(deleteRequest);
|
|
|
+
|
|
|
+ // === Then:删除成功 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertEquals("删除成功", result.getData());
|
|
|
+ verify(dailyCheckinService).deleteCheckin(CHECKIN_ID);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== Helper Methods ==========
|
|
|
+
|
|
|
+ private DailyHealthCheckin createCheckin(Long id, Long memberId, String checkinDate, Integer score) {
|
|
|
+ DailyHealthCheckin checkin = new DailyHealthCheckin();
|
|
|
+ checkin.setId(id);
|
|
|
+ checkin.setMemberId(memberId);
|
|
|
+ checkin.setCheckinDate(checkinDate);
|
|
|
+ checkin.setDietScore(score);
|
|
|
+ checkin.setExerciseScore(score);
|
|
|
+ checkin.setSleepScore(score);
|
|
|
+ checkin.setMoodScore(score);
|
|
|
+ return checkin;
|
|
|
+ }
|
|
|
+}
|