diff --git a/commons/src/main/java/com/tomassirio/wanderer/commons/domain/AchievementType.java b/commons/src/main/java/com/tomassirio/wanderer/commons/domain/AchievementType.java index 1ef18134..f91dcc69 100644 --- a/commons/src/main/java/com/tomassirio/wanderer/commons/domain/AchievementType.java +++ b/commons/src/main/java/com/tomassirio/wanderer/commons/domain/AchievementType.java @@ -4,6 +4,10 @@ @Getter public enum AchievementType { + // Activation achievements + FIRST_TRIP("First Steps", "Create your first trip", 1), + PROFILE_COMPLETED("All Set Up", "Fill in your display name and bio", 1), + // Distance achievements (in kilometers) DISTANCE_100KM("First Century", "Walk 100km in a single trip", 100), DISTANCE_200KM("4daagse", "Walk 200km in a single trip", 200), @@ -14,6 +18,7 @@ public enum AchievementType { DISTANCE_2200KM("The Hobbit", "Walk from the Shire to Mordor (2200km)", 2200), // Update achievements + UPDATES_1("Breaking the Ice", "Post your first update on a trip", 1), UPDATES_10("Getting Started", "Post 10 updates on a single trip", 10), UPDATES_50("Regular Reporter", "Post 50 updates on a single trip", 50), UPDATES_100("Century Poster", "Post 100 updates on a single trip", 100), @@ -25,11 +30,13 @@ public enum AchievementType { DURATION_60_DAYS("Two Month Journey", "Trip lasting 60 days", 60), // Social achievements - Followers + FOLLOWERS_1("First Fan", "Gain your first follower", 1), FOLLOWERS_10("Popular Walker", "Reach 10 followers", 10), FOLLOWERS_50("Influencer", "Reach 50 followers", 50), FOLLOWERS_100("Community Leader", "Reach 100 followers", 100), // Social achievements - Friends + FRIENDS_1("First Friend", "Make your first friend", 1), FRIENDS_5("Making Friends", "Make 5 friends", 5), FRIENDS_20("Social Butterfly", "Make 20 friends", 20), FRIENDS_50("Friend Collector", "Make 50 friends", 50); diff --git a/wanderer-command/src/main/java/com/tomassirio/wanderer/command/handler/TripCreatedEventHandler.java b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/handler/TripCreatedEventHandler.java index 802db957..91df26a1 100644 --- a/wanderer-command/src/main/java/com/tomassirio/wanderer/command/handler/TripCreatedEventHandler.java +++ b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/handler/TripCreatedEventHandler.java @@ -2,6 +2,7 @@ import com.tomassirio.wanderer.command.event.TripCreatedEvent; import com.tomassirio.wanderer.command.repository.TripRepository; +import com.tomassirio.wanderer.command.service.AchievementService; import com.tomassirio.wanderer.command.service.helper.TripEmbeddedObjectsInitializer; import com.tomassirio.wanderer.commons.domain.Trip; import com.tomassirio.wanderer.commons.domain.TripVisibility; @@ -25,6 +26,7 @@ public class TripCreatedEventHandler implements EventHandler { private final TripEmbeddedObjectsInitializer embeddedObjectsInitializer; private final TripRepository tripRepository; + private final AchievementService achievementCalculationService; @Override @EventListener @@ -65,6 +67,7 @@ public void handle(TripCreatedEvent event) { } tripRepository.save(trip); + achievementCalculationService.checkAndUnlockSocialAchievements(event.getOwnerId()); log.info("Trip created and persisted: {}", event.getTripId()); } } diff --git a/wanderer-command/src/main/java/com/tomassirio/wanderer/command/handler/UserDetailsUpdatedEventHandler.java b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/handler/UserDetailsUpdatedEventHandler.java index 289ccff9..e4969815 100644 --- a/wanderer-command/src/main/java/com/tomassirio/wanderer/command/handler/UserDetailsUpdatedEventHandler.java +++ b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/handler/UserDetailsUpdatedEventHandler.java @@ -2,6 +2,7 @@ import com.tomassirio.wanderer.command.event.UserDetailsUpdatedEvent; import com.tomassirio.wanderer.command.repository.UserRepository; +import com.tomassirio.wanderer.command.service.AchievementService; import com.tomassirio.wanderer.commons.domain.UserDetails; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -16,6 +17,7 @@ public class UserDetailsUpdatedEventHandler implements EventHandler { private final UserRepository userRepository; + private final AchievementService achievementCalculationService; @Override @EventListener @@ -42,6 +44,9 @@ public void handle(UserDetailsUpdatedEvent event) { user.setUserDetails(details); log.info("User details updated for user: {}", event.getUserId()); + + achievementCalculationService.checkAndUnlockSocialAchievements( + event.getUserId()); }); } } diff --git a/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/FollowerAchievementChecker.java b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/FollowerAchievementChecker.java index 3411e0b9..d107e310 100644 --- a/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/FollowerAchievementChecker.java +++ b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/FollowerAchievementChecker.java @@ -17,6 +17,7 @@ public class FollowerAchievementChecker implements SocialAchievementChecker { @Override public List getApplicableTypes() { return List.of( + AchievementType.FOLLOWERS_1, AchievementType.FOLLOWERS_10, AchievementType.FOLLOWERS_50, AchievementType.FOLLOWERS_100); diff --git a/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/FriendAchievementChecker.java b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/FriendAchievementChecker.java index 69400db3..b10c0abb 100644 --- a/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/FriendAchievementChecker.java +++ b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/FriendAchievementChecker.java @@ -17,7 +17,10 @@ public class FriendAchievementChecker implements SocialAchievementChecker { @Override public List getApplicableTypes() { return List.of( - AchievementType.FRIENDS_5, AchievementType.FRIENDS_20, AchievementType.FRIENDS_50); + AchievementType.FRIENDS_1, + AchievementType.FRIENDS_5, + AchievementType.FRIENDS_20, + AchievementType.FRIENDS_50); } @Override diff --git a/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/ProfileCompletionAchievementChecker.java b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/ProfileCompletionAchievementChecker.java new file mode 100644 index 00000000..19053d8d --- /dev/null +++ b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/ProfileCompletionAchievementChecker.java @@ -0,0 +1,40 @@ +package com.tomassirio.wanderer.command.service.impl.checker; + +import com.tomassirio.wanderer.command.repository.UserRepository; +import com.tomassirio.wanderer.commons.domain.AchievementType; +import com.tomassirio.wanderer.commons.domain.User; +import com.tomassirio.wanderer.commons.domain.UserDetails; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** Checks the profile-completed achievement (display name and bio both filled in). */ +@Component +@RequiredArgsConstructor +public class ProfileCompletionAchievementChecker implements SocialAchievementChecker { + + private final UserRepository userRepository; + + @Override + public List getApplicableTypes() { + return List.of(AchievementType.PROFILE_COMPLETED); + } + + @Override + public double computeMetric(UUID userId) { + User user = userRepository.findById(userId).orElse(null); + if (user == null) { + return 0.0; + } + + UserDetails details = user.getUserDetails(); + boolean complete = + details != null + && StringUtils.hasText(details.getDisplayName()) + && StringUtils.hasText(details.getBio()); + + return complete ? 1.0 : 0.0; + } +} diff --git a/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/TripCountAchievementChecker.java b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/TripCountAchievementChecker.java new file mode 100644 index 00000000..5a7f11ff --- /dev/null +++ b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/TripCountAchievementChecker.java @@ -0,0 +1,26 @@ +package com.tomassirio.wanderer.command.service.impl.checker; + +import com.tomassirio.wanderer.command.repository.TripRepository; +import com.tomassirio.wanderer.commons.domain.AchievementType; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** Checks achievements based on the number of trips a user has created. */ +@Component +@RequiredArgsConstructor +public class TripCountAchievementChecker implements SocialAchievementChecker { + + private final TripRepository tripRepository; + + @Override + public List getApplicableTypes() { + return List.of(AchievementType.FIRST_TRIP); + } + + @Override + public double computeMetric(UUID userId) { + return tripRepository.findAllByUserId(userId).size(); + } +} diff --git a/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/UpdateCountAchievementChecker.java b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/UpdateCountAchievementChecker.java index 5f4f0836..34346b8f 100644 --- a/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/UpdateCountAchievementChecker.java +++ b/wanderer-command/src/main/java/com/tomassirio/wanderer/command/service/impl/checker/UpdateCountAchievementChecker.java @@ -17,6 +17,7 @@ public class UpdateCountAchievementChecker implements TripAchievementChecker { @Override public List getApplicableTypes() { return List.of( + AchievementType.UPDATES_1, AchievementType.UPDATES_10, AchievementType.UPDATES_50, AchievementType.UPDATES_100); diff --git a/wanderer-command/src/main/resources/db/changelog/042-seed-activation-achievements-data.yaml b/wanderer-command/src/main/resources/db/changelog/042-seed-activation-achievements-data.yaml new file mode 100644 index 00000000..39df2adb --- /dev/null +++ b/wanderer-command/src/main/resources/db/changelog/042-seed-activation-achievements-data.yaml @@ -0,0 +1,115 @@ +databaseChangeLog: + - changeSet: + id: 042-seed-activation-achievements-data + author: tomassirio + comment: "Seed achievements table with new activation AchievementType values" + preConditions: + - onFail: MARK_RAN + - tableExists: + tableName: achievements + changes: + - insert: + tableName: achievements + columns: + - column: + name: id + valueComputed: gen_random_uuid() + - column: + name: type + value: FIRST_TRIP + - column: + name: name + value: "First Steps" + - column: + name: description + value: "Create your first trip" + - column: + name: threshold_value + valueNumeric: 1 + - column: + name: enabled + valueBoolean: true + - insert: + tableName: achievements + columns: + - column: + name: id + valueComputed: gen_random_uuid() + - column: + name: type + value: UPDATES_1 + - column: + name: name + value: "Breaking the Ice" + - column: + name: description + value: "Post your first update on a trip" + - column: + name: threshold_value + valueNumeric: 1 + - column: + name: enabled + valueBoolean: true + - insert: + tableName: achievements + columns: + - column: + name: id + valueComputed: gen_random_uuid() + - column: + name: type + value: FOLLOWERS_1 + - column: + name: name + value: "First Fan" + - column: + name: description + value: "Gain your first follower" + - column: + name: threshold_value + valueNumeric: 1 + - column: + name: enabled + valueBoolean: true + - insert: + tableName: achievements + columns: + - column: + name: id + valueComputed: gen_random_uuid() + - column: + name: type + value: FRIENDS_1 + - column: + name: name + value: "First Friend" + - column: + name: description + value: "Make your first friend" + - column: + name: threshold_value + valueNumeric: 1 + - column: + name: enabled + valueBoolean: true + - insert: + tableName: achievements + columns: + - column: + name: id + valueComputed: gen_random_uuid() + - column: + name: type + value: PROFILE_COMPLETED + - column: + name: name + value: "All Set Up" + - column: + name: description + value: "Fill in your display name and bio" + - column: + name: threshold_value + valueNumeric: 1 + - column: + name: enabled + valueBoolean: true diff --git a/wanderer-command/src/main/resources/db/changelog/db.changelog-master.yaml b/wanderer-command/src/main/resources/db/changelog/db.changelog-master.yaml index 34ed93f3..bf1853b6 100644 --- a/wanderer-command/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/wanderer-command/src/main/resources/db/changelog/db.changelog-master.yaml @@ -81,3 +81,5 @@ databaseChangeLog: file: db/changelog/040-add-performance-indexes.yaml - include: file: db/changelog/041-add-update-count-to-trips.yaml + - include: + file: db/changelog/042-seed-activation-achievements-data.yaml diff --git a/wanderer-command/src/test/java/com/tomassirio/wanderer/command/handler/TripCreatedEventHandlerTest.java b/wanderer-command/src/test/java/com/tomassirio/wanderer/command/handler/TripCreatedEventHandlerTest.java index b8c4067f..e291f51d 100644 --- a/wanderer-command/src/test/java/com/tomassirio/wanderer/command/handler/TripCreatedEventHandlerTest.java +++ b/wanderer-command/src/test/java/com/tomassirio/wanderer/command/handler/TripCreatedEventHandlerTest.java @@ -6,6 +6,7 @@ import com.tomassirio.wanderer.command.event.TripCreatedEvent; import com.tomassirio.wanderer.command.repository.TripRepository; +import com.tomassirio.wanderer.command.service.AchievementService; import com.tomassirio.wanderer.command.service.helper.TripEmbeddedObjectsInitializer; import com.tomassirio.wanderer.commons.domain.GeoLocation; import com.tomassirio.wanderer.commons.domain.Trip; @@ -29,6 +30,7 @@ class TripCreatedEventHandlerTest { @Mock private TripEmbeddedObjectsInitializer embeddedObjectsInitializer; @Mock private TripRepository tripRepository; + @Mock private AchievementService achievementCalculationService; @InjectMocks private TripCreatedEventHandler handler; @@ -73,6 +75,7 @@ void handle_whenEventWithMinimalData_shouldPersistTrip() { assertThat(savedTrip.getTripSettings()).isEqualTo(tripSettings); assertThat(savedTrip.getTripDetails()).isEqualTo(tripDetails); assertThat(savedTrip.getEnabled()).isTrue(); + verify(achievementCalculationService).checkAndUnlockSocialAchievements(ownerId); } @Test diff --git a/wanderer-command/src/test/java/com/tomassirio/wanderer/command/handler/UserDetailsUpdatedEventHandlerTest.java b/wanderer-command/src/test/java/com/tomassirio/wanderer/command/handler/UserDetailsUpdatedEventHandlerTest.java index 95c098c5..78e4adcd 100644 --- a/wanderer-command/src/test/java/com/tomassirio/wanderer/command/handler/UserDetailsUpdatedEventHandlerTest.java +++ b/wanderer-command/src/test/java/com/tomassirio/wanderer/command/handler/UserDetailsUpdatedEventHandlerTest.java @@ -1,11 +1,14 @@ package com.tomassirio.wanderer.command.handler; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.tomassirio.wanderer.command.event.UserDetailsUpdatedEvent; import com.tomassirio.wanderer.command.repository.UserRepository; +import com.tomassirio.wanderer.command.service.AchievementService; import com.tomassirio.wanderer.commons.domain.User; import com.tomassirio.wanderer.commons.domain.UserDetails; import java.util.Optional; @@ -20,6 +23,7 @@ class UserDetailsUpdatedEventHandlerTest { @Mock private UserRepository userRepository; + @Mock private AchievementService achievementCalculationService; @InjectMocks private UserDetailsUpdatedEventHandler handler; @@ -47,6 +51,7 @@ void handle_whenUserExists_shouldUpdateAllFields() { // Then assertThat(user.getUserDetails().getDisplayName()).isEqualTo("John Doe"); assertThat(user.getUserDetails().getBio()).isEqualTo("Walking the Camino"); + verify(achievementCalculationService).checkAndUnlockSocialAchievements(userId); } @Test @@ -73,6 +78,7 @@ void handle_whenPartialUpdate_shouldOnlyUpdateProvidedFields() { // Then assertThat(user.getUserDetails().getDisplayName()).isEqualTo("New Name"); assertThat(user.getUserDetails().getBio()).isEqualTo("Keep this bio"); + verify(achievementCalculationService).checkAndUnlockSocialAchievements(userId); } @Test @@ -98,6 +104,7 @@ void handle_whenUserDetailsIsNull_shouldCreateNewDetails() { assertThat(user.getUserDetails()).isNotNull(); assertThat(user.getUserDetails().getDisplayName()).isEqualTo("John"); assertThat(user.getUserDetails().getBio()).isEqualTo("Bio text"); + verify(achievementCalculationService).checkAndUnlockSocialAchievements(userId); } @Test @@ -114,5 +121,6 @@ void handle_whenUserNotFound_shouldDoNothing() { // Then verify(userRepository).findById(userId); + verify(achievementCalculationService, never()).checkAndUnlockSocialAchievements(any()); } } diff --git a/wanderer-command/src/test/java/com/tomassirio/wanderer/command/service/ProfileCompletionAchievementCheckerTest.java b/wanderer-command/src/test/java/com/tomassirio/wanderer/command/service/ProfileCompletionAchievementCheckerTest.java new file mode 100644 index 00000000..e131d4ff --- /dev/null +++ b/wanderer-command/src/test/java/com/tomassirio/wanderer/command/service/ProfileCompletionAchievementCheckerTest.java @@ -0,0 +1,104 @@ +package com.tomassirio.wanderer.command.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import com.tomassirio.wanderer.command.repository.UserRepository; +import com.tomassirio.wanderer.command.service.impl.checker.ProfileCompletionAchievementChecker; +import com.tomassirio.wanderer.commons.domain.AchievementType; +import com.tomassirio.wanderer.commons.domain.User; +import com.tomassirio.wanderer.commons.domain.UserDetails; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ProfileCompletionAchievementCheckerTest { + + @Mock private UserRepository userRepository; + + private ProfileCompletionAchievementChecker checker; + + private static final UUID USER_ID = UUID.randomUUID(); + + @BeforeEach + void setUp() { + checker = new ProfileCompletionAchievementChecker(userRepository); + } + + @Test + void getApplicableTypes_shouldReturnProfileCompletedOnly() { + List types = checker.getApplicableTypes(); + + assertThat(types).containsExactly(AchievementType.PROFILE_COMPLETED); + } + + @Test + void computeMetric_whenUserNotFound_shouldReturnZero() { + when(userRepository.findById(USER_ID)).thenReturn(Optional.empty()); + + double result = checker.computeMetric(USER_ID); + + assertThat(result).isEqualTo(0.0); + } + + @Test + void computeMetric_whenUserDetailsIsNull_shouldReturnZero() { + User user = User.builder().id(USER_ID).username("johndoe").build(); + when(userRepository.findById(USER_ID)).thenReturn(Optional.of(user)); + + double result = checker.computeMetric(USER_ID); + + assertThat(result).isEqualTo(0.0); + } + + @Test + void computeMetric_whenOnlyDisplayNameSet_shouldReturnZero() { + UserDetails details = UserDetails.builder().displayName("John").build(); + User user = User.builder().id(USER_ID).username("johndoe").userDetails(details).build(); + when(userRepository.findById(USER_ID)).thenReturn(Optional.of(user)); + + double result = checker.computeMetric(USER_ID); + + assertThat(result).isEqualTo(0.0); + } + + @Test + void computeMetric_whenOnlyBioSet_shouldReturnZero() { + UserDetails details = UserDetails.builder().bio("Walking the Camino").build(); + User user = User.builder().id(USER_ID).username("johndoe").userDetails(details).build(); + when(userRepository.findById(USER_ID)).thenReturn(Optional.of(user)); + + double result = checker.computeMetric(USER_ID); + + assertThat(result).isEqualTo(0.0); + } + + @Test + void computeMetric_whenDisplayNameAndBioSet_shouldReturnOne() { + UserDetails details = + UserDetails.builder().displayName("John").bio("Walking the Camino").build(); + User user = User.builder().id(USER_ID).username("johndoe").userDetails(details).build(); + when(userRepository.findById(USER_ID)).thenReturn(Optional.of(user)); + + double result = checker.computeMetric(USER_ID); + + assertThat(result).isEqualTo(1.0); + } + + @Test + void computeMetric_whenDisplayNameAndBioAreBlank_shouldReturnZero() { + UserDetails details = UserDetails.builder().displayName(" ").bio("").build(); + User user = User.builder().id(USER_ID).username("johndoe").userDetails(details).build(); + when(userRepository.findById(USER_ID)).thenReturn(Optional.of(user)); + + double result = checker.computeMetric(USER_ID); + + assertThat(result).isEqualTo(0.0); + } +} diff --git a/wanderer-command/src/test/java/com/tomassirio/wanderer/command/service/TripCountAchievementCheckerTest.java b/wanderer-command/src/test/java/com/tomassirio/wanderer/command/service/TripCountAchievementCheckerTest.java new file mode 100644 index 00000000..e0f36c22 --- /dev/null +++ b/wanderer-command/src/test/java/com/tomassirio/wanderer/command/service/TripCountAchievementCheckerTest.java @@ -0,0 +1,71 @@ +package com.tomassirio.wanderer.command.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import com.tomassirio.wanderer.command.repository.TripRepository; +import com.tomassirio.wanderer.command.service.impl.checker.TripCountAchievementChecker; +import com.tomassirio.wanderer.commons.domain.AchievementType; +import com.tomassirio.wanderer.commons.domain.Trip; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class TripCountAchievementCheckerTest { + + @Mock private TripRepository tripRepository; + + private TripCountAchievementChecker checker; + + private static final UUID USER_ID = UUID.randomUUID(); + + @BeforeEach + void setUp() { + checker = new TripCountAchievementChecker(tripRepository); + } + + @Test + void getApplicableTypes_shouldReturnFirstTripOnly() { + List types = checker.getApplicableTypes(); + + assertThat(types).containsExactly(AchievementType.FIRST_TRIP); + } + + @Test + void computeMetric_whenUserHasNoTrips_shouldReturnZero() { + when(tripRepository.findAllByUserId(USER_ID)).thenReturn(List.of()); + + double result = checker.computeMetric(USER_ID); + + assertThat(result).isEqualTo(0.0); + } + + @Test + void computeMetric_whenUserHasOneTrip_shouldReturnOne() { + when(tripRepository.findAllByUserId(USER_ID)) + .thenReturn(List.of(Trip.builder().id(UUID.randomUUID()).build())); + + double result = checker.computeMetric(USER_ID); + + assertThat(result).isEqualTo(1.0); + } + + @Test + void computeMetric_whenUserHasMultipleTrips_shouldReturnCorrectCount() { + when(tripRepository.findAllByUserId(USER_ID)) + .thenReturn( + List.of( + Trip.builder().id(UUID.randomUUID()).build(), + Trip.builder().id(UUID.randomUUID()).build(), + Trip.builder().id(UUID.randomUUID()).build())); + + double result = checker.computeMetric(USER_ID); + + assertThat(result).isEqualTo(3.0); + } +}