liujie
2026-09-09 39f6fca28e3f78abe48f08e474ec011abb990b36
backend/src/test/java/com/tourguide/modules/order/service/TourOrderServiceTest.java
@@ -1,45 +1,60 @@
package com.tourguide.modules.order.service;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tourguide.common.exception.BusinessException;
import com.tourguide.modules.order.domain.OrderStatus;
import com.tourguide.modules.order.domain.RefundStatus;
import com.tourguide.modules.order.dto.TourOrderResponse;
import com.tourguide.modules.order.entity.TourOrder;
import com.tourguide.modules.order.mapper.TourOrderMapper;
import com.tourguide.modules.review.mapper.OrderReviewMapper;
import com.tourguide.modules.review.entity.OrderReview;
import com.tourguide.modules.system.service.GuideNotificationService;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.ArgumentMatchers.any;
class TourOrderServiceTest {
    private TourOrderMapper orderMapper;
    private TourOrderService orderService;
    private OrderRefundService refundService;
    private GuideNotificationService notificationService;
    private static boolean tableInfoInitialized;
    private static boolean reviewTableInfoInitialized;
    @BeforeEach
    void setUp() {
        orderMapper = mock(TourOrderMapper.class);
        refundService = mock(OrderRefundService.class);
        notificationService = mock(GuideNotificationService.class);
        orderService = new TourOrderService(orderMapper, mock(OrderReviewMapper.class),
                mock(GuideNotificationService.class));
                notificationService, refundService);
    }
    @Test
    void shouldRefundRequestedAmount() {
        TourOrder order = refundableOrder();
        when(orderMapper.selectForUpdate(1L)).thenReturn(order);
        when(refundService.requestRefund(order, new BigDecimal("100.00"))).thenReturn(true);
        orderService.refund(1L, new BigDecimal("100.00"));
        assertThat(order.getRefundAmount()).isEqualByComparingTo("100.00");
        assertThat(order.getRefundStatus()).isEqualTo(RefundStatus.REFUNDED.getCode());
        assertThat(order.getStatus()).isEqualTo(OrderStatus.REFUNDED.getCode());
        verify(orderMapper).updateById(order);
        verify(refundService).requestRefund(order, new BigDecimal("100.00"));
    }
    @Test
@@ -52,6 +67,105 @@
                .hasMessage("退款金额不能超过当前可退金额");
    }
    @Test
    void shouldAllowFollowUpRefundForPartialOrder() {
        TourOrder order = refundableOrder();
        order.setStatus(OrderStatus.CANCELLED.getCode());
        order.setRefundStatus(RefundStatus.PARTIAL.getCode());
        order.setRefundedAmount(new BigDecimal("100.00"));
        when(orderMapper.selectForUpdate(1L)).thenReturn(order);
        when(refundService.requestRefund(order, new BigDecimal("56.00"))).thenReturn(true);
        TourOrderResponse response = orderService.refund(1L, new BigDecimal("56.00"));
        assertThat(response.getRefundableAmount()).isEqualByComparingTo("56.00");
        verify(refundService).requestRefund(order, new BigDecimal("56.00"));
    }
    @Test
    void shouldRejectFollowUpRefundAboveRemainingAmount() {
        TourOrder order = refundableOrder();
        order.setStatus(OrderStatus.CANCELLED.getCode());
        order.setRefundStatus(RefundStatus.PARTIAL.getCode());
        order.setRefundedAmount(new BigDecimal("100.00"));
        when(orderMapper.selectForUpdate(1L)).thenReturn(order);
        assertThatThrownBy(() -> orderService.refund(1L, new BigDecimal("56.01")))
                .isInstanceOf(BusinessException.class)
                .hasMessage("退款金额不能超过当前可退金额");
    }
    @Test
    void shouldReportProviderFailureInsteadOfSuccess() {
        TourOrder order = refundableOrder();
        when(orderMapper.selectForUpdate(1L)).thenReturn(order);
        when(refundService.requestRefund(order, new BigDecimal("100.00"))).thenReturn(false);
        assertThatThrownBy(() -> orderService.refund(1L, new BigDecimal("100.00")))
                .isInstanceOf(BusinessException.class)
                .hasMessage("支付渠道退款失败,请稍后重试");
    }
    @Test
    void notificationFailureShouldNotFailAcceptedRefund() {
        TourOrder order = refundableOrder();
        order.setStatus(OrderStatus.PENDING_MEETING.getCode());
        order.setGuideId(42L);
        when(orderMapper.selectForUpdate(1L)).thenReturn(order);
        when(refundService.requestRefund(order, new BigDecimal("100.00"))).thenReturn(true);
        doThrow(new IllegalStateException("notification unavailable"))
                .when(notificationService).notifyOrderCancelled(order);
        TourOrderResponse response = orderService.refund(1L, new BigDecimal("100.00"));
        assertThat(response).isNotNull();
        verify(notificationService).notifyOrderCancelled(order);
    }
    @Test
    void shouldFilterOrdersByGuideId() {
        initializeTableInfo();
        when(orderMapper.selectPage(any(Page.class), any())).thenAnswer(invocation -> invocation.getArgument(0));
        orderService.page(1, 10, null, null, 42L, null, null, null, null);
        ArgumentCaptor<LambdaQueryWrapper<TourOrder>> queryCaptor = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
        verify(orderMapper).selectPage(any(Page.class), queryCaptor.capture());
        queryCaptor.getValue().getSqlSegment();
        assertThat(queryCaptor.getValue().getParamNameValuePairs().values()).contains(42L);
    }
    @Test
    void detailShouldOnlyTreatVisibleReviewAsReviewed() {
        initializeTableInfo();
        initializeReviewTableInfo();
        OrderReviewMapper reviewMapper = mock(OrderReviewMapper.class);
        orderService = new TourOrderService(orderMapper, reviewMapper, notificationService, refundService);
        TourOrder order = new TourOrder();
        order.setId(1L);
        when(orderMapper.selectById(1L)).thenReturn(order);
        orderService.detail(1L);
        ArgumentCaptor<LambdaQueryWrapper<OrderReview>> queryCaptor = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
        verify(reviewMapper).selectOne(queryCaptor.capture());
        assertThat(queryCaptor.getValue().getSqlSegment()).contains("status");
    }
    private static void initializeTableInfo() {
        if (!tableInfoInitialized) {
            TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), TourOrder.class);
            tableInfoInitialized = true;
        }
    }
    private static void initializeReviewTableInfo() {
        if (!reviewTableInfoInitialized) {
            TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), OrderReview.class);
            reviewTableInfoInitialized = true;
        }
    }
    private TourOrder refundableOrder() {
        TourOrder order = new TourOrder();
        order.setId(1L);
@@ -60,6 +174,7 @@
        order.setRefundStatus(RefundStatus.NONE.getCode());
        order.setAmount(new BigDecimal("256.10"));
        order.setPaymentAmount(new BigDecimal("156.00"));
        order.setPaymentTime(LocalDateTime.now());
        order.setPaymentMethod("WECHAT");
        return order;
    }