Commit 6aea461e8d99f2cfaa00255a3e127faec10e7e2d

Authored by YunaiV
1 parent ebeb14be

mall + pay:

1. 完善 PayRefundServiceTest 的单元测试
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 {
212 212 }
213 213 }
214 214  
215   - public void notifyRefundSuccess(PayChannelDO channel, PayRefundRespDTO notify) {
  215 + private void notifyRefundSuccess(PayChannelDO channel, PayRefundRespDTO notify) {
216 216 // 1.1 查询 PayRefundDO
217 217 PayRefundDO refund = refundMapper.selectByAppIdAndNo(
218 218 channel.getAppId(), notify.getOutRefundNo());
... ... @@ -246,8 +246,7 @@ public class PayRefundServiceImpl implements PayRefundService {
246 246 .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build());
247 247 }
248 248  
249   - @Transactional(rollbackFor = Exception.class)
250   - public void notifyRefundFailure(PayChannelDO channel, PayRefundRespDTO notify) {
  249 + private void notifyRefundFailure(PayChannelDO channel, PayRefundRespDTO notify) {
251 250 // 1.1 查询 PayRefundDO
252 251 PayRefundDO refund = refundMapper.selectByAppIdAndNo(
253 252 channel.getAppId(), notify.getOutRefundNo());
... ...
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;
7 7 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
8 8 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
9 9 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
  10 +import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
10 11 import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
11 12 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
12 13 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;
17 18 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
18 19 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
19 20 import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
  21 +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
20 22 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
21 23 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
22 24 import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
23 25 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
24 26 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
25 27 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
  28 +import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
26 29 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
27 30 import org.junit.jupiter.api.BeforeEach;
28 31 import org.junit.jupiter.api.Test;
... ... @@ -35,6 +38,7 @@ import java.util.List;
35 38  
36 39 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
37 40 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
  41 +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
38 42 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
39 43 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
40 44 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;
42 46 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
43 47 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
44 48 import static org.assertj.core.api.Assertions.assertThat;
45   -import static org.junit.jupiter.api.Assertions.assertEquals;
46   -import static org.junit.jupiter.api.Assertions.assertNotNull;
  49 +import static org.junit.jupiter.api.Assertions.*;
47 50 import static org.mockito.ArgumentMatchers.any;
48 51 import static org.mockito.ArgumentMatchers.eq;
49 52 import static org.mockito.Mockito.*;
... ... @@ -439,4 +442,263 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
439 442 }
440 443 }
441 444  
  445 + @Test
  446 + public void testNotifyRefund() {
  447 + PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class);
  448 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  449 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class)))
  450 + .thenReturn(payRefundServiceImpl);
  451 +
  452 + // 准备参数
  453 + Long channelId = 10L;
  454 + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class);
  455 + // mock 方法(channel)
  456 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
  457 + when(channelService.validPayChannel(eq(10L))).thenReturn(channel);
  458 +
  459 + // 调用
  460 + refundService.notifyRefund(channelId, refundRespDTO);
  461 + // 断言
  462 + verify(payRefundServiceImpl).notifyRefund(same(channel), same(refundRespDTO));
  463 + }
  464 + }
  465 +
  466 + @Test
  467 + public void testNotifyRefundSuccess_notFound() {
  468 + // 准备参数
  469 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
  470 + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
  471 + o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100"));
  472 +
  473 + // 调用,并断言异常
  474 + assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO),
  475 + REFUND_NOT_FOUND);
  476 + }
  477 +
  478 + @Test
  479 + public void testNotifyRefundSuccess_isSuccess() {
  480 + // 准备参数
  481 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
  482 + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
  483 + o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100"));
  484 + // mock 数据(refund + 已支付)
  485 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
  486 + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()));
  487 + refundMapper.insert(refund);
  488 +
  489 + // 调用
  490 + refundService.notifyRefund(channel, refundRespDTO);
  491 + // 断言,refund 没有更新,因为已经退款成功
  492 + assertPojoEquals(refund, refundMapper.selectById(refund.getId()));
  493 + }
  494 +
  495 + @Test
  496 + public void testNotifyRefundSuccess_failure() {
  497 + // 准备参数
  498 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
  499 + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
  500 + o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100"));
  501 + // mock 数据(refund + 已支付)
  502 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
  503 + .setStatus(PayRefundStatusEnum.FAILURE.getStatus()));
  504 + refundMapper.insert(refund);
  505 +
  506 + // 调用,并断言异常
  507 + assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO),
  508 + REFUND_STATUS_IS_NOT_WAITING);
  509 + }
  510 +
  511 + @Test
  512 + public void testNotifyRefundSuccess_updateOrderException() {
  513 + // 准备参数
  514 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
  515 + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
  516 + o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100"));
  517 + // mock 数据(refund + 已支付)
  518 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
  519 + .setStatus(PayRefundStatusEnum.WAITING.getStatus())
  520 + .setOrderId(100L).setRefundPrice(23));
  521 + refundMapper.insert(refund);
  522 + // mock 方法(order + 更新异常)
  523 + doThrow(new RuntimeException()).when(orderService)
  524 + .updateOrderRefundPrice(eq(100L), eq(23));
  525 +
  526 + // 调用,并断言异常
  527 + assertThrows(RuntimeException.class, () -> refundService.notifyRefund(channel, refundRespDTO));
  528 + // 断言,refund 没有更新,因为事务回滚了
  529 + assertPojoEquals(refund, refundMapper.selectById(refund.getId()));
  530 + }
  531 +
  532 + @Test
  533 + public void testNotifyRefundSuccess_success() {
  534 + // 准备参数
  535 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
  536 + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
  537 + o -> o.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus()).setOutRefundNo("R100"));
  538 + // mock 数据(refund + 已支付)
  539 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
  540 + .setStatus(PayRefundStatusEnum.WAITING.getStatus())
  541 + .setOrderId(100L).setRefundPrice(23));
  542 + refundMapper.insert(refund);
  543 +
  544 + // 调用
  545 + refundService.notifyRefund(channel, refundRespDTO);
  546 + // 断言,refund
  547 + refund.setSuccessTime(refundRespDTO.getSuccessTime())
  548 + .setChannelRefundNo(refundRespDTO.getChannelRefundNo())
  549 + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus())
  550 + .setChannelNotifyData(toJsonString(refundRespDTO));
  551 + assertPojoEquals(refund, refundMapper.selectById(refund.getId()),
  552 + "updateTime", "updater");
  553 + // 断言,调用
  554 + verify(orderService).updateOrderRefundPrice(eq(100L), eq(23));
  555 + verify(notifyService).createPayNotifyTask(eq(PayNotifyTaskCreateReqDTO.builder()
  556 + .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build()));
  557 + }
  558 +
  559 + @Test
  560 + public void testNotifyRefundFailure_notFound() {
  561 + // 准备参数
  562 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
  563 + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
  564 + o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100"));
  565 +
  566 + // 调用,并断言异常
  567 + assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO),
  568 + REFUND_NOT_FOUND);
  569 + }
  570 +
  571 + @Test
  572 + public void testNotifyRefundFailure_isFailure() {
  573 + // 准备参数
  574 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
  575 + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
  576 + o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100"));
  577 + // mock 数据(refund + 退款失败)
  578 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
  579 + .setStatus(PayRefundStatusEnum.FAILURE.getStatus()));
  580 + refundMapper.insert(refund);
  581 +
  582 + // 调用
  583 + refundService.notifyRefund(channel, refundRespDTO);
  584 + // 断言,refund 没有更新,因为已经退款失败
  585 + assertPojoEquals(refund, refundMapper.selectById(refund.getId()));
  586 + }
  587 +
  588 + @Test
  589 + public void testNotifyRefundFailure_isSuccess() {
  590 + // 准备参数
  591 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
  592 + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
  593 + o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100"));
  594 + // mock 数据(refund + 已支付)
  595 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
  596 + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()));
  597 + refundMapper.insert(refund);
  598 +
  599 + // 调用,并断言异常
  600 + assertServiceException(() -> refundService.notifyRefund(channel, refundRespDTO),
  601 + REFUND_STATUS_IS_NOT_WAITING);
  602 + }
  603 +
  604 + @Test
  605 + public void testNotifyRefundFailure_success() {
  606 + // 准备参数
  607 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L).setAppId(1L));
  608 + PayRefundRespDTO refundRespDTO = randomPojo(PayRefundRespDTO.class,
  609 + o -> o.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus()).setOutRefundNo("R100"));
  610 + // mock 数据(refund + 已支付)
  611 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setNo("R100")
  612 + .setStatus(PayRefundStatusEnum.WAITING.getStatus())
  613 + .setOrderId(100L).setRefundPrice(23));
  614 + refundMapper.insert(refund);
  615 +
  616 + // 调用
  617 + refundService.notifyRefund(channel, refundRespDTO);
  618 + // 断言,refund
  619 + refund.setChannelRefundNo(refundRespDTO.getChannelRefundNo())
  620 + .setStatus(PayRefundStatusEnum.FAILURE.getStatus())
  621 + .setChannelNotifyData(toJsonString(refundRespDTO))
  622 + .setChannelErrorCode(refundRespDTO.getChannelErrorCode())
  623 + .setChannelErrorMsg(refundRespDTO.getChannelErrorMsg());
  624 + assertPojoEquals(refund, refundMapper.selectById(refund.getId()),
  625 + "updateTime", "updater");
  626 + // 断言,调用
  627 + verify(notifyService).createPayNotifyTask(eq(PayNotifyTaskCreateReqDTO.builder()
  628 + .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build()));
  629 + }
  630 +
  631 + @Test
  632 + public void testSyncRefund_notFound() {
  633 + // 准备参数
  634 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L)
  635 + .setStatus(PayRefundStatusEnum.WAITING.getStatus()));
  636 + refundMapper.insert(refund);
  637 +
  638 + // 调用
  639 + int count = refundService.syncRefund();
  640 + // 断言
  641 + assertEquals(count, 0);
  642 + }
  643 +
  644 + @Test
  645 + public void testSyncRefund_waiting() {
  646 + assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusRespEnum.WAITING.getStatus()), 0);
  647 + }
  648 +
  649 + @Test
  650 + public void testSyncRefund_success() {
  651 + assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusRespEnum.SUCCESS.getStatus()), 1);
  652 + }
  653 +
  654 + @Test
  655 + public void testSyncRefund_failure() {
  656 + assertEquals(testSyncRefund_waitingOrSuccessOrFailure(PayRefundStatusRespEnum.FAILURE.getStatus()), 1);
  657 + }
  658 +
  659 + private int testSyncRefund_waitingOrSuccessOrFailure(Integer status) {
  660 + PayRefundServiceImpl payRefundServiceImpl = mock(PayRefundServiceImpl.class);
  661 + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
  662 + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PayRefundServiceImpl.class)))
  663 + .thenReturn(payRefundServiceImpl);
  664 +
  665 + // 准备参数
  666 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setChannelId(10L)
  667 + .setStatus(PayRefundStatusEnum.WAITING.getStatus())
  668 + .setOrderNo("P110").setNo("R220"));
  669 + refundMapper.insert(refund);
  670 + // mock 方法(client)
  671 + PayClient client = mock(PayClient.class);
  672 + when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
  673 + // mock 方法(client 返回指定状态)
  674 + PayRefundRespDTO respDTO = randomPojo(PayRefundRespDTO.class, o -> o.setStatus(status));
  675 + when(client.getRefund(eq("P110"), eq("R220"))).thenReturn(respDTO);
  676 + // mock 方法(channel)
  677 + PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
  678 + when(channelService.validPayChannel(eq(10L))).thenReturn(channel);
  679 +
  680 + // 调用
  681 + return refundService.syncRefund();
  682 + }
  683 + }
  684 +
  685 + @Test
  686 + public void testSyncRefund_exception() {
  687 + // 准备参数
  688 + PayRefundDO refund = randomPojo(PayRefundDO.class, o -> o.setAppId(1L).setChannelId(10L)
  689 + .setStatus(PayRefundStatusEnum.WAITING.getStatus())
  690 + .setOrderNo("P110").setNo("R220"));
  691 + refundMapper.insert(refund);
  692 + // mock 方法(client)
  693 + PayClient client = mock(PayClient.class);
  694 + when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
  695 + // mock 方法(client 抛出异常)
  696 + when(client.getRefund(eq("P110"), eq("R220"))).thenThrow(new RuntimeException());
  697 +
  698 + // 调用
  699 + int count = refundService.syncRefund();
  700 + // 断言
  701 + assertEquals(count, 0);
  702 + }
  703 +
442 704 }
... ...