package com.recipeapp.auth; import com.recipeapp.auth.dto.*; import com.recipeapp.auth.entity.UserAccount; import com.recipeapp.common.ConflictException; import com.recipeapp.common.ResourceNotFoundException; import com.recipeapp.common.ValidationException; import com.recipeapp.household.HouseholdMemberRepository; import com.recipeapp.household.entity.Household; import com.recipeapp.household.entity.HouseholdMember; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Optional; import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class AuthServiceTest { @Mock private UserAccountRepository userAccountRepository; @Mock private HouseholdMemberRepository householdMemberRepository; @Mock private PasswordEncoder passwordEncoder; @InjectMocks private AuthServiceImpl authService; @Test void signupShouldCreateUserAndReturnResponse() { var request = new SignupRequest("sarah@example.com", "s3cure!Pass", "Sarah"); when(userAccountRepository.existsByEmailIgnoreCase("sarah@example.com")).thenReturn(false); when(passwordEncoder.encode("s3cure!Pass")).thenReturn("hashed"); when(userAccountRepository.save(any(UserAccount.class))).thenAnswer(invocation -> { UserAccount saved = invocation.getArgument(0); return saved; }); UserResponse result = authService.signup(request); assertThat(result.email()).isEqualTo("sarah@example.com"); assertThat(result.displayName()).isEqualTo("Sarah"); verify(userAccountRepository).save(any(UserAccount.class)); } @Test void signupShouldThrowConflictWhenEmailExists() { var request = new SignupRequest("sarah@example.com", "s3cure!Pass", "Sarah"); when(userAccountRepository.existsByEmailIgnoreCase("sarah@example.com")).thenReturn(true); assertThatThrownBy(() -> authService.signup(request)) .isInstanceOf(ConflictException.class); } @Test void loginShouldReturnUserWhenCredentialsValid() { var request = new LoginRequest("sarah@example.com", "s3cure!Pass"); var user = new UserAccount("sarah@example.com", "Sarah", "hashed"); when(userAccountRepository.findByEmailIgnoreCase("sarah@example.com")).thenReturn(Optional.of(user)); when(passwordEncoder.matches("s3cure!Pass", "hashed")).thenReturn(true); when(householdMemberRepository.findByUserEmailIgnoreCase("sarah@example.com")).thenReturn(Optional.empty()); UserResponse result = authService.login(request); assertThat(result.email()).isEqualTo("sarah@example.com"); assertThat(result.displayName()).isEqualTo("Sarah"); } @Test void loginShouldThrowWhenEmailNotFound() { var request = new LoginRequest("unknown@example.com", "password"); when(userAccountRepository.findByEmailIgnoreCase("unknown@example.com")).thenReturn(Optional.empty()); assertThatThrownBy(() -> authService.login(request)) .isInstanceOf(ResourceNotFoundException.class); } @Test void loginShouldThrowWhenPasswordInvalid() { var request = new LoginRequest("sarah@example.com", "wrongpass"); var user = new UserAccount("sarah@example.com", "Sarah", "hashed"); when(userAccountRepository.findByEmailIgnoreCase("sarah@example.com")).thenReturn(Optional.of(user)); when(passwordEncoder.matches("wrongpass", "hashed")).thenReturn(false); assertThatThrownBy(() -> authService.login(request)) .isInstanceOf(ValidationException.class); } @Test void loginShouldThrowWhenAccountInactive() { var request = new LoginRequest("sarah@example.com", "s3cure!Pass"); var user = new UserAccount("sarah@example.com", "Sarah", "hashed"); user.setActive(false); when(userAccountRepository.findByEmailIgnoreCase("sarah@example.com")).thenReturn(Optional.of(user)); assertThatThrownBy(() -> authService.login(request)) .isInstanceOf(ValidationException.class); } @Test void getCurrentUserShouldReturnUserWithHouseholdInfo() { var user = new UserAccount("sarah@example.com", "Sarah", "hashed"); var household = new Household("Smith family", user); var member = new HouseholdMember(household, user, "planner"); when(userAccountRepository.findByEmailIgnoreCase("sarah@example.com")).thenReturn(Optional.of(user)); when(householdMemberRepository.findByUserEmailIgnoreCase("sarah@example.com")).thenReturn(Optional.of(member)); UserResponse result = authService.getCurrentUser("sarah@example.com"); assertThat(result.email()).isEqualTo("sarah@example.com"); assertThat(result.householdName()).isEqualTo("Smith family"); assertThat(result.householdRole()).isEqualTo("planner"); } @Test void updateProfileShouldUpdateDisplayName() { var request = new UpdateProfileRequest("Sarah S.", null, null); var user = new UserAccount("sarah@example.com", "Sarah", "hashed"); when(userAccountRepository.findByEmailIgnoreCase("sarah@example.com")).thenReturn(Optional.of(user)); when(userAccountRepository.save(any(UserAccount.class))).thenAnswer(i -> i.getArgument(0)); UserResponse result = authService.updateProfile("sarah@example.com", request); assertThat(result.displayName()).isEqualTo("Sarah S."); } @Test void updateProfileShouldChangePasswordWhenCurrentPasswordValid() { var request = new UpdateProfileRequest(null, "oldpass", "newpassword"); var user = new UserAccount("sarah@example.com", "Sarah", "hashed_old"); when(userAccountRepository.findByEmailIgnoreCase("sarah@example.com")).thenReturn(Optional.of(user)); when(passwordEncoder.matches("oldpass", "hashed_old")).thenReturn(true); when(passwordEncoder.encode("newpassword")).thenReturn("hashed_new"); when(userAccountRepository.save(any(UserAccount.class))).thenAnswer(i -> i.getArgument(0)); authService.updateProfile("sarah@example.com", request); verify(passwordEncoder).encode("newpassword"); } @Test void updateProfileShouldThrowWhenCurrentPasswordWrong() { var request = new UpdateProfileRequest(null, "wrongpass", "newpassword"); var user = new UserAccount("sarah@example.com", "Sarah", "hashed_old"); when(userAccountRepository.findByEmailIgnoreCase("sarah@example.com")).thenReturn(Optional.of(user)); when(passwordEncoder.matches("wrongpass", "hashed_old")).thenReturn(false); assertThatThrownBy(() -> authService.updateProfile("sarah@example.com", request)) .isInstanceOf(ValidationException.class); } @Test void updateProfileShouldThrowWhenNewPasswordWithoutCurrentPassword() { var request = new UpdateProfileRequest(null, null, "newpassword"); var user = new UserAccount("sarah@example.com", "Sarah", "hashed"); when(userAccountRepository.findByEmailIgnoreCase("sarah@example.com")).thenReturn(Optional.of(user)); assertThatThrownBy(() -> authService.updateProfile("sarah@example.com", request)) .isInstanceOf(ValidationException.class); } }