chore: merge main into feat/persons-redesign-concept-a
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled

Resolved conflicts in messages/de.json, en.json, es.json by keeping
both the persons-redesign keys (feature branch) and the notification
keys (main) in all three locale files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit was merged in pull request #159.
This commit is contained in:
Marcel
2026-03-29 21:30:54 +02:00
49 changed files with 1025 additions and 102 deletions

View File

@@ -75,7 +75,7 @@ class NotificationControllerTest {
AppUser user = AppUser.builder().id(USER_ID).username("testuser").build();
NotificationDTO dto = new NotificationDTO(
UUID.randomUUID(), NotificationType.REPLY, UUID.randomUUID(),
UUID.randomUUID(), null, false, LocalDateTime.now(), "Anna Smith");
UUID.randomUUID(), null, false, LocalDateTime.now(), "Anna Smith", "Testdokument");
when(userService.findByUsername("testuser")).thenReturn(user);
when(notificationService.getNotifications(eq(USER_ID), any(), any(), any()))
@@ -123,6 +123,20 @@ class NotificationControllerTest {
.andExpect(status().isBadRequest());
}
@Test
@WithMockUser(username = "testuser", authorities = {"READ_ALL"})
void getNotifications_returns400_whenSizeExceedsMaximum() throws Exception {
mockMvc.perform(get("/api/notifications").param("size", "200"))
.andExpect(status().isBadRequest());
}
@Test
@WithMockUser(username = "testuser", authorities = {"READ_ALL"})
void getNotifications_returns400_whenSizeIsZero() throws Exception {
mockMvc.perform(get("/api/notifications").param("size", "0"))
.andExpect(status().isBadRequest());
}
// ─── POST /api/notifications/read-all ────────────────────────────────────
@Test

View File

@@ -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 —