diff --git a/crm-auth/src/test/java/com/crm/auth/service/impl/AuthServiceImplTest.java b/crm-auth/src/test/java/com/crm/auth/service/impl/AuthServiceImplTest.java index bc6bd6d..7eafe96 100644 --- a/crm-auth/src/test/java/com/crm/auth/service/impl/AuthServiceImplTest.java +++ b/crm-auth/src/test/java/com/crm/auth/service/impl/AuthServiceImplTest.java @@ -5,8 +5,10 @@ import com.crm.auth.domain.dto.LoginResultDTO; import com.crm.auth.domain.dto.UserInfoDTO; import com.crm.auth.domain.entity.AuthIdentity; import com.crm.auth.domain.entity.AuthUser; +import com.crm.auth.domain.enums.ClientTypeEnum; import com.crm.auth.domain.enums.IdentityTypeEnum; import com.crm.auth.security.AuthLoginUser; +import com.crm.auth.security.PreLoginSession; import com.crm.auth.security.TokenService; import com.crm.auth.service.IAuthIdentityService; import com.crm.auth.service.IAuthUserService; @@ -21,6 +23,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.InjectMocks; import org.mockito.MockedStatic; import org.mockito.Mock; @@ -36,6 +39,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; /** @@ -99,10 +103,10 @@ class AuthServiceImplTest { when(authIdentityService.getByTypeAndUnionId(TYPE, "union-001")).thenReturn(identity); when(authUserService.getById(1001L)).thenReturn(user); - LoginResultDTO result = authService.login(TYPE, AUTH_CODE); + LoginResultDTO result = authService.login(TYPE, AUTH_CODE, null); + assertThat(result.getStatus()).isEqualTo(LoginResultDTO.LoginStatus.SUCCESS); assertThat(result.getToken()).isEqualTo(TOKEN); - assertThat(result.getUserInfo()).isNotNull(); assertThat(result.getUserInfo().getId()).isEqualTo(1001L); assertThat(result.getUserInfo().getUsername()).isEqualTo("鑰佺敤鎴?); @@ -129,6 +133,8 @@ class AuthServiceImplTest { assertThat(loginUser.getUsername()).isEqualTo("鑰佺敤鎴?); assertThat(loginUser.getMobile()).isEqualTo("13800000000"); assertThat(loginUser.getAvatar()).isEqualTo(user.getAvatar()); + // 涓嶄紶 clientType 榛樿 PC锛岀櫥褰曟€佹惡甯︾绫诲瀷渚涘弽鍚戠储寮曚娇鐢?+ assertThat(loginUser.getClientType()).isEqualTo(ClientTypeEnum.PC); } @Test @@ -140,7 +146,7 @@ class AuthServiceImplTest { when(authIdentityService.getByTypeAndUnionId(TYPE, "union-001")).thenReturn(null); when(authUserService.getByMobile("13800000000")).thenReturn(existing); - LoginResultDTO result = authService.login(TYPE, AUTH_CODE); + LoginResultDTO result = authService.login(TYPE, AUTH_CODE, null); assertThat(result.getToken()).isEqualTo(TOKEN); assertThat(result.getUserInfo().getId()).isEqualTo(2002L); @@ -167,7 +173,7 @@ class AuthServiceImplTest { return true; }).when(authUserService).save(any(AuthUser.class)); - LoginResultDTO result = authService.login(TYPE, AUTH_CODE); + LoginResultDTO result = authService.login(TYPE, AUTH_CODE, null); assertThat(result.getToken()).isEqualTo(TOKEN); assertThat(result.getUserInfo().getId()).isEqualTo(3003L); @@ -194,7 +200,7 @@ class AuthServiceImplTest { when(authUserService.getByMobile("13800000000")).thenReturn(null); when(thirdPartyClient.isOrgMember(thirdUser)).thenReturn(false); - assertThatThrownBy(() -> authService.login(TYPE, AUTH_CODE)) + assertThatThrownBy(() -> authService.login(TYPE, AUTH_CODE, null)) .isInstanceOf(BusinessErrorException.class) .hasFieldOrPropertyWithValue("code", AuthConstants.CODE_NOT_ORG_MEMBER); @@ -217,7 +223,7 @@ class AuthServiceImplTest { return true; }).when(authUserService).save(any(AuthUser.class)); - authService.login(TYPE, AUTH_CODE); + authService.login(TYPE, AUTH_CODE, null); verify(authUserService, never()).getByMobile(anyString()); verify(authUserService).save(any(AuthUser.class)); @@ -237,7 +243,7 @@ class AuthServiceImplTest { when(authIdentityService.getByTypeAndUnionId(TYPE, "union-001")).thenReturn(identity); when(authUserService.getById(5005L)).thenReturn(user); - assertThatThrownBy(() -> authService.login(TYPE, AUTH_CODE)) + assertThatThrownBy(() -> authService.login(TYPE, AUTH_CODE, null)) .isInstanceOf(BusinessErrorException.class) .hasFieldOrPropertyWithValue("code", AuthConstants.CODE_USER_DISABLED); @@ -263,7 +269,7 @@ class AuthServiceImplTest { return true; }).when(authUserService).save(any(AuthUser.class)); - authService.login(TYPE, AUTH_CODE); + authService.login(TYPE, AUTH_CODE, null); // 鏈皟鐢?bind锛坕dentity 宸插瓨鍦級 verify(authIdentityService, never()).bind(any(), any(), any()); @@ -307,6 +313,110 @@ class AuthServiceImplTest { } } + @Test + @DisplayName("璇?(鐢ㄦ埛, 绔? 宸叉湁娲讳細璇?-> 涓嶇姝e紡 token锛岃繑鍥?DEVICE_ONLINE + preLoginToken锛堜粛鍒?lastLoginTime锛?) + void login_whenDeviceOnline_returnsDeviceOnlineWithPreLoginToken() { + when(clientFactory.getClient(TYPE)).thenReturn(thirdPartyClient); + when(thirdPartyClient.getUserInfo(AUTH_CODE)).thenReturn(thirdUser); + + AuthIdentity identity = new AuthIdentity(); + identity.setId(9001L); + identity.setUserId(1001L); + AuthUser user = buildUser(1001L, "鑰佺敤鎴?, "13800000000", true); + when(authIdentityService.getByTypeAndUnionId(TYPE, "union-001")).thenReturn(identity); + when(authUserService.getById(1001L)).thenReturn(user); + when(tokenService.isOnline("1001", ClientTypeEnum.PC)).thenReturn(true); + when(tokenService.createPreLoginToken("1001", ClientTypeEnum.PC)).thenReturn("plt-001"); + + LoginResultDTO result = authService.login(TYPE, AUTH_CODE, null); + + assertThat(result.getStatus()).isEqualTo(LoginResultDTO.LoginStatus.DEVICE_ONLINE); + assertThat(result.getPreLoginToken()).isEqualTo("plt-001"); + assertThat(result.getToken()).isNull(); + assertThat(result.getUserInfo()).isNull(); + + verify(tokenService, never()).createToken(any()); + // 鍐茬獊涔熷埛 lastLoginTime锛堥€夐」 Y锛?+ verify(authUserService).updateById(any(AuthUser.class)); + } + + @Test + @DisplayName("clientType=Mobile锛堝ぇ灏忓啓涓嶆晱鎰燂級-> 鍙寜 MOBILE 绔娴嬩笌绛惧彂锛屼笉纰?PC 绔?) + void login_whenClientTypeMobile_detectsAndIssuesOnMobileOnly() { + stubThirdPartyHappyPath(); + stubLocatedUser(1001L); + + LoginResultDTO result = authService.login(TYPE, AUTH_CODE, "Mobile"); + + assertThat(result.getStatus()).isEqualTo(LoginResultDTO.LoginStatus.SUCCESS); + verify(tokenService).isOnline("1001", ClientTypeEnum.MOBILE); + verify(tokenService, never()).isOnline(anyString(), eq(ClientTypeEnum.PC)); + + ArgumentCaptor loginUserCap = ArgumentCaptor.forClass(AuthLoginUser.class); + verify(tokenService).createToken(loginUserCap.capture()); + assertThat(loginUserCap.getValue().getClientType()).isEqualTo(ClientTypeEnum.MOBILE); + } + + @Test + @DisplayName("clientType 鏄犲皠涓嶅埌 -> 鎶?61016锛宖ail-fast 涓嶅幓鎹?authCode") + void login_whenClientTypeUnsupported_throwsEarly() { + assertThatThrownBy(() -> authService.login(TYPE, AUTH_CODE, "tv")) + .isInstanceOf(BusinessErrorException.class) + .hasFieldOrPropertyWithValue("code", AuthConstants.CODE_UNSUPPORTED_CLIENT_TYPE); + + verifyNoInteractions(clientFactory, tokenService, authUserService, authIdentityService); + } + + @Test + @DisplayName("confirmLogin锛氭秷璐?preLoginToken -> 鍏堣涪鏃т細璇濆啀绛炬柊 token锛堝甫绔被鍨嬶級-> SUCCESS") + void confirmLogin_kicksOldAndIssuesNew() { + PreLoginSession pre = new PreLoginSession(); + pre.setUserId("1001"); + pre.setClientType("pc"); + when(tokenService.consumePreLoginToken("plt-001")).thenReturn(pre); + when(authUserService.getByIdOrThrow("1001")).thenReturn(buildUser(1001L, "鑰佺敤鎴?, "13800000000", true)); + when(tokenService.createToken(any(AuthLoginUser.class))).thenReturn(TOKEN); + + LoginResultDTO result = authService.confirmLogin("plt-001"); + + assertThat(result.getStatus()).isEqualTo(LoginResultDTO.LoginStatus.SUCCESS); + assertThat(result.getToken()).isEqualTo(TOKEN); + assertThat(result.getPreLoginToken()).isNull(); + assertThat(result.getUserInfo().getId()).isEqualTo(1001L); + + // 椤哄簭锛氬厛韪㈡棫鍐嶇鏂帮紙韪㈡棭浜嗘棤濡紝绛炬棭浜嗕細鎶婅嚜宸辫涪鎺夛級 + InOrder inOrder = Mockito.inOrder(tokenService); + inOrder.verify(tokenService).kickSession("1001", ClientTypeEnum.PC); + inOrder.verify(tokenService).createToken(any(AuthLoginUser.class)); + + ArgumentCaptor loginUserCap = ArgumentCaptor.forClass(AuthLoginUser.class); + verify(tokenService).createToken(loginUserCap.capture()); + assertThat(loginUserCap.getValue().getClientType()).isEqualTo(ClientTypeEnum.PC); + assertThat(loginUserCap.getValue().getUserId()).isEqualTo("1001"); + } + + @Test + @DisplayName("confirmLogin锛歱reLoginToken 杩囨湡/宸叉秷璐?浼€?-> 缁熶竴鎶?61014锛屼笉韪笉绛?) + void confirmLogin_whenPreloginInvalid_throws() { + when(tokenService.consumePreLoginToken("plt-gone")).thenReturn(null); + + assertThatThrownBy(() -> authService.confirmLogin("plt-gone")) + .isInstanceOf(BusinessErrorException.class) + .hasFieldOrPropertyWithValue("code", AuthConstants.CODE_PRELOGIN_INVALID); + + verify(tokenService, never()).kickSession(anyString(), any()); + verify(tokenService, never()).createToken(any()); + verifyNoInteractions(authUserService); + } + + private void stubLocatedUser(Long userId) { + AuthIdentity identity = new AuthIdentity(); + identity.setId(9000L); + identity.setUserId(userId); + when(authIdentityService.getByTypeAndUnionId(TYPE, "union-001")).thenReturn(identity); + when(authUserService.getById(userId)).thenReturn(buildUser(userId, "鑰佺敤鎴?, "13800000000", true)); + } + private AuthUser buildUser(Long id, String username, String mobile, boolean enabled) { AuthUser user = new AuthUser(); user.setId(id);