Doc Audit Findings β Notification System
Audited: 2026-03-09
Summary
- VALIDATED: 7
- MISMATCH: 6
- UNTESTED: 5
- UNDOCUMENTED: 5
MISMATCH
M1 β Migration file number (V25 vs V33)
Doc claims: βLocation: services/event-management-service/src/main/resources/db/migration/V25__Create_notifications_table.sqlβ
Test asserts: NotificationControllerIntegrationTest comment states βMigration V33 applied (notifications table)β β the controller integration test explicitly cites V33, not V25.
Action: Update doc migration path to V33__Create_notifications_table.sql.
M2 β HTTP client class name: UserServiceClient vs UserApiClient
Doc claims: NotificationService depends on UserServiceClient (see code snippets throughout the doc, e.g. private final UserServiceClient userServiceClient)
Test asserts: NotificationServiceTest#setUp constructs NotificationService(notificationRepository, emailService, userApiClient, ...) where userApiClient is typed UserApiClient β a different class name with a different import (ch.batbern.events.client.UserApiClient).
Action: Replace all occurrences of UserServiceClient with UserApiClient in the doc.
M3 β EmailService method signature: send(Notification) vs sendHtmlEmail(String, String, String)
Doc claims: emailService.send(notification) (full Notification object passed; AWS SES built inside EmailService)
Test asserts: NotificationServiceTest#should_createNotificationRecord_when_sendingEmail verifies verify(emailService).sendHtmlEmail(eq("john.doe@example.com"), eq("Test Subject"), anyString()) β three scalar arguments: recipient address, subject, HTML body.
Action: Update the EmailService integration snippet to show emailService.sendHtmlEmail(recipientEmail, notification.getSubject(), htmlContent).
M4 β In-app query method: findPublishedAfter vs findByPublishedAtAfter
Doc claims: List<Event> newEvents = eventRepository.findPublishedAfter(lastLogin);
Test asserts: NotificationServiceTest#should_queryDynamically_when_gettingInAppNotifications stubs when(eventRepository.findByPublishedAtAfter(lastLogin)).thenReturn(...) β method name follows Spring Data naming convention findByPublishedAtAfter.
Action: Update doc method call to eventRepository.findByPublishedAtAfter(lastLogin).
M5 β TaskDeadlineReminderJob: class name, lookup window, and delegation target
Doc claims: Class TaskDeadlineReminderJob runs @Scheduled(cron = "0 0 9 * * *"), calls taskRepository.findByDueDateBeforeAndStatusNot(threeDaysFromNow, "completed") (3-day lookahead), and calls notificationService.createAndSendEmailNotification(...) directly.
Test asserts: TaskDeadlineReminderSchedulerTest tests class TaskDeadlineReminderScheduler, which calls eventTaskRepository.findTasksDueForReminder(from, to) with a tomorrow-only window (start-of-tomorrow β start-of-day-after-tomorrow in Europe/Zurich), and delegates to TaskReminderEmailService#sendTaskDeadlineReminder(task, Locale.GERMAN) β not NotificationService.
Three specific assertions differ: (1) class name, (2) 1-day vs 3-day lookahead, (3) delegation target (TaskReminderEmailService vs NotificationService).
Action: Replace the TaskDeadlineReminderJob section with TaskDeadlineReminderScheduler, update the lookup window to tomorrow-only in Swiss timezone, and update the delegation target.
M6 β Notification status UNREAD not in documented status set
Doc claims: Entity comment lists valid statuses as PENDING, SENT, FAILED, READ.
Test asserts: NotificationControllerIntegrationTest#setUp saves three notifications with status("UNREAD"), and should_returnUnreadOnly_when_statusUnread / should_returnUnreadCount_when_statusUnread filter by status=UNREAD. UNREAD is a live status in use.
Action: Add UNREAD to the documented status enum: PENDING, UNREAD, SENT, FAILED, READ.
UNTESTED
U1 β EscalationRuleEngine thresholds
Doc claims: Three specific thresholds in EscalationRuleEngine:
DEADLINE_WARNINGescalates whentimeUntilDeadline.toDays() <= 3QUALITY_REVIEW_PENDINGescalates when review age>= 7 daysVOTING_REQUIREDescalates when< 50%voted after>= 3 daysNo test class forEscalationRuleEnginewas found in either test directory. Risk: high β these thresholds could drift silently; the 50%-voting and 7-day-review rules are completely unverified.
U2 β Quiet hours logic
Doc claims: isInQuietHours(UserPreferences prefs) uses LocalTime comparison to suppress notifications during configured quiet hours (default 22:00β07:00). Also contains a suspected bug: the if (start.isBefore(end)) branch comment says βNormal range (e.g., 22:00-07:00 next day)β but 22:00β07:00 crosses midnight so start.isBefore(end) is false β the described range falls into the else branch.
No test exercises quiet hours suppression or the midnight-crossing edge case.
Risk: high β the example in the doc may reflect a logic inversion bug that would cause quiet-hours notifications to fire, and morning-hours notifications to be suppressed.
U3 β onEventPublished event listener
Doc claims: @EventListener @Async public void onEventPublished(EventPublishedEvent event) sends EMAIL notifications to all registered attendees when an event is published, using registrationRepository.findUsernamesByEventId(event.getEventId()).
No test mocks or verifies this listener firing.
Risk: medium β the listener is async and the findUsernamesByEventId method name is never exercised in tests (deadline tests use findUsernamesByEventCode).
U4 β escalateToOrganizers sending URGENT notifications
Doc claims: When escalation is triggered, escalateToOrganizers() calls userServiceClient.getOrganizerUsernames() and sends URGENT-priority notifications to all organizers.
No test verifies escalation email dispatch or the organizer-lookup HTTP call.
Risk: medium β escalation is silent failure territory.
U5 β DEADLINE_REMINDER notification type
Doc claims: The NotificationType enum defines: SPEAKER_INVITED, SPEAKER_ACCEPTED, SPEAKER_DECLINED, CONTENT_SUBMITTED, QUALITY_REVIEW_PENDING, QUALITY_REVIEW_APPROVED, QUALITY_REVIEW_REQUIRES_CHANGES, SLOT_ASSIGNED, DEADLINE_WARNING, OVERFLOW_DETECTED, VOTING_REQUIRED, EVENT_PUBLISHED.
Test uses: NotificationControllerIntegrationTest#setUp creates a notification with notificationType("DEADLINE_REMINDER") β a string value absent from the documented enum.
Risk: low β notificationType is a VARCHAR column, so the value works at runtime, but the enum documentation is incomplete.
UNDOCUMENTED
N1 β SimpMessagingTemplate injected into NotificationService
Test: NotificationServiceTest#setUp constructs NotificationService(notificationRepository, emailService, userApiClient, eventRepository, registrationRepository, messagingTemplate) where messagingTemplate is typed org.springframework.messaging.simp.SimpMessagingTemplate. The doc explicitly states βReal-Time Notifications (Future Work) β Status: Not implemented β¦ deferred to future story.β
Action: Remove the βnot implementedβ statement or add a note that SimpMessagingTemplate is already wired into the service constructor (likely for the watch/ subsystem integration).
N2 β TaskDeadlineReminderScheduler uses Europe/Zurich timezone
Test: TaskDeadlineReminderSchedulerTest#should_queryTomorrowWindow_in_swissTimezone asserts the scheduler computes start-of-tomorrow and start-of-day-after-tomorrow using ZoneId.of("Europe/Zurich").
Action: Add to the scheduler section: βTomorrow window is computed in Europe/Zurich timezone.β
N3 β TaskDeadlineReminderScheduler forces German locale
Test: TaskDeadlineReminderSchedulerTest#should_useGermanLocale_for_allReminders verifies sendTaskDeadlineReminder(task, Locale.GERMAN) β all task reminder emails are German-only regardless of user preference.
Action: Add to the scheduler section: βEmails rendered in Locale.GERMAN; user language preference is not consulted for task reminders.β
N4 β TaskDeadlineReminderScheduler swallows repository exceptions
Test: TaskDeadlineReminderSchedulerTest#should_notThrow_when_repositoryFails verifies the scheduler completes without re-throwing when the repository throws RuntimeException.
Action: Add error-handling note: βScheduler catches and logs all RuntimeException from the repository; the job does not fail the Spring scheduler thread.β
N5 β TASK_DEADLINE_WARNING notification type never exercised
Test: DeadlineReminderJobTest#should_includeDeadlineInfo_when_sendingNotification verifies type "DEADLINE_WARNING" is used for registration deadline reminders. The docβs TaskDeadlineReminderJob snippet uses "TASK_DEADLINE_WARNING", but the actual TaskDeadlineReminderScheduler delegates to TaskReminderEmailService and no NotificationService call is verified β "TASK_DEADLINE_WARNING" is never asserted in any test.
Action: Clarify whether TASK_DEADLINE_WARNING is still a valid type or whether the scheduler bypasses the notifications table entirely by delegating directly to TaskReminderEmailService.
VALIDATED
- βCreate notification record before sending email (audit trail)β β
NotificationServiceTest#should_createNotificationRecord_when_sendingEmail(2Γrepository.saveverified) - βSkip notification when user opted out of emailβ β
NotificationServiceTest#should_skipNotification_when_userOptedOut - βStatus β SENT + sentAt set on successful deliveryβ β
NotificationServiceTest#should_updateStatusToSent_when_emailDeliverySucceeds - βStatus β FAILED + failedAt + failureReason set on delivery exceptionβ β
NotificationServiceTest#should_updateStatusToFailed_when_emailDeliveryFails - βIn-app notifications queried dynamically (zero DB writes)β β
NotificationServiceTest#should_queryDynamically_when_gettingInAppNotifications - βDeadline reminder finds events with deadlines in next 3 days via
findByRegistrationDeadlineBetweenβ βDeadlineReminderJobTest#should_findUpcomingDeadlines_when_jobExecutes - βRepository query methods:
findByRecipientUsername,findByRecipientUsernameAndStatus,countByRecipientUsernameAndStatus,findByRecipientUsernameAndChannelOrderByCreatedAtDesc,findByStatusAndCreatedAtBeforeβ βNotificationRepositoryTest(5 tests)