refactor(auth): route password reset through service layer + e2e helper
- PasswordResetService injects UserService instead of AppUserRepository.
- New UserService.findByEmailOptional preserves the silent-fail behaviour of
the old findByEmail-returning-Optional path; the existing throwing
findByEmail is unchanged.
- New PasswordResetService.findLatestActiveTokenForEmail exposes the latest
active reset token without leaking the repository upward.
- New @Profile("e2e") PasswordResetTestHelper wraps that read so the
AuthE2EController no longer touches PasswordResetTokenRepository directly.
Profile guard moves from the controller-only annotation to also cover the
helper bean, so the production graph never instantiates either.
Refs #417 (C6.1 violation #2 + C6.2 violation #12).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,6 @@ import org.raddatz.familienarchiv.dto.ResetPasswordRequest;
|
||||
import org.raddatz.familienarchiv.exception.DomainException;
|
||||
import org.raddatz.familienarchiv.model.AppUser;
|
||||
import org.raddatz.familienarchiv.model.PasswordResetToken;
|
||||
import org.raddatz.familienarchiv.repository.AppUserRepository;
|
||||
import org.raddatz.familienarchiv.repository.PasswordResetTokenRepository;
|
||||
import org.springframework.mail.MailSendException;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
@@ -33,7 +32,7 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PasswordResetServiceTest {
|
||||
|
||||
@Mock AppUserRepository userRepository;
|
||||
@Mock UserService userService;
|
||||
@Mock PasswordResetTokenRepository tokenRepository;
|
||||
@Mock PasswordEncoder passwordEncoder;
|
||||
@Mock JavaMailSender mailSender;
|
||||
@@ -53,7 +52,7 @@ class PasswordResetServiceTest {
|
||||
@Test
|
||||
void requestReset_savesTokenForKnownEmail() {
|
||||
AppUser user = makeUser("user@example.com");
|
||||
when(userRepository.findByEmail("user@example.com")).thenReturn(Optional.of(user));
|
||||
when(userService.findByEmailOptional("user@example.com")).thenReturn(Optional.of(user));
|
||||
|
||||
service.requestReset("user@example.com", "http://localhost:3000");
|
||||
|
||||
@@ -65,7 +64,7 @@ class PasswordResetServiceTest {
|
||||
|
||||
@Test
|
||||
void requestReset_doesNothingForUnknownEmail() {
|
||||
when(userRepository.findByEmail("ghost@example.com")).thenReturn(Optional.empty());
|
||||
when(userService.findByEmailOptional("ghost@example.com")).thenReturn(Optional.empty());
|
||||
|
||||
service.requestReset("ghost@example.com", "http://localhost:3000");
|
||||
|
||||
@@ -93,7 +92,7 @@ class PasswordResetServiceTest {
|
||||
service.resetPassword(req);
|
||||
|
||||
verify(passwordEncoder).encode("newpass");
|
||||
verify(userRepository).save(argThat(u -> u.getPassword().equals("hashed-newpass")));
|
||||
verify(userService).save(argThat(u -> u.getPassword().equals("hashed-newpass")));
|
||||
assertThat(token.isUsed()).isTrue();
|
||||
}
|
||||
|
||||
@@ -153,7 +152,7 @@ class PasswordResetServiceTest {
|
||||
void requestReset_skipsEmail_whenMailSenderIsNull() {
|
||||
ReflectionTestUtils.setField(service, "mailSender", null);
|
||||
AppUser user = makeUser("user@example.com");
|
||||
when(userRepository.findByEmail("user@example.com")).thenReturn(Optional.of(user));
|
||||
when(userService.findByEmailOptional("user@example.com")).thenReturn(Optional.of(user));
|
||||
|
||||
// Must not throw even without mail sender
|
||||
service.requestReset("user@example.com", "http://localhost:3000");
|
||||
@@ -167,7 +166,7 @@ class PasswordResetServiceTest {
|
||||
// mailSender is @Autowired(required=false) — not in constructor, so needs explicit injection
|
||||
ReflectionTestUtils.setField(service, "mailSender", mailSender);
|
||||
AppUser user = makeUser("user@example.com");
|
||||
when(userRepository.findByEmail("user@example.com")).thenReturn(Optional.of(user));
|
||||
when(userService.findByEmailOptional("user@example.com")).thenReturn(Optional.of(user));
|
||||
doThrow(new MailSendException("SMTP error")).when(mailSender).send(any(SimpleMailMessage.class));
|
||||
|
||||
// Must not propagate the MailException
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.raddatz.familienarchiv.service;
|
||||
|
||||
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 java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PasswordResetTestHelperTest {
|
||||
|
||||
@Mock PasswordResetService passwordResetService;
|
||||
@InjectMocks PasswordResetTestHelper helper;
|
||||
|
||||
@Test
|
||||
void getResetTokenForTest_returnsToken_whenPresent() {
|
||||
when(passwordResetService.findLatestActiveTokenForEmail("user@example.com"))
|
||||
.thenReturn(Optional.of("abc123"));
|
||||
|
||||
assertThat(helper.getResetTokenForTest("user@example.com")).contains("abc123");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResetTokenForTest_returnsEmpty_whenAbsent() {
|
||||
when(passwordResetService.findLatestActiveTokenForEmail("ghost@example.com"))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
assertThat(helper.getResetTokenForTest("ghost@example.com")).isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,34 @@ class UserServiceTest {
|
||||
assertThat(userService.findByEmail("admin@example.com")).isEqualTo(user);
|
||||
}
|
||||
|
||||
// ─── findByEmailOptional ──────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void findByEmailOptional_returnsEmpty_whenMissing() {
|
||||
when(userRepository.findByEmail("ghost@example.com")).thenReturn(Optional.empty());
|
||||
|
||||
assertThat(userService.findByEmailOptional("ghost@example.com")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void findByEmailOptional_returnsUser_whenFound() {
|
||||
AppUser user = AppUser.builder().id(UUID.randomUUID()).email("admin@example.com").build();
|
||||
when(userRepository.findByEmail("admin@example.com")).thenReturn(Optional.of(user));
|
||||
|
||||
assertThat(userService.findByEmailOptional("admin@example.com")).contains(user);
|
||||
}
|
||||
|
||||
// ─── save ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void save_delegatesToRepository() {
|
||||
AppUser user = AppUser.builder().id(UUID.randomUUID()).email("u@example.com").build();
|
||||
when(userRepository.save(user)).thenReturn(user);
|
||||
|
||||
assertThat(userService.save(user)).isEqualTo(user);
|
||||
verify(userRepository).save(user);
|
||||
}
|
||||
|
||||
// ─── deleteUser ───────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user