package com.tcm.training.admin.app.settings.service;
|
|
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
import cn.dev33.satoken.stp.SaTokenInfo;
|
import com.tcm.training.admin.app.auth.service.AppAuthSessionService;
|
import com.tcm.training.admin.app.auth.vo.AppLoginResponse;
|
import com.tcm.training.admin.app.auth.service.AppSmsCodeService;
|
import com.tcm.training.admin.app.auth.service.AppWechatAuthClient;
|
import com.tcm.training.admin.app.settings.dto.AppSettingsPhoneBindRequest;
|
import com.tcm.training.admin.app.settings.mapper.AppUserNotificationSettingMapper;
|
import com.tcm.training.admin.app.share.service.AppShareService;
|
import com.tcm.training.admin.member.entity.AppUser;
|
import com.tcm.training.admin.member.mapper.UserMapper;
|
import com.tcm.training.admin.upload.ObsUploadService;
|
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.mockito.ArgumentCaptor;
|
import org.mockito.Captor;
|
import org.mockito.Mock;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.Mockito.doNothing;
|
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.when;
|
|
/**
|
* APP 设置服务手机号绑定测试。
|
*/
|
@ExtendWith(MockitoExtension.class)
|
class AppSettingsServiceTest {
|
|
@BeforeAll
|
static void initLambdaCache() {
|
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
|
TableInfoHelper.initTableInfo(assistant, AppUser.class);
|
}
|
|
@Mock
|
private AppAuthSessionService appAuthSessionService;
|
|
@Mock
|
private UserMapper userMapper;
|
|
@Mock
|
private AppSmsCodeService appSmsCodeService;
|
|
@Mock
|
private BCryptPasswordEncoder passwordEncoder;
|
|
@Mock
|
private AppWechatAuthClient appWechatAuthClient;
|
|
@Mock
|
private AppShareService appShareService;
|
|
@Mock
|
private AppUserNotificationSettingMapper appUserNotificationSettingMapper;
|
|
@Mock
|
private ObsUploadService obsUploadService;
|
|
@Captor
|
private ArgumentCaptor<AppUser> appUserCaptor;
|
|
private AppSettingsService appSettingsService;
|
|
/**
|
* 每个用例前重新构造被测设置服务。
|
*/
|
@BeforeEach
|
void setUp() {
|
appSettingsService = new AppSettingsService(
|
appAuthSessionService,
|
userMapper,
|
appSmsCodeService,
|
passwordEncoder,
|
appWechatAuthClient,
|
appShareService,
|
appUserNotificationSettingMapper,
|
obsUploadService
|
);
|
}
|
|
/**
|
* 微信登录后未绑定手机号的用户,可通过验证码首次绑定手机号。
|
*/
|
@Test
|
void bindPhoneShouldUpdateCurrentUserWhenPhoneMissing() {
|
AppSettingsPhoneBindRequest request = new AppSettingsPhoneBindRequest();
|
request.setNewPhone("13800138000");
|
request.setCode("123456");
|
AppUser currentUser = buildEnabledUser(null);
|
|
when(appAuthSessionService.requireLoginUserId()).thenReturn(1L);
|
when(userMapper.selectById(1L)).thenReturn(currentUser);
|
when(userMapper.selectOne(any())).thenReturn(null);
|
doNothing().when(appSmsCodeService).validatePhoneChangeCode("13800138000", "123456");
|
when(appAuthSessionService.getTokenInfo()).thenReturn(buildToken("bind-token"));
|
|
AppLoginResponse response = appSettingsService.bindPhone(request);
|
|
verify(userMapper).updateById(appUserCaptor.capture());
|
verify(appAuthSessionService).login(1L);
|
assertEquals("13800138000", appUserCaptor.getValue().getPhone());
|
assertEquals("13800138000", response.getUser().getPhone());
|
assertEquals("bind-token", response.getToken());
|
}
|
|
/**
|
* 已绑定手机号的用户不允许走首次绑定接口。
|
*/
|
@Test
|
void bindPhoneShouldRejectWhenCurrentUserAlreadyHasPhone() {
|
AppSettingsPhoneBindRequest request = new AppSettingsPhoneBindRequest();
|
request.setNewPhone("13800138000");
|
request.setCode("123456");
|
|
when(appAuthSessionService.requireLoginUserId()).thenReturn(1L);
|
when(userMapper.selectById(1L)).thenReturn(buildEnabledUser("13900139000"));
|
|
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
|
() -> appSettingsService.bindPhone(request));
|
|
assertEquals("当前账号已绑定手机号", exception.getMessage());
|
verify(appSmsCodeService, never()).validatePhoneChangeCode(any(), any());
|
verify(userMapper, never()).updateById(any());
|
}
|
|
/**
|
* 微信临时账号绑定已注册但未绑定微信的手机号时,应合并到手机号账号。
|
*/
|
@Test
|
void bindPhoneShouldMergeWechatUserIntoExistingPhoneUser() {
|
AppSettingsPhoneBindRequest request = new AppSettingsPhoneBindRequest();
|
request.setNewPhone("13800138000");
|
request.setCode("123456");
|
AppUser currentUser = buildEnabledUser(null);
|
currentUser.setWechatOpenid("wx-openid");
|
currentUser.setNickname(null);
|
currentUser.setWechatNickname("微信昵称");
|
AppUser occupiedUser = buildEnabledUser("13800138000");
|
occupiedUser.setId(2L);
|
occupiedUser.setNickname(null);
|
|
when(appAuthSessionService.requireLoginUserId()).thenReturn(1L);
|
when(userMapper.selectById(1L)).thenReturn(currentUser);
|
when(userMapper.selectOne(any())).thenReturn(occupiedUser);
|
doNothing().when(appSmsCodeService).validatePhoneChangeCode("13800138000", "123456");
|
when(appAuthSessionService.getTokenInfo()).thenReturn(buildToken("merged-token"));
|
|
AppLoginResponse response = appSettingsService.bindPhone(request);
|
|
verify(userMapper, times(2)).updateById(appUserCaptor.capture());
|
AppUser mergedUser = appUserCaptor.getAllValues().get(0);
|
AppUser deletedTemporaryUser = appUserCaptor.getAllValues().get(1);
|
verify(appAuthSessionService).login(2L);
|
assertEquals("wx-openid", mergedUser.getWechatOpenid());
|
assertEquals("微信昵称", mergedUser.getNickname());
|
assertEquals(Integer.valueOf(1), deletedTemporaryUser.getDeleted());
|
assertEquals(Long.valueOf(2L), response.getUser().getId());
|
assertEquals("merged-token", response.getToken());
|
}
|
|
/**
|
* 微信临时账号绑定已绑定微信的手机号时,应直接覆盖目标账号微信票据。
|
*/
|
@Test
|
void bindPhoneShouldOverwriteWechatBindingWhenPhoneUserAlreadyBoundWechat() {
|
AppSettingsPhoneBindRequest request = new AppSettingsPhoneBindRequest();
|
request.setNewPhone("13800138000");
|
request.setCode("123456");
|
AppUser currentUser = buildEnabledUser(null);
|
currentUser.setWechatOpenid("wx-openid");
|
currentUser.setWechatNickname("新微信昵称");
|
AppUser occupiedUser = buildEnabledUser("13800138000");
|
occupiedUser.setId(2L);
|
occupiedUser.setWechatOpenid("other-wx-openid");
|
|
when(appAuthSessionService.requireLoginUserId()).thenReturn(1L);
|
when(userMapper.selectById(1L)).thenReturn(currentUser);
|
when(userMapper.selectOne(any())).thenReturn(occupiedUser);
|
doNothing().when(appSmsCodeService).validatePhoneChangeCode("13800138000", "123456");
|
when(appAuthSessionService.getTokenInfo()).thenReturn(buildToken("wechat-overwrite-token"));
|
|
AppLoginResponse response = appSettingsService.bindPhone(request);
|
|
verify(userMapper, times(2)).updateById(appUserCaptor.capture());
|
AppUser mergedUser = appUserCaptor.getAllValues().get(0);
|
AppUser deletedTemporaryUser = appUserCaptor.getAllValues().get(1);
|
verify(appAuthSessionService).login(2L);
|
assertEquals("wx-openid", mergedUser.getWechatOpenid());
|
assertEquals("新微信昵称", mergedUser.getWechatNickname());
|
assertEquals(Integer.valueOf(1), deletedTemporaryUser.getDeleted());
|
assertEquals(Long.valueOf(2L), response.getUser().getId());
|
}
|
|
/**
|
* 苹果临时账号绑定已注册但未绑定苹果的手机号时,应合并到手机号账号。
|
*/
|
@Test
|
void bindPhoneShouldMergeAppleUserIntoExistingPhoneUser() {
|
AppSettingsPhoneBindRequest request = new AppSettingsPhoneBindRequest();
|
request.setNewPhone("13800138000");
|
request.setCode("123456");
|
AppUser currentUser = buildEnabledUser(null);
|
currentUser.setAppleIdentitySubject("apple-subject");
|
currentUser.setAppleUserIdentifier("apple-user");
|
AppUser occupiedUser = buildEnabledUser("13800138000");
|
occupiedUser.setId(2L);
|
|
when(appAuthSessionService.requireLoginUserId()).thenReturn(1L);
|
when(userMapper.selectById(1L)).thenReturn(currentUser);
|
when(userMapper.selectOne(any())).thenReturn(occupiedUser);
|
doNothing().when(appSmsCodeService).validatePhoneChangeCode("13800138000", "123456");
|
when(appAuthSessionService.getTokenInfo()).thenReturn(buildToken("apple-merged-token"));
|
|
AppLoginResponse response = appSettingsService.bindPhone(request);
|
|
verify(userMapper, times(2)).updateById(appUserCaptor.capture());
|
AppUser mergedUser = appUserCaptor.getAllValues().get(0);
|
verify(appAuthSessionService).login(2L);
|
assertEquals("apple-subject", mergedUser.getAppleIdentitySubject());
|
assertEquals("apple-user", mergedUser.getAppleUserIdentifier());
|
assertEquals(Long.valueOf(2L), response.getUser().getId());
|
}
|
|
/**
|
* 苹果临时账号绑定已绑定苹果的手机号时,应直接覆盖目标账号苹果票据。
|
*/
|
@Test
|
void bindPhoneShouldOverwriteAppleBindingWhenPhoneUserAlreadyBoundApple() {
|
AppSettingsPhoneBindRequest request = new AppSettingsPhoneBindRequest();
|
request.setNewPhone("13800138000");
|
request.setCode("123456");
|
AppUser currentUser = buildEnabledUser(null);
|
currentUser.setAppleIdentitySubject("apple-subject");
|
currentUser.setAppleUserIdentifier("apple-user");
|
AppUser occupiedUser = buildEnabledUser("13800138000");
|
occupiedUser.setId(2L);
|
occupiedUser.setAppleIdentitySubject("other-apple-subject");
|
|
when(appAuthSessionService.requireLoginUserId()).thenReturn(1L);
|
when(userMapper.selectById(1L)).thenReturn(currentUser);
|
when(userMapper.selectOne(any())).thenReturn(occupiedUser);
|
doNothing().when(appSmsCodeService).validatePhoneChangeCode("13800138000", "123456");
|
when(appAuthSessionService.getTokenInfo()).thenReturn(buildToken("apple-overwrite-token"));
|
|
AppLoginResponse response = appSettingsService.bindPhone(request);
|
|
verify(userMapper, times(2)).updateById(appUserCaptor.capture());
|
AppUser mergedUser = appUserCaptor.getAllValues().get(0);
|
AppUser deletedTemporaryUser = appUserCaptor.getAllValues().get(1);
|
verify(appAuthSessionService).login(2L);
|
assertEquals("apple-subject", mergedUser.getAppleIdentitySubject());
|
assertEquals("apple-user", mergedUser.getAppleUserIdentifier());
|
assertEquals(Integer.valueOf(1), deletedTemporaryUser.getDeleted());
|
assertEquals(Long.valueOf(2L), response.getUser().getId());
|
}
|
|
/**
|
* 非第三方临时账号绑定已注册手机号时仍按占用处理。
|
*/
|
@Test
|
void bindPhoneShouldRejectOccupiedPhoneWithoutThirdPartyBinding() {
|
AppSettingsPhoneBindRequest request = new AppSettingsPhoneBindRequest();
|
request.setNewPhone("13800138000");
|
request.setCode("123456");
|
AppUser occupiedUser = buildEnabledUser("13800138000");
|
occupiedUser.setId(2L);
|
|
when(appAuthSessionService.requireLoginUserId()).thenReturn(1L);
|
when(userMapper.selectById(1L)).thenReturn(buildEnabledUser(null));
|
when(userMapper.selectOne(any())).thenReturn(occupiedUser);
|
|
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
|
() -> appSettingsService.bindPhone(request));
|
|
assertEquals("该手机号已注册", exception.getMessage());
|
verify(appSmsCodeService, never()).validatePhoneChangeCode(any(), any());
|
verify(userMapper, never()).updateById(any());
|
}
|
|
/**
|
* 验证码错误时不应更新用户手机号。
|
*/
|
@Test
|
void bindPhoneShouldNotUpdateWhenSmsCodeInvalid() {
|
AppSettingsPhoneBindRequest request = new AppSettingsPhoneBindRequest();
|
request.setNewPhone("13800138000");
|
request.setCode("000000");
|
|
when(appAuthSessionService.requireLoginUserId()).thenReturn(1L);
|
when(userMapper.selectById(1L)).thenReturn(buildEnabledUser(null));
|
when(userMapper.selectOne(any())).thenReturn(null);
|
org.mockito.Mockito.doThrow(new IllegalArgumentException("验证码错误"))
|
.when(appSmsCodeService).validatePhoneChangeCode("13800138000", "000000");
|
|
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
|
() -> appSettingsService.bindPhone(request));
|
|
assertEquals("验证码错误", exception.getMessage());
|
verify(userMapper, never()).updateById(any());
|
}
|
|
/**
|
* 构造测试登录态。
|
*/
|
private SaTokenInfo buildToken(String tokenValue) {
|
SaTokenInfo tokenInfo = new SaTokenInfo();
|
tokenInfo.setTokenValue(tokenValue);
|
tokenInfo.setTokenTimeout(7200L);
|
return tokenInfo;
|
}
|
|
/**
|
* 构造启用状态 APP 用户。
|
*/
|
private AppUser buildEnabledUser(String phone) {
|
AppUser user = new AppUser();
|
user.setId(1L);
|
user.setNickname("微信用户");
|
user.setAvatar("avatar.png");
|
user.setPhone(phone);
|
user.setType(0);
|
user.setStatus(1);
|
user.setDeleted(0);
|
return user;
|
}
|
}
|