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.AppAuthSessionService; import com.tcm.training.admin.app.auth.service.AppSmsCodeService; import com.tcm.training.admin.app.auth.vo.AppLoginResponse; 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.AppRegisterOperatorCompleteRequest; 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.share.service.AppShareService; 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 cn.dev33.satoken.stp.SaTokenInfo; import com.tcm.training.admin.utils.VzanHttpUtil; 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 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.never; 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 AppSmsCodeService appSmsCodeService; @Mock private AppAuthSessionService appAuthSessionService; @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; @Mock private AppShareService appShareService; @Mock private VzanHttpUtil vzanHttpUtil; /** * 使用 Mockito mock 重新组装被测服务。 */ @BeforeEach void setUp() { appRegisterService = new AppRegisterService( userMapper, clinicMapper, appSmsCodeService, appAuthSessionService, appRegisterRegionService, hztAppUserMapper, hztClinicMapper, stringRedisTemplate, new ObjectMapper(), appShareService, vzanHttpUtil ); } /** * 普通用户注册时应同步海诊通资料并绑定邀请码对应的上级诊所用户。 */ @Test void registerUserShouldSyncHaizhentongProfileAndBindSuperiorClinicUser() { AppRegisterUserRequest request = new AppRegisterUserRequest(); request.setPhone("13800138000"); request.setCode("666666"); 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); AppShareService.InviteBindingTarget bindingTarget = new AppShareService.InviteBindingTarget(); bindingTarget.setClinicId(19L); bindingTarget.setSuperiorClinicUserId(88L); when(appShareService.resolveInviteBindingTarget("ABC123")).thenReturn(bindingTarget); when(hztAppUserMapper.selectOne(any())).thenReturn(hztAppUser); when(userMapper.selectOne(any())).thenReturn(null); doNothing().when(appSmsCodeService).validateRegisterCode("13800138000", "666666"); when(userMapper.insert(any(AppUser.class))).thenAnswer(invocation -> { AppUser inserted = invocation.getArgument(0); inserted.setId(1001L); return 1; }); when(appAuthSessionService.getTokenInfo()).thenReturn(buildTokenInfo("register-token", 7200L)); AppLoginResponse result = appRegisterService.registerUser(request); verify(userMapper).insert(appUserCaptor.capture()); verify(appAuthSessionService).login(1001L); 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()); assertNull(inserted.getPassword()); assertEquals("register-token", result.getToken()); assertEquals("Bearer", result.getTokenType()); assertEquals(7200L, result.getExpiresIn()); assertEquals(Long.valueOf(1001L), result.getUser().getId()); assertEquals(Integer.valueOf(0), result.getUser().getType()); } /** * 普通用户注册在海诊通未命中时应回退为手机号兜底昵称。 */ @Test void registerUserShouldFallbackToPhoneNicknameWhenHaizhentongUserMissing() { AppRegisterUserRequest request = new AppRegisterUserRequest(); request.setPhone("13800138123"); request.setCode("666666"); 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; }); when(appAuthSessionService.getTokenInfo()).thenReturn(buildTokenInfo("fallback-token", 3600L)); AppLoginResponse result = appRegisterService.registerUser(request); verify(userMapper).insert(appUserCaptor.capture()); verify(appAuthSessionService).login(1002L); AppUser inserted = appUserCaptor.getValue(); assertEquals("138****8123", inserted.getNickname()); assertNull(inserted.getAvatar()); assertNull(inserted.getGender()); assertNull(inserted.getPassword()); assertEquals(Long.valueOf(1002L), result.getUser().getId()); assertEquals(Integer.valueOf(0), result.getUser().getType()); } /** * 经营者第一步在海诊通未命中诊所时仍应允许继续进入第二步手填。 */ @Test void prepareOperatorShouldAllowManualWhenClinicNotMatched() { AppRegisterOperatorPrepareRequest request = new AppRegisterOperatorPrepareRequest(); request.setPhone("13800138001"); request.setCode("666666"); 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"); 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(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()); } /** * 经营者第二步完成注册后应直接返回登录响应。 */ @Test void completeOperatorShouldReturnLoginResponse() throws Exception { AppRegisterOperatorCompleteRequest request = new AppRegisterOperatorCompleteRequest(); request.setRegisterTicket("ticket-1"); request.setInstitutionName("本地诊所"); request.setProvinceName("浙江省"); request.setCityName("杭州市"); request.setDistrictName("西湖区"); request.setDetailAddress("文三路 2 号"); request.setBusinessLicenseUrl("https://cdn.test/license.png"); AppRegisterService.OperatorRegisterDraft draft = new AppRegisterService.OperatorRegisterDraft(); draft.setPhone("13800138003"); draft.setContactName("负责人"); Clinic clinicExists = null; when(stringRedisTemplate.opsForValue()).thenReturn(valueOperations); when(valueOperations.get("app:register:operator:ticket-1")).thenReturn(new ObjectMapper().writeValueAsString(draft)); when(userMapper.selectOne(any())).thenReturn(null); when(clinicMapper.selectOne(any())).thenReturn(clinicExists); when(clinicMapper.insert(any(Clinic.class))).thenAnswer(invocation -> { Clinic inserted = invocation.getArgument(0); inserted.setId(2001L); inserted.setInviteCode("ABC123"); return 1; }); when(userMapper.insert(any(AppUser.class))).thenAnswer(invocation -> { AppUser inserted = invocation.getArgument(0); inserted.setId(3001L); return 1; }); when(appAuthSessionService.getTokenInfo()).thenReturn(buildTokenInfo("operator-token", 7200L)); AppLoginResponse response = appRegisterService.completeOperatorRegister(request); verify(userMapper).insert(appUserCaptor.capture()); verify(appAuthSessionService).login(3001L); AppUser inserted = appUserCaptor.getValue(); assertEquals("本地诊所", inserted.getNickname()); assertEquals("13800138003", inserted.getPhone()); assertNull(inserted.getPassword()); assertEquals(Integer.valueOf(1), inserted.getType()); assertEquals(Long.valueOf(2001L), inserted.getClinicId()); assertEquals("operator-token", response.getToken()); assertEquals(Long.valueOf(3001L), response.getUser().getId()); assertEquals(Integer.valueOf(1), response.getUser().getType()); assertEquals(Long.valueOf(2001L), response.getUser().getClinicId()); } /** * 构造测试用登录 token 信息。 */ private SaTokenInfo buildTokenInfo(String tokenValue, long timeout) { SaTokenInfo tokenInfo = new SaTokenInfo(); tokenInfo.setTokenValue(tokenValue); tokenInfo.setTokenTimeout(timeout); return tokenInfo; } }