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,7 +19,6 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
19 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; 19 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
20 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; 20 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
21 import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; 21 import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
22 -import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;  
23 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; 22 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
24 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; 23 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
25 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; 24 import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
@@ -92,7 +91,6 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -92,7 +91,6 @@ public class PayRefundServiceImpl implements PayRefundService {
92 } 91 }
93 92
94 @Override 93 @Override
95 - @Transactional(rollbackFor = Exception.class)  
96 public Long createPayRefund(PayRefundCreateReqDTO reqDTO) { 94 public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
97 // 1.1 校验 App 95 // 1.1 校验 App
98 PayAppDO app = appService.validPayApp(reqDTO.getAppId()); 96 PayAppDO app = appService.validPayApp(reqDTO.getAppId());
@@ -109,7 +107,7 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -109,7 +107,7 @@ public class PayRefundServiceImpl implements PayRefundService {
109 PayRefundDO refund = refundMapper.selectByAppIdAndMerchantRefundId( 107 PayRefundDO refund = refundMapper.selectByAppIdAndMerchantRefundId(
110 app.getId(), reqDTO.getMerchantRefundId()); 108 app.getId(), reqDTO.getMerchantRefundId());
111 if (refund != null) { 109 if (refund != null) {
112 - throw exception(ErrorCodeConstants.REFUND_EXISTS); 110 + throw exception(REFUND_EXISTS);
113 } 111 }
114 112
115 // 2.1 插入退款单 113 // 2.1 插入退款单
@@ -158,7 +156,7 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -158,7 +156,7 @@ public class PayRefundServiceImpl implements PayRefundService {
158 private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO) { 156 private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO) {
159 PayOrderDO order = orderService.getOrder(reqDTO.getAppId(), reqDTO.getMerchantOrderId()); 157 PayOrderDO order = orderService.getOrder(reqDTO.getAppId(), reqDTO.getMerchantOrderId());
160 if (order == null) { 158 if (order == null) {
161 - throw exception(ErrorCodeConstants.ORDER_NOT_FOUND); 159 + throw exception(ORDER_NOT_FOUND);
162 } 160 }
163 // 校验状态,必须是已支付、或者已退款 161 // 校验状态,必须是已支付、或者已退款
164 if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) { 162 if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) {
@@ -167,12 +165,12 @@ public class PayRefundServiceImpl implements PayRefundService { @@ -167,12 +165,12 @@ public class PayRefundServiceImpl implements PayRefundService {
167 165
168 // 校验金额,退款金额不能大于原定的金额 166 // 校验金额,退款金额不能大于原定的金额
169 if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()){ 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 if (refundMapper.selectCountByAppIdAndOrderId(reqDTO.getAppId(), order.getId(), 171 if (refundMapper.selectCountByAppIdAndOrderId(reqDTO.getAppId(), order.getId(),
174 PayRefundStatusEnum.WAITING.getStatus()) > 0) { 172 PayRefundStatusEnum.WAITING.getStatus()) > 0) {
175 - throw exception(ErrorCodeConstants.REFUND_HAS_REFUNDING); 173 + throw exception(REFUND_HAS_REFUNDING);
176 } 174 }
177 return order; 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 package cn.iocoder.yudao.module.pay.service.refund; 1 package cn.iocoder.yudao.module.pay.service.refund;
2 2
  3 +import cn.hutool.extra.spring.SpringUtil;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.pay.core.client.PayClient; 5 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
5 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 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 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; 9 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
7 import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; 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 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; 13 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
11 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; 14 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
12 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; 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,12 +24,13 @@ import cn.iocoder.yudao.module.pay.service.app.PayAppService;
21 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; 24 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
22 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; 25 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
23 import cn.iocoder.yudao.module.pay.service.order.PayOrderService; 26 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
  27 +import org.junit.jupiter.api.BeforeEach;
24 import org.junit.jupiter.api.Test; 28 import org.junit.jupiter.api.Test;
  29 +import org.mockito.MockedStatic;
25 import org.springframework.boot.test.mock.mockito.MockBean; 30 import org.springframework.boot.test.mock.mockito.MockBean;
26 import org.springframework.context.annotation.Import; 31 import org.springframework.context.annotation.Import;
27 32
28 import javax.annotation.Resource; 33 import javax.annotation.Resource;
29 -import java.time.LocalDateTime;  
30 import java.util.List; 34 import java.util.List;
31 35
32 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; 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,10 +41,12 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
37 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; 41 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
38 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; 42 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
39 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; 43 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
  44 +import static org.assertj.core.api.Assertions.assertThat;
40 import static org.junit.jupiter.api.Assertions.assertEquals; 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 import static org.mockito.ArgumentMatchers.eq; 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 * {@link PayRefundServiceImpl} 的单元测试类 52 * {@link PayRefundServiceImpl} 的单元测试类
@@ -69,6 +75,41 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { @@ -69,6 +75,41 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
69 @MockBean 75 @MockBean
70 private PayNotifyService notifyService; 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 @Test 113 @Test
73 public void testGetRefundPage() { 114 public void testGetRefundPage() {
74 // mock 数据 115 // mock 数据
@@ -276,7 +317,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { @@ -276,7 +317,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
276 // 准备参数 317 // 准备参数
277 PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class, 318 PayRefundCreateReqDTO reqDTO = randomPojo(PayRefundCreateReqDTO.class,
278 o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9) 319 o -> o.setAppId(1L).setMerchantOrderId("100").setPrice(9)
279 - .setReason("测试退款")); 320 + .setMerchantRefundId("200").setReason("测试退款"));
280 // mock 方法(app) 321 // mock 方法(app)
281 PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L)); 322 PayAppDO app = randomPojo(PayAppDO.class, o -> o.setId(1L));
282 when(appService.validPayApp(eq(1L))).thenReturn(app); 323 when(appService.validPayApp(eq(1L))).thenReturn(app);
@@ -295,7 +336,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { @@ -295,7 +336,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
295 when(payClientFactory.getPayClient(eq(10L))).thenReturn(client); 336 when(payClientFactory.getPayClient(eq(10L))).thenReturn(client);
296 // mock 数据(refund 已存在) 337 // mock 数据(refund 已存在)
297 PayRefundDO refund = randomPojo(PayRefundDO.class, o -> 338 PayRefundDO refund = randomPojo(PayRefundDO.class, o ->
298 - o.setOrderId(order.getId()).setStatus(PayOrderStatusEnum.WAITING.getStatus())); 339 + o.setAppId(1L).setMerchantRefundId("200"));
299 refundMapper.insert(refund); 340 refundMapper.insert(refund);
300 341
301 // 调用,并断言异常 342 // 调用,并断言异常
@@ -305,12 +346,97 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest { @@ -305,12 +346,97 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
305 346
306 @Test 347 @Test
307 public void testCreateRefund_invokeException() { 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 @Test 386 @Test
312 public void testCreateRefund_invokeSuccess() { 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,6 +87,7 @@ CREATE TABLE IF NOT EXISTS `pay_refund` (
87 `channel_id` bigint(20) NOT NULL, 87 `channel_id` bigint(20) NOT NULL,
88 `channel_code` varchar(32) NOT NULL, 88 `channel_code` varchar(32) NOT NULL,
89 `order_id` bigint(20) NOT NULL, 89 `order_id` bigint(20) NOT NULL,
  90 + `order_no` varchar(64) NOT NULL,
90 `merchant_order_id` varchar(64) NOT NULL, 91 `merchant_order_id` varchar(64) NOT NULL,
91 `merchant_refund_id` varchar(64) NOT NULL, 92 `merchant_refund_id` varchar(64) NOT NULL,
92 `notify_url` varchar(1024) NOT NULL, 93 `notify_url` varchar(1024) NOT NULL,