fix(notifications): add missing unread-only filter branch in service and repository
NullX Finding 1: GET /api/notifications?read=false with no type param fell through to the all-notifications branch, silently ignoring the read filter. Added findByRecipientIdAndReadFalseOrderByCreatedAtDesc to NotificationRepository and the missing Boolean.FALSE.equals(read) branch in NotificationService. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import org.raddatz.familienarchiv.dto.NotificationDTO;
|
||||
import org.raddatz.familienarchiv.exception.DomainException;
|
||||
import org.raddatz.familienarchiv.model.*;
|
||||
import org.raddatz.familienarchiv.repository.NotificationRepository;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.mail.MailException;
|
||||
import org.springframework.mail.MailSendException;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
@@ -19,6 +20,7 @@ import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@@ -34,6 +36,7 @@ class NotificationServiceTest {
|
||||
|
||||
@Mock NotificationRepository notificationRepository;
|
||||
@Mock UserService userService;
|
||||
@Mock DocumentService documentService;
|
||||
@Mock JavaMailSender mailSender;
|
||||
@Mock SseEmitterRegistry sseEmitterRegistry;
|
||||
|
||||
@@ -45,7 +48,7 @@ class NotificationServiceTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
notificationService = new NotificationService(notificationRepository, userService, Optional.of(mailSender), sseEmitterRegistry);
|
||||
notificationService = new NotificationService(notificationRepository, userService, documentService, Optional.of(mailSender), sseEmitterRegistry);
|
||||
|
||||
userA = AppUser.builder().id(UUID.randomUUID()).username("userA")
|
||||
.firstName("Anna").lastName("Smith").email("a@test.com")
|
||||
@@ -258,7 +261,7 @@ class NotificationServiceTest {
|
||||
@Test
|
||||
void notifyReply_skipsEmail_whenMailSenderIsAbsent() {
|
||||
NotificationService serviceWithoutMail = new NotificationService(
|
||||
notificationRepository, userService, Optional.empty(), sseEmitterRegistry);
|
||||
notificationRepository, userService, documentService, Optional.empty(), sseEmitterRegistry);
|
||||
|
||||
userA.setNotifyOnReply(true);
|
||||
DocumentComment reply = commentWithAuthor(UUID.randomUUID(), null, userC.getId(), "Clara Doe");
|
||||
@@ -274,7 +277,7 @@ class NotificationServiceTest {
|
||||
@Test
|
||||
void notifyMentions_skipsEmail_whenMailSenderIsAbsent() {
|
||||
NotificationService serviceWithoutMail = new NotificationService(
|
||||
notificationRepository, userService, Optional.empty(), sseEmitterRegistry);
|
||||
notificationRepository, userService, documentService, Optional.empty(), sseEmitterRegistry);
|
||||
|
||||
userA.setNotifyOnMention(true);
|
||||
DocumentComment comment = commentWithAuthor(UUID.randomUUID(), null, userC.getId(), "Clara Doe");
|
||||
@@ -401,6 +404,63 @@ class NotificationServiceTest {
|
||||
.findByRecipientIdAndTypeAndReadFalseOrderByCreatedAtDesc(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNotifications_withReadFalseAndNoType_usesUnreadOnlyRepoMethod() {
|
||||
when(notificationRepository.findByRecipientIdAndReadFalseOrderByCreatedAtDesc(
|
||||
eq(userA.getId()), any()))
|
||||
.thenReturn(Page.empty());
|
||||
|
||||
notificationService.getNotifications(userA.getId(), null, false, Pageable.ofSize(10));
|
||||
|
||||
verify(notificationRepository).findByRecipientIdAndReadFalseOrderByCreatedAtDesc(
|
||||
eq(userA.getId()), any());
|
||||
verify(notificationRepository, never()).findByRecipientIdOrderByCreatedAtDesc(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNotifications_mapsDocumentTitleFromDocumentService() {
|
||||
UUID docId = UUID.randomUUID();
|
||||
Notification notification = Notification.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.recipient(userA)
|
||||
.type(NotificationType.REPLY)
|
||||
.documentId(docId)
|
||||
.referenceId(UUID.randomUUID())
|
||||
.actorName("Clara Doe")
|
||||
.build();
|
||||
when(notificationRepository.findByRecipientIdOrderByCreatedAtDesc(eq(userA.getId()), any()))
|
||||
.thenReturn(new PageImpl<>(List.of(notification)));
|
||||
when(documentService.findTitlesByIds(Set.of(docId)))
|
||||
.thenReturn(Map.of(docId, "Geburtsurkunde Opa Karl"));
|
||||
|
||||
Page<NotificationDTO> result = notificationService.getNotifications(userA.getId(), null, null, Pageable.ofSize(10));
|
||||
|
||||
assertThat(result.getContent()).hasSize(1);
|
||||
assertThat(result.getContent().getFirst().documentTitle()).isEqualTo("Geburtsurkunde Opa Karl");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNotifications_mapsDocumentTitleAsNull_whenDocumentDoesNotExist() {
|
||||
UUID docId = UUID.randomUUID();
|
||||
Notification notification = Notification.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.recipient(userA)
|
||||
.type(NotificationType.MENTION)
|
||||
.documentId(docId)
|
||||
.referenceId(UUID.randomUUID())
|
||||
.actorName("Bob Jones")
|
||||
.build();
|
||||
when(notificationRepository.findByRecipientIdOrderByCreatedAtDesc(eq(userA.getId()), any()))
|
||||
.thenReturn(new PageImpl<>(List.of(notification)));
|
||||
when(documentService.findTitlesByIds(Set.of(docId)))
|
||||
.thenReturn(Map.of());
|
||||
|
||||
Page<NotificationDTO> result = notificationService.getNotifications(userA.getId(), null, null, Pageable.ofSize(10));
|
||||
|
||||
assertThat(result.getContent()).hasSize(1);
|
||||
assertThat(result.getContent().getFirst().documentTitle()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNotifications_withTypeAndReadTrue_fallsBackToTypeOnlyQuery() {
|
||||
// read=true with a type filter falls through to the type-only branch —
|
||||
|
||||
Reference in New Issue
Block a user