From bfcfd4e0f53596b3ac19b80b686874419b5d4023 Mon Sep 17 00:00:00 2001 From: 5803024019 Date: Fri, 15 May 2026 21:29:26 +0700 Subject: [PATCH] test: add AuthServiceTest - register & login --- .../walkguide/service/AuthServiceTest.java | 292 ++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 walkguide-backend/demo/src/test/java/com/walkguide/service/AuthServiceTest.java diff --git a/walkguide-backend/demo/src/test/java/com/walkguide/service/AuthServiceTest.java b/walkguide-backend/demo/src/test/java/com/walkguide/service/AuthServiceTest.java new file mode 100644 index 0000000..bd55d4c --- /dev/null +++ b/walkguide-backend/demo/src/test/java/com/walkguide/service/AuthServiceTest.java @@ -0,0 +1,292 @@ +package com.walkguide.service; + +import com.walkguide.dto.request.LoginRequest; +import com.walkguide.dto.request.RegisterRequest; +import com.walkguide.dto.response.AuthDataResponse; +import com.walkguide.entity.RefreshToken; +import com.walkguide.entity.User; +import com.walkguide.entity.UserSettings; +import com.walkguide.enums.ActivityLogType; +import com.walkguide.repository.RefreshTokenRepository; +import com.walkguide.repository.UserRepository; +import com.walkguide.repository.UserSettingsRepository; +import com.walkguide.security.JwtUtil; +import org.junit.jupiter.api.BeforeEach; +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.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("AuthService Unit Tests") +class AuthServiceTest { + + @Mock UserRepository userRepository; + @Mock RefreshTokenRepository refreshTokenRepository; + @Mock UserSettingsRepository userSettingsRepository; + @Mock ActivityLogService activityLogService; + @Mock JwtUtil jwtUtil; + @Mock PasswordEncoder passwordEncoder; + + @InjectMocks AuthService authService; + + private User sampleUser; + private User sampleGuardian; + + @BeforeEach + void setUp() { + sampleUser = User.builder() + .id(1L) + .email("user@test.com") + .password("encodedPassword") + .role("ROLE_USER") + .displayName("Test User") + .uniqueUserId("ABC123DEF456") + .build(); + + sampleGuardian = User.builder() + .id(2L) + .email("guardian@test.com") + .password("encodedPassword") + .role("ROLE_GUARDIAN") + .displayName("Test Guardian") + .build(); + } + + // ===== REGISTER TESTS ===== + + @Test + @DisplayName("register - ROLE_USER: harus generate uniqueUserId dan buat UserSettings") + void register_asUser_shouldGenerateUniqueUserIdAndCreateSettings() { + RegisterRequest req = new RegisterRequest(); + req.setEmail("new@test.com"); + req.setPassword("password123"); + req.setDisplayName("New User"); + req.setRole("USER"); + + when(userRepository.existsByEmail("new@test.com")).thenReturn(false); + when(userRepository.findByUniqueUserId(anyString())).thenReturn(Optional.empty()); + when(passwordEncoder.encode("password123")).thenReturn("hashed"); + when(userRepository.save(any(User.class))).thenAnswer(inv -> { + User u = inv.getArgument(0); + u.setId(10L); + return u; + }); + when(jwtUtil.generateAccessToken(anyString(), anyString(), anyLong())) + .thenReturn("access-token"); + when(jwtUtil.generateRefreshToken()).thenReturn("refresh-token"); + when(refreshTokenRepository.save(any())).thenReturn(new RefreshToken()); + + AuthDataResponse result = authService.register(req); + + assertThat(result).isNotNull(); + assertThat(result.getRole()).isEqualTo("ROLE_USER"); + assertThat(result.getAccessToken()).isEqualTo("access-token"); + + // Verifikasi UserSettings dibuat untuk ROLE_USER + verify(userSettingsRepository, times(1)).save(any(UserSettings.class)); + } + + @Test + @DisplayName("register - ROLE_GUARDIAN: tidak boleh generate uniqueUserId dan tidak buat UserSettings") + void register_asGuardian_shouldNotCreateUserSettings() { + RegisterRequest req = new RegisterRequest(); + req.setEmail("guardian2@test.com"); + req.setPassword("password123"); + req.setDisplayName("New Guardian"); + req.setRole("GUARDIAN"); + + when(userRepository.existsByEmail("guardian2@test.com")).thenReturn(false); + when(passwordEncoder.encode("password123")).thenReturn("hashed"); + when(userRepository.save(any(User.class))).thenAnswer(inv -> { + User u = inv.getArgument(0); + u.setId(11L); + return u; + }); + when(jwtUtil.generateAccessToken(anyString(), anyString(), anyLong())) + .thenReturn("access-token"); + when(jwtUtil.generateRefreshToken()).thenReturn("refresh-token"); + when(refreshTokenRepository.save(any())).thenReturn(new RefreshToken()); + + AuthDataResponse result = authService.register(req); + + assertThat(result.getRole()).isEqualTo("ROLE_GUARDIAN"); + // Tidak boleh buat UserSettings untuk Guardian + verify(userSettingsRepository, never()).save(any()); + + // Verifikasi uniqueUserId null pada Guardian + ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class); + verify(userRepository).save(userCaptor.capture()); + assertThat(userCaptor.getValue().getUniqueUserId()).isNull(); + } + + @Test + @DisplayName("register - email duplikat harus throw RuntimeException") + void register_duplicateEmail_shouldThrow() { + RegisterRequest req = new RegisterRequest(); + req.setEmail("user@test.com"); + req.setPassword("password123"); + req.setDisplayName("User"); + req.setRole("USER"); + + when(userRepository.existsByEmail("user@test.com")).thenReturn(true); + + assertThatThrownBy(() -> authService.register(req)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Email sudah terdaftar"); + + verify(userRepository, never()).save(any()); + } + + // ===== LOGIN TESTS ===== + + @Test + @DisplayName("login - credentials valid harus return AuthDataResponse") + void login_validCredentials_shouldReturnToken() { + LoginRequest req = new LoginRequest(); + req.setEmail("user@test.com"); + req.setPassword("correctPassword"); + + when(userRepository.findByEmail("user@test.com")).thenReturn(Optional.of(sampleUser)); + when(passwordEncoder.matches("correctPassword", "encodedPassword")).thenReturn(true); + when(jwtUtil.generateAccessToken(anyString(), anyString(), anyLong())) + .thenReturn("access-token"); + when(jwtUtil.generateRefreshToken()).thenReturn("refresh-token"); + when(refreshTokenRepository.save(any())).thenReturn(new RefreshToken()); + + AuthDataResponse result = authService.login(req); + + assertThat(result).isNotNull(); + assertThat(result.getAccessToken()).isEqualTo("access-token"); + assertThat(result.getRefreshToken()).isEqualTo("refresh-token"); + assertThat(result.getUserId()).isEqualTo(1L); + + // Harus hapus refresh token lama sebelum buat yang baru + verify(refreshTokenRepository).deleteByUserId(1L); + // Harus catat activity log LOGIN + verify(activityLogService).createLog( + eq(sampleUser), eq(ActivityLogType.LOGIN), anyString(), any()); + } + + @Test + @DisplayName("login - email tidak terdaftar harus throw RuntimeException") + void login_emailNotFound_shouldThrow() { + LoginRequest req = new LoginRequest(); + req.setEmail("ghost@test.com"); + req.setPassword("any"); + + when(userRepository.findByEmail("ghost@test.com")).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> authService.login(req)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Email tidak terdaftar"); + } + + @Test + @DisplayName("login - password salah harus throw RuntimeException") + void login_wrongPassword_shouldThrow() { + LoginRequest req = new LoginRequest(); + req.setEmail("user@test.com"); + req.setPassword("wrongPassword"); + + when(userRepository.findByEmail("user@test.com")).thenReturn(Optional.of(sampleUser)); + when(passwordEncoder.matches("wrongPassword", "encodedPassword")).thenReturn(false); + + assertThatThrownBy(() -> authService.login(req)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Password salah"); + } + + // ===== REFRESH TOKEN TESTS ===== + + @Test + @DisplayName("refreshToken - token valid harus return access token baru") + void refreshToken_validToken_shouldReturnNewAccessToken() { + RefreshToken storedToken = RefreshToken.builder() + .token("valid-refresh-token") + .userId(1L) + .expiresAt(LocalDateTime.now().plusDays(10)) + .build(); + + when(refreshTokenRepository.findByToken("valid-refresh-token")) + .thenReturn(Optional.of(storedToken)); + when(userRepository.findById(1L)).thenReturn(Optional.of(sampleUser)); + when(jwtUtil.generateAccessToken(anyString(), anyString(), anyLong())) + .thenReturn("new-access-token"); + + AuthDataResponse result = authService.refreshToken("valid-refresh-token"); + + assertThat(result.getAccessToken()).isEqualTo("new-access-token"); + // Refresh token tidak diganti — tetap sama + assertThat(result.getRefreshToken()).isEqualTo("valid-refresh-token"); + } + + @Test + @DisplayName("refreshToken - token tidak ada di DB harus throw RuntimeException") + void refreshToken_invalidToken_shouldThrow() { + when(refreshTokenRepository.findByToken("fake-token")).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> authService.refreshToken("fake-token")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Refresh token tidak valid"); + } + + @Test + @DisplayName("refreshToken - token kadaluarsa harus throw dan hapus dari DB") + void refreshToken_expiredToken_shouldThrowAndDelete() { + RefreshToken expiredToken = RefreshToken.builder() + .token("expired-token") + .userId(1L) + .expiresAt(LocalDateTime.now().minusDays(1)) // sudah lewat + .build(); + + when(refreshTokenRepository.findByToken("expired-token")) + .thenReturn(Optional.of(expiredToken)); + + assertThatThrownBy(() -> authService.refreshToken("expired-token")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("kadaluarsa"); + + // Token kadaluarsa harus dihapus dari DB + verify(refreshTokenRepository).delete(expiredToken); + } + + // ===== LOGOUT TESTS ===== + + @Test + @DisplayName("logout - harus hapus refresh token dan catat activity log") + void logout_shouldDeleteTokenAndLog() { + when(userRepository.findById(1L)).thenReturn(Optional.of(sampleUser)); + + authService.logout(1L); + + verify(refreshTokenRepository).deleteByUserId(1L); + verify(activityLogService).createLog( + eq(sampleUser), eq(ActivityLogType.LOGOUT), anyString(), any()); + } + + // ===== UPDATE FCM TOKEN TESTS ===== + + @Test + @DisplayName("updateFcmToken - harus update dan simpan fcmToken user") + void updateFcmToken_shouldSaveNewToken() { + when(userRepository.findById(1L)).thenReturn(Optional.of(sampleUser)); + + authService.updateFcmToken(1L, "new-fcm-token-xyz"); + + ArgumentCaptor captor = ArgumentCaptor.forClass(User.class); + verify(userRepository).save(captor.capture()); + assertThat(captor.getValue().getFcmToken()).isEqualTo("new-fcm-token-xyz"); + } +} \ No newline at end of file