From 10037de043978e26bb223e8e0c3a24ce50c59b2d Mon Sep 17 00:00:00 2001 From: Marcus Fihlon Date: Wed, 29 Apr 2026 09:54:31 +0200 Subject: [PATCH] fix(analytics): count summary achievements by activity period Signed-off-by: Marcus Fihlon --- .../repository/AchievementRepository.java | 17 ++++ .../service/ActivitySummaryService.java | 2 +- .../service/ActivitySummaryServiceTest.java | 95 +++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/test/java/net/javahippie/fitpub/service/ActivitySummaryServiceTest.java diff --git a/src/main/java/net/javahippie/fitpub/repository/AchievementRepository.java b/src/main/java/net/javahippie/fitpub/repository/AchievementRepository.java index 130869c..2660051 100644 --- a/src/main/java/net/javahippie/fitpub/repository/AchievementRepository.java +++ b/src/main/java/net/javahippie/fitpub/repository/AchievementRepository.java @@ -60,6 +60,23 @@ public interface AchievementRepository extends JpaRepository @Param("endDate") LocalDateTime endDate ); + /** + * Count achievements whose triggering activity started within a date range. + */ + @Query(value = """ + SELECT COUNT(*) + FROM achievements ach + JOIN activities act ON act.id = ach.activity_id + WHERE ach.user_id = :userId + AND act.started_at >= :startDate + AND act.started_at < :endDate + """, nativeQuery = true) + long countByUserIdAndActivityStartedDateRange( + @Param("userId") UUID userId, + @Param("startDate") LocalDateTime startDate, + @Param("endDate") LocalDateTime endDate + ); + /** * Find recent achievements for a user. */ diff --git a/src/main/java/net/javahippie/fitpub/service/ActivitySummaryService.java b/src/main/java/net/javahippie/fitpub/service/ActivitySummaryService.java index 357bc8d..6579e45 100644 --- a/src/main/java/net/javahippie/fitpub/service/ActivitySummaryService.java +++ b/src/main/java/net/javahippie/fitpub/service/ActivitySummaryService.java @@ -172,7 +172,7 @@ public class ActivitySummaryService { startDateTime, endDateTime ); - long achievementsEarned = achievementRepository.countByUserIdAndDateRange( + long achievementsEarned = achievementRepository.countByUserIdAndActivityStartedDateRange( userId, startDateTime, endDateTime diff --git a/src/test/java/net/javahippie/fitpub/service/ActivitySummaryServiceTest.java b/src/test/java/net/javahippie/fitpub/service/ActivitySummaryServiceTest.java new file mode 100644 index 0000000..fdccc74 --- /dev/null +++ b/src/test/java/net/javahippie/fitpub/service/ActivitySummaryServiceTest.java @@ -0,0 +1,95 @@ +package net.javahippie.fitpub.service; + +import net.javahippie.fitpub.model.entity.Activity; +import net.javahippie.fitpub.model.entity.ActivitySummary; +import net.javahippie.fitpub.repository.ActivityRepository; +import net.javahippie.fitpub.repository.ActivitySummaryRepository; +import net.javahippie.fitpub.repository.AchievementRepository; +import net.javahippie.fitpub.repository.PersonalRecordRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +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.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ActivitySummaryServiceTest { + + @Mock + private ActivitySummaryRepository activitySummaryRepository; + + @Mock + private ActivityRepository activityRepository; + + @Mock + private PersonalRecordRepository personalRecordRepository; + + @Mock + private AchievementRepository achievementRepository; + + @InjectMocks + private ActivitySummaryService activitySummaryService; + + private UUID userId; + + @BeforeEach + void setUp() { + userId = UUID.randomUUID(); + } + + @Test + @DisplayName("Should count achievements in summaries by triggering activity start date") + void testUpdateWeeklySummary_CountsAchievementsByActivityStartDate() { + LocalDate weekDate = LocalDate.of(2025, 12, 3); + LocalDate weekStart = LocalDate.of(2025, 12, 1); + LocalDateTime startDateTime = weekStart.atStartOfDay(); + LocalDateTime endDateTime = weekStart.plusDays(7).atStartOfDay(); + + Activity activity = Activity.builder() + .id(UUID.randomUUID()) + .userId(userId) + .activityType(Activity.ActivityType.RUN) + .startedAt(LocalDateTime.of(2025, 12, 3, 23, 30)) + .endedAt(LocalDateTime.of(2025, 12, 4, 0, 15)) + .totalDistance(BigDecimal.valueOf(5000)) + .totalDurationSeconds(2700L) + .elevationGain(BigDecimal.valueOf(120)) + .build(); + + when(activitySummaryRepository.findByUserIdAndPeriodTypeAndPeriodStart( + userId, + ActivitySummary.PeriodType.WEEK, + weekStart + )).thenReturn(Optional.empty()); + when(activityRepository.findByUserIdAndStartedAtBetweenOrderByStartedAtDesc( + userId, + startDateTime, + endDateTime + )).thenReturn(List.of(activity)); + when(personalRecordRepository.countByUserIdAndDateRange(userId, startDateTime, endDateTime)).thenReturn(0L); + when(achievementRepository.countByUserIdAndActivityStartedDateRange(userId, startDateTime, endDateTime)).thenReturn(1L); + when(activitySummaryRepository.save(org.mockito.ArgumentMatchers.any(ActivitySummary.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + activitySummaryService.updateWeeklySummary(userId, weekDate); + + verify(achievementRepository).countByUserIdAndActivityStartedDateRange(userId, startDateTime, endDateTime); + verify(activitySummaryRepository).save(org.mockito.ArgumentMatchers.argThat(summary -> + summary.getAchievementsEarned() == 1 && + summary.getActivityCount() == 1 + )); + } +}