|
|
@@ -83,6 +83,7 @@ class InfoUserControllerTest {
|
|
|
private BusinessPhoneService businessPhoneService;
|
|
|
private OAuthVerifyService oauthVerifyService;
|
|
|
private InfoUserOauthMapper infoUserOauthMapper;
|
|
|
+ private InfoUserDeviceMapper deviceMapper;
|
|
|
private RedisCache redisCache;
|
|
|
private static ConfigurableListableBeanFactory originalBeanFactory;
|
|
|
|
|
|
@@ -104,6 +105,11 @@ class InfoUserControllerTest {
|
|
|
messageSource.addMessage("merchant.subaccount.platform.managed", Locale.getDefault(),
|
|
|
"分管账号只能通过专用入口管理");
|
|
|
messageSource.addMessage("no.oauth.phone.blank", Locale.getDefault(), "手机号不能为空");
|
|
|
+ messageSource.addMessage("no.oauth.username.blank", Locale.getDefault(), "账号不能为空");
|
|
|
+ messageSource.addMessage("no.oauth.username.duplicate", Locale.getDefault(),
|
|
|
+ "该账号名关联多个账号,请联系平台");
|
|
|
+ messageSource.addMessage("no.user.password.not.null", Locale.getDefault(), "密码不能为空");
|
|
|
+ messageSource.addMessage("no.user.password.error", Locale.getDefault(), "密码错误");
|
|
|
messageSource.addMessage("no.oauth.needphone", Locale.getDefault(), "请绑定手机号");
|
|
|
messageSource.addMessage("no.user.login.success", Locale.getDefault(), "登录成功");
|
|
|
messageSource.addMessage("no.user.stop", Locale.getDefault(), "账号已停用");
|
|
|
@@ -148,8 +154,9 @@ class InfoUserControllerTest {
|
|
|
ReflectionTestUtils.setField(controller, "oauthVerifyService", oauthVerifyService);
|
|
|
ReflectionTestUtils.setField(controller, "infoUserOauthMapper", infoUserOauthMapper);
|
|
|
ReflectionTestUtils.setField(controller, "redisCache", redisCache);
|
|
|
+ deviceMapper = mock(InfoUserDeviceMapper.class);
|
|
|
ReflectionTestUtils.setField(controller, "deviceTrustService",
|
|
|
- new DeviceTrustService(mock(InfoUserDeviceMapper.class)));
|
|
|
+ new DeviceTrustService(deviceMapper));
|
|
|
when(infoUserOauthMapper.insert(any(InfoUserOauth.class))).thenReturn(1);
|
|
|
}
|
|
|
|
|
|
@@ -563,15 +570,26 @@ class InfoUserControllerTest {
|
|
|
@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(
|
|
|
+ void businessOauthBindsWithAccountPasswordAndIssuesItsOwnSession(
|
|
|
String provider, String role, String tokenPrefix) {
|
|
|
OAuthBindDto request = oauthRequest(provider);
|
|
|
+ // 业务端不再依赖手机号/短信(子账号可不登记 tel_phone),只提交账号密码
|
|
|
+ request.setPhone(null);
|
|
|
+ request.setCode(null);
|
|
|
+ request.setUserName("store-owner");
|
|
|
+ request.setPassword("encrypted-password");
|
|
|
InfoUser business = activeUser(72L, role);
|
|
|
- business.setTelPhone(request.getPhone());
|
|
|
+ business.setUserName("store-owner");
|
|
|
+ // 存储密文与提交密文不同(RSA 每次加密结果不同),防止实现退化为密文直接比对
|
|
|
+ business.setPassword("stored-encrypted-password");
|
|
|
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);
|
|
|
+ AjaxResult result;
|
|
|
+ try (MockedConstruction<RsaMima> ignored = mockConstruction(RsaMima.class,
|
|
|
+ (rsa, context) -> when(rsa.decryptByPrivateKey(any())).thenReturn("password"))) {
|
|
|
+ result = controller.oauthBindPhone(request);
|
|
|
+ }
|
|
|
|
|
|
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
|
|
|
String token = (String) result.get("token");
|
|
|
@@ -584,13 +602,19 @@ class InfoUserControllerTest {
|
|
|
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"));
|
|
|
+ assertTrue(query.getValue().getSqlSegment().contains("user_name"));
|
|
|
+ // 业务端绑定未经手机验证,不写设备信任(029 仅用户端适用)
|
|
|
+ org.mockito.Mockito.verifyNoInteractions(deviceMapper);
|
|
|
}
|
|
|
|
|
|
@ParameterizedTest
|
|
|
@CsvSource({"apple_rider", "google_rider", "apple_merchant", "google_merchant"})
|
|
|
void businessOauthCannotCreateAnAccount(String provider) {
|
|
|
- AjaxResult result = controller.oauthBindPhone(oauthRequest(provider));
|
|
|
+ OAuthBindDto request = oauthRequest(provider);
|
|
|
+ request.setUserName("ghost-account");
|
|
|
+ request.setPassword("encrypted-password");
|
|
|
+
|
|
|
+ AjaxResult result = controller.oauthBindPhone(request);
|
|
|
|
|
|
assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
|
|
|
assertEquals("账号不存在", result.get(AjaxResult.MSG_TAG));
|
|
|
@@ -890,43 +914,89 @@ class InfoUserControllerTest {
|
|
|
|
|
|
@ParameterizedTest
|
|
|
@CsvSource({"apple_rider", "google_merchant", "line_rider", "line_merchant"})
|
|
|
- void businessBindingRequiresAnActualSmsCode(String provider) {
|
|
|
+ void businessOauthBindingRejectsWrongPassword(String provider) {
|
|
|
OAuthBindDto request = oauthRequest(provider);
|
|
|
- request.setCode("8888");
|
|
|
+ request.setUserName("store-owner");
|
|
|
+ request.setPassword("encrypted-password");
|
|
|
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);
|
|
|
+ user.setPassword("stored-cipher");
|
|
|
+ when(infoUserService.list(any(Wrapper.class))).thenReturn(java.util.List.of(user));
|
|
|
+
|
|
|
+ try (MockedConstruction<RsaMima> ignored = mockConstruction(RsaMima.class,
|
|
|
+ (rsa, context) -> when(rsa.decryptByPrivateKey(any()))
|
|
|
+ .thenReturn("submitted-password", "stored-password"))) {
|
|
|
+ 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));
|
|
|
+ verify(infoUserService, never()).saveOrUpdate(any(InfoUser.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void businessOauthBindingRejectsDuplicateUserName() {
|
|
|
+ OAuthBindDto request = oauthRequest("apple_merchant");
|
|
|
+ request.setUserName("duplicated-name");
|
|
|
+ request.setPassword("encrypted-password");
|
|
|
+ when(infoUserService.list(any(Wrapper.class))).thenReturn(java.util.List.of(
|
|
|
+ activeUser(71L, "1"), activeUser(72L, "1")));
|
|
|
+
|
|
|
AjaxResult result = controller.oauthBindPhone(request);
|
|
|
+
|
|
|
assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
|
|
|
- assertEquals("验证码错误", result.get(AjaxResult.MSG_TAG));
|
|
|
+ assertEquals("该账号名关联多个账号,请联系平台", result.get(AjaxResult.MSG_TAG));
|
|
|
+ verify(infoUserOauthMapper, never()).insert(any(InfoUserOauth.class));
|
|
|
+ verify(infoUserService, never()).saveOrUpdate(any(InfoUser.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ @ParameterizedTest
|
|
|
+ @CsvSource({"true,false", "false,true"})
|
|
|
+ void businessOauthBindingRequiresUserNameAndPassword(boolean withName, boolean withPassword) {
|
|
|
+ OAuthBindDto request = oauthRequest("line_merchant");
|
|
|
+ request.setUserName(withName ? "store-owner" : null);
|
|
|
+ request.setPassword(withPassword ? "encrypted-password" : null);
|
|
|
+
|
|
|
+ AjaxResult result = controller.oauthBindPhone(request);
|
|
|
+
|
|
|
+ assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
|
|
|
+ assertEquals(withName ? "密码不能为空" : "账号不能为空", 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")));
|
|
|
+ void businessOauthBindingRejectsDeletedAccount() {
|
|
|
+ OAuthBindDto request = oauthRequest("apple_merchant");
|
|
|
+ request.setUserName("deleted-owner");
|
|
|
+ request.setPassword("encrypted-password");
|
|
|
+ InfoUser deleted = activeUser(72L, "1");
|
|
|
+ deleted.setUserName("deleted-owner");
|
|
|
+ deleted.setPassword("encrypted-password");
|
|
|
+ deleted.setDelFlag("2");
|
|
|
+ when(infoUserService.list(any(Wrapper.class))).thenReturn(java.util.List.of(deleted));
|
|
|
|
|
|
- ServiceException exception = assertThrows(ServiceException.class,
|
|
|
- () -> controller.oauthBindPhone(request));
|
|
|
+ AjaxResult result;
|
|
|
+ try (MockedConstruction<RsaMima> ignored = mockConstruction(RsaMima.class,
|
|
|
+ (rsa, context) -> when(rsa.decryptByPrivateKey(any())).thenReturn("password"))) {
|
|
|
+ result = controller.oauthBindPhone(request);
|
|
|
+ }
|
|
|
|
|
|
- assertEquals("该手机号关联多个账号,请联系平台", exception.getMessage());
|
|
|
+ assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
|
|
|
verify(infoUserOauthMapper, never()).insert(any(InfoUserOauth.class));
|
|
|
verify(infoUserService, never()).saveOrUpdate(any(InfoUser.class));
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void merchantOauthBindPhoneRejectsPhoneLinkedToMultipleStores() {
|
|
|
- OAuthBindDto request = oauthRequest("line_merchant");
|
|
|
+ void oauthBindPhoneRejectsPhoneLinkedToMultipleAccounts() {
|
|
|
+ OAuthBindDto request = oauthRequest("apple");
|
|
|
when(infoUserService.list(any(Wrapper.class))).thenReturn(java.util.List.of(
|
|
|
- activeUser(71L, "1"), activeUser(72L, "1")));
|
|
|
+ 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));
|
|
|
}
|
|
|
|
|
|
private OAuthBindDto oauthRequest(String provider) {
|