package com.tcm.training.admin.app.settings.service; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.tcm.training.admin.app.auth.service.AppAuthSessionService; 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.settings.vo.AppSettingsProfileVO; 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.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 AppUserNotificationSettingMapper appUserNotificationSettingMapper; @Mock private ObsUploadService obsUploadService; @Captor private ArgumentCaptor appUserCaptor; private AppSettingsService appSettingsService; /** * 每个用例前重新构造被测设置服务。 */ @BeforeEach void setUp() { appSettingsService = new AppSettingsService( appAuthSessionService, userMapper, appSmsCodeService, passwordEncoder, appWechatAuthClient, 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"); AppSettingsProfileVO response = appSettingsService.bindPhone(request); verify(userMapper).updateById(appUserCaptor.capture()); assertEquals("13800138000", appUserCaptor.getValue().getPhone()); assertEquals("13800138000", response.getPhone()); } /** * 已绑定手机号的用户不允许走首次绑定接口。 */ @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 bindPhoneShouldRejectOccupiedPhone() { 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()); } /** * 构造启用状态 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; } }