3 天以前 b1ad2d17c7f93e819c67fe5ab5c2ca94e764e517
yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImplTest.java
@@ -1,41 +1,26 @@
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;
@@ -63,17 +48,6 @@
    @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() {
@@ -205,152 +179,6 @@
        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
@@ -472,102 +300,6 @@
        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());
    }
}