diff --git a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java index 3002d32c3d68eae6b6713d7fe98bd541c5838547..2ca283a60476089b22b0e939a42796f897c6197d 100644 --- a/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java +++ b/src/test/java/com/safetypin/authentication/controller/AuthenticationControllerTest.java @@ -1,13 +1,10 @@ package com.safetypin.authentication.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import com.safetypin.authentication.dto.GoogleAuthDTO; -import com.safetypin.authentication.dto.PasswordResetRequest; -import com.safetypin.authentication.dto.RegistrationRequest; -import com.safetypin.authentication.dto.UserResponse; +import com.safetypin.authentication.dto.*; import com.safetypin.authentication.exception.InvalidCredentialsException; -import com.safetypin.authentication.model.Role; import com.safetypin.authentication.exception.UserAlreadyExistsException; +import com.safetypin.authentication.model.Role; import com.safetypin.authentication.model.User; import com.safetypin.authentication.service.AuthenticationService; import com.safetypin.authentication.service.GoogleAuthService; @@ -52,35 +49,6 @@ class AuthenticationControllerTest { @Autowired private ObjectMapper objectMapper; - @TestConfiguration - static class TestConfig { - @Bean - public AuthenticationService authenticationService() { - return Mockito.mock(AuthenticationService.class); - } - - @Bean - public GoogleAuthService googleAuthService() { - return Mockito.mock(GoogleAuthService.class); - } - - @Bean - public JwtService jwtService() { - return Mockito.mock(JwtService.class); - } - } - - @TestConfiguration - static class TestSecurityConfig { - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.csrf(AbstractHttpConfigurer::disable) - .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()); - return http.build(); - } - } - - @Test void testRegisterEmail() throws Exception { RegistrationRequest request = new RegistrationRequest(); @@ -152,7 +120,6 @@ class AuthenticationControllerTest { .andExpect(jsonPath("$.data").doesNotExist()); // No data expected in case of error } - @Test void testVerifyOTP_Success() throws Exception { Mockito.when(authenticationService.verifyOTP("email@example.com", "123456")).thenReturn(true); @@ -199,9 +166,74 @@ class AuthenticationControllerTest { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) - .andExpect(content().string("Password reset instructions have been sent to your email (simulated)")); + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("Password reset OTP has been sent to your email")); + } + + @Test + void testVerifyResetOTP_Success() throws Exception { + VerifyResetOTPRequest request = new VerifyResetOTPRequest(); + request.setEmail("email@example.com"); + request.setOtp("123456"); + + Mockito.when(authenticationService.verifyPasswordResetOTP("email@example.com", "123456")).thenReturn(true); + + mockMvc.perform(post("/api/auth/verify-reset-otp") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("OTP verified successfully. You can now reset your password.")); + } + + @Test + void testVerifyResetOTP_Failure() throws Exception { + VerifyResetOTPRequest request = new VerifyResetOTPRequest(); + request.setEmail("email@example.com"); + request.setOtp("123456"); + + Mockito.when(authenticationService.verifyPasswordResetOTP("email@example.com", "123456")).thenReturn(false); + + mockMvc.perform(post("/api/auth/verify-reset-otp") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.message").value("Invalid OTP")); + } + + @Test + void testResetPassword_Success() throws Exception { + PasswordResetWithOTPRequest request = new PasswordResetWithOTPRequest(); + request.setEmail("email@example.com"); + request.setNewPassword("newPassword123"); + + Mockito.doNothing().when(authenticationService).resetPassword("email@example.com", "newPassword123"); + + mockMvc.perform(post("/api/auth/reset-password") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("Password has been reset successfully")); } + @Test + void testResetPassword_NotVerified() throws Exception { + PasswordResetWithOTPRequest request = new PasswordResetWithOTPRequest(); + request.setEmail("email@example.com"); + request.setNewPassword("newPassword123"); + + Mockito.doThrow(new InvalidCredentialsException("You must verify your OTP before resetting password.")) + .when(authenticationService).resetPassword("email@example.com", "newPassword123"); + + mockMvc.perform(post("/api/auth/reset-password") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.message").value("You must verify your OTP before resetting password.")); + } @Test void testRegisterEmail_UserAlreadyExistsException() throws Exception { @@ -376,4 +408,32 @@ class AuthenticationControllerTest { .andExpect(jsonPath("$.data").doesNotExist()); // No data expected in case of error } + @TestConfiguration + static class TestConfig { + @Bean + public AuthenticationService authenticationService() { + return Mockito.mock(AuthenticationService.class); + } + + @Bean + public GoogleAuthService googleAuthService() { + return Mockito.mock(GoogleAuthService.class); + } + + @Bean + public JwtService jwtService() { + return Mockito.mock(JwtService.class); + } + } + + @TestConfiguration + static class TestSecurityConfig { + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()); + return http.build(); + } + } + } diff --git a/src/test/java/com/safetypin/authentication/dto/PasswordResetWithOTPRequestTest.java b/src/test/java/com/safetypin/authentication/dto/PasswordResetWithOTPRequestTest.java new file mode 100644 index 0000000000000000000000000000000000000000..41317eaf0d04826519b4bdef34b1d545a895f2ec --- /dev/null +++ b/src/test/java/com/safetypin/authentication/dto/PasswordResetWithOTPRequestTest.java @@ -0,0 +1,23 @@ +package com.safetypin.authentication.dto; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class PasswordResetWithOTPRequestTest { + + @Test + void testGettersAndSetters() { + // Arrange + PasswordResetWithOTPRequest request = new PasswordResetWithOTPRequest(); + String email = "test@example.com"; + String newPassword = "newSecurePassword"; + + // Act + request.setEmail(email); + request.setNewPassword(newPassword); + + // Assert + assertEquals(email, request.getEmail()); + assertEquals(newPassword, request.getNewPassword()); + } +} diff --git a/src/test/java/com/safetypin/authentication/dto/VerifyResetOTPRequestTest.java b/src/test/java/com/safetypin/authentication/dto/VerifyResetOTPRequestTest.java new file mode 100644 index 0000000000000000000000000000000000000000..74e170d03f1c64b8ba4ad6dbee1395c92ebfd96e --- /dev/null +++ b/src/test/java/com/safetypin/authentication/dto/VerifyResetOTPRequestTest.java @@ -0,0 +1,23 @@ +package com.safetypin.authentication.dto; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class VerifyResetOTPRequestTest { + + @Test + void testGettersAndSetters() { + // Arrange + VerifyResetOTPRequest request = new VerifyResetOTPRequest(); + String email = "test@example.com"; + String otp = "123456"; + + // Act + request.setEmail(email); + request.setOtp(otp); + + // Assert + assertEquals(email, request.getEmail()); + assertEquals(otp, request.getOtp()); + } +} diff --git a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java index 67358617ca0adcb807c47c55c196c98b43e77452..3109ede04bdaf96df8b0e766f11501ce49e4b855 100644 --- a/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java +++ b/src/test/java/com/safetypin/authentication/service/AuthenticationServiceTest.java @@ -230,6 +230,7 @@ class AuthenticationServiceTest { user.setProvider("EMAIL"); when(userService.findByEmail("test@example.com")).thenReturn(Optional.of(user)); + when(otpService.generateOTP("test@example.com")).thenReturn("123456"); assertDoesNotThrow(() -> authenticationService.forgotPassword("test@example.com")); } @@ -265,5 +266,111 @@ class AuthenticationServiceTest { assertTrue(exception.getMessage().contains("Password reset is only available for email-registered users")); } + @Test + void testVerifyPasswordResetOTP_Success() { + User user = new User(); + user.setEmail("test@example.com"); + user.setProvider("EMAIL"); + + when(userService.findByEmail("test@example.com")).thenReturn(Optional.of(user)); + when(otpService.verifyOTP("test@example.com", "123456")).thenReturn(true); + + boolean result = authenticationService.verifyPasswordResetOTP("test@example.com", "123456"); + assertTrue(result); + } + + @Test + void testVerifyPasswordResetOTP_Failure() { + User user = new User(); + user.setEmail("test@example.com"); + user.setProvider("EMAIL"); + + when(userService.findByEmail("test@example.com")).thenReturn(Optional.of(user)); + when(otpService.verifyOTP("test@example.com", "123456")).thenReturn(false); + + boolean result = authenticationService.verifyPasswordResetOTP("test@example.com", "123456"); + assertFalse(result); + } + + @Test + void testVerifyPasswordResetOTP_UserNotFound() { + when(userService.findByEmail("nonexistent@example.com")).thenReturn(Optional.empty()); + + Exception exception = assertThrows(IllegalArgumentException.class, () -> + authenticationService.verifyPasswordResetOTP("nonexistent@example.com", "123456") + ); + + assertTrue(exception.getMessage().contains("Password reset is only available for email-registered users")); + verify(otpService, never()).verifyOTP(anyString(), anyString()); + } + + @Test + void testVerifyPasswordResetOTP_NonEmailProvider() { + User user = new User(); + user.setEmail("social@example.com"); + user.setProvider("GOOGLE"); + + when(userService.findByEmail("social@example.com")).thenReturn(Optional.of(user)); + + Exception exception = assertThrows(IllegalArgumentException.class, () -> + authenticationService.verifyPasswordResetOTP("social@example.com", "123456") + ); + + assertTrue(exception.getMessage().contains("Password reset is only available for email-registered users")); + verify(otpService, never()).verifyOTP(anyString(), anyString()); + } + + @Test + void testResetPassword_NonEmailProvider_WithoutOTP() { + User user = new User(); + user.setEmail("social@example.com"); + user.setProvider("GOOGLE"); + + when(userService.findByEmail("social@example.com")).thenReturn(Optional.of(user)); + + Exception exception = assertThrows(IllegalArgumentException.class, () -> + authenticationService.resetPassword("social@example.com", "newPassword") + ); + + assertTrue(exception.getMessage().contains("Password reset is only available for email-registered users")); + verify(otpService, never()).isVerifiedForPasswordReset(anyString()); + verify(userService, never()).save(any(User.class)); + } + + + @Test + void testResetPassword_WithoutOTP_Success() { + User user = new User(); + user.setEmail("test@example.com"); + user.setPassword("oldEncodedPassword"); + user.setProvider("EMAIL"); + + when(userService.findByEmail("test@example.com")).thenReturn(Optional.of(user)); + when(otpService.isVerifiedForPasswordReset("test@example.com")).thenReturn(true); + when(passwordEncoder.encode("newPassword")).thenReturn("newEncodedPassword"); + + authenticationService.resetPassword("test@example.com", "newPassword"); + + verify(userService).save(user); + assertEquals("newEncodedPassword", user.getPassword()); + verify(otpService).clearPasswordResetVerification("test@example.com"); + } + + @Test + void testResetPassword_WithoutOTP_NotVerified() { + User user = new User(); + user.setEmail("test@example.com"); + user.setProvider("EMAIL"); + + when(userService.findByEmail("test@example.com")).thenReturn(Optional.of(user)); + when(otpService.isVerifiedForPasswordReset("test@example.com")).thenReturn(false); + + assertThrows(InvalidCredentialsException.class, () -> + authenticationService.resetPassword("test@example.com", "newPassword") + ); + + verify(userService, never()).save(any(User.class)); + } + // Add missing methods for other functionality if needed } \ No newline at end of file diff --git a/src/test/java/com/safetypin/authentication/service/OTPServiceTest.java b/src/test/java/com/safetypin/authentication/service/OTPServiceTest.java index f2e9b97facb5feb5ad0bc3860d88b81e76beb1f4..7d13f88c43e84545464b85d4a71f6246f8b539a0 100644 --- a/src/test/java/com/safetypin/authentication/service/OTPServiceTest.java +++ b/src/test/java/com/safetypin/authentication/service/OTPServiceTest.java @@ -211,4 +211,55 @@ class OTPServiceTest { assertTrue(exception.getMessage().contains("Failed to send OTP")); } + + @Test + void testIsVerifiedForPasswordResetWhenEmailNotVerified() { + // Test when email is not in the verifiedResetEmails map + boolean result = otpService.isVerifiedForPasswordReset("notverified@example.com"); + assertFalse(result, "Should return false for non-verified email"); + } + + @Test + void testIsVerifiedForPasswordResetWhenVerificationExpired() throws Exception { + String email = "expired@example.com"; + + // Use reflection to access and modify the private verifiedResetEmails map + java.lang.reflect.Field verifiedResetEmailsField = OTPService.class.getDeclaredField("verifiedResetEmails"); + verifiedResetEmailsField.setAccessible(true); + @SuppressWarnings("unchecked") + ConcurrentHashMap<String, LocalDateTime> verifiedResetEmails = + (ConcurrentHashMap<String, LocalDateTime>) verifiedResetEmailsField.get(otpService); + + // Add an expired verification (more than 5 minutes old) + verifiedResetEmails.put(email, LocalDateTime.now().minusMinutes(6)); + + // Verify that the method returns false for expired verification + boolean result = otpService.isVerifiedForPasswordReset(email); + assertFalse(result, "Should return false for expired verification"); + + // Verify that the expired entry was removed + assertFalse(verifiedResetEmails.containsKey(email), "Expired entry should be removed from the map"); + } + + @Test + void testIsVerifiedForPasswordResetSuccess() throws Exception { + String email = "valid@example.com"; + + // Use reflection to access and modify the private verifiedResetEmails map + java.lang.reflect.Field verifiedResetEmailsField = OTPService.class.getDeclaredField("verifiedResetEmails"); + verifiedResetEmailsField.setAccessible(true); + @SuppressWarnings("unchecked") + ConcurrentHashMap<String, LocalDateTime> verifiedResetEmails = + (ConcurrentHashMap<String, LocalDateTime>) verifiedResetEmailsField.get(otpService); + + // Add a valid verification time (just now) + verifiedResetEmails.put(email, LocalDateTime.now()); + + // Verify that the method returns true for valid verification + boolean result = otpService.isVerifiedForPasswordReset(email); + assertTrue(result, "Should return true for valid verification"); + + // Verify that the entry remains in the map + assertTrue(verifiedResetEmails.containsKey(email), "Valid entry should remain in the map"); + } } \ No newline at end of file