Jelajahi Sumber

mall + pay:
1. 完善 PayRefundServiceTest 的单元测试

YunaiV 1 tahun lalu
induk
melakukan
6aea461e8d

+ 2 - 3
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java

@@ -212,7 +212,7 @@ public class PayRefundServiceImpl implements PayRefundService {
         }
     }
 
-    public void notifyRefundSuccess(PayChannelDO channel, PayRefundRespDTO notify) {
+    private void notifyRefundSuccess(PayChannelDO channel, PayRefundRespDTO notify) {
         // 1.1 查询 PayRefundDO
         PayRefundDO refund = refundMapper.selectByAppIdAndNo(
                 channel.getAppId(), notify.getOutRefundNo());
@@ -246,8 +246,7 @@ public class PayRefundServiceImpl implements PayRefundService {
                 .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build());
     }
 
-    @Transactional(rollbackFor = Exception.class)
-    public void notifyRefundFailure(PayChannelDO channel, PayRefundRespDTO notify) {
+    private void notifyRefundFailure(PayChannelDO channel, PayRefundRespDTO notify) {
         // 1.1 查询 PayRefundDO
         PayRefundDO refund = refundMapper.selectByAppIdAndNo(
                 channel.getAppId(), notify.getOutRefundNo());

+ 264 - 2
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java

@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
+import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
@@ -17,12 +18,14 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
 import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
+import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
 import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
+import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -35,6 +38,7 @@ import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
@@ -42,8 +46,7 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.*;
@@ -439,4 +442,263 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
         }
     }
 
+    @Test
+    public void testNotifyRefund() {
+        PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class);
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class)))
+                    .thenReturn(payRefundServiceImpl);
+
+            // 准备参数
+            Long channelId = 10L;
+            PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class);
+            // mock 方法(channel)
+            PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
+            when(channelService.validPayChannel(eq(10L))).thenReturn(channel);
+
+            // 调用
+            refundService.notifyRefund(channelId, refundRespDTO);
+            // 断言
+            verify(payRefundServiceImpl).notifyRefund(same(channel), same(refundRespDTO));
+        }
+    }
+
+    @Test
+    public void testNotifyRefundSuccess_notFound() {
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
+        PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
+                o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100"));
+
+        // 调用,并断言异常
+        assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO),
+                REFUND_NOT_FOUND);
+    }
+
+    @Test
+    public void testNotifyRefundSuccess_isSuccess() {
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
+        PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
+                o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100"));
+        // mock 数据(refund + 已支付)
+        PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
+                .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()));
+        refundMapper.insert(refund);
+
+        // 调用
+        refundService.notifyRefund(channel, refundRespDTO);
+        // 断言,refund 没有更新,因为已经退款成功
+        assertPojoEquals(refund, refundMapper.selectById(refund.getId()));
+    }
+
+    @Test
+    public void testNotifyRefundSuccess_failure() {
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
+        PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
+                o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100"));
+        // mock 数据(refund + 已支付)
+        PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
+                .setStatus(PayRefundStatusEnum.FAILURE.getStatus()));
+        refundMapper.insert(refund);
+
+        // 调用,并断言异常
+        assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO),
+                REFUND_STATUS_IS_NOT_WAITING);
+    }
+
+    @Test
+    public void testNotifyRefundSuccess_updateOrderException() {
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
+        PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
+                o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100"));
+        // mock 数据(refund + 已支付)
+        PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
+                .setStatus(PayRefundStatusEnum.WAITING.getStatus())
+                .setOrderId(100L).setRefundPrice(23));
+        refundMapper.insert(refund);
+        // mock 方法(order + 更新异常)
+        doThrow(new RuntimeException()).when(orderService)
+                .updateOrderRefundPrice(eq(100L), eq(23));
+
+        // 调用,并断言异常
+        assertThrows(RuntimeException.class, () -> refundService.notifyRefund(channel, refundRespDTO));
+        // 断言,refund 没有更新,因为事务回滚了
+        assertPojoEquals(refund, refundMapper.selectById(refund.getId()));
+    }
+
+    @Test
+    public void testNotifyRefundSuccess_success() {
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
+        PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
+                o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100"));
+        // mock 数据(refund + 已支付)
+        PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
+                .setStatus(PayRefundStatusEnum.WAITING.getStatus())
+                .setOrderId(100L).setRefundPrice(23));
+        refundMapper.insert(refund);
+
+        // 调用
+        refundService.notifyRefund(channel, refundRespDTO);
+        // 断言,refund
+        refund.setSuccessTime(refundRespDTO.getSuccessTime())
+                .setChannelRefundNo(refundRespDTO.getChannelRefundNo())
+                .setStatus(PayRefundStatusEnum.SUCCESS.getStatus())
+                .setChannelNotifyData(toJsonString(refundRespDTO));
+        assertPojoEquals(refund, refundMapper.selectById(refund.getId()),
+                "updateTime", "updater");
+        // 断言,调用
+        verify(orderService).updateOrderRefundPrice(eq(100L), eq(23));
+        verify(notifyService).createPayNotifyTask(eq(PayNotifyTaskCreateReqDTO.builder()
+                .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build()));
+    }
+
+    @Test
+    public void testNotifyRefundFailure_notFound() {
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
+        PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
+                o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100"));
+
+        // 调用,并断言异常
+        assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO),
+                REFUND_NOT_FOUND);
+    }
+
+    @Test
+    public void testNotifyRefundFailure_isFailure() {
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
+        PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
+                o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100"));
+        // mock 数据(refund + 退款失败)
+        PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
+                .setStatus(PayRefundStatusEnum.FAILURE.getStatus()));
+        refundMapper.insert(refund);
+
+        // 调用
+        refundService.notifyRefund(channel, refundRespDTO);
+        // 断言,refund 没有更新,因为已经退款失败
+        assertPojoEquals(refund, refundMapper.selectById(refund.getId()));
+    }
+
+    @Test
+    public void testNotifyRefundFailure_isSuccess() {
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
+        PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
+                o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100"));
+        // mock 数据(refund + 已支付)
+        PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
+                .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()));
+        refundMapper.insert(refund);
+
+        // 调用,并断言异常
+        assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO),
+                REFUND_STATUS_IS_NOT_WAITING);
+    }
+
+    @Test
+    public void testNotifyRefundFailure_success() {
+        // 准备参数
+        PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
+        PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
+                o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100"));
+        // mock 数据(refund + 已支付)
+        PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
+                .setStatus(PayRefundStatusEnum.WAITING.getStatus())
+                .setOrderId(100L).setRefundPrice(23));
+        refundMapper.insert(refund);
+
+        // 调用
+        refundService.notifyRefund(channel, refundRespDTO);
+        // 断言,refund
+        refund.setChannelRefundNo(refundRespDTO.getChannelRefundNo())
+                .setStatus(PayRefundStatusEnum.FAILURE.getStatus())
+                .setChannelNotifyData(toJsonString(refundRespDTO))
+                .setChannelErrorCode(refundRespDTO.getChannelErrorCode())
+                .setChannelErrorMsg(refundRespDTO.getChannelErrorMsg());
+        assertPojoEquals(refund, refundMapper.selectById(refund.getId()),
+                "updateTime", "updater");
+        // 断言,调用
+        verify(notifyService).createPayNotifyTask(eq(PayNotifyTaskCreateReqDTO.builder()
+                .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build()));
+    }
+
+    @Test
+    public void testSyncRefund_notFound() {
+        // 准备参数
+        PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L)
+                .setStatus(PayRefundStatusEnum.WAITING.getStatus()));
+        refundMapper.insert(refund);
+
+        // 调用
+        int count = refundService.syncRefund();
+        // 断言
+        assertEquals(count, 0);
+    }
+
+    @Test
+    public void testSyncRefund_waiting() {
+        assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusRespEnum.WAITING.getStatus()), 0);
+    }
+
+    @Test
+    public void testSyncRefund_success() {
+        assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusRespEnum.SUCCESS.getStatus()), 1);
+    }
+
+    @Test
+    public void testSyncRefund_failure() {
+        assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusRespEnum.FAILURE.getStatus()), 1);
+    }
+
+    private int testSyncRefund_waitingOrSuccessOrFailure(Integer status) {
+        PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class);
+        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
+            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class)))
+                    .thenReturn(payRefundServiceImpl);
+
+            // 准备参数
+            PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setChannelId(10L)
+                    .setStatus(PayRefundStatusEnum.WAITING.getStatus())
+                    .setOrderNo("P110").setNo("R220"));
+            refundMapper.insert(refund);
+            // mock 方法(client)
+            PayClient client = mock(PayClient.class);
+            when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
+            // mock 方法(client 返回指定状态)
+            PayRefundRespDTO respDTO = randomPojo(PayRefundRespDTO.class, o -> o.setStatus(status));
+            when(client.getRefund(eq("P110"), eq("R220"))).thenReturn(respDTO);
+            // mock 方法(channel)
+            PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
+            when(channelService.validPayChannel(eq(10L))).thenReturn(channel);
+
+            // 调用
+            return refundService.syncRefund();
+        }
+    }
+
+    @Test
+    public void testSyncRefund_exception() {
+        // 准备参数
+        PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setChannelId(10L)
+                .setStatus(PayRefundStatusEnum.WAITING.getStatus())
+                .setOrderNo("P110").setNo("R220"));
+        refundMapper.insert(refund);
+        // mock 方法(client)
+        PayClient client = mock(PayClient.class);
+        when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
+        // mock 方法(client 抛出异常)
+        when(client.getRefund(eq("P110"), eq("R220"))).thenThrow(new RuntimeException());
+
+        // 调用
+        int count = refundService.syncRefund();
+        // 断言
+        assertEquals(count, 0);
+    }
+
 }