Преглед изворни кода

test(order): 测评订单全流程集成测试

Xiaogang Liao пре 2 месеци
родитељ
комит
254c87307e

+ 392 - 0
cfc-backend/src/test/java/com/etotem/cfc/integration/AssessmentOrderFlowTest.java

@@ -0,0 +1,392 @@
+package com.etotem.cfc.integration;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.controller.assessment.AssessmentOrderController;
+import com.etotem.cfc.controller.assessment.AssessmentAppointmentController;
+import com.etotem.cfc.controller.DanAssessmentController;
+import com.etotem.cfc.entity.*;
+import com.etotem.cfc.mapper.*;
+import com.etotem.cfc.service.*;
+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. 规划师完成测评,录入认知评估结果
+ *   6. 家长/孩子查看测评报告
+ *
+ * 关键里程碑:
+ *   - [Milestone-1] 订单创建成功,获得订单号
+ *   - [Milestone-2] 支付成功,预约自动创建
+ *   - [Milestone-3] 规划师确认预约
+ *   - [Milestone-4] 测评结果录入完成
+ *   - [Milestone-5] 家长可查询到孩子的最新报告
+ * ================================================================
+ */
+@SpringBootTest
+public class AssessmentOrderFlowTest {
+
+    @Resource
+    private AssessmentOrderController orderController;
+
+    @Resource
+    private AssessmentAppointmentController appointmentController;
+
+    @Resource
+    private DanAssessmentController danAssessmentController;
+
+    @MockBean
+    private AssessmentOrderService orderService;
+
+    @MockBean
+    private AssessmentAppointmentService appointmentService;
+
+    @MockBean
+    private AssessmentService assessmentService;
+
+    @MockBean
+    private PaymentService paymentService;
+
+    @MockBean
+    private FamilyMapper familyMapper;
+
+    @MockBean
+    private UserMapper userMapper;
+
+    @MockBean
+    private DanAssessmentResultMapper danAssessmentResultMapper;
+
+    // ========== 测试数据 ==========
+
+    private static final Long PARENT_USER_ID = 1001L;
+    private static final Long CHILD_ID = 2001L;
+    private static final Long FAMILY_ID = 3001L;
+    private static final Long GUIDE_ID = 4001L;
+    private static final Long PACKAGE_ID = 5001L;
+    private static final String ORDER_NO = "ASR20260628001";
+    private static final String PACKAGE_NAME = "DAN认知测评年度版";
+    private static final String GUIDE_NAME = "张规划师";
+
+    // ========== 场景1:正常支付流程 ==========
+
+    /**
+     * 场景:家长选择规划师并购买测评套餐,完成支付
+     * 角色:家长
+     * 触发条件:家长选择了具体的规划师和测评套餐
+     */
+    @Test
+    @DisplayName("[场景1] 家长购买测评套餐并完成支付 - 期望全流程成功")
+    void parentPurchasesAssessmentPackage_Success() {
+        // === Given:家长已选择规划师和孩子 ===
+        Map<String, Object> createOrderRequest = new HashMap<>();
+        createOrderRequest.put("familyId", FAMILY_ID);
+        createOrderRequest.put("childId", CHILD_ID);
+        createOrderRequest.put("guideId", GUIDE_ID);
+        createOrderRequest.put("packageId", PACKAGE_ID);
+        createOrderRequest.put("guideName", GUIDE_NAME);
+        createOrderRequest.put("packageName", PACKAGE_NAME);
+        createOrderRequest.put("totalPrice", 198000L);
+        createOrderRequest.put("discountAmount", 0L);
+
+        // Mock 创建订单服务
+        AssessmentOrder mockOrder = createMockOrder(ORDER_NO, "pending", 198000L);
+        when(orderService.createOrder(
+                eq(FAMILY_ID), eq(PARENT_USER_ID), eq(CHILD_ID), eq(GUIDE_ID), eq(PACKAGE_ID),
+                eq(GUIDE_NAME), eq(PACKAGE_NAME), eq(198000L), eq(0L)
+        )).thenReturn(mockOrder);
+
+        // === When:家长创建订单 ===
+        Result<Map<String, Object>> createResult = orderController.createOrder(PARENT_USER_ID, createOrderRequest);
+
+        // === Then:Milestone-1 - 订单创建成功 ===
+        assertEquals(200, createResult.getCode());
+        assertNotNull(createResult.getData());
+        assertEquals(ORDER_NO, createResult.getData().get("orderNo"));
+        assertEquals("pending", createResult.getData().get("status"));
+        verify(orderService).createOrder(
+                eq(FAMILY_ID), eq(PARENT_USER_ID), eq(CHILD_ID), eq(GUIDE_ID), eq(PACKAGE_ID),
+                eq(GUIDE_NAME), eq(PACKAGE_NAME), eq(198000L), eq(0L)
+        );
+
+        // === Given:订单已创建,现在进行支付 ===
+        when(orderService.getByOrderNo(ORDER_NO)).thenReturn(mockOrder);
+        when(paymentService.processPayment(eq(ORDER_NO), anyString())).thenReturn(true);
+
+        // Mock 预约服务
+        AssessmentAppointment mockAppointment = createMockAppointment(1L, "pending");
+        when(appointmentService.confirmAppointmentByOrder(any(AssessmentOrder.class))).thenReturn(mockAppointment);
+
+        // === When:家长使用微信支付 ===
+        Map<String, Object> payRequest = new HashMap<>();
+        payRequest.put("orderNo", ORDER_NO);
+        payRequest.put("payType", "wechat");
+        Result<Map<String, Object>> payResult = orderController.payOrder(PARENT_USER_ID, payRequest);
+
+        // === Then:Milestone-2 - 支付成功,预约自动创建 ===
+        assertEquals(200, payResult.getCode());
+        assertEquals("paid", payResult.getData().get("status"));
+        assertNotNull(payResult.getData().get("payTime"));
+        verify(paymentService).processPayment(eq(ORDER_NO), eq("wechat"));
+        verify(appointmentService).confirmAppointmentByOrder(any(AssessmentOrder.class));
+    }
+
+    // ========== 场景2:自动分配规划师流程 ==========
+
+    /**
+     * 场景:家长未指定规划师,系统自动分配家庭绑定的规划师
+     * 角色:家长
+     * 触发条件:家长未指定 guideId,但家庭已绑定规划师
+     */
+    @Test
+    @DisplayName("[场景2] 未指定规划师时系统自动分配 - 期望使用家庭绑定的规划师")
+    void parentCreatesOrderWithoutGuide_AutoAssignFromFamily() {
+        // === Given:家庭已绑定规划师 ===
+        Family family = new Family();
+        family.setId(FAMILY_ID);
+        family.setTeacherId(GUIDE_ID);
+        when(familyMapper.selectById(FAMILY_ID)).thenReturn(family);
+
+        Map<String, Object> createOrderRequest = new HashMap<>();
+        createOrderRequest.put("familyId", FAMILY_ID);
+        createOrderRequest.put("childId", CHILD_ID);
+        // guideId 未指定
+        createOrderRequest.put("packageId", PACKAGE_ID);
+        createOrderRequest.put("guideName", "自动分配");
+        createOrderRequest.put("packageName", PACKAGE_NAME);
+        createOrderRequest.put("totalPrice", 98000L);
+        createOrderRequest.put("discountAmount", 0L);
+
+        AssessmentOrder mockOrder = createMockOrder(ORDER_NO, "pending", 98000L);
+        when(orderService.createOrder(
+                eq(FAMILY_ID), eq(PARENT_USER_ID), eq(CHILD_ID), eq(GUIDE_ID), eq(PACKAGE_ID),
+                anyString(), anyString(), eq(98000L), eq(0L)
+        )).thenReturn(mockOrder);
+
+        // === When:家长创建订单(不指定规划师)===
+        Result<Map<String, Object>> createResult = orderController.createOrder(PARENT_USER_ID, createOrderRequest);
+
+        // === Then:系统自动使用家庭绑定的规划师 ===
+        assertEquals(200, createResult.getCode());
+        assertEquals(ORDER_NO, createResult.getData().get("orderNo"));
+        verify(familyMapper).selectById(FAMILY_ID);
+        verify(orderService).createOrder(
+                eq(FAMILY_ID), eq(PARENT_USER_ID), eq(CHILD_ID), eq(GUIDE_ID), eq(PACKAGE_ID),
+                anyString(), anyString(), eq(98000L), eq(0L)
+        );
+    }
+
+    // ========== 场景3:规划师录入测评结果 ==========
+
+    /**
+     * 场景:规划师完成测评后,录入孩子的认知评估结果
+     * 角色:成长规划师
+     * 触发条件:预约已完成,规划师完成了一对一测评
+     */
+    @Test
+    @DisplayName("[场景3] 规划师录入测评结果 - 期望结果保存并可查询")
+    void guideRecordsAssessmentResult_Success() {
+        // === Given:存在已确认的预约 ===
+        Long appointmentId = 1L;
+        when(assessmentService.getByAppointmentId(appointmentId)).thenReturn(null);
+
+        DanAssessmentResult inputResult = new DanAssessmentResult();
+        inputResult.setChildId(CHILD_ID);
+        inputResult.setChildName("王小明");
+        inputResult.setFamilyId(FAMILY_ID);
+        inputResult.setAppointmentId(appointmentId);
+        inputResult.setOverallScore(85);
+        inputResult.setAttentionScore(88);
+        inputResult.setFocusScore(82);
+        inputResult.setMemoryScore(90);
+        inputResult.setLogicScore(78);
+        inputResult.setPerceptionScore(85);
+        inputResult.setSpatialScore(80);
+        inputResult.setProcessingSpeedScore(83);
+        inputResult.setAnalysisReport("孩子整体认知发展良好...");
+        inputResult.setGrowthSuggestions("建议加强逻辑思维训练...");
+        inputResult.setStatus("completed");
+
+        when(assessmentService.recordResult(any(DanAssessmentResult.class))).thenAnswer(invocation -> {
+            DanAssessmentResult result = invocation.getArgument(0);
+            result.setId(1L);
+            return result;
+        });
+
+        // === When:规划师提交测评结果 ===
+        Result<DanAssessmentResult> recordResult = danAssessmentController.recordResult(inputResult);
+
+        // === Then:Milestone-4 - 测评结果保存成功 ===
+        assertEquals(200, recordResult.getCode());
+        assertNotNull(recordResult.getData());
+        assertNotNull(recordResult.getData().getId());
+        assertEquals("completed", recordResult.getData().getStatus());
+        assertEquals(85, recordResult.getData().getOverallScore());
+        verify(assessmentService).recordResult(any(DanAssessmentResult.class));
+    }
+
+    // ========== 场景4:家长查询孩子测评报告 ==========
+
+    /**
+     * 场景:家长为孩子购买测评后,查看孩子的最新认知评估报告
+     * 角色:家长
+     * 触发条件:规划师已完成测评并录入结果
+     */
+    @Test
+    @DisplayName("[场景4] 家长查询孩子最新报告 - 期望返回完整报告内容")
+    void parentQueriesChildLatestReport_Success() {
+        // === Given:存在孩子的最新测评结果 ===
+        DanAssessmentResult latestResult = new DanAssessmentResult();
+        latestResult.setId(1L);
+        latestResult.setChildId(CHILD_ID);
+        latestResult.setChildName("王小明");
+        latestResult.setOverallScore(85);
+        latestResult.setAttentionScore(88);
+        latestResult.setFocusScore(82);
+        latestResult.setMemoryScore(90);
+        latestResult.setLogicScore(78);
+        latestResult.setPerceptionScore(85);
+        latestResult.setSpatialScore(80);
+        latestResult.setProcessingSpeedScore(83);
+        latestResult.setAnalysisReport("孩子整体认知发展良好...");
+        latestResult.setGrowthSuggestions("建议加强逻辑思维训练...");
+        latestResult.setAssessmentDate(new Date());
+
+        when(assessmentService.getChildLatestResult(CHILD_ID)).thenReturn(latestResult);
+
+        // === When:家长请求查看孩子最新测评报告 ===
+        Map<String, Object> request = new HashMap<>();
+        request.put("childId", CHILD_ID);
+        Result<?> result = danAssessmentController.getLatestResult(request);
+
+        // === Then:Milestone-5 - 返回完整报告内容 ===
+        assertEquals(200, result.getCode());
+        assertNotNull(result.getData());
+        verify(assessmentService).getChildLatestResult(CHILD_ID);
+    }
+
+    // ========== 场景5:取消未支付订单 ==========
+
+    /**
+     * 场景:家长创建订单后决定取消,选择关闭交易
+     * 角色:家长
+     * 触发条件:订单状态为"待支付"
+     */
+    @Test
+    @DisplayName("[场景5] 家长取消未支付订单 - 期望订单关闭成功")
+    void parentCancelsPendingOrder_Success() {
+        // === Given:存在待支付的订单 ===
+        AssessmentOrder pendingOrder = createMockOrder(ORDER_NO, "pending", 198000L);
+        pendingOrder.setUserId(PARENT_USER_ID);
+        when(orderService.getByOrderNo(ORDER_NO)).thenReturn(pendingOrder);
+        when(orderService.cancelOrder(ORDER_NO)).thenReturn(true);
+
+        // === When:家长取消订单 ===
+        Map<String, Object> cancelRequest = new HashMap<>();
+        cancelRequest.put("orderNo", ORDER_NO);
+        Result<Boolean> cancelResult = orderController.cancelOrder(PARENT_USER_ID, cancelRequest);
+
+        // === Then:订单成功取消 ===
+        assertEquals(200, cancelResult.getCode());
+        assertTrue(cancelResult.getData());
+        verify(orderService).cancelOrder(ORDER_NO);
+    }
+
+    // ========== 场景6:已支付订单退款 ==========
+
+    /**
+     * 场景:家长支付后因故需要退款,由规划师确认后完成退款
+     * 角色:家长(发起退款)→ 系统处理
+     * 触发条件:订单状态为"已支付"
+     */
+    @Test
+    @DisplayName("[场景6] 已支付订单申请退款 - 期望退款成功")
+    void parentRequestsRefundForPaidOrder_Success() {
+        // === Given:存在已支付的订单 ===
+        AssessmentOrder paidOrder = createMockOrder(ORDER_NO, "paid", 198000L);
+        paidOrder.setUserId(PARENT_USER_ID);
+        when(orderService.getByOrderNo(ORDER_NO)).thenReturn(paidOrder);
+        when(orderService.refundOrder(ORDER_NO)).thenReturn(true);
+
+        // === When:家长申请退款 ===
+        Map<String, Object> refundRequest = new HashMap<>();
+        refundRequest.put("orderNo", ORDER_NO);
+        Result<Boolean> refundResult = orderController.refundOrder(PARENT_USER_ID, refundRequest);
+
+        // === Then:退款成功 ===
+        assertEquals(200, refundResult.getCode());
+        assertTrue(refundResult.getData());
+        verify(orderService).refundOrder(ORDER_NO);
+    }
+
+    // ========== 场景7:无权查看他人订单 ==========
+
+    /**
+     * 场景:家长尝试查看其他家庭的订单详情
+     * 角色:家长
+     * 触发条件:订单不属于当前用户
+     */
+    @Test
+    @DisplayName("[场景7] 查看非本人订单 - 期望拒绝访问")
+    void parentViewsOtherFamilyOrder_Forbidden() {
+        // === Given:存在其他用户的订单 ===
+        AssessmentOrder otherOrder = createMockOrder(ORDER_NO, "paid", 198000L);
+        otherOrder.setUserId(9999L); // 不同用户
+        when(orderService.getByOrderNo(ORDER_NO)).thenReturn(otherOrder);
+
+        // === When:家长尝试查看他人订单 ===
+        Map<String, Object> detailRequest = new HashMap<>();
+        detailRequest.put("orderNo", ORDER_NO);
+        Result<AssessmentOrder> detailResult = orderController.getOrderDetail(PARENT_USER_ID, detailRequest);
+
+        // === Then:拒绝访问 ===
+        assertEquals(500, detailResult.getCode());
+        assertEquals("无权查看", detailResult.getMessage());
+    }
+
+    // ========== Helper Methods ==========
+
+    private AssessmentOrder createMockOrder(String orderNo, String status, Long actualPrice) {
+        AssessmentOrder order = new AssessmentOrder();
+        order.setId(1L);
+        order.setOrderNo(orderNo);
+        order.setFamilyId(FAMILY_ID);
+        order.setUserId(PARENT_USER_ID);
+        order.setChildId(CHILD_ID);
+        order.setGuideId(GUIDE_ID);
+        order.setPackageId(PACKAGE_ID);
+        order.setGuideName(GUIDE_NAME);
+        order.setPackageName(PACKAGE_NAME);
+        order.setActualPrice(actualPrice);
+        order.setStatus(status);
+        return order;
+    }
+
+    private AssessmentAppointment createMockAppointment(Long id, String status) {
+        AssessmentAppointment appointment = new AssessmentAppointment();
+        appointment.setId(id);
+        appointment.setChildId(CHILD_ID);
+        appointment.setGuideId(GUIDE_ID);
+        appointment.setStatus(0); // 0: pending
+        appointment.setAppointmentDate("2026-07-15");
+        appointment.setAppointmentTime("14:00");
+        return appointment;
+    }
+}