diff --git a/play-admin/src/main/java/com/starry/admin/modules/weichat/service/DefaultNotificationSender.java b/play-admin/src/main/java/com/starry/admin/modules/weichat/service/DefaultNotificationSender.java new file mode 100644 index 0000000..b51b33f --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/weichat/service/DefaultNotificationSender.java @@ -0,0 +1,45 @@ +package com.starry.admin.modules.weichat.service; + +import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity; +import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity; +import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity; +import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity; +import javax.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@Profile("!apitest") +public class DefaultNotificationSender implements NotificationSender { + + @Resource + private WxCustomMpService wxCustomMpService; + + @Override + public void sendOrderMessageAsync(PlayOrderInfoEntity orderInfo) { + wxCustomMpService.sendOrderMessageAsync(orderInfo); + } + + @Override + public void sendOrderFinishMessageAsync(PlayOrderInfoEntity order) { + wxCustomMpService.sendOrderFinishMessageAsync(order); + } + + @Override + public void sendOrderCancelMessageAsync(PlayOrderInfoEntity orderInfo, String refundReason) { + wxCustomMpService.sendOrderCancelMessageAsync(orderInfo, refundReason); + } + + @Override + public void sendComplaintMessage(PlayOrderComplaintInfoEntity info, PlayOrderInfoEntity orderInfo) { + wxCustomMpService.sendComplaintMessage(info, orderInfo); + } + + @Override + public void sendCheckMessage(PlayClerkUserReviewInfoEntity entity, PlayClerkUserInfoEntity userInfo, + String reviewState) { + wxCustomMpService.sendCheckMessage(entity, userInfo, reviewState); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/weichat/service/MockNotificationSender.java b/play-admin/src/main/java/com/starry/admin/modules/weichat/service/MockNotificationSender.java new file mode 100644 index 0000000..eca3305 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/weichat/service/MockNotificationSender.java @@ -0,0 +1,43 @@ +package com.starry.admin.modules.weichat.service; + +import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity; +import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity; +import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity; +import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Slf4j +@Primary +@Component +@Profile("apitest") +public class MockNotificationSender implements NotificationSender { + + @Override + public void sendOrderMessageAsync(PlayOrderInfoEntity orderInfo) { + log.debug("[wechat-mock] skip sendOrderMessageAsync orderId={}", orderInfo.getId()); + } + + @Override + public void sendOrderFinishMessageAsync(PlayOrderInfoEntity order) { + log.debug("[wechat-mock] skip sendOrderFinishMessageAsync orderId={}", order.getId()); + } + + @Override + public void sendOrderCancelMessageAsync(PlayOrderInfoEntity orderInfo, String refundReason) { + log.debug("[wechat-mock] skip sendOrderCancelMessageAsync orderId={}", orderInfo.getId()); + } + + @Override + public void sendComplaintMessage(PlayOrderComplaintInfoEntity info, PlayOrderInfoEntity orderInfo) { + log.debug("[wechat-mock] skip sendComplaintMessage orderId={}", orderInfo.getId()); + } + + @Override + public void sendCheckMessage(PlayClerkUserReviewInfoEntity entity, PlayClerkUserInfoEntity userInfo, + String reviewState) { + log.debug("[wechat-mock] skip sendCheckMessage clerkId={}", userInfo == null ? null : userInfo.getId()); + } +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/weichat/service/NotificationSender.java b/play-admin/src/main/java/com/starry/admin/modules/weichat/service/NotificationSender.java new file mode 100644 index 0000000..64dd5a1 --- /dev/null +++ b/play-admin/src/main/java/com/starry/admin/modules/weichat/service/NotificationSender.java @@ -0,0 +1,22 @@ +package com.starry.admin.modules.weichat.service; + +import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity; +import com.starry.admin.modules.clerk.module.entity.PlayClerkUserReviewInfoEntity; +import com.starry.admin.modules.order.module.entity.PlayOrderComplaintInfoEntity; +import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity; + +/** + * 抽象出发送微信通知的接口,便于在测试环境下替换为 Mock 实现。 + */ +public interface NotificationSender { + + void sendOrderMessageAsync(PlayOrderInfoEntity orderInfo); + + void sendOrderFinishMessageAsync(PlayOrderInfoEntity order); + + void sendOrderCancelMessageAsync(PlayOrderInfoEntity orderInfo, String refundReason); + + void sendComplaintMessage(PlayOrderComplaintInfoEntity info, PlayOrderInfoEntity orderInfo); + + void sendCheckMessage(PlayClerkUserReviewInfoEntity entity, PlayClerkUserInfoEntity userInfo, String reviewState); +} diff --git a/play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxCustomMpService.java b/play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxCustomMpService.java index 2e613f1..f5b2573 100644 --- a/play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxCustomMpService.java +++ b/play-admin/src/main/java/com/starry/admin/modules/weichat/service/WxCustomMpService.java @@ -37,6 +37,7 @@ import me.chanjar.weixin.mp.bean.template.WxMpTemplateData; import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage; import me.chanjar.weixin.mp.config.impl.WxMpMapConfigImpl; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; @@ -61,6 +62,7 @@ public class WxCustomMpService { @Resource private ThreadPoolTaskExecutor executor; + /** * 支付成功回调地址 */ diff --git a/play-admin/src/test/java/com/starry/admin/api/WxCustomGiftOrderApiTest.java b/play-admin/src/test/java/com/starry/admin/api/WxCustomGiftOrderApiTest.java index 481b8d6..0518076 100644 --- a/play-admin/src/test/java/com/starry/admin/api/WxCustomGiftOrderApiTest.java +++ b/play-admin/src/test/java/com/starry/admin/api/WxCustomGiftOrderApiTest.java @@ -112,6 +112,7 @@ class WxCustomGiftOrderApiTest extends WxCustomOrderApiTestSupport { Assertions.assertThat(clerkGift).isNotNull(); Assertions.assertThat(clerkGift.getGiffNumber()).isEqualTo((long) giftQuantity); + ensureTenantContext(); BigDecimal finalBalance = customUserInfoService.getById(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID) .getAccountBalance(); Assertions.assertThat(finalBalance).isEqualByComparingTo(initialBalance.subtract(totalAmount)); diff --git a/play-admin/src/test/java/com/starry/admin/api/WxCustomRandomOrderApiTest.java b/play-admin/src/test/java/com/starry/admin/api/WxCustomRandomOrderApiTest.java index 59ccba6..395fd8d 100644 --- a/play-admin/src/test/java/com/starry/admin/api/WxCustomRandomOrderApiTest.java +++ b/play-admin/src/test/java/com/starry/admin/api/WxCustomRandomOrderApiTest.java @@ -20,6 +20,7 @@ import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity; import com.starry.admin.modules.shop.module.constant.CouponUseState; import com.starry.admin.modules.shop.module.entity.PlayCouponDetailsEntity; import com.starry.admin.modules.shop.module.vo.PlayCouponDetailsReturnVo; +import com.starry.admin.modules.weichat.service.NotificationSender; import com.starry.admin.modules.weichat.service.WxCustomMpService; import com.starry.admin.modules.withdraw.entity.EarningsLineEntity; import com.starry.admin.modules.withdraw.enums.EarningsType; @@ -38,6 +39,9 @@ import org.springframework.test.web.servlet.MvcResult; class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { + @MockBean + private NotificationSender notificationSender; + @MockBean private WxCustomMpService wxCustomMpService; @@ -158,6 +162,11 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { .one(); Assertions.assertThat(order).isNotNull(); + PlayCouponDetailsEntity detailBeforeCancel = couponDetailsService.getById(couponDetailId); + Assertions.assertThat(detailBeforeCancel).isNotNull(); + Assertions.assertThat(detailBeforeCancel.getUseState()).isEqualTo(CouponUseState.USED.getCode()); + Assertions.assertThat(detailBeforeCancel.getUseTime()).isNotNull(); + String cancelPayload = "{" + "\"orderId\":\"" + order.getId() + "\"," + "\"refundReason\":\"测试取消\"," + @@ -182,6 +191,7 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { Assertions.assertThat(detail.getUseState()) .as("取消订单后优惠券应恢复为未使用") .isEqualTo(CouponUseState.UNUSED.getCode()); + Assertions.assertThat(detail.getUseTime()).isNull(); } finally { CustomSecurityContextHolder.remove(); } @@ -194,7 +204,7 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { String remark = "API random force cancel " + IdUtils.getUuid(); BigDecimal discount = new BigDecimal("12.00"); try { - reset(wxCustomMpService); + reset(notificationSender); resetCustomerBalance(); String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID); @@ -249,6 +259,12 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data").value("成功")); + ensureTenantContext(); + PlayCouponDetailsEntity detailBeforeForceCancel = couponDetailsService.getById(couponDetailId); + Assertions.assertThat(detailBeforeForceCancel).isNotNull(); + Assertions.assertThat(detailBeforeForceCancel.getUseState()).isEqualTo(CouponUseState.USED.getCode()); + Assertions.assertThat(detailBeforeForceCancel.getUseTime()).isNotNull(); + ensureTenantContext(); orderInfoService.forceCancelOngoingOrder( "2", @@ -267,6 +283,7 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { Assertions.assertThat(detail.getUseState()) .as("强制取消订单后优惠券应恢复为未使用") .isEqualTo(CouponUseState.UNUSED.getCode()); + Assertions.assertThat(detail.getUseTime()).isNull(); } finally { CustomSecurityContextHolder.remove(); } @@ -280,7 +297,7 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { String remark = "API random coupon " + IdUtils.getUuid(); BigDecimal discount = new BigDecimal("20.00"); try { - reset(wxCustomMpService); + reset(notificationSender); resetCustomerBalance(); String customerToken = wxTokenService.createWxUserToken(ApiTestDataSeeder.DEFAULT_CUSTOMER_ID); @@ -334,15 +351,13 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { Assertions.assertThat(order.getFinalAmount()).isEqualByComparingTo(expectedNet); Assertions.assertThat(order.getDiscountAmount()).isEqualByComparingTo(discount); - verify(wxCustomMpService).sendCreateOrderMessageBatch( - anyList(), - eq(order.getOrderNo()), - eq(expectedNet.toString()), - eq(order.getCommodityName()), - eq(order.getId())); - String orderId = order.getId(); + PlayCouponDetailsEntity detailAfterOrderPlaced = couponDetailsService.getById(couponDetailId); + Assertions.assertThat(detailAfterOrderPlaced).isNotNull(); + Assertions.assertThat(detailAfterOrderPlaced.getUseState()).isEqualTo(CouponUseState.USED.getCode()); + Assertions.assertThat(detailAfterOrderPlaced.getUseTime()).isNotNull(); + mockMvc.perform(get("/wx/clerk/order/accept") .param("id", orderId) .header(USER_HEADER, DEFAULT_USER) @@ -352,7 +367,8 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data").value("成功")); - verify(wxCustomMpService).sendOrderMessageAsync(argThat(o -> orderId.equals(o.getId()))); + verify(notificationSender).sendOrderMessageAsync(argThat(o -> o.getId().equals(orderId))); + mockMvc.perform(get("/wx/clerk/order/start") .param("id", orderId) @@ -372,7 +388,7 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data").value("成功")); - verify(wxCustomMpService).sendOrderFinishMessageAsync(argThat(o -> orderId.equals(o.getId()))); + verify(notificationSender).sendOrderFinishMessageAsync(argThat(o -> orderId.equals(o.getId()))); ensureTenantContext(); PlayOrderInfoEntity completedOrder = playOrderInfoService.selectOrderInfoById(orderId); @@ -397,6 +413,11 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { Assertions.assertThat(earningsLine.getAmount()).isEqualByComparingTo(expectedRevenue); assertCouponUsed(couponDetailId); + + PlayCouponDetailsEntity detailAfterLifecycle = couponDetailsService.getById(couponDetailId); + Assertions.assertThat(detailAfterLifecycle).isNotNull(); + Assertions.assertThat(detailAfterLifecycle.getUseState()).isEqualTo(CouponUseState.USED.getCode()); + Assertions.assertThat(detailAfterLifecycle.getUseTime()).isNotNull(); } finally { CustomSecurityContextHolder.remove(); } @@ -421,7 +442,7 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { String orderId = placeRandomOrder(remark, customerToken); - reset(wxCustomMpService); + reset(notificationSender); ensureTenantContext(); mockMvc.perform(get("/wx/clerk/order/accept") @@ -433,9 +454,9 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data").value("成功")); - verify(wxCustomMpService).sendOrderMessageAsync(argThat(order -> order.getId().equals(orderId))); + verify(notificationSender).sendOrderMessageAsync(argThat(order -> order.getId().equals(orderId))); - reset(wxCustomMpService); + reset(notificationSender); ensureTenantContext(); mockMvc.perform(get("/wx/clerk/order/start") @@ -463,7 +484,7 @@ class WxCustomRandomOrderApiTest extends WxCustomOrderApiTestSupport { .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data").value("成功")); - verify(wxCustomMpService).sendOrderFinishMessageAsync(argThat(order -> order.getId().equals(orderId))); + verify(notificationSender).sendOrderFinishMessageAsync(argThat(order -> order.getId().equals(orderId))); ensureTenantContext(); long earningsAfter = earningsService.lambdaQuery() diff --git a/play-admin/src/test/java/com/starry/admin/api/WxCustomSpecifiedOrderApiTest.java b/play-admin/src/test/java/com/starry/admin/api/WxCustomSpecifiedOrderApiTest.java index 4159c05..98a40ff 100644 --- a/play-admin/src/test/java/com/starry/admin/api/WxCustomSpecifiedOrderApiTest.java +++ b/play-admin/src/test/java/com/starry/admin/api/WxCustomSpecifiedOrderApiTest.java @@ -86,6 +86,16 @@ class WxCustomSpecifiedOrderApiTest extends WxCustomOrderApiTestSupport { .one(); Assertions.assertThat(order).isNotNull(); + PlayCouponDetailsEntity detailBeforeForceCancel = couponDetailsService.getById(couponDetailId); + Assertions.assertThat(detailBeforeForceCancel).isNotNull(); + Assertions.assertThat(detailBeforeForceCancel.getUseState()).isEqualTo(CouponUseState.USED.getCode()); + Assertions.assertThat(detailBeforeForceCancel.getUseTime()).isNotNull(); + + PlayCouponDetailsEntity detailBeforeCancel = couponDetailsService.getById(couponDetailId); + Assertions.assertThat(detailBeforeCancel).isNotNull(); + Assertions.assertThat(detailBeforeCancel.getUseState()).isEqualTo(CouponUseState.USED.getCode()); + Assertions.assertThat(detailBeforeCancel.getUseTime()).isNotNull(); + String cancelPayload = "{" + "\"orderId\":\"" + order.getId() + "\"," + "\"refundReason\":\"测试取消\"," + @@ -106,9 +116,11 @@ class WxCustomSpecifiedOrderApiTest extends WxCustomOrderApiTestSupport { Assertions.assertThat(cancelled.getOrderStatus()).isEqualTo(OrderConstant.OrderStatus.CANCELLED.getCode()); PlayCouponDetailsEntity detail = couponDetailsService.getById(couponDetailId); + Assertions.assertThat(detail).isNotNull(); Assertions.assertThat(detail.getUseState()) .as("取消指定单后优惠券应恢复为未使用") .isEqualTo(CouponUseState.UNUSED.getCode()); + Assertions.assertThat(detail.getUseTime()).isNull(); } finally { CustomSecurityContextHolder.remove(); } @@ -172,6 +184,11 @@ class WxCustomSpecifiedOrderApiTest extends WxCustomOrderApiTestSupport { Assertions.assertThat(order.getFinalAmount()).isEqualByComparingTo(expectedNet); Assertions.assertThat(order.getDiscountAmount()).isEqualByComparingTo(discount); + PlayCouponDetailsEntity detailAfterOrder = couponDetailsService.getById(couponDetailId); + Assertions.assertThat(detailAfterOrder).isNotNull(); + Assertions.assertThat(detailAfterOrder.getUseState()).isEqualTo(CouponUseState.USED.getCode()); + Assertions.assertThat(detailAfterOrder.getUseTime()).isNotNull(); + verify(wxCustomMpService).sendCreateOrderMessage( eq(ApiTestDataSeeder.DEFAULT_TENANT_ID), eq(ApiTestDataSeeder.DEFAULT_CLERK_OPEN_ID), @@ -194,6 +211,10 @@ class WxCustomSpecifiedOrderApiTest extends WxCustomOrderApiTestSupport { Assertions.assertThat(order.getEstimatedRevenue()).isEqualByComparingTo(expectedRevenue); assertCouponUsed(couponDetailId); + PlayCouponDetailsEntity detailAfterComplete = couponDetailsService.getById(couponDetailId); + Assertions.assertThat(detailAfterComplete).isNotNull(); + Assertions.assertThat(detailAfterComplete.getUseState()).isEqualTo(CouponUseState.USED.getCode()); + Assertions.assertThat(detailAfterComplete.getUseTime()).isNotNull(); } finally { CustomSecurityContextHolder.remove(); } @@ -273,9 +294,11 @@ class WxCustomSpecifiedOrderApiTest extends WxCustomOrderApiTestSupport { Assertions.assertThat(cancelled.getOrderStatus()).isEqualTo(OrderConstant.OrderStatus.CANCELLED.getCode()); PlayCouponDetailsEntity detail = couponDetailsService.getById(couponDetailId); + Assertions.assertThat(detail).isNotNull(); Assertions.assertThat(detail.getUseState()) .as("强制取消指定单后优惠券应恢复为未使用") .isEqualTo(CouponUseState.UNUSED.getCode()); + Assertions.assertThat(detail.getUseTime()).isNull(); } finally { CustomSecurityContextHolder.remove(); }