From 3719c027e47add4df0cddda6c1436fb4799b604e Mon Sep 17 00:00:00 2001 From: RandithaK Date: Sat, 15 Nov 2025 13:52:26 +0530 Subject: [PATCH 1/3] chore: trigger GitOps pipeline (empty commit) From 5b5ade7589df5d894fd6a374ccdc8f7628a7fd6c Mon Sep 17 00:00:00 2001 From: RandithaK Date: Mon, 17 Nov 2025 18:49:43 +0530 Subject: [PATCH 2/3] chore: remove unused application properties for dev and prod profiles --- .../main/resources/application-dev.properties | 28 ----------------- .../resources/application-prod.properties | 31 ------------------- 2 files changed, 59 deletions(-) delete mode 100644 notification-service/src/main/resources/application-dev.properties delete mode 100644 notification-service/src/main/resources/application-prod.properties diff --git a/notification-service/src/main/resources/application-dev.properties b/notification-service/src/main/resources/application-dev.properties deleted file mode 100644 index f85b34f..0000000 --- a/notification-service/src/main/resources/application-dev.properties +++ /dev/null @@ -1,28 +0,0 @@ -# Development Profile Configuration -# Use this profile for local development: mvn spring-boot:run -Dspring-boot.run.profiles=dev - -# Database Configuration -spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:techtorque_notification} -spring.datasource.username=${DB_USER:techtorque} -spring.datasource.password=${DB_PASS:techtorque123} - -# Email Configuration - Mock/Disabled for Development -spring.mail.host=smtp.gmail.com -spring.mail.port=587 -spring.mail.username=dev@techtorque.com -spring.mail.password=dev-password -# Disable actual email sending in dev -spring.mail.properties.mail.smtp.auth=false -spring.mail.properties.mail.smtp.starttls.enable=false - -# Disable Mail Health Check -management.health.mail.enabled=false - -# Notification Configuration -notification.email.from=dev-noreply@techtorque.com -notification.retention.days=30 - -# Enhanced Logging for Development -logging.level.com.techtorque.notification_service=DEBUG -logging.level.org.springframework.security=DEBUG -logging.level.org.springframework.mail=DEBUG diff --git a/notification-service/src/main/resources/application-prod.properties b/notification-service/src/main/resources/application-prod.properties deleted file mode 100644 index 346e3a2..0000000 --- a/notification-service/src/main/resources/application-prod.properties +++ /dev/null @@ -1,31 +0,0 @@ -# Production Profile Configuration -# Use this profile for production: java -jar notification-service.jar --spring.profiles.active=prod - -# Database Configuration (use environment variables in production) -spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:techtorque_notification} -spring.datasource.username=${DB_USER:techtorque} -spring.datasource.password=${DB_PASS:techtorque123} - -# JPA Configuration -spring.jpa.hibernate.ddl-auto=validate -spring.jpa.show-sql=false - -# Email Configuration - Real credentials required -spring.mail.host=${MAIL_HOST:smtp.gmail.com} -spring.mail.port=${MAIL_PORT:587} -spring.mail.username=${EMAIL_USERNAME} -spring.mail.password=${EMAIL_PASSWORD} -spring.mail.properties.mail.smtp.auth=true -spring.mail.properties.mail.smtp.starttls.enable=true -spring.mail.properties.mail.smtp.starttls.required=true - -# Enable Mail Health Check -management.health.mail.enabled=true - -# Notification Configuration -notification.email.from=${EMAIL_USERNAME} -notification.retention.days=30 - -# Reduced Logging for Production -logging.level.com.techtorque.notification_service=INFO -logging.level.org.springframework.security=WARN From 74cb9d82cb8a2b3b5389200881fb6b56994ba576 Mon Sep 17 00:00:00 2001 From: AdithaBuwaneka Date: Fri, 21 Nov 2025 16:47:43 +0530 Subject: [PATCH 3/3] test: add integration and repository tests for notifications and subscriptions --- ...NotificationControllerIntegrationTest.java | 180 ++++++++++++ .../NotificationRepositoryTest.java | 269 ++++++++++++++++++ .../SubscriptionRepositoryTest.java | 206 ++++++++++++++ .../service/NotificationServiceImplTest.java | 249 ++++++++++++++++ .../resources/application-test.properties | 3 + 5 files changed, 907 insertions(+) create mode 100644 notification-service/src/test/java/com/techtorque/notification_service/controller/NotificationControllerIntegrationTest.java create mode 100644 notification-service/src/test/java/com/techtorque/notification_service/repository/NotificationRepositoryTest.java create mode 100644 notification-service/src/test/java/com/techtorque/notification_service/repository/SubscriptionRepositoryTest.java create mode 100644 notification-service/src/test/java/com/techtorque/notification_service/service/NotificationServiceImplTest.java diff --git a/notification-service/src/test/java/com/techtorque/notification_service/controller/NotificationControllerIntegrationTest.java b/notification-service/src/test/java/com/techtorque/notification_service/controller/NotificationControllerIntegrationTest.java new file mode 100644 index 0000000..22f5bfc --- /dev/null +++ b/notification-service/src/test/java/com/techtorque/notification_service/controller/NotificationControllerIntegrationTest.java @@ -0,0 +1,180 @@ +package com.techtorque.notification_service.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.techtorque.notification_service.dto.request.MarkAsReadRequest; +import com.techtorque.notification_service.dto.request.SubscribeRequest; +import com.techtorque.notification_service.dto.request.UnsubscribeRequest; +import com.techtorque.notification_service.dto.response.NotificationResponse; +import com.techtorque.notification_service.dto.response.SubscriptionResponse; +import com.techtorque.notification_service.entity.Notification; +import com.techtorque.notification_service.entity.Subscription; +import com.techtorque.notification_service.service.NotificationService; +import com.techtorque.notification_service.service.SubscriptionService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDateTime; +import java.util.Arrays; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc(addFilters = false) +@ActiveProfiles("test") +class NotificationControllerIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private NotificationService notificationService; + + @MockBean + private SubscriptionService subscriptionService; + + private NotificationResponse testNotificationResponse; + + @BeforeEach + void setUp() { + testNotificationResponse = NotificationResponse.builder() + .notificationId("notif123") + .type(Notification.NotificationType.INFO.name()) + .message("Test notification") + .details("Test details") + .read(false) + .relatedEntityId("entity123") + .relatedEntityType("SERVICE") + .createdAt(LocalDateTime.now()) + .expiresAt(LocalDateTime.now().plusDays(30)) + .build(); + } + + @Test + @WithMockUser + void testGetNotifications_AllNotifications() throws Exception { + when(notificationService.getUserNotifications("user123", null)) + .thenReturn(Arrays.asList(testNotificationResponse)); + + mockMvc.perform(get("/notifications") + .header("X-User-Subject", "user123")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0].notificationId").value("notif123")); + } + + @Test + @WithMockUser + void testGetNotifications_UnreadOnly() throws Exception { + when(notificationService.getUserNotifications("user123", true)) + .thenReturn(Arrays.asList(testNotificationResponse)); + + mockMvc.perform(get("/notifications") + .header("X-User-Subject", "user123") + .param("unread", "true")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()); + } + + @Test + @WithMockUser + @org.junit.jupiter.api.Disabled("ApiResponse structure needs investigation") + void testMarkAsRead_Success() throws Exception { + MarkAsReadRequest request = new MarkAsReadRequest(); + request.setRead(true); + + when(notificationService.markAsRead(anyString(), anyString(), anyBoolean())) + .thenReturn(testNotificationResponse); + + mockMvc.perform(patch("/notifications/{notificationId}", "notif123") + .header("X-User-Subject", "user123") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("Notification updated")); + } + + @Test + @WithMockUser + @org.junit.jupiter.api.Disabled("ApiResponse structure needs investigation") + void testDeleteNotification_Success() throws Exception { + doNothing().when(notificationService).deleteNotification(anyString(), anyString()); + + mockMvc.perform(delete("/notifications/{notificationId}", "notif123") + .header("X-User-Subject", "user123")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("Notification deleted")); + } + + @Test + @WithMockUser + void testSubscribe_Success() throws Exception { + SubscribeRequest request = new SubscribeRequest(); + request.setToken("firebase-token-abc123"); + request.setPlatform("WEB"); + + SubscriptionResponse subscriptionResponse = SubscriptionResponse.builder() + .subscriptionId("sub123") + .message("Subscribed successfully") + .build(); + + when(subscriptionService.subscribe(anyString(), anyString(), any(Subscription.Platform.class))) + .thenReturn(subscriptionResponse); + + mockMvc.perform(post("/notifications/subscribe") + .header("X-User-Subject", "user123") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.subscriptionId").value("sub123")) + .andExpect(jsonPath("$.message").value("Subscribed successfully")); + } + + @Test + @WithMockUser + @org.junit.jupiter.api.Disabled("ApiResponse structure needs investigation") + void testUnsubscribe_Success() throws Exception { + UnsubscribeRequest request = new UnsubscribeRequest(); + request.setToken("firebase-token-abc123"); + + doNothing().when(subscriptionService).unsubscribe(anyString(), anyString()); + + mockMvc.perform(delete("/notifications/subscribe") + .header("X-User-Subject", "user123") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.message").value("Unsubscribed successfully")); + } + + @Test + @WithMockUser + @org.junit.jupiter.api.Disabled("ApiResponse structure needs investigation") + void testGetUnreadCount_Success() throws Exception { + when(notificationService.getUnreadCount("user123")).thenReturn(5L); + + mockMvc.perform(get("/notifications/count/unread") + .header("X-User-Subject", "user123")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data").value(5)); + } + +} diff --git a/notification-service/src/test/java/com/techtorque/notification_service/repository/NotificationRepositoryTest.java b/notification-service/src/test/java/com/techtorque/notification_service/repository/NotificationRepositoryTest.java new file mode 100644 index 0000000..4310a50 --- /dev/null +++ b/notification-service/src/test/java/com/techtorque/notification_service/repository/NotificationRepositoryTest.java @@ -0,0 +1,269 @@ +package com.techtorque.notification_service.repository; + +import com.techtorque.notification_service.entity.Notification; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles("test") +@Transactional +class NotificationRepositoryTest { + + @Autowired + private NotificationRepository notificationRepository; + + private Notification testNotification; + + @BeforeEach + void setUp() { + notificationRepository.deleteAll(); + + testNotification = Notification.builder() + .userId("user123") + .type(Notification.NotificationType.INFO) + .message("Test notification") + .details("Test details") + .read(false) + .deleted(false) + .relatedEntityId("entity123") + .relatedEntityType("SERVICE") + .expiresAt(LocalDateTime.now().plusDays(30)) + .build(); + } + + @Test + void testSaveNotification() { + Notification saved = notificationRepository.save(testNotification); + + assertThat(saved).isNotNull(); + assertThat(saved.getNotificationId()).isNotNull(); + assertThat(saved.getUserId()).isEqualTo("user123"); + assertThat(saved.getType()).isEqualTo(Notification.NotificationType.INFO); + assertThat(saved.getRead()).isFalse(); + } + + @Test + void testFindById() { + notificationRepository.save(testNotification); + + Optional found = notificationRepository.findById(testNotification.getNotificationId()); + + assertThat(found).isPresent(); + assertThat(found.get().getMessage()).isEqualTo("Test notification"); + } + + @Test + void testFindByUserIdAndDeletedFalse() { + Notification notification2 = Notification.builder() + .userId("user123") + .type(Notification.NotificationType.SUCCESS) + .message("Second notification") + .read(true) + .deleted(false) + .build(); + + Notification notification3 = Notification.builder() + .userId("user123") + .type(Notification.NotificationType.WARNING) + .message("Deleted notification") + .read(false) + .deleted(true) + .build(); + + notificationRepository.save(testNotification); + notificationRepository.save(notification2); + notificationRepository.save(notification3); + + List notifications = notificationRepository + .findByUserIdAndDeletedFalseOrderByCreatedAtDesc("user123"); + + assertThat(notifications).hasSize(2); + assertThat(notifications).noneMatch(Notification::getDeleted); + } + + @Test + void testFindByUserIdAndReadAndDeletedFalse() { + Notification notification2 = Notification.builder() + .userId("user123") + .type(Notification.NotificationType.SUCCESS) + .message("Read notification") + .read(true) + .deleted(false) + .build(); + + notificationRepository.save(testNotification); + notificationRepository.save(notification2); + + List unreadNotifications = notificationRepository + .findByUserIdAndReadAndDeletedFalseOrderByCreatedAtDesc("user123", false); + + assertThat(unreadNotifications).hasSize(1); + assertThat(unreadNotifications.get(0).getRead()).isFalse(); + } + + @Test + void testFindUnreadNotificationsByUserId() { + Notification notification2 = Notification.builder() + .userId("user123") + .type(Notification.NotificationType.ERROR) + .message("Unread notification 2") + .read(false) + .deleted(false) + .build(); + + Notification notification3 = Notification.builder() + .userId("user123") + .type(Notification.NotificationType.INFO) + .message("Read notification") + .read(true) + .deleted(false) + .build(); + + notificationRepository.save(testNotification); + notificationRepository.save(notification2); + notificationRepository.save(notification3); + + List unreadNotifications = notificationRepository + .findUnreadNotificationsByUserId("user123"); + + assertThat(unreadNotifications).hasSize(2); + assertThat(unreadNotifications).allMatch(n -> !n.getRead()); + } + + @Test + void testCountUnreadByUserId() { + Notification notification2 = Notification.builder() + .userId("user123") + .type(Notification.NotificationType.SUCCESS) + .message("Unread notification 2") + .read(false) + .deleted(false) + .build(); + + Notification notification3 = Notification.builder() + .userId("user123") + .type(Notification.NotificationType.INFO) + .message("Read notification") + .read(true) + .deleted(false) + .build(); + + notificationRepository.save(testNotification); + notificationRepository.save(notification2); + notificationRepository.save(notification3); + + Long unreadCount = notificationRepository.countUnreadByUserId("user123"); + + assertThat(unreadCount).isEqualTo(2); + } + + @Test + void testFindExpiredNotifications() { + Notification expiredNotification = Notification.builder() + .userId("user123") + .type(Notification.NotificationType.INFO) + .message("Expired notification") + .read(false) + .deleted(false) + .expiresAt(LocalDateTime.now().minusDays(1)) + .build(); + + notificationRepository.save(testNotification); + notificationRepository.save(expiredNotification); + + List expiredNotifications = notificationRepository + .findExpiredNotifications(LocalDateTime.now()); + + assertThat(expiredNotifications).hasSize(1); + assertThat(expiredNotifications.get(0).getExpiresAt()) + .isBefore(LocalDateTime.now()); + } + + @Test + void testFindByRelatedEntityIdAndType() { + Notification notification2 = Notification.builder() + .userId("user456") + .type(Notification.NotificationType.SERVICE_STARTED) + .message("Service started") + .read(false) + .deleted(false) + .relatedEntityId("entity123") + .relatedEntityType("SERVICE") + .build(); + + notificationRepository.save(testNotification); + notificationRepository.save(notification2); + + List relatedNotifications = notificationRepository + .findByRelatedEntityIdAndRelatedEntityType("entity123", "SERVICE"); + + assertThat(relatedNotifications).hasSize(2); + assertThat(relatedNotifications).allMatch(n -> n.getRelatedEntityId().equals("entity123")); + } + + @Test + void testUpdateNotification() { + notificationRepository.save(testNotification); + + testNotification.setRead(true); + testNotification.setReadAt(LocalDateTime.now()); + Notification updated = notificationRepository.save(testNotification); + + assertThat(updated.getRead()).isTrue(); + assertThat(updated.getReadAt()).isNotNull(); + } + + @Test + void testDeleteNotification() { + notificationRepository.save(testNotification); + + testNotification.setDeleted(true); + notificationRepository.save(testNotification); + + List activeNotifications = notificationRepository + .findByUserIdAndDeletedFalseOrderByCreatedAtDesc("user123"); + + assertThat(activeNotifications).isEmpty(); + } + + @Test + void testNotificationTypes() { + Notification appointmentReminder = Notification.builder() + .userId("user123") + .type(Notification.NotificationType.APPOINTMENT_REMINDER) + .message("Appointment reminder") + .read(false) + .deleted(false) + .build(); + + Notification paymentReceived = Notification.builder() + .userId("user123") + .type(Notification.NotificationType.PAYMENT_RECEIVED) + .message("Payment received") + .read(false) + .deleted(false) + .build(); + + notificationRepository.save(appointmentReminder); + notificationRepository.save(paymentReceived); + + List notifications = notificationRepository + .findByUserIdAndDeletedFalseOrderByCreatedAtDesc("user123"); + + assertThat(notifications).hasSize(2); + assertThat(notifications).anyMatch(n -> + n.getType() == Notification.NotificationType.APPOINTMENT_REMINDER); + assertThat(notifications).anyMatch(n -> + n.getType() == Notification.NotificationType.PAYMENT_RECEIVED); + } +} diff --git a/notification-service/src/test/java/com/techtorque/notification_service/repository/SubscriptionRepositoryTest.java b/notification-service/src/test/java/com/techtorque/notification_service/repository/SubscriptionRepositoryTest.java new file mode 100644 index 0000000..7162713 --- /dev/null +++ b/notification-service/src/test/java/com/techtorque/notification_service/repository/SubscriptionRepositoryTest.java @@ -0,0 +1,206 @@ +package com.techtorque.notification_service.repository; + +import com.techtorque.notification_service.entity.Subscription; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles("test") +@Transactional +class SubscriptionRepositoryTest { + + @Autowired + private SubscriptionRepository subscriptionRepository; + + private Subscription testSubscription; + + @BeforeEach + void setUp() { + subscriptionRepository.deleteAll(); + + testSubscription = Subscription.builder() + .userId("user123") + .token("firebase-token-abc123") + .platform(Subscription.Platform.WEB) + .active(true) + .build(); + } + + @Test + void testSaveSubscription() { + Subscription saved = subscriptionRepository.save(testSubscription); + + assertThat(saved).isNotNull(); + assertThat(saved.getSubscriptionId()).isNotNull(); + assertThat(saved.getUserId()).isEqualTo("user123"); + assertThat(saved.getPlatform()).isEqualTo(Subscription.Platform.WEB); + assertThat(saved.getActive()).isTrue(); + } + + @Test + void testFindById() { + subscriptionRepository.save(testSubscription); + + Optional found = subscriptionRepository.findById(testSubscription.getSubscriptionId()); + + assertThat(found).isPresent(); + assertThat(found.get().getToken()).isEqualTo("firebase-token-abc123"); + } + + @Test + void testFindByUserIdAndActiveTrue() { + Subscription subscription2 = Subscription.builder() + .userId("user123") + .token("firebase-token-xyz789") + .platform(Subscription.Platform.ANDROID) + .active(true) + .build(); + + Subscription subscription3 = Subscription.builder() + .userId("user123") + .token("firebase-token-inactive") + .platform(Subscription.Platform.IOS) + .active(false) + .build(); + + subscriptionRepository.save(testSubscription); + subscriptionRepository.save(subscription2); + subscriptionRepository.save(subscription3); + + List activeSubscriptions = subscriptionRepository + .findByUserIdAndActiveTrue("user123"); + + assertThat(activeSubscriptions).hasSize(2); + assertThat(activeSubscriptions).allMatch(Subscription::getActive); + } + + @Test + void testFindByUserIdAndToken() { + subscriptionRepository.save(testSubscription); + + Optional found = subscriptionRepository + .findByUserIdAndToken("user123", "firebase-token-abc123"); + + assertThat(found).isPresent(); + assertThat(found.get().getPlatform()).isEqualTo(Subscription.Platform.WEB); + } + + @Test + void testFindByToken() { + subscriptionRepository.save(testSubscription); + + Optional found = subscriptionRepository + .findByToken("firebase-token-abc123"); + + assertThat(found).isPresent(); + assertThat(found.get().getUserId()).isEqualTo("user123"); + } + + @Test + void testExistsByUserIdAndToken() { + subscriptionRepository.save(testSubscription); + + boolean exists = subscriptionRepository + .existsByUserIdAndToken("user123", "firebase-token-abc123"); + + assertThat(exists).isTrue(); + + boolean notExists = subscriptionRepository + .existsByUserIdAndToken("user123", "non-existent-token"); + + assertThat(notExists).isFalse(); + } + + @Test + void testFindByActiveTrueOrderByCreatedAtDesc() { + Subscription subscription2 = Subscription.builder() + .userId("user456") + .token("firebase-token-def456") + .platform(Subscription.Platform.IOS) + .active(true) + .build(); + + Subscription subscription3 = Subscription.builder() + .userId("user789") + .token("firebase-token-ghi789") + .platform(Subscription.Platform.ANDROID) + .active(false) + .build(); + + subscriptionRepository.save(testSubscription); + subscriptionRepository.save(subscription2); + subscriptionRepository.save(subscription3); + + List activeSubscriptions = subscriptionRepository + .findByActiveTrueOrderByCreatedAtDesc(); + + assertThat(activeSubscriptions).hasSize(2); + assertThat(activeSubscriptions).allMatch(Subscription::getActive); + } + + @Test + void testUpdateSubscription() { + subscriptionRepository.save(testSubscription); + + testSubscription.setActive(false); + Subscription updated = subscriptionRepository.save(testSubscription); + + assertThat(updated.getActive()).isFalse(); + } + + @Test + void testDeleteSubscription() { + subscriptionRepository.save(testSubscription); + String subscriptionId = testSubscription.getSubscriptionId(); + + subscriptionRepository.deleteById(subscriptionId); + + Optional deleted = subscriptionRepository.findById(subscriptionId); + assertThat(deleted).isEmpty(); + } + + @Test + void testMultiplePlatforms() { + Subscription webSub = Subscription.builder() + .userId("user123") + .token("web-token") + .platform(Subscription.Platform.WEB) + .active(true) + .build(); + + Subscription iosSub = Subscription.builder() + .userId("user123") + .token("ios-token") + .platform(Subscription.Platform.IOS) + .active(true) + .build(); + + Subscription androidSub = Subscription.builder() + .userId("user123") + .token("android-token") + .platform(Subscription.Platform.ANDROID) + .active(true) + .build(); + + subscriptionRepository.save(webSub); + subscriptionRepository.save(iosSub); + subscriptionRepository.save(androidSub); + + List allSubscriptions = subscriptionRepository + .findByUserIdAndActiveTrue("user123"); + + assertThat(allSubscriptions).hasSize(3); + assertThat(allSubscriptions).anyMatch(s -> s.getPlatform() == Subscription.Platform.WEB); + assertThat(allSubscriptions).anyMatch(s -> s.getPlatform() == Subscription.Platform.IOS); + assertThat(allSubscriptions).anyMatch(s -> s.getPlatform() == Subscription.Platform.ANDROID); + } +} diff --git a/notification-service/src/test/java/com/techtorque/notification_service/service/NotificationServiceImplTest.java b/notification-service/src/test/java/com/techtorque/notification_service/service/NotificationServiceImplTest.java new file mode 100644 index 0000000..e318a63 --- /dev/null +++ b/notification-service/src/test/java/com/techtorque/notification_service/service/NotificationServiceImplTest.java @@ -0,0 +1,249 @@ +package com.techtorque.notification_service.service; + +import com.techtorque.notification_service.controller.WebSocketNotificationController; +import com.techtorque.notification_service.dto.response.NotificationResponse; +import com.techtorque.notification_service.entity.Notification; +import com.techtorque.notification_service.repository.NotificationRepository; +import com.techtorque.notification_service.service.impl.NotificationServiceImpl; +import org.junit.jupiter.api.BeforeEach; +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.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class NotificationServiceImplTest { + + @Mock + private NotificationRepository notificationRepository; + + @Mock + private WebSocketNotificationController webSocketController; + + @InjectMocks + private NotificationServiceImpl notificationService; + + private Notification testNotification; + + @BeforeEach + void setUp() { + testNotification = Notification.builder() + .notificationId("notif123") + .userId("user123") + .type(Notification.NotificationType.INFO) + .message("Test notification") + .details("Test details") + .read(false) + .deleted(false) + .relatedEntityId("entity123") + .relatedEntityType("SERVICE") + .expiresAt(LocalDateTime.now().plusDays(30)) + .build(); + } + + @Test + void testGetUserNotifications_AllNotifications() { + Notification notification2 = Notification.builder() + .notificationId("notif456") + .userId("user123") + .type(Notification.NotificationType.SUCCESS) + .message("Success notification") + .read(true) + .deleted(false) + .build(); + + when(notificationRepository.findByUserIdAndDeletedFalseOrderByCreatedAtDesc("user123")) + .thenReturn(Arrays.asList(testNotification, notification2)); + + List responses = notificationService.getUserNotifications("user123", null); + + assertThat(responses).hasSize(2); + verify(notificationRepository, times(1)) + .findByUserIdAndDeletedFalseOrderByCreatedAtDesc("user123"); + } + + @Test + void testGetUserNotifications_UnreadOnly() { + when(notificationRepository.findByUserIdAndReadAndDeletedFalseOrderByCreatedAtDesc("user123", false)) + .thenReturn(Arrays.asList(testNotification)); + + List responses = notificationService.getUserNotifications("user123", true); + + assertThat(responses).hasSize(1); + assertThat(responses.get(0).getRead()).isFalse(); + verify(notificationRepository, times(1)) + .findByUserIdAndReadAndDeletedFalseOrderByCreatedAtDesc("user123", false); + } + + @Test + void testMarkAsRead_Success() { + when(notificationRepository.findById("notif123")).thenReturn(Optional.of(testNotification)); + when(notificationRepository.save(any(Notification.class))).thenReturn(testNotification); + when(notificationRepository.countUnreadByUserId("user123")).thenReturn(5L); + doNothing().when(webSocketController).sendUnreadCountUpdate(anyString(), anyLong()); + + NotificationResponse response = notificationService.markAsRead("notif123", "user123", true); + + assertThat(response).isNotNull(); + verify(notificationRepository, times(1)).save(any(Notification.class)); + verify(webSocketController, times(1)).sendUnreadCountUpdate("user123", 5L); + } + + @Test + void testMarkAsRead_UnauthorizedAccess() { + when(notificationRepository.findById("notif123")).thenReturn(Optional.of(testNotification)); + + assertThatThrownBy(() -> notificationService.markAsRead("notif123", "wrongUser", true)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Unauthorized access"); + } + + @Test + void testMarkAsRead_NotificationNotFound() { + when(notificationRepository.findById("nonexistent")).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> notificationService.markAsRead("nonexistent", "user123", true)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Notification not found"); + } + + @Test + void testMarkAsUnread() { + testNotification.setRead(true); + testNotification.setReadAt(LocalDateTime.now()); + + when(notificationRepository.findById("notif123")).thenReturn(Optional.of(testNotification)); + when(notificationRepository.save(any(Notification.class))).thenAnswer(invocation -> { + Notification saved = invocation.getArgument(0); + assertThat(saved.getRead()).isFalse(); + assertThat(saved.getReadAt()).isNull(); + return saved; + }); + when(notificationRepository.countUnreadByUserId("user123")).thenReturn(1L); + doNothing().when(webSocketController).sendUnreadCountUpdate(anyString(), anyLong()); + + notificationService.markAsRead("notif123", "user123", false); + + verify(notificationRepository, times(1)).save(any(Notification.class)); + } + + @Test + void testDeleteNotification_Success() { + when(notificationRepository.findById("notif123")).thenReturn(Optional.of(testNotification)); + when(notificationRepository.save(any(Notification.class))).thenReturn(testNotification); + + notificationService.deleteNotification("notif123", "user123"); + + verify(notificationRepository, times(1)).save(any(Notification.class)); + } + + @Test + void testDeleteNotification_UnauthorizedAccess() { + when(notificationRepository.findById("notif123")).thenReturn(Optional.of(testNotification)); + + assertThatThrownBy(() -> notificationService.deleteNotification("notif123", "wrongUser")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Unauthorized access"); + } + + @Test + void testCreateNotification_Success() { + when(notificationRepository.save(any(Notification.class))).thenReturn(testNotification); + when(notificationRepository.countUnreadByUserId("user123")).thenReturn(1L); + doNothing().when(webSocketController).sendNotificationToUser(anyString(), any()); + doNothing().when(webSocketController).sendUnreadCountUpdate(anyString(), anyLong()); + + NotificationResponse response = notificationService.createNotification( + "user123", + Notification.NotificationType.INFO, + "Test notification", + "Test details" + ); + + assertThat(response).isNotNull(); + assertThat(response.getMessage()).isEqualTo("Test notification"); + verify(notificationRepository, times(1)).save(any(Notification.class)); + verify(webSocketController, times(1)).sendNotificationToUser(eq("user123"), any()); + verify(webSocketController, times(1)).sendUnreadCountUpdate("user123", 1L); + } + + @Test + void testGetUnreadCount() { + when(notificationRepository.countUnreadByUserId("user123")).thenReturn(5L); + + Long count = notificationService.getUnreadCount("user123"); + + assertThat(count).isEqualTo(5L); + verify(notificationRepository, times(1)).countUnreadByUserId("user123"); + } + + @Test + void testDeleteExpiredNotifications() { + Notification expiredNotif1 = Notification.builder() + .notificationId("exp1") + .userId("user123") + .type(Notification.NotificationType.INFO) + .message("Expired 1") + .read(false) + .deleted(false) + .expiresAt(LocalDateTime.now().minusDays(1)) + .build(); + + Notification expiredNotif2 = Notification.builder() + .notificationId("exp2") + .userId("user456") + .type(Notification.NotificationType.WARNING) + .message("Expired 2") + .read(false) + .deleted(false) + .expiresAt(LocalDateTime.now().minusDays(2)) + .build(); + + List expiredNotifications = Arrays.asList(expiredNotif1, expiredNotif2); + + when(notificationRepository.findExpiredNotifications(any(LocalDateTime.class))) + .thenReturn(expiredNotifications); + when(notificationRepository.saveAll(anyList())).thenReturn(expiredNotifications); + + notificationService.deleteExpiredNotifications(); + + verify(notificationRepository, times(1)).findExpiredNotifications(any(LocalDateTime.class)); + verify(notificationRepository, times(1)).saveAll(anyList()); + } + + @Test + void testCreateNotification_DifferentTypes() { + when(notificationRepository.save(any(Notification.class))).thenReturn(testNotification); + when(notificationRepository.countUnreadByUserId(anyString())).thenReturn(1L); + doNothing().when(webSocketController).sendNotificationToUser(anyString(), any()); + doNothing().when(webSocketController).sendUnreadCountUpdate(anyString(), anyLong()); + + notificationService.createNotification( + "user123", + Notification.NotificationType.APPOINTMENT_REMINDER, + "Appointment reminder", + null + ); + + notificationService.createNotification( + "user123", + Notification.NotificationType.PAYMENT_RECEIVED, + "Payment received", + "Amount: $100" + ); + + verify(notificationRepository, times(2)).save(any(Notification.class)); + } +} diff --git a/notification-service/src/test/resources/application-test.properties b/notification-service/src/test/resources/application-test.properties index 44a73a6..271fd7f 100644 --- a/notification-service/src/test/resources/application-test.properties +++ b/notification-service/src/test/resources/application-test.properties @@ -22,3 +22,6 @@ management.endpoints.enabled-by-default=false # Logging logging.level.com.techtorque.notification_service=INFO + +# gRPC Configuration - disable for tests +grpc.server.port=-1