|
|
@@ -0,0 +1,431 @@
|
|
|
+package com.etotem.cfc.integration;
|
|
|
+
|
|
|
+import com.etotem.cfc.common.Result;
|
|
|
+import com.etotem.cfc.controller.wish.WishController;
|
|
|
+import com.etotem.cfc.dto.CreateWishDTO;
|
|
|
+import com.etotem.cfc.dto.WishDTO;
|
|
|
+import com.etotem.cfc.service.WishService;
|
|
|
+import com.etotem.cfc.service.PointsService;
|
|
|
+import com.etotem.cfc.mapper.WishMapper;
|
|
|
+import com.etotem.cfc.entity.Wish;
|
|
|
+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. 家长查看心愿并设定所需积分(如 500 积分)
|
|
|
+ * 3. 孩子通过完成任务积累积分后,申请兑换心愿
|
|
|
+ * 4. 家长审批通过,系统自动扣除积分
|
|
|
+ * 5. 家长购买物品后标记"已购买"
|
|
|
+ * 6. 孩子确认收货,心愿流程完成
|
|
|
+ *
|
|
|
+ * 关键里程碑:
|
|
|
+ * - [Milestone-1] 心愿创建成功,获得心愿ID
|
|
|
+ * - [Milestone-2] 家长定价成功,心愿状态变更为"已定价"
|
|
|
+ * - [Milestone-3] 兑换申请提交,状态变更为"兑换中"
|
|
|
+ * - [Milestone-4] 家长审批通过,积分立即扣减
|
|
|
+ * - [Milestone-5] 标记已购买,等待孩子确认
|
|
|
+ * - [Milestone-6] 孩子确认收货,心愿完成
|
|
|
+ * ================================================================
|
|
|
+ */
|
|
|
+@SpringBootTest
|
|
|
+public class WishExchangeFlowTest {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private WishController wishController;
|
|
|
+
|
|
|
+ @MockBean
|
|
|
+ private WishService wishService;
|
|
|
+
|
|
|
+ @MockBean
|
|
|
+ private PointsService pointsService;
|
|
|
+
|
|
|
+ @MockBean
|
|
|
+ private WishMapper wishMapper;
|
|
|
+
|
|
|
+ // ========== 测试数据 ==========
|
|
|
+
|
|
|
+ private static final Long CHILD_USER_ID = 2001L;
|
|
|
+ private static final Long PARENT_USER_ID = 1001L;
|
|
|
+ private static final Long FAMILY_ID = 3001L;
|
|
|
+ private static final Long WISH_ID = 1L;
|
|
|
+ private static final String WISH_TITLE = "科学实验套装";
|
|
|
+ private static final Integer POINTS_REQUIRED = 500;
|
|
|
+
|
|
|
+ // ========== 场景1:孩子创建心愿 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:孩子创建一个新的心愿
|
|
|
+ * 角色:孩子
|
|
|
+ * 触发条件:孩子点击"创建心愿"并填写表单
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景1] 孩子创建心愿 - 期望心愿创建成功")
|
|
|
+ void childCreatesWish_Success() {
|
|
|
+ // === Given:孩子填写了心愿信息 ===
|
|
|
+ CreateWishDTO createDTO = new CreateWishDTO();
|
|
|
+ createDTO.setTitle(WISH_TITLE);
|
|
|
+ createDTO.setDescription("包含50个实验的科学实验套装");
|
|
|
+ createDTO.setImageUrl("https://example.com/science-kit.jpg");
|
|
|
+ createDTO.setLinkUrl("https://shop.example.com/science-kit");
|
|
|
+ createDTO.setEstimatedPrice(29900L); // 299元
|
|
|
+
|
|
|
+ when(wishService.createByChild(eq(CHILD_USER_ID), any(CreateWishDTO.class)))
|
|
|
+ .thenReturn(WISH_ID);
|
|
|
+
|
|
|
+ // === When:孩子提交创建心愿表单 ===
|
|
|
+ Result<Long> result = wishController.createWish(
|
|
|
+ CHILD_USER_ID, "child", createDTO);
|
|
|
+
|
|
|
+ // === Then:Milestone-1 - 心愿创建成功 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertNotNull(result.getData());
|
|
|
+ assertEquals(WISH_ID, result.getData());
|
|
|
+ verify(wishService).createByChild(eq(CHILD_USER_ID), any(CreateWishDTO.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景2:家长定价 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:家长为孩子的心愿设定所需积分
|
|
|
+ * 角色:家长
|
|
|
+ * 触发条件:家长打开心愿详情,决定设定多少积分可兑换
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景2] 家长设定心愿积分 - 期望积分设定成功且状态更新")
|
|
|
+ void parentSetsWishPrice_Success() {
|
|
|
+ // === Given:家长决定设定 500 积分 ===
|
|
|
+ Map<String, Object> priceRequest = new HashMap<>();
|
|
|
+ priceRequest.put("pointsRequired", POINTS_REQUIRED);
|
|
|
+
|
|
|
+ when(wishService.setPrice(eq(WISH_ID), eq(PARENT_USER_ID), eq(POINTS_REQUIRED)))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ // === When:家长提交定价 ===
|
|
|
+ Result<Boolean> result = wishController.setPrice(
|
|
|
+ WISH_ID, PARENT_USER_ID, priceRequest);
|
|
|
+
|
|
|
+ // === Then:Milestone-2 - 定价成功 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertTrue(result.getData());
|
|
|
+ verify(wishService).setPrice(eq(WISH_ID), eq(PARENT_USER_ID), eq(POINTS_REQUIRED));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:家长设定的积分值为负数或零
|
|
|
+ * 角色:家长
|
|
|
+ * 触发条件:表单校验未通过
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景2b] 家长设定无效积分值 - 期望返回错误提示")
|
|
|
+ void parentSetsInvalidPrice_Fails() {
|
|
|
+ // === Given:设定了无效的积分值 ===
|
|
|
+ Map<String, Object> priceRequest = new HashMap<>();
|
|
|
+ priceRequest.put("pointsRequired", 0); // 无效值
|
|
|
+
|
|
|
+ // === When:尝试提交定价 ===
|
|
|
+ Result<Boolean> result = wishController.setPrice(
|
|
|
+ WISH_ID, PARENT_USER_ID, priceRequest);
|
|
|
+
|
|
|
+ // === Then:返回错误提示 ===
|
|
|
+ assertEquals(500, result.getCode());
|
|
|
+ assertTrue(result.getMessage().contains("有效") || result.getMessage().contains("大于0"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景3:孩子申请兑换 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:孩子积累足够积分后,申请兑换心愿
|
|
|
+ * 角色:孩子
|
|
|
+ * 触发条件:孩子点击心愿的"申请兑换"按钮
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景3] 孩子申请兑换心愿 - 期望兑换申请提交成功")
|
|
|
+ void childRequestsExchange_Success() {
|
|
|
+ // === Given:孩子已积累足够积分 ===
|
|
|
+ when(wishService.requestExchange(eq(WISH_ID), eq(CHILD_USER_ID)))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ // === When:孩子点击"申请兑换" ===
|
|
|
+ Result<Boolean> result = wishController.requestExchange(WISH_ID, CHILD_USER_ID);
|
|
|
+
|
|
|
+ // === Then:Milestone-3 - 兑换申请提交成功 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertTrue(result.getData());
|
|
|
+ verify(wishService).requestExchange(eq(WISH_ID), eq(CHILD_USER_ID));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:孩子积分不足时尝试兑换
|
|
|
+ * 角色:孩子
|
|
|
+ * 触发条件:孩子积分不足以兑换该心愿
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景3b] 积分不足时申请兑换 - 期望返回错误提示")
|
|
|
+ void childRequestsExchangeWithInsufficientPoints_Fails() {
|
|
|
+ // === Given:孩子积分不足 ===
|
|
|
+ when(wishService.requestExchange(eq(WISH_ID), eq(CHILD_USER_ID)))
|
|
|
+ .thenThrow(new RuntimeException("积分不足,当前积分:200,需要:500"));
|
|
|
+
|
|
|
+ // === When:孩子尝试兑换 ===
|
|
|
+ Result<Boolean> result = wishController.requestExchange(WISH_ID, CHILD_USER_ID);
|
|
|
+
|
|
|
+ // === Then:返回积分不足提示 ===
|
|
|
+ assertEquals(500, result.getCode());
|
|
|
+ assertTrue(result.getMessage().contains("积分不足"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景4:家长审批兑换 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:家长审批通过孩子的兑换申请,积分立即扣除
|
|
|
+ * 角色:家长
|
|
|
+ * 触发条件:家长查看兑换申请并点击"批准"
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景4] 家长审批通过兑换 - 期望审批成功且积分扣除")
|
|
|
+ void parentApprovesExchange_Success() {
|
|
|
+ // === Given:家长同意孩子的兑换申请 ===
|
|
|
+ Map<String, Object> approveRequest = new HashMap<>();
|
|
|
+ approveRequest.put("approved", true);
|
|
|
+
|
|
|
+ when(wishService.approveExchange(eq(WISH_ID), eq(PARENT_USER_ID), eq(true)))
|
|
|
+ .thenReturn(true);
|
|
|
+ when(pointsService.deductForWish(eq(CHILD_USER_ID), eq(POINTS_REQUIRED)))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ // === When:家长点击"批准" ===
|
|
|
+ Result<Boolean> result = wishController.approveExchange(
|
|
|
+ WISH_ID, PARENT_USER_ID, approveRequest);
|
|
|
+
|
|
|
+ // === Then:Milestone-4 - 审批通过 + Milestone-5 积分扣减 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertTrue(result.getData());
|
|
|
+ verify(wishService).approveExchange(eq(WISH_ID), eq(PARENT_USER_ID), eq(true));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:家长拒绝孩子的兑换申请
|
|
|
+ * 角色:家长
|
|
|
+ * 触发条件:家长认为该心愿不合适,点击"拒绝"
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景4b] 家长拒绝兑换申请 - 期望心愿状态回退")
|
|
|
+ void parentRejectsExchange_Success() {
|
|
|
+ // === Given:家长拒绝兑换申请 ===
|
|
|
+ Map<String, Object> approveRequest = new HashMap<>();
|
|
|
+ approveRequest.put("approved", false);
|
|
|
+ approveRequest.put("reason", "本月预算已用完,下个月再兑换好吗?");
|
|
|
+
|
|
|
+ when(wishService.approveExchange(eq(WISH_ID), eq(PARENT_USER_ID), eq(false)))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ // === When:家长点击"拒绝" ===
|
|
|
+ Result<Boolean> result = wishController.approveExchange(
|
|
|
+ WISH_ID, PARENT_USER_ID, approveRequest);
|
|
|
+
|
|
|
+ // === Then:拒绝成功,心愿状态回退 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertTrue(result.getData());
|
|
|
+ verify(wishService).approveExchange(eq(WISH_ID), eq(PARENT_USER_ID), eq(false));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景5:标记已购买 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:家长购买物品后标记心愿为"已购买"
|
|
|
+ * 角色:家长
|
|
|
+ * 触发条件:家长完成购买操作
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景5] 家长标记心愿已购买 - 期望状态变更为待收货")
|
|
|
+ void parentMarksFulfilled_Success() {
|
|
|
+ // === Given:家长已完成购买 ===
|
|
|
+ when(wishService.markFulfilled(eq(WISH_ID), eq(PARENT_USER_ID)))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ // === When:家长点击"已购买" ===
|
|
|
+ Result<Boolean> result = wishController.markFulfilled(WISH_ID, PARENT_USER_ID);
|
|
|
+
|
|
|
+ // === Then:Milestone-5 - 标记成功 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertTrue(result.getData());
|
|
|
+ verify(wishService).markFulfilled(eq(WISH_ID), eq(PARENT_USER_ID));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景6:孩子确认收货 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:孩子收到物品后确认收货,心愿流程完成
|
|
|
+ * 角色:孩子
|
|
|
+ * 触发条件:孩子收到实物并确认无误
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景6] 孩子确认收货 - 期望心愿流程完成")
|
|
|
+ void childConfirmsReceived_Success() {
|
|
|
+ // === Given:孩子已收到物品 ===
|
|
|
+ when(wishService.confirmReceived(eq(WISH_ID), eq(CHILD_USER_ID)))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ // === When:孩子点击"确认收货" ===
|
|
|
+ Result<Boolean> result = wishController.confirmReceived(WISH_ID, CHILD_USER_ID);
|
|
|
+
|
|
|
+ // === Then:Milestone-6 - 心愿完成 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertTrue(result.getData());
|
|
|
+ verify(wishService).confirmReceived(eq(WISH_ID), eq(CHILD_USER_ID));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景7:完整心愿流程(端到端)============
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:完整的心愿从创建到完成的整个生命周期
|
|
|
+ * 角色:孩子 → 家长 → 孩子
|
|
|
+ * 触发条件:心愿的完整业务流程
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景7] 完整心愿生命周期 - 期望全流程成功")
|
|
|
+ void completeWishLifecycle_Success() {
|
|
|
+ // === Step 1:孩子创建心愿 ===
|
|
|
+ CreateWishDTO createDTO = new CreateWishDTO();
|
|
|
+ createDTO.setTitle("一本科学杂志");
|
|
|
+ createDTO.setEstimatedPrice(3000L);
|
|
|
+
|
|
|
+ Long newWishId = 2L;
|
|
|
+ when(wishService.createByChild(eq(CHILD_USER_ID), any(CreateWishDTO.class)))
|
|
|
+ .thenReturn(newWishId);
|
|
|
+
|
|
|
+ Result<Long> createResult = wishController.createWish(
|
|
|
+ CHILD_USER_ID, "child", createDTO);
|
|
|
+ assertEquals(200, createResult.getCode());
|
|
|
+ assertEquals(newWishId, createResult.getData());
|
|
|
+
|
|
|
+ // === Step 2:家长定价 ===
|
|
|
+ Map<String, Object> priceRequest = new HashMap<>();
|
|
|
+ priceRequest.put("pointsRequired", 100);
|
|
|
+
|
|
|
+ when(wishService.setPrice(eq(newWishId), eq(PARENT_USER_ID), eq(100)))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ Result<Boolean> priceResult = wishController.setPrice(
|
|
|
+ newWishId, PARENT_USER_ID, priceRequest);
|
|
|
+ assertEquals(200, priceResult.getCode());
|
|
|
+ assertTrue(priceResult.getData());
|
|
|
+
|
|
|
+ // === Step 3:孩子申请兑换 ===
|
|
|
+ when(wishService.requestExchange(eq(newWishId), eq(CHILD_USER_ID)))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ Result<Boolean> exchangeResult = wishController.requestExchange(newWishId, CHILD_USER_ID);
|
|
|
+ assertEquals(200, exchangeResult.getCode());
|
|
|
+ assertTrue(exchangeResult.getData());
|
|
|
+
|
|
|
+ // === Step 4:家长审批通过 ===
|
|
|
+ Map<String, Object> approveRequest = new HashMap<>();
|
|
|
+ approveRequest.put("approved", true);
|
|
|
+
|
|
|
+ when(wishService.approveExchange(eq(newWishId), eq(PARENT_USER_ID), eq(true)))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ Result<Boolean> approveResult = wishController.approveExchange(
|
|
|
+ newWishId, PARENT_USER_ID, approveRequest);
|
|
|
+ assertEquals(200, approveResult.getCode());
|
|
|
+ assertTrue(approveResult.getData());
|
|
|
+
|
|
|
+ // === Step 5:家长标记已购买 ===
|
|
|
+ when(wishService.markFulfilled(eq(newWishId), eq(PARENT_USER_ID)))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ Result<Boolean> fulfillResult = wishController.markFulfilled(newWishId, PARENT_USER_ID);
|
|
|
+ assertEquals(200, fulfillResult.getCode());
|
|
|
+ assertTrue(fulfillResult.getData());
|
|
|
+
|
|
|
+ // === Step 6:孩子确认收货 ===
|
|
|
+ when(wishService.confirmReceived(eq(newWishId), eq(CHILD_USER_ID)))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ Result<Boolean> confirmResult = wishController.confirmReceived(newWishId, CHILD_USER_ID);
|
|
|
+ assertEquals(200, confirmResult.getCode());
|
|
|
+ assertTrue(confirmResult.getData());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景8:家长拒绝心愿 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:家长认为某个心愿不合适,直接拒绝
|
|
|
+ * 角色:家长
|
|
|
+ * 触发条件:家长点击"拒绝"并填写原因
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景8] 家长直接拒绝心愿 - 期望心愿被关闭")
|
|
|
+ void parentRejectsWish_Success() {
|
|
|
+ // === Given:家长决定拒绝该心愿 ===
|
|
|
+ Map<String, String> rejectRequest = new HashMap<>();
|
|
|
+ rejectRequest.put("reason", "这套书家里已经有了,换个其他的吧");
|
|
|
+
|
|
|
+ when(wishService.rejectWish(eq(WISH_ID), eq(PARENT_USER_ID), anyString()))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ // === When:家长点击"拒绝" ===
|
|
|
+ Result<Boolean> result = wishController.rejectWish(
|
|
|
+ WISH_ID, PARENT_USER_ID, rejectRequest);
|
|
|
+
|
|
|
+ // === Then:心愿被关闭 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertTrue(result.getData());
|
|
|
+ verify(wishService).rejectWish(eq(WISH_ID), eq(PARENT_USER_ID), anyString());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 场景9:孩子取消心愿 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 场景:孩子改变主意,主动取消心愿
|
|
|
+ * 角色:孩子
|
|
|
+ * 触发条件:孩子点击"取消心愿"
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ @DisplayName("[场景9] 孩子取消心愿 - 期望心愿被取消")
|
|
|
+ void childCancelsWish_Success() {
|
|
|
+ // === Given:孩子改变主意 ===
|
|
|
+ Map<String, String> cancelRequest = new HashMap<>();
|
|
|
+ cancelRequest.put("reason", "想了想还是不要了");
|
|
|
+
|
|
|
+ when(wishService.cancelWish(eq(WISH_ID), eq(CHILD_USER_ID), anyString()))
|
|
|
+ .thenReturn(true);
|
|
|
+
|
|
|
+ // === When:孩子点击"取消" ===
|
|
|
+ Result<Boolean> result = wishController.cancelWish(
|
|
|
+ WISH_ID, CHILD_USER_ID, cancelRequest);
|
|
|
+
|
|
|
+ // === Then:心愿被取消 ===
|
|
|
+ assertEquals(200, result.getCode());
|
|
|
+ assertTrue(result.getData());
|
|
|
+ verify(wishService).cancelWish(eq(WISH_ID), eq(CHILD_USER_ID), anyString());
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== Helper Methods ==========
|
|
|
+
|
|
|
+ private WishDTO createWishDTO(Long id, String title, String status) {
|
|
|
+ WishDTO dto = new WishDTO();
|
|
|
+ dto.setId(id);
|
|
|
+ dto.setTitle(title);
|
|
|
+ dto.setStatus(status);
|
|
|
+ dto.setPointsRequired(POINTS_REQUIRED);
|
|
|
+ return dto;
|
|
|
+ }
|
|
|
+}
|