Commit ebeb14be2f57d67636a8daba2052bb5b78dceef9

Authored by YunaiV
1 parent 98128810

mall + pay:

1. 完善 PayRefundServiceTest 的单元测试
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
... ... @@ -19,7 +19,6 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
19 19 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
20 20 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
21 21 import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
22   -import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
23 22 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
24 23 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
25 24 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
... ... @@ -92,7 +91,6 @@ public class PayRefundServiceImpl implements PayRefundService {
92 91 }
93 92  
94 93 @Override
95   - @Transactional(rollbackFor = Exception.class)
96 94 public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
97 95 // 1.1 校验 App
98 96 PayAppDO app = appService.validPayApp(reqDTO.getAppId());
... ... @@ -109,7 +107,7 @@ public class PayRefundServiceImpl implements PayRefundService {
109 107 PayRefundDO refund = refundMapper.selectByAppIdAndMerchantRefundId(
110 108 app.getId(), reqDTO.getMerchantRefundId());
111 109 if (refund != null) {
112   - throw exception(ErrorCodeConstants.REFUND_EXISTS);
  110 + throw exception(REFUND_EXISTS);
113 111 }
114 112  
115 113 // 2.1 插入退款单
... ... @@ -158,7 +156,7 @@ public class PayRefundServiceImpl implements PayRefundService {
158 156 private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO) {
159 157 PayOrderDO order = orderService.getOrder(reqDTO.getAppId(), reqDTO.getMerchantOrderId());
160 158 if (order == null) {
161   - throw exception(ErrorCodeConstants.ORDER_NOT_FOUND);
  159 + throw exception(ORDER_NOT_FOUND);
162 160 }
163 161 // 校验状态,必须是已支付、或者已退款
164 162 if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) {
... ... @@ -167,12 +165,12 @@ public class PayRefundServiceImpl implements PayRefundService {
167 165  
168 166 // 校验金额,退款金额不能大于原定的金额
169 167 if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()){
170   - throw exception(ErrorCodeConstants.REFUND_PRICE_EXCEED);
  168 + throw exception(REFUND_PRICE_EXCEED);
171 169 }
172 170 // 是否有退款中的订单
173 171 if (refundMapper.selectCountByAppIdAndOrderId(reqDTO.getAppId(), order.getId(),
174 172 PayRefundStatusEnum.WAITING.getStatus()) > 0) {
175   - throw exception(ErrorCodeConstants.REFUND_HAS_REFUNDING);
  173 + throw exception(REFUND_HAS_REFUNDING);
176 174 }
177 175 return order;
178 176 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java
1 1 package cn.iocoder.yudao.module.pay.service.refund;
2 2  
  3 +import cn.hutool.extra.spring.SpringUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
5 6 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
  7 +import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
  8 +import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
6 9 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
7 10 import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
8   -import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
9   -import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
  11 +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
  12 +import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
10 13 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
11 14 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
12 15 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
... ... @@ -21,12 +24,13 @@ import cn.iocoder.yudao.module.pay.service.app.PayAppService;
21 24 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
22 25 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
23 26 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
  27 +import org.junit.jupiter.api.BeforeEach;
24 28 import org.junit.jupiter.api.Test;
  29 +import org.mockito.MockedStatic;
25 30 import org.springframework.boot.test.mock.mockito.MockBean;
26 31 import org.springframework.context.annotation.Import;
27 32  
28 33 import javax.annotation.Resource;
29   -import java.time.LocalDateTime;
30 34 import java.util.List;
31 35  
32 36 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
... ... @@ -37,10 +41,12 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
37 41 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
38 42 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
39 43 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
  44 +import static org.assertj.core.api.Assertions.assertThat;
40 45 import static org.junit.jupiter.api.Assertions.assertEquals;
  46 +import static org.junit.jupiter.api.Assertions.assertNotNull;
  47 +import static org.mockito.ArgumentMatchers.any;
41 48 import static org.mockito.ArgumentMatchers.eq;
42   -import static org.mockito.Mockito.mock;
43   -import static org.mockito.Mockito.when;
  49 +import static org.mockito.Mockito.*;
44 50  
45 51 /**
46 52 * {@link PayRefundServiceImpl} 的单元测试类
... ... @@ -69,6 +75,41 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
69 75 @MockBean
70 76 private PayNotifyService notifyService;
71 77  
  78 + @BeforeEach
  79 + public void setUp() {
  80 + when(payProperties.getRefundNotifyUrl()).thenReturn("http://127.0.0.1");
  81 + }
  82 +
  83 + @Test
  84 + public void testGetRefund() {
  85 + // mock 数据
  86 + PayRefundDO refund = randomPojo(PayRefundDO.class);
  87 + refundMapper.insert(refund);
  88 + // 准备参数
  89 + Long id = refund.getId();
  90 +
  91 + // 调用
  92 + PayRefundDO dbRefund = refundService.getRefund(id);
  93 + // 断言
  94 + assertPojoEquals(dbRefund, refund);
  95 + }
  96 +
  97 + @Test
  98 + public void testGetRefundCountByAppId() {
  99 + // mock 数据
  100 + PayRefundDO refund01 = randomPojo(PayRefundDO.class);
  101 + refundMapper.insert(refund01);
  102 + PayRefundDO refund02 = randomPojo(PayRefundDO.class);
  103 + refundMapper.insert(refund02);
  104 + // 准备参数
  105 + Long appId = refund01.getAppId();
  106 +
  107 + // 调用
  108 + Long count = refundService.getRefundCountByAppId(appId);
  109 + // 断言
  110 + assertEquals(count, 1);
  111 + }
  112 +
72 113 @Test
73 114 public void testGetRefundPage() {
74 115 // mock 数据
... ... @@ -276,7 +317,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
276 317 // 准备参数
277 318 PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
278 319 o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
279   - .setReason("测试退款"));
  320 + .setMerchantRefundId("200").setReason("测试退款"));
280 321 // mock 方法(app)
281 322 PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
282 323 when(appService.validPayApp(eq(1L))).thenReturn(app);
... ... @@ -295,7 +336,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
295 336 when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
296 337 // mock 数据(refund 已存在)
297 338 PayRefundDO refund = randomPojo(PayRefundDO.class, o ->
298   - o.setOrderId(order.getId()).setStatus(PayOrderStatusEnum.WAITING.getStatus()));
  339 + o.setAppId(1L).setMerchantRefundId("200"));
299 340 refundMapper.insert(refund);
300 341  
301 342 // 调用,并断言异常
... ... @@ -305,12 +346,97 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
305 346  
306 347 @Test
307 348 public void testCreateRefund_invokeException() {
  349 + // 准备参数
  350 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
  351 + o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
  352 + .setMerchantRefundId("200").setReason("测试退款"));
  353 + // mock 方法(app)
  354 + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
  355 + when(appService.validPayApp(eq(1L))).thenReturn(app);
  356 + // mock 数据(order)
  357 + PayOrderDO order = randomPojo(PayOrderDO.class, o ->
  358 + o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
  359 + .setPrice(10).setRefundPrice(1)
  360 + .setChannelId(10L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
  361 + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
  362 + // mock 方法(channel)
  363 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
  364 + .setCode(PayChannelEnum.ALIPAY_APP.getCode()));
  365 + when(channelService.validPayChannel(eq(10L))).thenReturn(channel);
  366 + // mock 方法(client)
  367 + PayClient client = mock(PayClient.class);
  368 + when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
  369 + // mock 方法(client 调用发生异常)
  370 + when(client.unifiedRefund(any(PayRefundUnifiedReqDTO.class))).thenThrow(new RuntimeException());
308 371  
  372 + // 调用
  373 + Long refundId = refundService.createPayRefund(reqDTO);
  374 + // 断言
  375 + PayRefundDO refundDO = refundMapper.selectById(refundId);
  376 + assertPojoEquals(reqDTO, refundDO);
  377 + assertNotNull(refundDO.getNo());
  378 + assertThat(refundDO)
  379 + .extracting("orderId", "orderNo", "channelId", "channelCode",
  380 + "notifyUrl", "channelOrderNo", "status", "payPrice", "refundPrice")
  381 + .containsExactly(order.getId(), order.getNo(), channel.getId(), channel.getCode(),
  382 + app.getRefundNotifyUrl(), order.getChannelOrderNo(), PayRefundStatusEnum.WAITING.getStatus(),
  383 + order.getPrice(), reqDTO.getPrice());
309 384 }
310 385  
311 386 @Test
312 387 public void testCreateRefund_invokeSuccess() {
  388 + PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class);
  389 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  390 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class)))
  391 + .thenReturn(payRefundServiceImpl);
  392 +
  393 + // 准备参数
  394 + PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
  395 + o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
  396 + .setMerchantRefundId("200").setReason("测试退款"));
  397 + // mock 方法(app)
  398 + PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
  399 + when(appService.validPayApp(eq(1L))).thenReturn(app);
  400 + // mock 数据(order)
  401 + PayOrderDO order = randomPojo(PayOrderDO.class, o ->
  402 + o.setStatus(PayOrderStatusEnum.REFUND.getStatus())
  403 + .setPrice(10).setRefundPrice(1)
  404 + .setChannelId(10L).setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()));
  405 + when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
  406 + // mock 方法(channel)
  407 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)
  408 + .setCode(PayChannelEnum.ALIPAY_APP.getCode()));
  409 + when(channelService.validPayChannel(eq(10L))).thenReturn(channel);
  410 + // mock 方法(client)
  411 + PayClient client = mock(PayClient.class);
  412 + when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
  413 + // mock 方法(client 成功)
  414 + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class);
  415 + when(client.unifiedRefund(argThat(unifiedReqDTO -> {
  416 + assertNotNull(unifiedReqDTO.getOutRefundNo());
  417 + assertThat(unifiedReqDTO)
  418 + .extracting("payPrice", "refundPrice", "outTradeNo",
  419 + "notifyUrl", "reason")
  420 + .containsExactly(order.getPrice(), reqDTO.getPrice(), order.getNo(),
  421 + "http://127.0.0.1/10", reqDTO.getReason());
  422 + return true;
  423 + }))).thenReturn(refundRespDTO);
313 424  
  425 + // 调用
  426 + Long refundId = refundService.createPayRefund(reqDTO);
  427 + // 断言
  428 + PayRefundDO refundDO = refundMapper.selectById(refundId);
  429 + assertPojoEquals(reqDTO, refundDO);
  430 + assertNotNull(refundDO.getNo());
  431 + assertThat(refundDO)
  432 + .extracting("orderId", "orderNo", "channelId", "channelCode",
  433 + "notifyUrl", "channelOrderNo", "status", "payPrice", "refundPrice")
  434 + .containsExactly(order.getId(), order.getNo(), channel.getId(), channel.getCode(),
  435 + app.getRefundNotifyUrl(), order.getChannelOrderNo(), PayRefundStatusEnum.WAITING.getStatus(),
  436 + order.getPrice(), reqDTO.getPrice());
  437 + // 断言调用
  438 + verify(payRefundServiceImpl).notifyRefund(same(channel), same(refundRespDTO));
  439 + }
314 440 }
315 441  
316 442 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/test/resources/sql/create_tables.sql
... ... @@ -87,6 +87,7 @@ CREATE TABLE IF NOT EXISTS `pay_refund` (
87 87 `channel_id` bigint(20) NOT NULL,
88 88 `channel_code` varchar(32) NOT NULL,
89 89 `order_id` bigint(20) NOT NULL,
  90 + `order_no` varchar(64) NOT NULL,
90 91 `merchant_order_id` varchar(64) NOT NULL,
91 92 `merchant_refund_id` varchar(64) NOT NULL,
92 93 `notify_url` varchar(1024) NOT NULL,
... ...