package com.tcm.training.admin.app.register.service; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.fasterxml.jackson.databind.ObjectMapper; import com.tcm.training.admin.app.auth.service.AppSmsCodeService; import com.tcm.training.admin.app.integration.haizhentong.entity.HztAppUser; import com.tcm.training.admin.app.integration.haizhentong.entity.HztClinic; import com.tcm.training.admin.app.integration.haizhentong.mapper.HztAppUserMapper; import com.tcm.training.admin.app.integration.haizhentong.mapper.HztClinicMapper; import com.tcm.training.admin.app.register.dto.AppRegisterOperatorPrepareRequest; import com.tcm.training.admin.app.register.dto.AppRegisterUserRequest; import com.tcm.training.admin.app.register.vo.AppRegisterOperatorPrepareResponse; import com.tcm.training.admin.app.register.vo.AppRegisterResultVO; import com.tcm.training.admin.live.entity.Clinic; import com.tcm.training.admin.live.mapper.ClinicMapper; import com.tcm.training.admin.member.entity.AppUser; import com.tcm.training.admin.member.mapper.UserMapper; 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.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * APP 注册服务测试。 */ @ExtendWith(MockitoExtension.class) class AppRegisterServiceTest { @BeforeAll static void initLambdaCache() { MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), ""); TableInfoHelper.initTableInfo(assistant, AppUser.class); TableInfoHelper.initTableInfo(assistant, Clinic.class); TableInfoHelper.initTableInfo(assistant, HztAppUser.class); TableInfoHelper.initTableInfo(assistant, HztClinic.class); } @Mock private UserMapper userMapper; @Mock private ClinicMapper clinicMapper; @Mock private BCryptPasswordEncoder passwordEncoder; @Mock private AppSmsCodeService appSmsCodeService; @Mock private AppRegisterRegionService appRegisterRegionService; @Mock private HztAppUserMapper hztAppUserMapper; @Mock private HztClinicMapper hztClinicMapper; @Mock private StringRedisTemplate stringRedisTemplate; @Mock private ValueOperations valueOperations; private AppRegisterService appRegisterService; @Captor private ArgumentCaptor appUserCaptor; /** * 使用 Mockito mock 重新组装被测服务。 */ @BeforeEach void setUp() { appRegisterService = new AppRegisterService( userMapper, clinicMapper, passwordEncoder, appSmsCodeService, appRegisterRegionService, hztAppUserMapper, hztClinicMapper, stringRedisTemplate, new ObjectMapper() ); } /** * 普通用户注册时应同步海诊通资料并绑定邀请码对应的上级诊所用户。 */ @Test void registerUserShouldSyncHaizhentongProfileAndBindSuperiorClinicUser() { AppRegisterUserRequest request = new AppRegisterUserRequest(); request.setPhone("13800138000"); request.setCode("666666"); request.setPassword("abc123!!"); request.setInviteCode("abc123"); HztAppUser hztAppUser = new HztAppUser(); hztAppUser.setNickName("海诊通昵称"); hztAppUser.setAvatar("https://cdn.test/avatar.png"); hztAppUser.setSex(2); Clinic clinic = new Clinic(); clinic.setId(19L); AppUser clinicUser = new AppUser(); clinicUser.setId(88L); clinicUser.setClinicId(19L); when(passwordEncoder.encode("abc123!!")).thenReturn("ENCODED"); when(hztAppUserMapper.selectOne(any())).thenReturn(hztAppUser); when(clinicMapper.selectOne(any())).thenReturn(clinic); when(userMapper.selectOne(any())).thenReturn(null, clinicUser); doNothing().when(appSmsCodeService).validateRegisterCode("13800138000", "666666"); when(userMapper.insert(any(AppUser.class))).thenAnswer(invocation -> { AppUser inserted = invocation.getArgument(0); inserted.setId(1001L); return 1; }); AppRegisterResultVO result = appRegisterService.registerUser(request); verify(userMapper).insert(appUserCaptor.capture()); AppUser inserted = appUserCaptor.getValue(); assertEquals("海诊通昵称", inserted.getNickname()); assertEquals("https://cdn.test/avatar.png", inserted.getAvatar()); assertEquals(Integer.valueOf(0), inserted.getGender()); assertEquals(Long.valueOf(19L), inserted.getClinicId()); assertEquals(Long.valueOf(88L), inserted.getSuperiorClinicUserId()); assertEquals("ENCODED", inserted.getPassword()); assertEquals(Long.valueOf(1001L), result.getUserId()); assertEquals(Integer.valueOf(0), result.getUserType()); } /** * 普通用户注册在海诊通未命中时应回退为手机号兜底昵称。 */ @Test void registerUserShouldFallbackToPhoneNicknameWhenHaizhentongUserMissing() { AppRegisterUserRequest request = new AppRegisterUserRequest(); request.setPhone("13800138123"); request.setCode("666666"); request.setPassword("abc123!!"); when(passwordEncoder.encode("abc123!!")).thenReturn("ENCODED"); when(userMapper.selectOne(any())).thenReturn(null); when(hztAppUserMapper.selectOne(any())).thenReturn(null); doNothing().when(appSmsCodeService).validateRegisterCode("13800138123", "666666"); when(userMapper.insert(any(AppUser.class))).thenAnswer(invocation -> { AppUser inserted = invocation.getArgument(0); inserted.setId(1002L); return 1; }); AppRegisterResultVO result = appRegisterService.registerUser(request); verify(userMapper).insert(appUserCaptor.capture()); AppUser inserted = appUserCaptor.getValue(); assertEquals("138****8123", inserted.getNickname()); assertNull(inserted.getAvatar()); assertNull(inserted.getGender()); assertEquals(Long.valueOf(1002L), result.getUserId()); assertEquals(Integer.valueOf(0), result.getUserType()); } /** * 经营者第一步在海诊通未命中诊所时仍应允许继续进入第二步手填。 */ @Test void prepareOperatorShouldAllowManualWhenClinicNotMatched() { AppRegisterOperatorPrepareRequest request = new AppRegisterOperatorPrepareRequest(); request.setPhone("13800138001"); request.setCode("666666"); request.setPassword("abc123!!"); when(passwordEncoder.encode("abc123!!")).thenReturn("ENCODED"); when(userMapper.selectOne(any())).thenReturn(null); when(hztClinicMapper.selectOne(any())).thenReturn(null); when(stringRedisTemplate.opsForValue()).thenReturn(valueOperations); doNothing().when(appSmsCodeService).validateRegisterCode("13800138001", "666666"); AppRegisterOperatorPrepareResponse response = appRegisterService.prepareOperatorRegister(request); assertTrue(response.getRegisterTicket() != null && !response.getRegisterTicket().trim().isEmpty()); assertFalse(response.isClinicMatched()); assertNotNull(response.getClinicInfo()); assertNull(response.getClinicInfo().getInstitutionName()); verify(valueOperations).set(any(String.class), any(String.class), anyLong(), any(TimeUnit.class)); } /** * 经营者第一步应使用 sys_region 解析后的地区编码回填诊所信息。 */ @Test void prepareOperatorShouldFillResolvedRegionSelection() { AppRegisterOperatorPrepareRequest request = new AppRegisterOperatorPrepareRequest(); request.setPhone("13800138002"); request.setCode("666666"); request.setPassword("abc123!!"); HztClinic hztClinic = new HztClinic(); hztClinic.setClinicName("海诊通诊所"); hztClinic.setProvinceName("浙江省"); hztClinic.setCityName("杭州市"); hztClinic.setDistrictName("西湖区"); hztClinic.setClinicAddress("文三路 1 号"); AppRegisterRegionService.RegionSelection regionSelection = new AppRegisterRegionService.RegionSelection(); regionSelection.setProvinceCode("330000"); regionSelection.setProvinceName("浙江省"); regionSelection.setCityCode("330100"); regionSelection.setCityName("杭州市"); regionSelection.setDistrictCode("330106"); regionSelection.setDistrictName("西湖区"); when(passwordEncoder.encode("abc123!!")).thenReturn("ENCODED"); when(userMapper.selectOne(any())).thenReturn(null); when(hztClinicMapper.selectOne(any())).thenReturn(hztClinic); when(appRegisterRegionService.resolveRegionSelection(null, "浙江省", null, "杭州市", null, "西湖区")).thenReturn(regionSelection); when(stringRedisTemplate.opsForValue()).thenReturn(valueOperations); doNothing().when(appSmsCodeService).validateRegisterCode("13800138002", "666666"); AppRegisterOperatorPrepareResponse response = appRegisterService.prepareOperatorRegister(request); assertTrue(response.isClinicMatched()); assertEquals("海诊通诊所", response.getClinicInfo().getInstitutionName()); assertEquals("330000", response.getClinicInfo().getProvinceCode()); assertEquals("330100", response.getClinicInfo().getCityCode()); assertEquals("330106", response.getClinicInfo().getDistrictCode()); assertEquals("文三路 1 号", response.getClinicInfo().getDetailAddress()); } }