diff --git a/README.md b/README.md index cd6173d..96e6545 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ dummy.api.base-url=https://dummyjson.com/ | `POST` | `/random-users` | Create a new user | ✅ | | `PUT` | `/random-users/{id}` | Update user | ✅ | | `DELETE` | `/random-users/{id}` | Delete user | ✅ | +| `GET` | `/random-users/filter` | Filter users by criteria | ✅ | ### Example Request @@ -115,6 +116,12 @@ curl -X POST "http://localhost:8080/random-users" \ "nat": "FR" }' +# Filter users by gender and nationality +curl -X GET "http://localhost:8080/random-users/filter?gender=MALE&nat=FR" + +# Filter users by firstname (partial match, case-insensitive) +curl -X GET "http://localhost:8080/random-users/filter?firstname=john" + # Update user curl -X PUT "http://localhost:8080/random-users/1" \ -H "Content-Type: application/json" \ diff --git a/pom.xml b/pom.xml index d8d2ec5..802600f 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ org.springframework.boot - spring-boot-starter-data-jdbc + spring-boot-starter-data-jpa org.springframework.boot @@ -78,11 +78,6 @@ org.springframework.boot spring-boot-starter-actuator-test test - - - org.springframework.boot - spring-boot-starter-data-jdbc-test - test org.springframework.boot diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/database/User.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/database/User.java index 501cc58..bdd3a8a 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/database/User.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/database/User.java @@ -1,34 +1,39 @@ package com.xpeho.spring_boot_java_random_user.data.models.database; -import org.springframework.data.annotation.Id; -import org.springframework.data.relational.core.mapping.Column; -import org.springframework.data.relational.core.mapping.Table; - -@Table("users") +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity +@Table(name = "users") public class User { @Id - @Column("id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") private Long id; - @Column("gender") + @Column(name = "gender") private String gender; - @Column("firstname") + @Column(name = "firstname") private String firstname; - @Column("lastname") + @Column(name = "lastname") private String lastname; - @Column("civility") + @Column(name = "civility") private String civility; - @Column("email") + @Column(name = "email") private String email; - @Column("phone") + @Column(name = "phone") private String phone; - @Column("picture") + @Column(name = "picture") private String picture; - @Column("nationality") + @Column(name = "nationality") private String nationality; - // Required by Spring Data JDBC to instantiate the entity via reflection + // Required by JPA public User() { - // No initialization needed; fields are populated by Spring Data JDBC after instantiation + // No initialization needed } public Long getId() { diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/services/UserServiceImpl.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/services/UserServiceImpl.java index 0ccb6e3..76271af 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/services/UserServiceImpl.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/services/UserServiceImpl.java @@ -3,7 +3,9 @@ import com.xpeho.spring_boot_java_random_user.data.converters.UserConverter; import com.xpeho.spring_boot_java_random_user.data.models.database.User; import com.xpeho.spring_boot_java_random_user.data.sources.database.UserRepository; +import com.xpeho.spring_boot_java_random_user.data.sources.database.UserSpecifications; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter; import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; import org.springframework.stereotype.Service; @@ -47,4 +49,11 @@ public UserEntity save(UserEntity user) { public void deleteById(long id) { userRepository.deleteById(id); } + + @Override + public List filterUsers(UserFilter filter) { + return userRepository.findAll(UserSpecifications.byFilter(filter)).stream() + .map(userConverter::toDomain) + .toList(); + } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserRepository.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserRepository.java index 9fc516e..b23c856 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserRepository.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserRepository.java @@ -1,7 +1,8 @@ package com.xpeho.spring_boot_java_random_user.data.sources.database; import com.xpeho.spring_boot_java_random_user.data.models.database.User; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -public interface UserRepository extends CrudRepository { +public interface UserRepository extends JpaRepository, JpaSpecificationExecutor { } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserSpecifications.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserSpecifications.java new file mode 100644 index 0000000..f4be154 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserSpecifications.java @@ -0,0 +1,55 @@ +package com.xpeho.spring_boot_java_random_user.data.sources.database; + +import com.xpeho.spring_boot_java_random_user.data.models.database.User; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +public final class UserSpecifications { + private UserSpecifications() { + } + + public static Specification byFilter(UserFilter filter) { + return (user, query, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + + if (filter.gender() != null) { + predicates.add(criteriaBuilder.equal( + criteriaBuilder.lower(user.get("gender")), + filter.gender().name().toLowerCase() + )); + } + + addContainsPredicate(predicates, criteriaBuilder, user.get("firstname"), filter.firstname()); + addContainsPredicate(predicates, criteriaBuilder, user.get("lastname"), filter.lastname()); + addContainsPredicate(predicates, criteriaBuilder, user.get("civility"), filter.civility()); + addContainsPredicate(predicates, criteriaBuilder, user.get("email"), filter.email()); + addContainsPredicate(predicates, criteriaBuilder, user.get("phone"), filter.phone()); + addContainsPredicate(predicates, criteriaBuilder, user.get("nationality"), filter.nat()); + + return criteriaBuilder.and(predicates.toArray(new Predicate[0])); + }; + } + + private static void addContainsPredicate( + List predicates, + CriteriaBuilder criteriaBuilder, + Path field, + String value + ) { + if (!StringUtils.hasText(value)) { + return; + } + + predicates.add(criteriaBuilder.like( + criteriaBuilder.lower(field), + "%" + value.toLowerCase() + "%" + )); + } +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserFilter.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserFilter.java new file mode 100644 index 0000000..7b1a63f --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserFilter.java @@ -0,0 +1,14 @@ +package com.xpeho.spring_boot_java_random_user.domain.entities; + +import com.xpeho.spring_boot_java_random_user.domain.enums.Gender; + +public record UserFilter( + Gender gender, + String firstname, + String lastname, + String civility, + String email, + String phone, + String nat +) { +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/enums/Gender.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/enums/Gender.java new file mode 100644 index 0000000..1904a32 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/enums/Gender.java @@ -0,0 +1,6 @@ +package com.xpeho.spring_boot_java_random_user.domain.enums; + +public enum Gender { + MALE, + FEMALE +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/LocalUserService.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/LocalUserService.java index 80104a2..8bb472e 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/LocalUserService.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/LocalUserService.java @@ -1,6 +1,7 @@ package com.xpeho.spring_boot_java_random_user.domain.services; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter; import java.util.List; import java.util.Optional; @@ -13,5 +14,7 @@ public interface LocalUserService { UserEntity save(UserEntity user); void deleteById(long id); + + List filterUsers(UserFilter filter); } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FilterUsersUseCase.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FilterUsersUseCase.java new file mode 100644 index 0000000..a28a71d --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FilterUsersUseCase.java @@ -0,0 +1,21 @@ +package com.xpeho.spring_boot_java_random_user.domain.usecases; + +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter; +import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class FilterUsersUseCase { + private final LocalUserService userService; + + public FilterUsersUseCase(LocalUserService userService) { + this.userService = userService; + } + + public List execute(UserFilter filter) { + return userService.filterUsers(filter); + } +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/controllers/UserController.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/controllers/UserController.java index e029f3b..abe1376 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/controllers/UserController.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/controllers/UserController.java @@ -2,6 +2,7 @@ import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; import com.xpeho.spring_boot_java_random_user.domain.entities.UserRequest; +import com.xpeho.spring_boot_java_random_user.domain.enums.Gender; import com.xpeho.spring_boot_java_random_user.domain.enums.UserSource; import com.xpeho.spring_boot_java_random_user.presentation.dto.UserResponseDTO; import io.swagger.v3.oas.annotations.Operation; @@ -13,6 +14,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RequestMapping("/random-users") @@ -92,6 +95,32 @@ ResponseEntity updateRandomUser( ResponseEntity createUser(@RequestBody UserRequest user); + @GetMapping("/filter") + @Operation( + summary = "Filter users", + description = "Search users by optional filters on gender, firstname, lastname, civility, email, phone and nationality. All filters are case-insensitive and support partial matching.", + parameters = { + @Parameter(name = "gender", description = "Filter by gender (MALE or FEMALE)"), + @Parameter(name = "firstname", description = "Filter by firstname"), + @Parameter(name = "lastname", description = "Filter by lastname"), + @Parameter(name = "civility", description = "Filter by civility"), + @Parameter(name = "email", description = "Filter by email"), + @Parameter(name = "phone", description = "Filter by phone"), + @Parameter(name = "nat", description = "Filter by nationality") + } + ) + @ApiResponse(responseCode = "200", description = "Filtered list of users") + @ApiResponse(responseCode = "500", description = "Internal server error") + ResponseEntity> filterUsers( + @RequestParam(required = false) Gender gender, + @RequestParam(required = false) String firstname, + @RequestParam(required = false) String lastname, + @RequestParam(required = false) String civility, + @RequestParam(required = false) String email, + @RequestParam(required = false) String phone, + @RequestParam(required = false) String nat + ); + @DeleteMapping("/{id}") @Operation( summary = "Delete user by id", diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/handlers/UserHandler.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/handlers/UserHandler.java index 9b0b01a..9872731 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/handlers/UserHandler.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/handlers/UserHandler.java @@ -2,7 +2,9 @@ import com.xpeho.spring_boot_java_random_user.domain.entities.PaginatedUsers; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter; import com.xpeho.spring_boot_java_random_user.domain.entities.UserRequest; +import com.xpeho.spring_boot_java_random_user.domain.enums.Gender; import com.xpeho.spring_boot_java_random_user.domain.enums.UserSource; import com.xpeho.spring_boot_java_random_user.domain.exceptions.InvalidPaginationException; import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException; @@ -17,6 +19,7 @@ import org.springframework.web.bind.annotation.RestController; import java.io.IOException; +import java.util.List; @RestController @@ -30,20 +33,22 @@ public class UserHandler implements UserController { private final GetUserByIdUseCase getUserByIdUseCase; private final CreateUserUseCase createUserUseCase; private final DeleteUserByIdUseCase deleteUserUseCase; + private final FilterUsersUseCase filterUsersUseCase; public UserHandler( FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase, UpdateRandomUserUseCase updateRandomUserUseCase, GetUserByIdUseCase getUserByIdUseCase, CreateUserUseCase createUserUseCase, - DeleteUserByIdUseCase deleteUserUseCase + DeleteUserByIdUseCase deleteUserUseCase, + FilterUsersUseCase filterUsersUseCase ) { this.fetchAndSaveRandomUsersUseCase = fetchAndSaveRandomUsersUseCase; this.updateRandomUserUseCase = updateRandomUserUseCase; this.getUserByIdUseCase = getUserByIdUseCase; this.createUserUseCase = createUserUseCase; this.deleteUserUseCase = deleteUserUseCase; - + this.filterUsersUseCase = filterUsersUseCase; } @@ -99,6 +104,16 @@ public ResponseEntity createUser(@RequestBody UserRequest user) { return ResponseEntity.status(HttpStatus.CREATED).body(createdUser); } + @Override + public ResponseEntity> filterUsers( + Gender gender, String firstname, String lastname, + String civility, String email, String phone, String nat + ) { + UserFilter filter = new UserFilter(gender, firstname, lastname, civility, email, phone, nat); + List users = filterUsersUseCase.execute(filter); + return ResponseEntity.ok(users); + } + @Override public void deleteUserById(int id) { try { diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/services/UserServiceImplTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/services/UserServiceImplTest.java index 85adecb..942d99e 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/services/UserServiceImplTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/services/UserServiceImplTest.java @@ -4,10 +4,15 @@ import com.xpeho.spring_boot_java_random_user.data.models.database.User; import com.xpeho.spring_boot_java_random_user.data.sources.database.UserRepository; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter; +import com.xpeho.spring_boot_java_random_user.domain.enums.Gender; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.data.jpa.domain.Specification; +import java.util.Collections; +import java.util.List; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -84,4 +89,63 @@ void shouldSaveMappedUserAndReturnMappedDomainEntity() { verify(userRepository).save(daoToSave); verify(userConverter).toDomain(savedDao); } + + @Test + @DisplayName("Should build a specification and call repository for filtered users") + void shouldFilterUsersWithGender() { + UserFilter filter = new UserFilter(Gender.MALE, "John", null, null, null, null, null); + + User dao = new User(); + dao.setId(1L); + dao.setFirstname("John"); + + UserEntity expected = new UserEntity(1L, "male", "John", "Doe", "Mr", "john@doe.com", "1234", "pic.jpg", "FR"); + + when(userRepository.findAll(org.mockito.ArgumentMatchers.>any())) + .thenReturn(List.of(dao)); + when(userConverter.toDomain(dao)).thenReturn(expected); + + List result = userService.filterUsers(filter); + + assertEquals(1, result.size()); + assertEquals(expected, result.get(0)); + verify(userRepository).findAll(org.mockito.ArgumentMatchers.>any()); + verify(userConverter).toDomain(dao); + } + + @Test + @DisplayName("Should call repository when gender filter is null") + void shouldFilterUsersWithNullGender() { + UserFilter filter = new UserFilter(null, null, "Smith", null, null, null, null); + + User dao = new User(); + dao.setId(2L); + dao.setLastname("Smith"); + + UserEntity expected = new UserEntity(2L, "female", "Alice", "Smith", "Ms", "alice@smith.com", "5678", "pic2.jpg", "US"); + + when(userRepository.findAll(org.mockito.ArgumentMatchers.>any())) + .thenReturn(List.of(dao)); + when(userConverter.toDomain(dao)).thenReturn(expected); + + List result = userService.filterUsers(filter); + + assertEquals(1, result.size()); + assertEquals(expected, result.get(0)); + verify(userRepository).findAll(org.mockito.ArgumentMatchers.>any()); + } + + @Test + @DisplayName("Should return empty list when no users match filter") + void shouldReturnEmptyListWhenNoUsersMatchFilter() { + UserFilter filter = new UserFilter(Gender.FEMALE, "Unknown", null, null, null, null, null); + + when(userRepository.findAll(org.mockito.ArgumentMatchers.>any())) + .thenReturn(Collections.emptyList()); + + List result = userService.filterUsers(filter); + + assertTrue(result.isEmpty()); + verify(userRepository).findAll(org.mockito.ArgumentMatchers.>any()); + } } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserSpecificationsTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserSpecificationsTest.java new file mode 100644 index 0000000..cbe3095 --- /dev/null +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserSpecificationsTest.java @@ -0,0 +1,59 @@ +package com.xpeho.spring_boot_java_random_user.data.sources.database; + +import com.xpeho.spring_boot_java_random_user.data.models.database.User; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter; +import jakarta.persistence.criteria.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.data.jpa.domain.Specification; + +import static org.mockito.Mockito.*; + +class UserSpecificationsTest { + private Root user; + private CriteriaQuery query; + private CriteriaBuilder cb; + private Expression lowerExpr; + private Predicate predicate; + + @BeforeEach + @SuppressWarnings("unchecked") + void setUp() { + user = mock(Root.class); + query = mock(CriteriaQuery.class); + cb = mock(CriteriaBuilder.class); + lowerExpr = mock(Expression.class); + predicate = mock(Predicate.class); + when(cb.lower(any())).thenReturn(lowerExpr); + when(cb.equal(any(), anyString())).thenReturn(predicate); + when(cb.like(any(Expression.class), anyString())).thenReturn(predicate); + when(cb.and(any(Predicate[].class))).thenReturn(predicate); + } + + + + @Test + @DisplayName("Should add like predicates for all text fields") + void shouldAddLikePredicatesForAllTextFields() { + UserFilter filter = new UserFilter(null, "John", "Doe", "Mr", "john@doe.com", "1234", "FR"); + + Specification spec = UserSpecifications.byFilter(filter); + spec.toPredicate(user, query, cb); + + verify(user).get("firstname"); + verify(user).get("lastname"); + verify(user).get("civility"); + verify(user).get("email"); + verify(user).get("phone"); + verify(user).get("nationality"); + verify(cb).like(lowerExpr, "%john%"); + verify(cb).like(lowerExpr, "%doe%"); + verify(cb).like(lowerExpr, "%mr%"); + verify(cb).like(lowerExpr, "%john@doe.com%"); + verify(cb).like(lowerExpr, "%1234%"); + verify(cb).like(lowerExpr, "%fr%"); + } + + +} diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FilterUsersUseCaseTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FilterUsersUseCaseTest.java new file mode 100644 index 0000000..368f69f --- /dev/null +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FilterUsersUseCaseTest.java @@ -0,0 +1,69 @@ +package com.xpeho.spring_boot_java_random_user.domain.usecases; + +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter; +import com.xpeho.spring_boot_java_random_user.domain.enums.Gender; +import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class FilterUsersUseCaseTest { + private LocalUserService userService; + private FilterUsersUseCase useCase; + + @BeforeEach + void setUp() { + userService = mock(LocalUserService.class); + useCase = new FilterUsersUseCase(userService); + } + + @Test + @DisplayName("Should return filtered users matching the filter") + void shouldReturnFilteredUsers() { + UserFilter filter = new UserFilter(Gender.MALE, "John", null, null, null, null, null); + List expected = List.of( + new UserEntity(1L, "male", "John", "Doe", "Mr", "john@example.com", "0600000000", "http://pic.jpg", "FR") + ); + when(userService.filterUsers(filter)).thenReturn(expected); + + List result = useCase.execute(filter); + + assertEquals(expected, result); + verify(userService).filterUsers(filter); + } + + @Test + @DisplayName("Should return empty list when no users match the filter") + void shouldReturnEmptyListWhenNoMatch() { + UserFilter filter = new UserFilter(Gender.FEMALE, "Unknown", null, null, null, null, null); + when(userService.filterUsers(filter)).thenReturn(Collections.emptyList()); + + List result = useCase.execute(filter); + + assertTrue(result.isEmpty()); + verify(userService).filterUsers(filter); + } + + @Test + @DisplayName("Should pass filter with all fields to the service") + void shouldPassFilterWithAllFields() { + UserFilter filter = new UserFilter(Gender.FEMALE, "Alice", "Smith", "Ms", "alice@example.com", "0611111111", "US"); + List expected = List.of( + new UserEntity(5L, "female", "Alice", "Smith", "Ms", "alice@example.com", "0611111111", "http://pic2.jpg", "US") + ); + when(userService.filterUsers(filter)).thenReturn(expected); + + List result = useCase.execute(filter); + + assertEquals(expected, result); + verify(userService, times(1)).filterUsers(filter); + verifyNoMoreInteractions(userService); + } +} diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserHandlerTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserHandlerTest.java index 25b2a8b..9dcd49d 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserHandlerTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserHandlerTest.java @@ -2,7 +2,9 @@ import com.xpeho.spring_boot_java_random_user.domain.entities.PaginatedUsers; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter; import com.xpeho.spring_boot_java_random_user.domain.entities.UserRequest; +import com.xpeho.spring_boot_java_random_user.domain.enums.Gender; import com.xpeho.spring_boot_java_random_user.domain.enums.UserSource; import com.xpeho.spring_boot_java_random_user.domain.exceptions.InvalidPaginationException; import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException; @@ -30,6 +32,7 @@ class UserHandlerTest { private GetUserByIdUseCase getUserByIdUseCase; private CreateUserUseCase createUserUseCase; private DeleteUserByIdUseCase deleteUserUseCase; + private FilterUsersUseCase filterUsersUseCase; private UserHandler userHandler; @BeforeEach @@ -39,7 +42,8 @@ void setUp() { getUserByIdUseCase = mock(GetUserByIdUseCase.class); createUserUseCase = mock(CreateUserUseCase.class); deleteUserUseCase = mock(DeleteUserByIdUseCase.class); - userHandler = new UserHandler(fetchAndSaveRandomUsersUseCase, updateRandomUserUseCase, getUserByIdUseCase, createUserUseCase, deleteUserUseCase); + filterUsersUseCase = mock(FilterUsersUseCase.class); + userHandler = new UserHandler(fetchAndSaveRandomUsersUseCase, updateRandomUserUseCase, getUserByIdUseCase, createUserUseCase, deleteUserUseCase, filterUsersUseCase); } @Test @@ -177,4 +181,36 @@ void shouldLogWarningWhenDeleteUserByIdFails() { verify(deleteUserUseCase, times(1)).execute(userId); } + + @Test + @DisplayName("Should return 200 and filtered users when filterUsers succeeds") + void shouldReturnOkWhenFilterUsersSucceeds() { + UserFilter filter = new UserFilter(Gender.MALE, null, null, null, null, null, "FR"); + List users = List.of( + new UserEntity(1L, "male", "John", "Doe", "Mr", "john@example.com", "0600000000", "pic.jpg", "FR") + ); + when(filterUsersUseCase.execute(filter)).thenReturn(users); + + ResponseEntity> response = userHandler.filterUsers(Gender.MALE, null, null, null, null, null, "FR"); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(1, response.getBody().size()); + assertEquals(users, response.getBody()); + verify(filterUsersUseCase, times(1)).execute(filter); + } + + @Test + @DisplayName("Should return 200 and empty list when no users match filter") + void shouldReturnOkWithEmptyListWhenNoUsersMatchFilter() { + UserFilter filter = new UserFilter(null, "NonExistent", null, null, null, null, null); + when(filterUsersUseCase.execute(filter)).thenReturn(List.of()); + + ResponseEntity> response = userHandler.filterUsers(null, "NonExistent", null, null, null, null, null); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().isEmpty()); + verify(filterUsersUseCase, times(1)).execute(filter); + } }