| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744 |
- package com.ruoyi.app.user;
- import com.auth0.jwt.JWT;
- import com.baomidou.mybatisplus.core.MybatisConfiguration;
- import com.baomidou.mybatisplus.core.conditions.Wrapper;
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
- import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
- import com.ruoyi.common.constant.HttpStatus;
- import com.ruoyi.common.core.domain.AjaxResult;
- import com.ruoyi.common.core.redis.RedisCache;
- import com.ruoyi.common.exception.ServiceException;
- import com.ruoyi.app.user.dto.OAuthBindDto;
- import com.ruoyi.app.user.dto.OAuthLoginDto;
- import com.ruoyi.app.utils.oauth.OAuthVerifyService;
- import com.ruoyi.app.utils.oauth.LineOAuthStateService;
- import com.ruoyi.system.domain.InfoUser;
- import com.ruoyi.system.domain.InfoUserOauth;
- import com.ruoyi.system.domain.PosOrder;
- import com.ruoyi.system.domain.vo.UserDTO;
- import com.ruoyi.system.mapper.InfoUserOauthMapper;
- import com.ruoyi.system.service.IInfoUserService;
- import com.ruoyi.system.service.IPosOrderService;
- import com.ruoyi.system.service.IUserWalletService;
- import com.ruoyi.system.service.MerchantStoreAccessService;
- import com.ruoyi.system.utils.AuthContext;
- import com.ruoyi.system.utils.JwtUtil;
- import com.ruoyi.common.utils.spring.SpringUtils;
- import org.apache.ibatis.builder.MapperBuilderAssistant;
- import org.junit.jupiter.api.AfterAll;
- import org.junit.jupiter.params.ParameterizedTest;
- import org.junit.jupiter.params.provider.CsvSource;
- import org.junit.jupiter.api.AfterEach;
- import org.junit.jupiter.api.BeforeAll;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
- import org.mockito.ArgumentCaptor;
- import org.mockito.MockedStatic;
- import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
- import org.springframework.beans.factory.support.DefaultListableBeanFactory;
- import org.springframework.context.support.StaticMessageSource;
- import org.springframework.mock.web.MockHttpServletRequest;
- import org.springframework.test.util.ReflectionTestUtils;
- import org.springframework.web.context.request.RequestContextHolder;
- import org.springframework.web.context.request.ServletRequestAttributes;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Locale;
- import static org.junit.jupiter.api.Assertions.assertEquals;
- import static org.junit.jupiter.api.Assertions.assertNull;
- import static org.junit.jupiter.api.Assertions.assertThrows;
- import static org.junit.jupiter.api.Assertions.assertTrue;
- import static org.mockito.ArgumentMatchers.any;
- import static org.mockito.Mockito.mock;
- import static org.mockito.Mockito.mockStatic;
- import static org.mockito.Mockito.never;
- import static org.mockito.Mockito.doThrow;
- import static org.mockito.Mockito.verify;
- import static org.mockito.Mockito.when;
- class InfoUserControllerTest {
- private InfoUserController controller;
- private IPosOrderService posOrderService;
- private IInfoUserService infoUserService;
- private IUserWalletService userWalletService;
- private MerchantStoreAccessService merchantStoreAccessService;
- private MerchantTokenSessionService merchantTokenSessionService;
- private BusinessPhoneService businessPhoneService;
- private OAuthVerifyService oauthVerifyService;
- private InfoUserOauthMapper infoUserOauthMapper;
- private RedisCache redisCache;
- private static ConfigurableListableBeanFactory originalBeanFactory;
- @BeforeAll
- static void initializeTableMetadata() {
- TableInfoHelper.initTableInfo(
- new MapperBuilderAssistant(new MybatisConfiguration(), ""), PosOrder.class);
- TableInfoHelper.initTableInfo(
- new MapperBuilderAssistant(new MybatisConfiguration(), ""), InfoUser.class);
- TableInfoHelper.initTableInfo(
- new MapperBuilderAssistant(new MybatisConfiguration(), ""), InfoUserOauth.class);
- originalBeanFactory = (ConfigurableListableBeanFactory)
- ReflectionTestUtils.getField(SpringUtils.class, "beanFactory");
- DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
- StaticMessageSource messageSource = new StaticMessageSource();
- messageSource.addMessage("no.user.audit.reject.reason.required", Locale.getDefault(),
- "审核不通过时必须填写审核不通过原因");
- messageSource.addMessage("no.action.success", Locale.getDefault(), "操作成功");
- messageSource.addMessage("merchant.subaccount.platform.managed", Locale.getDefault(),
- "分管账号只能通过专用入口管理");
- messageSource.addMessage("no.oauth.phone.blank", Locale.getDefault(), "手机号不能为空");
- messageSource.addMessage("no.user.login.success", Locale.getDefault(), "登录成功");
- messageSource.addMessage("no.user.stop", Locale.getDefault(), "账号已停用");
- messageSource.addMessage("no.user.not.exist", Locale.getDefault(), "账号不存在");
- messageSource.addMessage("no.system.error", Locale.getDefault(), "系统错误");
- messageSource.addMessage("no.user.jcaptcha.error", Locale.getDefault(), "验证码错误");
- messageSource.addMessage("no.oauth.tempkey.expired", Locale.getDefault(), "登录凭证已过期");
- messageSource.addMessage("no.oauth.phone.duplicate", Locale.getDefault(), "该手机号关联多个账号,请联系平台");
- messageSource.addMessage("no.user.phone.duplicate", Locale.getDefault(), "该手机号关联多个账号,请联系平台");
- beanFactory.registerSingleton("messageSource", messageSource);
- beanFactory.registerSingleton("redisCache", mock(RedisCache.class));
- new SpringUtils().postProcessBeanFactory(beanFactory);
- }
- @AfterAll
- static void restoreBeanFactory() {
- new SpringUtils().postProcessBeanFactory(originalBeanFactory);
- }
- @BeforeEach
- void setUp() {
- MockHttpServletRequest request = new MockHttpServletRequest();
- request.setRemoteAddr("127.0.0.1");
- request.addHeader("User-Agent", "JUnit");
- RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
- controller = new TestInfoUserController();
- posOrderService = mock(IPosOrderService.class);
- infoUserService = mock(IInfoUserService.class);
- userWalletService = mock(IUserWalletService.class);
- merchantStoreAccessService = mock(MerchantStoreAccessService.class);
- merchantTokenSessionService = mock(MerchantTokenSessionService.class);
- businessPhoneService = mock(BusinessPhoneService.class);
- oauthVerifyService = mock(OAuthVerifyService.class);
- infoUserOauthMapper = mock(InfoUserOauthMapper.class);
- redisCache = mock(RedisCache.class);
- ReflectionTestUtils.setField(controller, "posOrderService", posOrderService);
- ReflectionTestUtils.setField(controller, "infoUserService", infoUserService);
- ReflectionTestUtils.setField(controller, "userWalletService", userWalletService);
- ReflectionTestUtils.setField(controller, "merchantStoreAccessService", merchantStoreAccessService);
- ReflectionTestUtils.setField(controller, "merchantTokenSessionService", merchantTokenSessionService);
- ReflectionTestUtils.setField(controller, "businessPhoneService", businessPhoneService);
- ReflectionTestUtils.setField(controller, "oauthVerifyService", oauthVerifyService);
- ReflectionTestUtils.setField(controller, "infoUserOauthMapper", infoUserOauthMapper);
- ReflectionTestUtils.setField(controller, "redisCache", redisCache);
- when(infoUserOauthMapper.insert(any(InfoUserOauth.class))).thenReturn(1);
- }
- @AfterEach
- void clearRequestContext() {
- RequestContextHolder.resetRequestAttributes();
- }
- @Test
- void refusesDeletionWhenUserHasAnUnfinishedOrder() {
- when(posOrderService.exists(any(Wrapper.class))).thenReturn(true);
- ServiceException exception = assertThrows(ServiceException.class,
- () -> controller.deleuser(tokenFor(42L)));
- assertEquals("抱歉,您還有未完成的訂單,無法刪除帳號", exception.getMessage());
- verify(infoUserService, never()).deleteInfoUserByUserId(42L);
- }
- @Test
- void checksOnlyLatestUnfinishedStatesForEveryOrderRole() {
- when(posOrderService.exists(any(Wrapper.class))).thenReturn(false);
- when(infoUserService.deleteInfoUserByUserId(42L)).thenReturn(1);
- AjaxResult result = controller.deleuser(tokenFor(42L));
- assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
- ArgumentCaptor<LambdaQueryWrapper<PosOrder>> captor =
- ArgumentCaptor.forClass(LambdaQueryWrapper.class);
- verify(posOrderService).exists(captor.capture());
- LambdaQueryWrapper<PosOrder> query = captor.getValue();
- String sql = query.getSqlSegment();
- Collection<Object> parameters = new ArrayList<>(query.getParamNameValuePairs().values());
- assertEquals(6, parameters.size());
- assertEquals(1, Collections.frequency(parameters, 0L));
- assertEquals(1, Collections.frequency(parameters, 1L));
- assertEquals(1, Collections.frequency(parameters, 2L));
- assertEquals(3, Collections.frequency(parameters, 42L));
- assertTrue(sql.contains("user_id"));
- assertTrue(sql.contains("qs_id"));
- assertTrue(sql.contains("sh_id"));
- }
- @Test
- void rejectsAuditRejectionWithoutReason() {
- InfoUser user = new InfoUser();
- user.setAuditStatus("2");
- user.setAuditRejectReason(" ");
- AjaxResult result = controller.edit(user);
- assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
- verify(infoUserService, never()).updateInfoUser(any(InfoUser.class));
- }
- @Test
- void rejectsNewAuditRejectionWithoutReason() {
- InfoUser user = new InfoUser();
- user.setUserName("merchant");
- user.setPhone("0912345678");
- user.setAuditStatus("2");
- user.setAuditRejectReason(" ");
- AjaxResult result = controller.add(user);
- assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
- verify(infoUserService, never()).insertInfoUser(any(InfoUser.class));
- }
- @Test
- void profileUpdateCannotChangeAuditStatus() {
- InfoUser request = new InfoUser();
- request.setAuditStatus("1");
- when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
- controller.setuser(tokenFor(42L), request);
- ArgumentCaptor<InfoUser> captor = ArgumentCaptor.forClass(InfoUser.class);
- verify(infoUserService).saveOrUpdate(captor.capture());
- assertNull(captor.getValue().getAuditStatus());
- }
- @Test
- void resubmittedRejectedProfileReturnsToPendingReview() {
- InfoUser request = new InfoUser();
- request.setUpdateUserInfo(true);
- when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
- controller.setuser(tokenFor(42L), request);
- ArgumentCaptor<LambdaUpdateWrapper> captor =
- ArgumentCaptor.forClass(LambdaUpdateWrapper.class);
- verify(infoUserService).update(captor.capture());
- LambdaUpdateWrapper<?> update = captor.getValue();
- Collection<Object> parameters = update.getParamNameValuePairs().values();
- assertTrue(update.getSqlSegment().contains("user_id"));
- assertTrue(update.getSqlSegment().contains("audit_status"));
- assertTrue(update.getSqlSet().contains("audit_status"));
- assertTrue(update.getSqlSet().contains("audit_reject_reason"));
- assertTrue(parameters.contains(42L));
- assertTrue(parameters.contains("2"));
- assertTrue(parameters.contains("0"));
- }
- @Test
- void ordinaryProfileUpdateDoesNotChangeReviewState() {
- InfoUser request = new InfoUser();
- request.setUpdateUserInfo(false);
- when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
- controller.setuser(tokenFor(42L), request);
- verify(infoUserService, never()).update(any(Wrapper.class));
- }
- @Test
- void trimsAndSavesAuditRejectionReason() {
- InfoUser user = new InfoUser();
- user.setAuditStatus("2");
- user.setAuditRejectReason(" 证件照片模糊 ");
- when(infoUserService.updateInfoUser(user)).thenReturn(1);
- controller.edit(user);
- assertEquals("证件照片模糊", user.getAuditRejectReason());
- verify(infoUserService).updateInfoUser(user);
- }
- @Test
- void clearsOldReasonWhenAuditPasses() {
- InfoUser user = new InfoUser();
- user.setAuditStatus("1");
- user.setAuditRejectReason("旧原因");
- when(infoUserService.updateInfoUser(user)).thenReturn(1);
- controller.edit(user);
- assertNull(user.getAuditRejectReason());
- verify(infoUserService).updateInfoUser(user);
- }
- @Test
- void platformGenericAddCannotCreateMerchantSubaccount() {
- InfoUser user = new InfoUser();
- user.setUserType("5");
- AjaxResult result = controller.add(user);
- assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
- verify(infoUserService, never()).insertInfoUser(any(InfoUser.class));
- }
- @Test
- void platformGenericEditCannotModifyMerchantSubaccount() {
- InfoUser existing = new InfoUser();
- existing.setUserId(55L);
- existing.setUserType("5");
- when(infoUserService.selectInfoUserByUserId(55L)).thenReturn(existing);
- InfoUser request = new InfoUser();
- request.setUserId(55L);
- AjaxResult result = controller.edit(request);
- assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
- verify(infoUserService, never()).updateInfoUser(request);
- }
- @Test
- void merchantLogoutRevokesOnlyCurrentTokenSession() {
- try (MockedStatic<AuthContext> authContext = mockStatic(AuthContext.class)) {
- authContext.when(AuthContext::requireJti)
- .thenReturn("qtw_tokens:sh:app:936:current-session");
- controller.merchantLogout("merchant-token");
- }
- verify(merchantTokenSessionService)
- .logoutCurrent("qtw_tokens:sh:app:936:current-session");
- }
- @Test
- void phoneRegistrationMarksTokenProviderAsPhone() {
- UserDTO request = new UserDTO();
- request.setPhone("0912345678");
- InfoUser created = new InfoUser();
- created.setUserId(42L);
- created.setPhone(request.getPhone());
- when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
- when(infoUserService.list(any(Wrapper.class))).thenReturn(java.util.List.of(created));
- AjaxResult result = controller.createUser(request);
- String token = (String) result.get("token");
- assertEquals("phone", JWT.decode(token).getClaim("provider").asString());
- }
- @Test
- void phoneLoginRejectsPhoneLinkedToMultipleAccounts() {
- UserDTO request = new UserDTO();
- request.setPhone("0912345678");
- request.setCode("8888");
- when(redisCache.getCacheObject("0912345678")).thenReturn(null);
- when(infoUserService.list(any(Wrapper.class))).thenReturn(java.util.List.of(
- activeUser(71L, "0"), activeUser(72L, "0")));
- ServiceException exception = assertThrows(ServiceException.class,
- () -> controller.lodeing(request));
- assertEquals("该手机号关联多个账号,请联系平台", exception.getMessage());
- verify(infoUserService, never()).saveOrUpdate(any(InfoUser.class));
- }
- @Test
- void phoneLoginOnlyMatchesMemberAccounts() {
- UserDTO request = new UserDTO();
- request.setPhone("0912345678");
- request.setCode("8888");
- when(redisCache.getCacheObject("0912345678")).thenReturn(null);
- InfoUser created = activeUser(42L, "0");
- created.setPhone("0912345678");
- // 查询限定 user_type=0:同号商家行不命中,未注册则新建会员(首查空 + 新建回查)
- when(infoUserService.list(any(Wrapper.class)))
- .thenReturn(java.util.List.of(), java.util.List.of(created));
- when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
- AjaxResult result = controller.lodeing(request);
- assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
- ArgumentCaptor<InfoUser> createdCaptor = ArgumentCaptor.forClass(InfoUser.class);
- verify(infoUserService).saveOrUpdate(createdCaptor.capture());
- assertEquals("0", createdCaptor.getValue().getUserType());
- }
- @Test
- void riderRegistrationRejectsDuplicateBusinessPhone() {
- com.ruoyi.system.domain.vo.UserDTO request = new com.ruoyi.system.domain.vo.UserDTO();
- request.setTelPhone("0912345678");
- org.mockito.Mockito.doThrow(new ServiceException("手机号已存在"))
- .when(businessPhoneService).ensureUnique("0912345678", null);
- assertThrows(ServiceException.class, () -> controller.createQsUser(request));
- verify(infoUserService, never()).insertInfoUser(any(InfoUser.class));
- }
- @Test
- void platformEditChecksBusinessPhoneExcludingCurrentAccount() {
- InfoUser existing = new InfoUser();
- existing.setUserId(42L);
- existing.setUserType("2");
- when(infoUserService.selectInfoUserByUserId(42L)).thenReturn(existing);
- when(businessPhoneService.isBusinessUserType("2")).thenReturn(true);
- when(infoUserService.updateInfoUser(any(InfoUser.class))).thenReturn(1);
- InfoUser request = new InfoUser();
- request.setUserId(42L);
- request.setTelPhone("0912345678");
- controller.edit(request);
- verify(businessPhoneService).ensureUnique("0912345678", 42L);
- }
- @Test
- void oauthBindPhoneRejectsMissingPhoneWithoutWritingBinding() {
- OAuthBindDto request = new OAuthBindDto();
- request.setTempKey("temp-key");
- when(redisCache.getCacheObject("oauth:bind:temp-key"))
- .thenReturn("line_user@line-uid");
- when(redisCache.deleteObject("oauth:bind:temp-key")).thenReturn(true);
- AjaxResult result = controller.oauthBindPhone(request);
- assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
- assertEquals("手机号不能为空", result.get(AjaxResult.MSG_TAG));
- verify(infoUserOauthMapper, never()).insert(any(InfoUserOauth.class));
- }
- @Test
- void lineUserBindingCreatesNormalUserInsteadOfBindingLegacyBusinessPhone() {
- OAuthBindDto request = new OAuthBindDto();
- request.setTempKey("temp-key");
- request.setPhone("0912345678");
- request.setCode("8888");
- when(redisCache.getCacheObject("oauth:bind:temp-key"))
- .thenReturn("line_user@line-uid");
- when(redisCache.deleteObject("oauth:bind:temp-key")).thenReturn(true);
- InfoUser legacyMerchant = activeUser(7L, "1");
- legacyMerchant.setPhone("0912345678");
- when(infoUserService.getuser("0912345678")).thenReturn(legacyMerchant);
- InfoUser createdUser = activeUser(99L, "0");
- createdUser.setPhone("0912345678");
- when(infoUserService.list(any(Wrapper.class)))
- .thenReturn(java.util.List.of(), java.util.List.of(createdUser));
- when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
- AjaxResult result = controller.oauthBindPhone(request);
- assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
- ArgumentCaptor<InfoUserOauth> binding = ArgumentCaptor.forClass(InfoUserOauth.class);
- verify(infoUserOauthMapper).insert(binding.capture());
- assertEquals(99L, binding.getValue().getUserId());
- verify(infoUserService, never()).getuser("0912345678");
- }
- @Test
- void profileUpdateCannotChangeOwnRoleOrStatus() {
- InfoUser current = activeUser(42L, "0");
- when(infoUserService.getById("42")).thenReturn(current);
- when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
- InfoUser request = new InfoUser();
- request.setUserType("2");
- request.setStatus("0");
- controller.setuser(tokenFor(42L), request);
- ArgumentCaptor<InfoUser> saved = ArgumentCaptor.forClass(InfoUser.class);
- verify(infoUserService).saveOrUpdate(saved.capture());
- assertNull(saved.getValue().getUserType());
- assertNull(saved.getValue().getStatus());
- }
- @Test
- void oauthBindPhoneRejectsTempKeyAlreadyClaimedByAnotherRequest() {
- OAuthBindDto request = new OAuthBindDto();
- request.setTempKey("replayed-key");
- request.setPhone("0912345678");
- request.setCode("8888");
- when(redisCache.getCacheObject("oauth:bind:replayed-key"))
- .thenReturn("line_rider@line-uid");
- when(redisCache.deleteObject("oauth:bind:replayed-key")).thenReturn(false);
- when(infoUserService.getOne(any(Wrapper.class))).thenReturn(activeUser(22L, "2"));
- AjaxResult result = controller.oauthBindPhone(request);
- assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
- verify(infoUserOauthMapper, never()).insert(any(InfoUserOauth.class));
- }
- @Test
- void lineMerchantBindingRejectsSubaccountWithUnavailableOwner() {
- OAuthBindDto request = new OAuthBindDto();
- request.setTempKey("merchant-key");
- when(redisCache.getCacheObject("oauth:bind:merchant-key"))
- .thenReturn("line_merchant@line-uid");
- when(redisCache.deleteObject("oauth:bind:merchant-key")).thenReturn(true);
- InfoUserOauth binding = new InfoUserOauth();
- binding.setUserId(55L);
- when(infoUserOauthMapper.selectOne(any())).thenReturn(binding);
- InfoUser subaccount = activeUser(55L, "5");
- subaccount.setSubaccountStatus("0");
- when(infoUserService.getById(55L)).thenReturn(subaccount);
- doThrow(new ServiceException("owner unavailable"))
- .when(merchantStoreAccessService).resolve(55L);
- AjaxResult result = controller.oauthBindPhone(request);
- assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
- assertEquals("账号已停用", result.get(AjaxResult.MSG_TAG));
- }
- @Test
- void oauthBindingRaceRejectsDisabledNonLineAccount() {
- OAuthBindDto request = new OAuthBindDto();
- request.setTempKey("google-key");
- when(redisCache.getCacheObject("oauth:bind:google-key"))
- .thenReturn("google@google-uid");
- when(redisCache.deleteObject("oauth:bind:google-key")).thenReturn(true);
- InfoUserOauth binding = new InfoUserOauth();
- binding.setUserId(66L);
- when(infoUserOauthMapper.selectOne(any())).thenReturn(binding);
- InfoUser disabled = activeUser(66L, "0");
- disabled.setStatus("1");
- when(infoUserService.getById(66L)).thenReturn(disabled);
- AjaxResult result = controller.oauthBindPhone(request);
- assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
- assertEquals("账号已停用", result.get(AjaxResult.MSG_TAG));
- }
- @ParameterizedTest
- @CsvSource({"apple_rider,2,qtw_tokens:qs:", "google_rider,2,qtw_tokens:qs:",
- "apple_merchant,1,qtw_tokens:sh:app:", "google_merchant,3,qtw_tokens:sh:app:"})
- void businessOauthBindsExistingBusinessPhoneAndIssuesItsOwnSession(
- String provider, String role, String tokenPrefix) {
- OAuthBindDto request = oauthRequest(provider);
- InfoUser business = activeUser(72L, role);
- business.setTelPhone(request.getPhone());
- when(infoUserService.list(any(Wrapper.class))).thenReturn(java.util.List.of(business));
- when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
- AjaxResult result = controller.oauthBindPhone(request);
- assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
- String token = (String) result.get("token");
- assertTrue(JWT.decode(token).getId().startsWith(tokenPrefix));
- assertEquals(provider, JWT.decode(token).getClaim("provider").asString());
- ArgumentCaptor<InfoUserOauth> binding = ArgumentCaptor.forClass(InfoUserOauth.class);
- verify(infoUserOauthMapper).insert(binding.capture());
- assertEquals(72L, binding.getValue().getUserId());
- assertEquals(provider, binding.getValue().getProvider());
- verify(infoUserService, never()).getuser(any());
- ArgumentCaptor<Wrapper<InfoUser>> query = ArgumentCaptor.forClass(Wrapper.class);
- verify(infoUserService).list(query.capture());
- assertTrue(query.getValue().getSqlSegment().contains("tel_phone"));
- }
- @ParameterizedTest
- @CsvSource({"apple_rider", "google_rider", "apple_merchant", "google_merchant"})
- void businessOauthCannotCreateAnAccount(String provider) {
- AjaxResult result = controller.oauthBindPhone(oauthRequest(provider));
- assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
- assertEquals("账号不存在", result.get(AjaxResult.MSG_TAG));
- verify(infoUserService, never()).saveOrUpdate(any(InfoUser.class));
- verify(infoUserOauthMapper, never()).insert(any(InfoUserOauth.class));
- }
- @ParameterizedTest
- @CsvSource({"apple_rider,1", "google_rider,0", "apple_merchant,2", "google_merchant,0"})
- void businessOauthRejectsBindingToTheWrongRole(String provider, String role) {
- OAuthBindDto request = oauthRequest(provider);
- InfoUserOauth binding = new InfoUserOauth();
- binding.setUserId(72L);
- when(infoUserOauthMapper.selectOne(any())).thenReturn(binding);
- when(infoUserService.getById(72L)).thenReturn(activeUser(72L, role));
- AjaxResult result = controller.oauthBindPhone(request);
- assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
- assertNull(result.get("token"));
- }
- @ParameterizedTest
- @CsvSource({"apple", "google"})
- void existingUserOauthKeepsProviderAndUserSession(String provider) {
- OAuthBindDto request = oauthRequest(provider);
- InfoUserOauth binding = new InfoUserOauth();
- binding.setUserId(72L);
- InfoUser user = activeUser(72L, "0");
- user.setPhone(request.getPhone());
- when(infoUserOauthMapper.selectOne(any())).thenReturn(binding);
- when(infoUserService.getById(72L)).thenReturn(user);
- AjaxResult result = controller.oauthBindPhone(request);
- assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
- String token = (String) result.get("token");
- assertTrue(JWT.decode(token).getId().startsWith("qtw_tokens:user:"));
- assertEquals(provider, JWT.decode(token).getClaim("provider").asString());
- }
- @ParameterizedTest
- @CsvSource({"apple_rider,2,qtw_tokens:qs:", "google_rider,2,qtw_tokens:qs:",
- "apple_merchant,4,qtw_tokens:sh:app:", "google_merchant,1,qtw_tokens:sh:app:",
- "apple,0,qtw_tokens:user:", "google,0,qtw_tokens:user:", "line,0,qtw_tokens:user:",
- "line_user,0,qtw_tokens:user:"})
- void boundOauthLoginKeepsEachClientSession(String provider, String role, String tokenPrefix) {
- OAuthLoginDto request = new OAuthLoginDto();
- request.setProvider(provider);
- request.setCredential("credential");
- when(oauthVerifyService.verify(provider, "credential")).thenReturn("provider-uid");
- InfoUserOauth binding = new InfoUserOauth();
- binding.setUserId(72L);
- when(infoUserOauthMapper.selectOne(any())).thenReturn(binding);
- InfoUser user = activeUser(72L, role);
- user.setPhone("0912345678");
- when(infoUserService.getOne(any(Wrapper.class))).thenReturn(user);
- AjaxResult result = controller.oauthLogin(request);
- assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
- assertTrue(JWT.decode((String) result.get("token")).getId().startsWith(tokenPrefix));
- assertEquals(provider, JWT.decode((String) result.get("token")).getClaim("provider").asString());
- if (provider.startsWith("line")) {
- ArgumentCaptor<LambdaQueryWrapper<InfoUserOauth>> query = ArgumentCaptor.forClass(LambdaQueryWrapper.class);
- verify(infoUserOauthMapper).selectOne(query.capture());
- query.getValue().getSqlSegment();
- assertTrue(query.getValue().getParamNameValuePairs().containsValue("line"));
- assertTrue(query.getValue().getParamNameValuePairs().containsValue("line_user"));
- }
- }
- @ParameterizedTest
- @CsvSource({"apple", "google"})
- void userFirstBindingStillCreatesAUserAndWallet(String provider) {
- OAuthBindDto request = oauthRequest(provider);
- InfoUser user = activeUser(72L, "0");
- user.setPhone(request.getPhone());
- when(infoUserService.list(any(Wrapper.class)))
- .thenReturn(java.util.List.of(), java.util.List.of(user));
- when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
- AjaxResult result = controller.oauthBindPhone(request);
- assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
- ArgumentCaptor<InfoUser> created = ArgumentCaptor.forClass(InfoUser.class);
- verify(infoUserService).saveOrUpdate(created.capture());
- assertEquals("0", created.getValue().getUserType());
- assertEquals(request.getPhone(), created.getValue().getPhone());
- verify(userWalletService).createUserWallet(72L);
- }
- @ParameterizedTest
- @CsvSource({"apple_merchant", "google_merchant"})
- void businessOauthRejectsSubaccountWhoseOwnerIsUnavailable(String provider) {
- OAuthBindDto request = oauthRequest(provider);
- InfoUser user = activeUser(72L, "5");
- user.setSubaccountStatus("0");
- when(infoUserService.list(any(Wrapper.class))).thenReturn(java.util.List.of(user));
- doThrow(new ServiceException("owner unavailable")).when(merchantStoreAccessService).resolve(72L);
- AjaxResult result = controller.oauthBindPhone(request);
- assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
- verify(infoUserOauthMapper, never()).insert(any(InfoUserOauth.class));
- }
- @Test
- void directBusinessLineLoginCannotBypassStateValidation() {
- OAuthLoginDto request = new OAuthLoginDto();
- request.setProvider("line_rider");
- request.setCredential("code");
- LineOAuthStateService stateService = mock(LineOAuthStateService.class);
- doThrow(new ServiceException("invalid state")).when(stateService).consume("line_rider", null);
- ReflectionTestUtils.setField(controller, "lineOAuthStateService", stateService);
- assertThrows(ServiceException.class, () -> controller.oauthLogin(request));
- verify(oauthVerifyService, never()).verify(any(), any());
- }
- @ParameterizedTest
- @CsvSource({"apple_rider", "google_merchant", "line_rider", "line_merchant"})
- void businessBindingRequiresAnActualSmsCode(String provider) {
- OAuthBindDto request = oauthRequest(provider);
- request.setCode("8888");
- InfoUser user = activeUser(72L, provider.endsWith("rider") ? "2" : "1");
- when(infoUserService.getOne(any(Wrapper.class))).thenReturn(user);
- when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);
- AjaxResult result = controller.oauthBindPhone(request);
- assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
- assertEquals("验证码错误", result.get(AjaxResult.MSG_TAG));
- verify(infoUserOauthMapper, never()).insert(any(InfoUserOauth.class));
- }
- @Test
- void oauthBindPhoneRejectsPhoneLinkedToMultipleAccounts() {
- OAuthBindDto request = oauthRequest("apple");
- when(infoUserService.list(any(Wrapper.class))).thenReturn(java.util.List.of(
- activeUser(71L, "0"), activeUser(72L, "0")));
- ServiceException exception = assertThrows(ServiceException.class,
- () -> controller.oauthBindPhone(request));
- assertEquals("该手机号关联多个账号,请联系平台", exception.getMessage());
- verify(infoUserOauthMapper, never()).insert(any(InfoUserOauth.class));
- verify(infoUserService, never()).saveOrUpdate(any(InfoUser.class));
- }
- @Test
- void merchantOauthBindPhoneRejectsPhoneLinkedToMultipleStores() {
- OAuthBindDto request = oauthRequest("line_merchant");
- when(infoUserService.list(any(Wrapper.class))).thenReturn(java.util.List.of(
- activeUser(71L, "1"), activeUser(72L, "1")));
- ServiceException exception = assertThrows(ServiceException.class,
- () -> controller.oauthBindPhone(request));
- assertEquals("该手机号关联多个账号,请联系平台", exception.getMessage());
- verify(infoUserOauthMapper, never()).insert(any(InfoUserOauth.class));
- }
- private OAuthBindDto oauthRequest(String provider) {
- OAuthBindDto request = new OAuthBindDto();
- request.setTempKey("business-key");
- request.setPhone("0912345678");
- request.setCode("456789");
- when(redisCache.getCacheObject("oauth:bind:business-key"))
- .thenReturn(provider + "@provider-uid");
- when(redisCache.deleteObject("oauth:bind:business-key")).thenReturn(true);
- when(redisCache.getCacheObject("0912345678")).thenReturn("456789");
- return request;
- }
- private InfoUser activeUser(Long userId, String userType) {
- InfoUser user = new InfoUser();
- user.setUserId(userId);
- user.setUserType(userType);
- user.setStatus("0");
- user.setDelFlag("0");
- user.setUserName("user-" + userId);
- return user;
- }
- private String tokenFor(Long userId) {
- return JwtUtil.setToken(String.valueOf(userId), "test-user");
- }
- private static class TestInfoUserController extends InfoUserController {
- @Override
- protected AjaxResult toAjax(int rows) {
- return new AjaxResult(HttpStatus.SUCCESS, "ok");
- }
- }
- }
|