| | |
| | | package cn.iocoder.yudao.module.system.service.social; |
| | | |
| | | import cn.binarywang.wx.miniapp.api.WxMaOrderShippingService; |
| | | import cn.binarywang.wx.miniapp.api.WxMaService; |
| | | import cn.binarywang.wx.miniapp.api.WxMaUserService; |
| | | import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; |
| | | import cn.binarywang.wx.miniapp.bean.shop.request.shipping.WxMaOrderShippingInfoUploadRequest; |
| | | import cn.binarywang.wx.miniapp.bean.shop.response.WxMaOrderShippingInfoBaseResponse; |
| | | import cn.hutool.core.util.ReflectUtil; |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; |
| | | import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderUploadShippingInfoReqDTO; |
| | | import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; |
| | | import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO; |
| | | import cn.iocoder.yudao.module.system.dal.mysql.social.SocialClientMapper; |
| | | import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; |
| | | import cn.iocoder.yudao.module.system.framework.justauth.core.AuthRequestFactory; |
| | | import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; |
| | | import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; |
| | | import jakarta.annotation.Resource; |
| | | import me.chanjar.weixin.common.bean.WxJsapiSignature; |
| | | import me.chanjar.weixin.common.error.WxError; |
| | | import me.chanjar.weixin.common.error.WxErrorException; |
| | | import me.chanjar.weixin.mp.api.WxMpService; |
| | | import me.zhyd.oauth.config.AuthConfig; |
| | | import me.zhyd.oauth.model.AuthResponse; |
| | | import me.zhyd.oauth.model.AuthUser; |
| | | import me.zhyd.oauth.request.AuthDefaultRequest; |
| | | import me.zhyd.oauth.request.AuthRequest; |
| | | import me.zhyd.oauth.utils.AuthStateUtils; |
| | | import org.junit.jupiter.api.BeforeAll; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.mockito.MockedStatic; |
| | | import org.springframework.context.annotation.Import; |
| | | import org.springframework.data.redis.core.StringRedisTemplate; |
| | | import org.springframework.test.context.bean.override.mockito.MockitoBean; |
| | | |
| | | import static cn.hutool.core.util.RandomUtil.randomEle; |
| | |
| | | |
| | | @MockitoBean |
| | | private AuthRequestFactory authRequestFactory; |
| | | |
| | | @MockitoBean |
| | | private WxMpService wxMpService; |
| | | @MockitoBean |
| | | private WxMpProperties wxMpProperties; |
| | | @MockitoBean |
| | | private StringRedisTemplate stringRedisTemplate; |
| | | @MockitoBean |
| | | private WxMaService wxMaService; |
| | | @MockitoBean |
| | | private WxMaProperties wxMaProperties; |
| | | |
| | | @Test |
| | | public void testGetAuthorizeUrl() { |
| | |
| | | assertNotSame(authConfig, ReflectUtil.getFieldValue(authRequest, "config")); |
| | | } |
| | | |
| | | // =================== 微信公众号独有 =================== |
| | | |
| | | @Test |
| | | public void testCreateWxMpJsapiSignature() throws WxErrorException { |
| | | // 准备参数 |
| | | Integer userType = randomPojo(UserTypeEnum.class).getValue(); |
| | | String url = randomString(); |
| | | // mock 方法 |
| | | WxJsapiSignature signature = randomPojo(WxJsapiSignature.class); |
| | | when(wxMpService.createJsapiSignature(eq(url))).thenReturn(signature); |
| | | |
| | | // 调用 |
| | | WxJsapiSignature result = socialClientService.createWxMpJsapiSignature(userType, url); |
| | | // 断言 |
| | | assertSame(signature, result); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetWxMpService_clientNull() { |
| | | // 准备参数 |
| | | Integer userType = randomPojo(UserTypeEnum.class).getValue(); |
| | | // mock 方法 |
| | | |
| | | // 调用 |
| | | WxMpService result = socialClientService.getWxMpService(userType); |
| | | // 断言 |
| | | assertSame(wxMpService, result); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetWxMpService_clientDisable() { |
| | | // 准备参数 |
| | | Integer userType = randomPojo(UserTypeEnum.class).getValue(); |
| | | // mock 数据 |
| | | SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()) |
| | | .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MP.getType())); |
| | | socialClientMapper.insert(client); |
| | | |
| | | // 调用 |
| | | WxMpService result = socialClientService.getWxMpService(userType); |
| | | // 断言 |
| | | assertSame(wxMpService, result); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetWxMpService_clientEnable() { |
| | | // 准备参数 |
| | | Integer userType = randomPojo(UserTypeEnum.class).getValue(); |
| | | // mock 数据 |
| | | SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()) |
| | | .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MP.getType())); |
| | | socialClientMapper.insert(client); |
| | | // mock 方法 |
| | | WxMpProperties.ConfigStorage configStorage = mock(WxMpProperties.ConfigStorage.class); |
| | | when(wxMpProperties.getConfigStorage()).thenReturn(configStorage); |
| | | |
| | | // 调用 |
| | | WxMpService result = socialClientService.getWxMpService(userType); |
| | | // 断言 |
| | | assertNotSame(wxMpService, result); |
| | | assertEquals(client.getClientId(), result.getWxMpConfigStorage().getAppId()); |
| | | assertEquals(client.getClientSecret(), result.getWxMpConfigStorage().getSecret()); |
| | | } |
| | | |
| | | // =================== 微信小程序独有 =================== |
| | | |
| | | @Test |
| | | public void testGetWxMaPhoneNumberInfo_success() throws WxErrorException { |
| | | // 准备参数 |
| | | Integer userType = randomPojo(UserTypeEnum.class).getValue(); |
| | | String phoneCode = randomString(); |
| | | // mock 方法 |
| | | WxMaUserService userService = mock(WxMaUserService.class); |
| | | when(wxMaService.getUserService()).thenReturn(userService); |
| | | WxMaPhoneNumberInfo phoneNumber = randomPojo(WxMaPhoneNumberInfo.class); |
| | | when(userService.getPhoneNumber(eq(phoneCode))).thenReturn(phoneNumber); |
| | | |
| | | // 调用 |
| | | WxMaPhoneNumberInfo result = socialClientService.getWxMaPhoneNumberInfo(userType, phoneCode); |
| | | // 断言 |
| | | assertSame(phoneNumber, result); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetWxMaPhoneNumberInfo_exception() throws WxErrorException { |
| | | // 准备参数 |
| | | Integer userType = randomPojo(UserTypeEnum.class).getValue(); |
| | | String phoneCode = randomString(); |
| | | // mock 方法 |
| | | WxMaUserService userService = mock(WxMaUserService.class); |
| | | when(wxMaService.getUserService()).thenReturn(userService); |
| | | WxErrorException wxErrorException = new WxErrorException(new NullPointerException()); |
| | | when(userService.getPhoneNumber(eq(phoneCode))).thenThrow(wxErrorException); |
| | | |
| | | // 调用并断言异常 |
| | | assertServiceException(() -> socialClientService.getWxMaPhoneNumberInfo(userType, phoneCode), |
| | | SOCIAL_CLIENT_WEIXIN_MINI_APP_PHONE_CODE_ERROR); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetWxMaService_clientNull() { |
| | | // 准备参数 |
| | | Integer userType = randomPojo(UserTypeEnum.class).getValue(); |
| | | // mock 方法 |
| | | |
| | | // 调用 |
| | | WxMaService result = socialClientService.getWxMaService(userType); |
| | | // 断言 |
| | | assertSame(wxMaService, result); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetWxMaService_clientDisable() { |
| | | // 准备参数 |
| | | Integer userType = randomPojo(UserTypeEnum.class).getValue(); |
| | | // mock 数据 |
| | | SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()) |
| | | .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MINI_PROGRAM.getType())); |
| | | socialClientMapper.insert(client); |
| | | |
| | | // 调用 |
| | | WxMaService result = socialClientService.getWxMaService(userType); |
| | | // 断言 |
| | | assertSame(wxMaService, result); |
| | | } |
| | | |
| | | @Test |
| | | public void testGetWxMaService_clientEnable() { |
| | | // 准备参数 |
| | | Integer userType = randomPojo(UserTypeEnum.class).getValue(); |
| | | // mock 数据 |
| | | SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()) |
| | | .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MINI_PROGRAM.getType())); |
| | | socialClientMapper.insert(client); |
| | | // mock 方法 |
| | | WxMaProperties.ConfigStorage configStorage = mock(WxMaProperties.ConfigStorage.class); |
| | | when(wxMaProperties.getConfigStorage()).thenReturn(configStorage); |
| | | |
| | | // 调用 |
| | | WxMaService result = socialClientService.getWxMaService(userType); |
| | | // 断言 |
| | | assertNotSame(wxMaService, result); |
| | | assertEquals(client.getClientId(), result.getWxMaConfig().getAppid()); |
| | | assertEquals(client.getClientSecret(), result.getWxMaConfig().getSecret()); |
| | | } |
| | | |
| | | // =================== 客户端管理 =================== |
| | | |
| | | @Test |
| | |
| | | assertEquals(1, pageResult.getTotal()); |
| | | assertEquals(1, pageResult.getList().size()); |
| | | assertPojoEquals(dbSocialClient, pageResult.getList().get(0)); |
| | | } |
| | | |
| | | // =================== 微信小程序订单发货 =================== |
| | | |
| | | @BeforeAll |
| | | public static void setUpUploadShippingBackoff() { |
| | | // 测试场景下把退避数组的每一项改为 1ms,避免拖慢用例(数组引用是 final 但元素可改) |
| | | long[] backoff = (long[]) ReflectUtil.getFieldValue(SocialClientServiceImpl.class, |
| | | "UPLOAD_SHIPPING_INFO_RETRY_BACKOFF_MILLIS"); |
| | | for (int i = 0; i < backoff.length; i++) { |
| | | backoff[i] = 1L; |
| | | } |
| | | } |
| | | |
| | | @Test |
| | | public void testUploadWxaOrderShippingInfo_success() throws WxErrorException { |
| | | // 准备参数 |
| | | Integer userType = randomPojo(UserTypeEnum.class).getValue(); |
| | | SocialWxaOrderUploadShippingInfoReqDTO reqDTO = randomUploadShippingReqDTO(); |
| | | // mock 方法:首次调用就成功 |
| | | WxMaOrderShippingService shippingService = mockWxMaOrderShippingService(); |
| | | when(shippingService.upload(any(WxMaOrderShippingInfoUploadRequest.class))) |
| | | .thenReturn(new WxMaOrderShippingInfoBaseResponse()); |
| | | |
| | | // 调用 |
| | | socialClientService.uploadWxaOrderShippingInfo(userType, reqDTO); |
| | | // 断言:仅调用 1 次,无重试 |
| | | verify(shippingService, times(1)).upload(any(WxMaOrderShippingInfoUploadRequest.class)); |
| | | } |
| | | |
| | | @Test |
| | | public void testUploadWxaOrderShippingInfo_retryThenSuccess() throws WxErrorException { |
| | | // 准备参数 |
| | | Integer userType = randomPojo(UserTypeEnum.class).getValue(); |
| | | SocialWxaOrderUploadShippingInfoReqDTO reqDTO = randomUploadShippingReqDTO(); |
| | | // mock 方法:首次抛 10060001,第二次成功 |
| | | WxMaOrderShippingService shippingService = mockWxMaOrderShippingService(); |
| | | when(shippingService.upload(any(WxMaOrderShippingInfoUploadRequest.class))) |
| | | .thenThrow(buildWxErrorException(10060001)) |
| | | .thenReturn(new WxMaOrderShippingInfoBaseResponse()); |
| | | |
| | | // 调用 |
| | | socialClientService.uploadWxaOrderShippingInfo(userType, reqDTO); |
| | | // 断言:上传调用了 2 次,触发了 1 次重试 |
| | | verify(shippingService, times(2)).upload(any(WxMaOrderShippingInfoUploadRequest.class)); |
| | | } |
| | | |
| | | @Test |
| | | public void testUploadWxaOrderShippingInfo_retryExhausted() throws WxErrorException { |
| | | // 准备参数 |
| | | Integer userType = randomPojo(UserTypeEnum.class).getValue(); |
| | | SocialWxaOrderUploadShippingInfoReqDTO reqDTO = randomUploadShippingReqDTO(); |
| | | // mock 方法:始终抛 10060001 |
| | | WxMaOrderShippingService shippingService = mockWxMaOrderShippingService(); |
| | | when(shippingService.upload(any(WxMaOrderShippingInfoUploadRequest.class))) |
| | | .thenThrow(buildWxErrorException(10060001)); |
| | | |
| | | // 调用并断言:重试用尽抛业务异常 |
| | | assertServiceException(() -> socialClientService.uploadWxaOrderShippingInfo(userType, reqDTO), |
| | | SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR); |
| | | // 断言:共 4 次尝试(1 次首发 + 3 次重试) |
| | | verify(shippingService, times(4)).upload(any(WxMaOrderShippingInfoUploadRequest.class)); |
| | | } |
| | | |
| | | @Test |
| | | public void testUploadWxaOrderShippingInfo_otherErrorNoRetry() throws WxErrorException { |
| | | // 准备参数 |
| | | Integer userType = randomPojo(UserTypeEnum.class).getValue(); |
| | | SocialWxaOrderUploadShippingInfoReqDTO reqDTO = randomUploadShippingReqDTO(); |
| | | // mock 方法:抛非 10060001 错误(如 access_token 失效) |
| | | WxMaOrderShippingService shippingService = mockWxMaOrderShippingService(); |
| | | when(shippingService.upload(any(WxMaOrderShippingInfoUploadRequest.class))) |
| | | .thenThrow(buildWxErrorException(40001)); |
| | | |
| | | // 调用并断言:立即抛业务异常,无重试 |
| | | assertServiceException(() -> socialClientService.uploadWxaOrderShippingInfo(userType, reqDTO), |
| | | SOCIAL_CLIENT_WEIXIN_MINI_APP_ORDER_UPLOAD_SHIPPING_INFO_ERROR); |
| | | verify(shippingService, times(1)).upload(any(WxMaOrderShippingInfoUploadRequest.class)); |
| | | } |
| | | |
| | | /** 构造一个发货上传请求 */ |
| | | private SocialWxaOrderUploadShippingInfoReqDTO randomUploadShippingReqDTO() { |
| | | return randomPojo(SocialWxaOrderUploadShippingInfoReqDTO.class, |
| | | o -> o.setLogisticsType(SocialWxaOrderUploadShippingInfoReqDTO.LOGISTICS_TYPE_EXPRESS)); |
| | | } |
| | | |
| | | /** mock 出 wxMaService.getWxMaOrderShippingService() 的返回值并返回该 mock */ |
| | | private WxMaOrderShippingService mockWxMaOrderShippingService() { |
| | | WxMaOrderShippingService shippingService = mock(WxMaOrderShippingService.class); |
| | | when(wxMaService.getWxMaOrderShippingService()).thenReturn(shippingService); |
| | | return shippingService; |
| | | } |
| | | |
| | | /** 构造指定 errorCode 的 WxErrorException */ |
| | | private WxErrorException buildWxErrorException(int errorCode) { |
| | | return new WxErrorException(WxError.builder().errorCode(errorCode).errorMsg("mock error").build()); |
| | | } |
| | | |
| | | } |