Repository: ita-social-projects/GreenCity
Pull request #8351: fixed problem with sorting
Author: login: NazarVavrushchak
Created: 2025-04-22T13:08:07Z | Merged: 2025-04-24T10:36:40Z

Link to problem [#8230](https://github.com/ita-social-projects/GreenCity/issues/8230)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **Refactor**
  - Improved the logic for fetching and merging notifications from multiple sources, resulting in more accurate and efficient pagination when viewing notifications.
- **Tests**
  - Enhanced test coverage for notification filtering and pagination, ensuring more realistic scenarios and improved reliability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/service/src/main/java/greencity/service/UserNotificationServiceImpl.java b/service/src/main/java/greencity/service/UserNotificationServiceImpl.java
index a70208a385..ff3ad471c2 100644
--- a/service/src/main/java/greencity/service/UserNotificationServiceImpl.java
+++ b/service/src/main/java/greencity/service/UserNotificationServiceImpl.java
@@ -22,6 +22,7 @@
 import lombok.RequiredArgsConstructor;
 import org.modelmapper.ModelMapper;
 import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
 import org.springframework.data.domain.Pageable;
 import org.springframework.messaging.simp.SimpMessagingTemplate;
 import org.springframework.scheduling.annotation.Scheduled;
@@ -83,50 +84,86 @@ public PageableAdvancedDto<NotificationDto> getNotificationsFiltered(Pageable pa
             case null -> {
                 long notificationSourcesCount = 2L;
 
-                PageableAdvancedDto<NotificationDto> notificationsFromGreenCity;
-                PageableAdvancedDto<NotificationDto> notificationsFromUbs;
+                long greenCityTotalElements;
+                long ubsTotalElements;
                 try (ExecutorService executorService = Executors.newFixedThreadPool((int) notificationSourcesCount)) {
-                    CompletableFuture<PageableAdvancedDto<NotificationDto>> notificationsFromGreenCityFuture =
-                        getNotificationsForUserFromGreenCity(
-                            executorService,
-                            page,
-                            principal,
-                            language,
-                            projectName,
-                            notificationTypes,
-                            viewed);
-                    CompletableFuture<PageableAdvancedDto<NotificationDto>> notificationsFromUbsFuture =
-                        getNotificationsForUserFromUbs(
-                            executorService,
-                            principal,
-                            page);
-
-                    notificationsFromGreenCity = notificationsFromGreenCityFuture.join();
-                    notificationsFromUbs = notificationsFromUbsFuture.join();
+                    CompletableFuture<Long> greenCityTotalFuture = CompletableFuture.supplyAsync(() -> {
+                        Pageable tempPageable = PageRequest.of(0, 1);
+                        return getNotificationsForUserFromGreenCity(tempPageable, principal, language, projectName,
+                            notificationTypes, viewed).getTotalElements();
+                    }, executorService);
+
+                    CompletableFuture<Long> ubsTotalFuture = CompletableFuture.supplyAsync(() -> {
+                        Pageable tempPageable = PageRequest.of(0, 1);
+                        return getNotificationsForUserFromUbs(principal, tempPageable).getTotalElements();
+                    }, executorService);
+
+                    greenCityTotalElements = greenCityTotalFuture.join();
+                    ubsTotalElements = ubsTotalFuture.join();
                 }
 
-                long mergedPageSize = notificationSourcesCount * page.getPageSize();
+                long totalElements = greenCityTotalElements + ubsTotalElements;
 
-                List<NotificationDto> mergedNotifications = Stream
-                    .concat(
-                        notificationsFromGreenCity.getPage().stream(),
-                        notificationsFromUbs.getPage().stream())
-                    .sorted(sortByRecentNotificationsComparator)
-                    .limit(mergedPageSize)
-                    .toList();
+                int pageSize = page.getPageSize();
+                int pageNumber = page.getPageNumber();
+                int startIndex = pageNumber * pageSize;
+                int endIndex = Math.min(startIndex + pageSize, (int) totalElements);
+
+                List<NotificationDto> mergedNotifications;
+                try (ExecutorService executorService = Executors.newFixedThreadPool((int) notificationSourcesCount)) {
+                    CompletableFuture<List<NotificationDto>> greenCityFuture = CompletableFuture.supplyAsync(() -> {
+                        List<NotificationDto> greenCityNotifications = new ArrayList<>();
+                        int currentPage = 0;
+                        Pageable tempPageable = PageRequest.of(currentPage, pageSize);
+                        PageableAdvancedDto<NotificationDto> greenCityPage;
+
+                        do {
+                            greenCityPage = getNotificationsForUserFromGreenCity(tempPageable, principal, language,
+                                projectName, notificationTypes, viewed);
+                            greenCityNotifications.addAll(greenCityPage.getPage());
+                            currentPage++;
+                            tempPageable = PageRequest.of(currentPage, pageSize);
+                        } while (greenCityPage.isHasNext() && greenCityNotifications.size() < endIndex);
+                        return greenCityNotifications;
+                    }, executorService);
+
+                    CompletableFuture<List<NotificationDto>> ubsFuture = CompletableFuture.supplyAsync(() -> {
+                        List<NotificationDto> ubsNotifications = new ArrayList<>();
+                        int currentPage = 0;
+                        Pageable tempPageable = PageRequest.of(currentPage, pageSize);
+                        PageableAdvancedDto<NotificationDto> ubsPage;
+
+                        do {
+                            ubsPage = getNotificationsForUserFromUbs(principal, tempPageable);
+                            ubsNotifications.addAll(ubsPage.getPage());
+                            currentPage++;
+                            tempPageable = PageRequest.of(currentPage, pageSize);
+                        } while (ubsPage.isHasNext() && ubsNotifications.size() < endIndex);
+                        return ubsNotifications;
+                    }, executorService);
+
+                    mergedNotifications = Stream
+                        .concat(
+                            greenCityFuture.join().stream(),
+                            ubsFuture.join().stream())
+                        .filter(dto -> dto.getTime() != null)
+                        .sorted(sortByRecentNotificationsComparator)
+                        .limit(endIndex)
+                        .toList();
+                }
 
-                long totalElements =
-                    notificationsFromGreenCity.getTotalElements() + notificationsFromUbs.getTotalElements();
+                List<NotificationDto> pagedNotifications = mergedNotifications.subList(
+                    Math.min(startIndex, mergedNotifications.size()),
+                    Math.min(endIndex, mergedNotifications.size()));
 
-                int totalPages = (int) Math.ceilDiv(totalElements, page.getPageSize());
-                int pageNumber = page.getPageNumber();
+                int totalPages = (int) Math.ceilDiv(totalElements, pageSize);
                 boolean hasPrevious = pageNumber > 0;
                 boolean hasNext = (pageNumber + 1) < totalPages;
                 boolean isFirst = pageNumber == 0;
                 boolean isLast = !hasNext;
 
                 yield PageableAdvancedDto.<NotificationDto>builder()
-                    .page(mergedNotifications)
+                    .page(pagedNotifications)
                     .totalElements(totalElements)
                     .currentPage(pageNumber)
                     .totalPages(totalPages)
@@ -521,16 +558,6 @@ private void generateNotification(final UserVO targetUserVO, final UserVO action
         sendNotification(notification.getTargetUser().getId());
     }
 
-    private CompletableFuture<PageableAdvancedDto<NotificationDto>> getNotificationsForUserFromGreenCity(
-        ExecutorService executorService, Pageable page,
-        Principal principal, String language, ProjectName projectName, List<NotificationType> notificationTypes,
-        Boolean viewed) {
-        return CompletableFuture.supplyAsync(
-            () -> getNotificationsForUserFromGreenCity(page, principal, language, projectName, notificationTypes,
-                viewed),
-            executorService);
-    }
-
     private PageableAdvancedDto<NotificationDto> getNotificationsForUserFromGreenCity(Pageable page,
         Principal principal, String language, ProjectName projectName, List<NotificationType> notificationTypes,
         Boolean viewed) {
@@ -543,13 +570,6 @@ private PageableAdvancedDto<NotificationDto> getNotificationsForUserFromGreenCit
         return buildPageableAdvancedDto(notificationsFromGreenCityPage, language);
     }
 
-    private CompletableFuture<PageableAdvancedDto<NotificationDto>> getNotificationsForUserFromUbs(
-        ExecutorService executorService, Principal principal, Pageable page) {
-        return CompletableFuture.supplyAsync(
-            () -> getNotificationsForUserFromUbs(principal, page),
-            executorService);
-    }
-
     private PageableAdvancedDto<NotificationDto> getNotificationsForUserFromUbs(Principal principal, Pageable page) {
         PageableAdvancedDto<UbsNotificationDto> notificationsFromUbs =
             restClient.findAllNotificationsForUserFromUbs(principal, page);
diff --git a/service/src/test/java/greencity/service/UserNotificationServiceImplTest.java b/service/src/test/java/greencity/service/UserNotificationServiceImplTest.java
index bc1c3927a3..788e0a3840 100644
--- a/service/src/test/java/greencity/service/UserNotificationServiceImplTest.java
+++ b/service/src/test/java/greencity/service/UserNotificationServiceImplTest.java
@@ -117,46 +117,74 @@ void getNotificationsFilteredTestWhenProjectNameIsNull() {
         ProjectName projectName = null;
         List<NotificationType> notificationTypes = Collections.emptyList();
         Boolean viewed = false;
-        PageableAdvancedDto<UbsNotificationDto> notificationsFromUbs = Mockito.mock(PageableAdvancedDto.class);
-        UbsNotificationDto ubsNotificationDto = mock(UbsNotificationDto.class);
-        List<UbsNotificationDto> page = List.of(
-            ubsNotificationDto,
-            ubsNotificationDto);
-        NotificationDto notificationDto = Mockito.mock(NotificationDto.class);
-        ZonedDateTime zonedDateTime = ZonedDateTime.now();
-        List<NotificationDto> expectedPage = List.of(
-            notificationDto,
-            notificationDto);
-        Pageable pageable = Mockito.mock(Pageable.class);
-        long expectedPageSize = expectedPage.size();
-        int pageSize = 10;
         int pageNumber = 0;
-        int totalPages = Math.ceilDiv((int) expectedPageSize, pageSize);
-        boolean first = pageNumber == 0;
-        boolean last = (pageNumber + 1) >= totalPages;
-        Page<Notification> notificationPage = new PageImpl<>(List.of(), pageable, 0);
+        int pageSize = 10;
+        Pageable pageable = PageRequest.of(pageNumber, pageSize);
+        Pageable countPageableGreenCity = PageRequest.of(0, 1);
+        Pageable countPageableUbs = PageRequest.of(0, 1);
+        Page<Notification> countGreenCityPage = new PageImpl<>(Collections.emptyList(), countPageableGreenCity, 5);
+        PageableAdvancedDto<UbsNotificationDto> countUbsPage = new PageableAdvancedDto<>(
+            Collections.emptyList(), 5, 0, 1, 0, false, false, true, true);
+
+        when(userService.findByEmail(email)).thenReturn(testUserVo);
+        when(notificationRepo.findNotificationsByFilter(testUser.getId(), projectName, notificationTypes, viewed,
+            countPageableGreenCity))
+            .thenReturn(countGreenCityPage);
+        when(restClient.findAllNotificationsForUserFromUbs(principal, countPageableUbs))
+            .thenReturn(countUbsPage);
+
+        Pageable greenCityPageable = PageRequest.of(0, pageSize);
+        Pageable ubsPageable = PageRequest.of(0, pageSize);
+
+        Notification notification1 = new Notification();
+        notification1.setNotificationType(NotificationType.HABIT_COMMENT_REPLY);
+        Notification notification2 = new Notification();
+        notification2.setNotificationType(NotificationType.EVENT_COMMENT);
+        Page<Notification> greenCityNotificationsPage =
+            new PageImpl<>(List.of(notification1, notification2), greenCityPageable, 2);
+
+        UbsNotificationDto ubsNotificationDto1 = mock(UbsNotificationDto.class);
+        UbsNotificationDto ubsNotificationDto2 = mock(UbsNotificationDto.class);
+        PageableAdvancedDto<UbsNotificationDto> ubsNotificationsPage = new PageableAdvancedDto<>(
+            List.of(ubsNotificationDto1, ubsNotificationDto2), 2, 0, 1, 0, false, false, true, true);
 
-        when(userService.findByEmail(email))
-            .thenReturn(testUserVo);
         when(notificationRepo.findNotificationsByFilter(testUser.getId(), projectName, notificationTypes, viewed,
-            pageable))
-            .thenReturn(notificationPage);
-        when(restClient.findAllNotificationsForUserFromUbs(principal, pageable))
-            .thenReturn(notificationsFromUbs);
-        when(notificationsFromUbs.getPage())
-            .thenReturn(page);
-        when(notificationsFromUbs.getTotalElements())
-            .thenReturn(expectedPageSize);
-        when(notificationsFromUbs.getTotalPages())
-            .thenReturn(totalPages);
-        when(modelMapper.map(any(UbsNotificationDto.class), eq(NotificationDto.class)))
-            .thenReturn(notificationDto);
-        when(notificationDto.getTime())
-            .thenReturn(zonedDateTime);
-        when(pageable.getPageSize())
-            .thenReturn(pageSize);
-        when(pageable.getPageNumber())
-            .thenReturn(pageNumber);
+            greenCityPageable))
+            .thenReturn(greenCityNotificationsPage);
+        when(restClient.findAllNotificationsForUserFromUbs(principal, ubsPageable))
+            .thenReturn(ubsNotificationsPage);
+
+        NotificationDto notificationDto1 = mock(NotificationDto.class);
+        NotificationDto notificationDto2 = mock(NotificationDto.class);
+        NotificationDto notificationDto3 = mock(NotificationDto.class);
+        NotificationDto notificationDto4 = mock(NotificationDto.class);
+
+        when(modelMapper.map(notification1, NotificationDto.class)).thenReturn(notificationDto1);
+        when(modelMapper.map(notification2, NotificationDto.class)).thenReturn(notificationDto2);
+        when(modelMapper.map(ubsNotificationDto1, NotificationDto.class)).thenReturn(notificationDto3);
+        when(modelMapper.map(ubsNotificationDto2, NotificationDto.class)).thenReturn(notificationDto4);
+
+        when(notificationDto1.getNotificationType()).thenReturn(NotificationType.ECONEWS_COMMENT_LIKE.name());
+        when(notificationDto2.getNotificationType()).thenReturn(EVENT_COMMENT_USER_TAG.name());
+
+        ZonedDateTime time1 = ZonedDateTime.now();
+        ZonedDateTime time2 = time1.minusMinutes(1);
+        ZonedDateTime time3 = time2.minusMinutes(1);
+        ZonedDateTime time4 = time3.minusMinutes(1);
+
+        when(notificationDto1.getTime()).thenReturn(time1);
+        when(notificationDto2.getTime()).thenReturn(time2);
+        when(notificationDto3.getTime()).thenReturn(time3);
+        when(notificationDto4.getTime()).thenReturn(time4);
+
+        List<NotificationDto> expectedPage =
+            List.of(notificationDto1, notificationDto2, notificationDto3, notificationDto4);
+        long totalElements = 10;
+        int totalPages = (int) Math.ceilDiv(totalElements, pageSize);
+        boolean hasPrevious = false;
+        boolean hasNext = (pageNumber + 1) < totalPages;
+        boolean isFirst = true;
+        boolean isLast = !hasNext;
 
         PageableAdvancedDto<NotificationDto> actualResult = userNotificationService.getNotificationsFiltered(
             pageable,
@@ -167,19 +195,24 @@ void getNotificationsFilteredTestWhenProjectNameIsNull() {
             viewed);
 
         assertEquals(expectedPage, actualResult.getPage());
-        assertEquals(expectedPage.size(), actualResult.getTotalElements());
-        assertEquals(notificationsFromUbs.getCurrentPage(), actualResult.getCurrentPage());
+        assertEquals(totalElements, actualResult.getTotalElements());
+        assertEquals(pageNumber, actualResult.getCurrentPage());
         assertEquals(totalPages, actualResult.getTotalPages());
-        assertEquals(notificationsFromUbs.getNumber(), actualResult.getNumber());
-        assertEquals(notificationsFromUbs.isHasPrevious(), actualResult.isHasPrevious());
-        assertEquals(notificationsFromUbs.isHasNext(), actualResult.isHasNext());
-        assertEquals(first, actualResult.isFirst());
-        assertEquals(last, actualResult.isLast());
-        verify(userService).findByEmail(email);
+        assertEquals(pageNumber, actualResult.getNumber());
+        assertEquals(hasPrevious, actualResult.isHasPrevious());
+        assertEquals(hasNext, actualResult.isHasNext());
+        assertEquals(isFirst, actualResult.isFirst());
+        assertEquals(isLast, actualResult.isLast());
+
+        verify(userService, times(2)).findByEmail(email);
         verify(notificationRepo).findNotificationsByFilter(testUser.getId(), projectName, notificationTypes, viewed,
-            pageable);
-        verify(restClient).findAllNotificationsForUserFromUbs(principal, pageable);
-        verify(modelMapper, times(page.size())).map(any(UbsNotificationDto.class), eq(NotificationDto.class));
+            countPageableGreenCity);
+        verify(restClient).findAllNotificationsForUserFromUbs(principal, countPageableUbs);
+        verify(notificationRepo).findNotificationsByFilter(testUser.getId(), projectName, notificationTypes, viewed,
+            greenCityPageable);
+        verify(restClient).findAllNotificationsForUserFromUbs(principal, ubsPageable);
+        verify(modelMapper, times(2)).map(any(Notification.class), eq(NotificationDto.class));
+        verify(modelMapper, times(2)).map(any(UbsNotificationDto.class), eq(NotificationDto.class));
     }
 
     @Test


Reviews:
user: login: Andrii-Danylenko
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-22T13:16:19Z
user: login: oleksandra-bulhakova
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T13:58:39Z
user: login: maks741
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T08:42:12Z

Review comments:
diff_hunk: @@ -83,50 +84,86 @@ public PageableAdvancedDto<NotificationDto> getNotificationsFiltered(Pageable pa
             case null -> {
                 long notificationSourcesCount = 2L;
 
-                PageableAdvancedDto<NotificationDto> notificationsFromGreenCity;
-                PageableAdvancedDto<NotificationDto> notificationsFromUbs;
+                long greenCityTotalElements;
+                long ubsTotalElements;
                 try (ExecutorService executorService = Executors.newFixedThreadPool((int) notificationSourcesCount)) {
-                    CompletableFuture<PageableAdvancedDto<NotificationDto>> notificationsFromGreenCityFuture =
-                        getNotificationsForUserFromGreenCity(
-                            executorService,
-                            page,
-                            principal,
-                            language,
-                            projectName,
-                            notificationTypes,
-                            viewed);
-                    CompletableFuture<PageableAdvancedDto<NotificationDto>> notificationsFromUbsFuture =
-                        getNotificationsForUserFromUbs(
-                            executorService,
-                            principal,
-                            page);
-
-                    notificationsFromGreenCity = notificationsFromGreenCityFuture.join();
-                    notificationsFromUbs = notificationsFromUbsFuture.join();
+                    CompletableFuture<Long> greenCityTotalFuture = CompletableFuture.supplyAsync(() -> {
+                        Pageable tempPageable = PageRequest.of(0, 1);
+                        return getNotificationsForUserFromGreenCity(tempPageable, principal, language, projectName,
+                            notificationTypes, viewed).getTotalElements();
+                    }, executorService);
+
+                    CompletableFuture<Long> ubsTotalFuture = CompletableFuture.supplyAsync(() -> {
+                        Pageable tempPageable = PageRequest.of(0, 1);
+                        return getNotificationsForUserFromUbs(principal, tempPageable).getTotalElements();
+                    }, executorService);
+
+                    greenCityTotalElements = greenCityTotalFuture.join();
+                    ubsTotalElements = ubsTotalFuture.join();
                 }
 
-                long mergedPageSize = notificationSourcesCount * page.getPageSize();
+                long totalElements = greenCityTotalElements + ubsTotalElements;
 
-                List<NotificationDto> mergedNotifications = Stream
-                    .concat(
-                        notificationsFromGreenCity.getPage().stream(),
-                        notificationsFromUbs.getPage().stream())
-                    .sorted(sortByRecentNotificationsComparator)
-                    .limit(mergedPageSize)
-                    .toList();
+                int pageSize = page.getPageSize();
+                int pageNumber = page.getPageNumber();
+                int startIndex = pageNumber * pageSize;
+                int endIndex = Math.min(startIndex + pageSize, (int) totalElements);
+
+                List<NotificationDto> mergedNotifications;
+                try (ExecutorService executorService = Executors.newFixedThreadPool((int) notificationSourcesCount)) {
+                    CompletableFuture<List<NotificationDto>> greenCityFuture = CompletableFuture.supplyAsync(() -> {
+                        List<NotificationDto> greenCityNotifications = new ArrayList<>();
+                        int currentPage = 0;
+                        Pageable tempPageable = PageRequest.of(currentPage, pageSize);
+                        PageableAdvancedDto<NotificationDto> greenCityPage;
+
+                        do {
+                            greenCityPage = getNotificationsForUserFromGreenCity(tempPageable, principal, language,
+                                projectName, notificationTypes, viewed);
+                            greenCityNotifications.addAll(greenCityPage.getPage());
+                            currentPage++;
+                            tempPageable = PageRequest.of(currentPage, pageSize);
+                        } while (greenCityPage.isHasNext() && greenCityNotifications.size() < endIndex);
path: service/src/main/java/greencity/service/UserNotificationServiceImpl.java
user: login: Andrii-Danylenko
body: As a suggestion: maybe it's worth implementing an Iterable interface instead of do-while
created_at: 2025-04-22T13:12:15Z
author_association: MEMBER
diff_hunk: @@ -117,46 +117,74 @@ void getNotificationsFilteredTestWhenProjectNameIsNull() {
         ProjectName projectName = null;
         List<NotificationType> notificationTypes = Collections.emptyList();
         Boolean viewed = false;
-        PageableAdvancedDto<UbsNotificationDto> notificationsFromUbs = Mockito.mock(PageableAdvancedDto.class);
-        UbsNotificationDto ubsNotificationDto = mock(UbsNotificationDto.class);
-        List<UbsNotificationDto> page = List.of(
-            ubsNotificationDto,
-            ubsNotificationDto);
-        NotificationDto notificationDto = Mockito.mock(NotificationDto.class);
-        ZonedDateTime zonedDateTime = ZonedDateTime.now();
-        List<NotificationDto> expectedPage = List.of(
-            notificationDto,
-            notificationDto);
-        Pageable pageable = Mockito.mock(Pageable.class);
-        long expectedPageSize = expectedPage.size();
-        int pageSize = 10;
         int pageNumber = 0;
-        int totalPages = Math.ceilDiv((int) expectedPageSize, pageSize);
-        boolean first = pageNumber == 0;
-        boolean last = (pageNumber + 1) >= totalPages;
-        Page<Notification> notificationPage = new PageImpl<>(List.of(), pageable, 0);
+        int pageSize = 10;
+        Pageable pageable = PageRequest.of(pageNumber, pageSize);
+        Pageable countPageableGreenCity = PageRequest.of(0, 1);
+        Pageable countPageableUbs = PageRequest.of(0, 1);
+        Page<Notification> countGreenCityPage = new PageImpl<>(Collections.emptyList(), countPageableGreenCity, 5);
+        PageableAdvancedDto<UbsNotificationDto> countUbsPage = new PageableAdvancedDto<>(
+            Collections.emptyList(), 5, 0, 1, 0, false, false, true, true);
+
+        when(userService.findByEmail(email)).thenReturn(testUserVo);
+        when(notificationRepo.findNotificationsByFilter(testUser.getId(), projectName, notificationTypes, viewed,
+            countPageableGreenCity))
+            .thenReturn(countGreenCityPage);
+        when(restClient.findAllNotificationsForUserFromUbs(principal, countPageableUbs))
+            .thenReturn(countUbsPage);
+
+        Pageable greenCityPageable = PageRequest.of(0, pageSize);
+        Pageable ubsPageable = PageRequest.of(0, pageSize);
+
+        Notification notification1 = new Notification();
+        notification1.setNotificationType(NotificationType.HABIT_COMMENT_REPLY);
+        Notification notification2 = new Notification();
+        notification2.setNotificationType(NotificationType.EVENT_COMMENT);
+        Page<Notification> greenCityNotificationsPage =
+            new PageImpl<>(List.of(notification1, notification2), greenCityPageable, 2);
+
+        UbsNotificationDto ubsNotificationDto1 = mock(UbsNotificationDto.class);
+        UbsNotificationDto ubsNotificationDto2 = mock(UbsNotificationDto.class);
+        PageableAdvancedDto<UbsNotificationDto> ubsNotificationsPage = new PageableAdvancedDto<>(
+            List.of(ubsNotificationDto1, ubsNotificationDto2), 2, 0, 1, 0, false, false, true, true);
 
-        when(userService.findByEmail(email))
-            .thenReturn(testUserVo);
         when(notificationRepo.findNotificationsByFilter(testUser.getId(), projectName, notificationTypes, viewed,
-            pageable))
-            .thenReturn(notificationPage);
-        when(restClient.findAllNotificationsForUserFromUbs(principal, pageable))
-            .thenReturn(notificationsFromUbs);
-        when(notificationsFromUbs.getPage())
-            .thenReturn(page);
-        when(notificationsFromUbs.getTotalElements())
-            .thenReturn(expectedPageSize);
-        when(notificationsFromUbs.getTotalPages())
-            .thenReturn(totalPages);
-        when(modelMapper.map(any(UbsNotificationDto.class), eq(NotificationDto.class)))
-            .thenReturn(notificationDto);
-        when(notificationDto.getTime())
-            .thenReturn(zonedDateTime);
-        when(pageable.getPageSize())
-            .thenReturn(pageSize);
-        when(pageable.getPageNumber())
-            .thenReturn(pageNumber);
+            greenCityPageable))
+            .thenReturn(greenCityNotificationsPage);
+        when(restClient.findAllNotificationsForUserFromUbs(principal, ubsPageable))
+            .thenReturn(ubsNotificationsPage);
+
+        NotificationDto notificationDto1 = mock(NotificationDto.class);
+        NotificationDto notificationDto2 = mock(NotificationDto.class);
+        NotificationDto notificationDto3 = mock(NotificationDto.class);
+        NotificationDto notificationDto4 = mock(NotificationDto.class);
+
+        when(modelMapper.map(notification1, NotificationDto.class)).thenReturn(notificationDto1);
+        when(modelMapper.map(notification2, NotificationDto.class)).thenReturn(notificationDto2);
+        when(modelMapper.map(ubsNotificationDto1, NotificationDto.class)).thenReturn(notificationDto3);
+        when(modelMapper.map(ubsNotificationDto2, NotificationDto.class)).thenReturn(notificationDto4);
+
+        when(notificationDto1.getNotificationType()).thenReturn(NotificationType.ECONEWS_COMMENT_LIKE.name());
+        when(notificationDto2.getNotificationType()).thenReturn(EVENT_COMMENT_USER_TAG.name());
+
+        ZonedDateTime time1 = ZonedDateTime.now();
path: service/src/test/java/greencity/service/UserNotificationServiceImplTest.java
user: login: Andrii-Danylenko
body: Please do not use the .now() method in tests. Tests must produce predictable and consistent results
created_at: 2025-04-22T13:15:46Z
author_association: MEMBER

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-22T13:08:13Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The `getNotificationsFiltered` method in `UserNotificationServiceImpl` was refactored to improve how notifications are fetched and merged when `projectName` is `null`. Instead of fetching a single page from each source, the method now first retrieves the total notification counts from both sources, then fetches multiple pages as needed to assemble the requested page range. The merging, sorting, and pagination logic was updated accordingly. Two private helper methods for asynchronous fetching were removed in favor of direct use of `CompletableFuture.supplyAsync`. Corresponding tests were updated to use concrete pageable and notification objects, with more detailed assertions and setup.

## Changes

| File(s)                                                                                       | Change Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
|-----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `service/src/main/java/greencity/service/UserNotificationServiceImpl.java`                    | Refactored `getNotificationsFiltered` to fetch notification counts first, then retrieve multiple pages as needed from GreenCity and UBS, merge, sort, and paginate results. Removed two private helper methods for async fetching, replacing them with direct `CompletableFuture.supplyAsync` usage.                                                                                                                    |
| `service/src/test/java/greencity/service/UserNotificationServiceImplTest.java`                | Refactored `getNotificationsFilteredTestWhenProjectNameIsNull` to use concrete pageable and notification objects, explicitly set up notification DTOs, and updated assertions and verifications to match the new merging and pagination logic. No changes to public or exported entities.                                                                                                                             |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant UserNotificationServiceImpl
    participant GreenCityService
    participant UBSService

    Client->>UserNotificationServiceImpl: getNotificationsFiltered(...)
    UserNotificationServiceImpl->>GreenCityService: getNotificationsCount(...)
    UserNotificationServiceImpl->>UBSService: getNotificationsCount(...)
    par Fetch notifications as needed
        loop While more notifications needed from GreenCity
            UserNotificationServiceImpl->>GreenCityService: getNotifications(page)
        end
        loop While more notifications needed from UBS
            UserNotificationServiceImpl->>UBSService: getNotifications(page)
        end
    end
    UserNotificationServiceImpl->>UserNotificationServiceImpl: Merge, sort, and paginate results
    UserNotificationServiceImpl-->>Client: Return paginated notifications
```

## Possibly related PRs

- [ita-social-projects/GreenCity#8244](https://github.com/ita-social-projects/GreenCity/pull/8244): Refactors notification fetching and sorting logic in `UserNotificationServiceImpl`, consolidating notification retrieval and improving sorting, which is directly related to the changes in this PR.

## Suggested reviewers

- oleksandra-bulhakova
- DmytroLysenko1
- ChernenkoVitaliy

## Poem

> Notifications gathered from near and far,  
> Merged and sorted, no matter where they are.  
> Pages fetched with care, counts tallied just right,  
> Tests shine brighter, logic clear in sight.  
> With teamwork and code, improvements unfurled—  
> A tidier inbox for a greener world! 🌱

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 3883a7bc81bb571ad606cf9bcd16357370d2bcb8 and ef89cbf8a21646150c8a3d328353e9fedcbbf30f.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `service/src/main/java/greencity/service/UserNotificationServiceImpl.java` (2 hunks)
* `service/src/test/java/greencity/service/UserNotificationServiceImplTest.java` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (2)</summary>

<details>
<summary>📓 Common learnings</summary>

```
Learnt from: maks741
PR: ita-social-projects/GreenCity#8244
File: service/src/main/java/greencity/service/UserNotificationServiceImpl.java:78-123
Timestamp: 2025-03-14T16:22:58.043Z
Learning: In UserNotificationServiceImpl, the use of .limit(2L * page.getPageSize()) when merging notifications from GreenCity and UBS is intentional. This ensures that all notifications from both sources can be accommodated without losing any data during the merging process.
```

</details>
<details>
<summary>service/src/main/java/greencity/service/UserNotificationServiceImpl.java (1)</summary>

```
Learnt from: maks741
PR: ita-social-projects/GreenCity#8244
File: service/src/main/java/greencity/service/UserNotificationServiceImpl.java:78-123
Timestamp: 2025-03-14T16:22:58.043Z
Learning: In UserNotificationServiceImpl, the use of .limit(2L * page.getPageSize()) when merging notifications from GreenCity and UBS is intentional. This ensures that all notifications from both sources can be accommodated without losing any data during the merging process.
```

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: build

</details>

<details>
<summary>🔇 Additional comments (14)</summary><blockquote>

<details>
<summary>service/src/main/java/greencity/service/UserNotificationServiceImpl.java (7)</summary>

`25-25`: **Good addition of necessary import.**

Added PageRequest import which is needed for the refactored implementation.

---

`87-103`: **Well-structured concurrent retrieval of notification counts.**

The implementation efficiently retrieves total counts from both sources concurrently using CompletableFuture, which is a good approach for improving performance. The code creates temporary PageRequest objects with minimal size (1) just to retrieve the total counts.

---

`105-111`: **Proper calculation of pagination parameters.**

This segment correctly calculates the total elements and pagination boundaries that will be needed for merging notifications from both sources. The use of Math.min helps ensure we don't exceed array bounds.

---

`112-153`: **Improved pagination logic with proper concurrent fetching.**

This refactoring significantly improves how notifications are collected from both sources. Instead of fetching just one page from each source, the code now properly:
1. Fetches multiple pages concurrently until enough data is collected
2. Uses do-while loops with proper termination conditions (hasNext check and size limit)
3. Merges results appropriately with filtering and sorting

This addresses the core sorting issue mentioned in the PR objectives.

---

`155-158`: **Effective sublist extraction for final pagination.**

This code correctly extracts the requested page range from the sorted merged list, using Math.min to safeguard against out-of-bounds scenarios.

---

`159-164`: **Proper recalculation of pagination metadata.**

The pagination metadata is correctly recalculated based on the total elements and requested page, ensuring consistent pagination behavior.

---

`165-175`: **Clean PageableAdvancedDto construction.**

The builder is used effectively to construct the result with all the necessary pagination metadata.

</details>
<details>
<summary>service/src/test/java/greencity/service/UserNotificationServiceImplTest.java (7)</summary>

`121-128`: **Well-structured test setup with concrete paging objects.**

The test now uses concrete PageRequest and PageImpl objects instead of mocks, which makes the test more realistic and easier to understand. The setup also defines countPageable objects for both GreenCity and UBS notifications.

---

`129-135`: **Proper mocking of notification count operations.**

This segment correctly mocks the behavior for retrieving notification counts from both sources, which is essential for testing the first phase of the refactored method.

---

`136-156`: **Comprehensive test setup for notification fetching.**

This segment properly sets up the test for the second phase of the refactored method:
1. Creates concrete page objects for both GreenCity and UBS
2. Sets up distinct notification types
3. Mocks the repository and client responses

All of these changes properly test the refactored implementation.

---

`157-179`: **Well-structured test data with chronological time ordering.**

This segment creates mock notification DTOs with specific timestamps in descending order, which is essential for testing the sorting functionality. The mock setup is thorough and tests the key functionality being fixed in this PR.

---

`180-188`: **Appropriate expected result calculation.**

The test correctly calculates the expected results including:
1. The expected page content
2. Total elements and pages
3. Pagination flags (hasPrevious, hasNext, isFirst, isLast)

This ensures that the test verifies all aspects of the pagination functionality.

---

`197-205`: **Comprehensive assertions for verifying results.**

The assertions thoroughly verify all aspects of the result:
1. Page content
2. Total elements and pages
3. Pagination metadata and flags

This ensures that the pagination and sorting behavior is correctly tested.

---

`207-215`: **Proper verification of method calls.**

The test verifies:
1. That userService.findByEmail is called twice (once for each notification source)
2. That repository and client methods are called with the correct parameters
3. That the mapping occurs the correct number of times for both notification types

This ensures the internal behavior of the method is correctly tested.

</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=ita-social-projects/GreenCity&utm_content=8351):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-04-22T13:14:02Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=ita-social-projects-green-city&pullRequest=8351) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=ita-social-projects-green-city&pullRequest=8351&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=ita-social-projects-green-city&pullRequest=8351&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=ita-social-projects-green-city&pullRequest=8351&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [87.7% Coverage on New Code](https://sonarcloud.io/component_measures?id=ita-social-projects-green-city&pullRequest=8351&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=ita-social-projects-green-city&pullRequest=8351&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=ita-social-projects-green-city&pullRequest=8351)


author_association: NONE