From f79e558658cb3bfeecc5bc606ef3b7142b8cb392 Mon Sep 17 00:00:00 2001 From: Martho Evan Date: Mon, 4 May 2026 11:54:28 +0200 Subject: [PATCH] fix(architecture): adapt for clean architecture --- pom.xml | 2 +- .../config/UseCaseConfig.java | 45 +++++++++ .../data/converters/UserConverter.java | 8 +- .../database/{User.java => UserDao.java} | 5 +- .../data/services/UserServiceImpl.java | 24 +++-- ...mUserApiConfig.java => UserApiConfig.java} | 20 +++- .../sources/api/dummy/DummyUserApiConfig.java | 25 ----- .../data/sources/database/UserRepository.java | 4 +- .../sources/database/UserSpecifications.java | 4 +- ...LocalUserService.java => UserService.java} | 6 +- .../domain/usecases/CreateUserUseCase.java | 25 +---- .../usecases/DeleteUserByIdUseCase.java | 8 +- .../FetchAndSaveRandomUsersUseCase.java | 12 +-- .../domain/usecases/FilterUsersUseCase.java | 16 ++-- .../domain/usecases/GetUserByIdUseCase.java | 8 +- .../usecases/UpdateRandomUserUseCase.java | 11 +-- .../controllers/UserController.java | 58 ++++-------- .../presentation/dto/UserDTO.java | 15 +++ .../dto/UserRequestDTO.java} | 5 +- .../presentation/dto/UserResponseDTO.java | 4 +- .../presentation/handlers/UserHandler.java | 54 +++++++---- .../presentation/mappers/UserMapper.java | 25 +++++ .../data/converters/UserConverterTest.java | 4 +- .../data/services/UserServiceImplTest.java | 67 +++++++------ .../database/UserSpecificationsTest.java | 9 +- .../usecases/CreateUserUseCaseTest.java | 22 ++--- .../usecases/DeleteUserByIdUseCaseTest.java | 6 +- .../FetchAndSaveRandomUsersUseCaseTest.java | 6 +- .../usecases/FilterUsersUseCaseTest.java | 38 ++++---- .../usecases/GetUserByIdUseCaseTest.java | 6 +- .../usecases/UpdateRandomUserUseCaseTest.java | 27 +++--- .../UserGetByIdContainerTest.java | 16 ++-- .../presentation/UserHandlerTest.java | 94 +++++++++---------- .../CucumberIntegrationTest.java | 4 +- .../CucumberTypeConfig.java | 10 +- .../SpringIntegrationTest.java | 2 +- .../{feature => features}/StepDefinition.java | 12 +-- 37 files changed, 375 insertions(+), 332 deletions(-) create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/config/UseCaseConfig.java rename src/main/java/com/xpeho/spring_boot_java_random_user/data/models/database/{User.java => UserDao.java} (97%) rename src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/{randomuser/RandomUserApiConfig.java => UserApiConfig.java} (52%) delete mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/dummy/DummyUserApiConfig.java rename src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/{LocalUserService.java => UserService.java} (70%) create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserDTO.java rename src/main/java/com/xpeho/spring_boot_java_random_user/{domain/entities/UserRequest.java => presentation/dto/UserRequestDTO.java} (66%) create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/presentation/mappers/UserMapper.java rename src/test/java/{feature => features}/CucumberIntegrationTest.java (96%) rename src/test/java/{feature => features}/CucumberTypeConfig.java (86%) rename src/test/java/{feature => features}/SpringIntegrationTest.java (99%) rename src/test/java/{feature => features}/StepDefinition.java (94%) diff --git a/pom.xml b/pom.xml index 80f07a0..7e1a68c 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ 2.0.4 false **/*Application.java - **/feature/SpringIntegrationTest.java + **/features/SpringIntegrationTest.java ${project.build.directory}/site/jacoco/jacoco.xml diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/config/UseCaseConfig.java b/src/main/java/com/xpeho/spring_boot_java_random_user/config/UseCaseConfig.java new file mode 100644 index 0000000..db8d07c --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/config/UseCaseConfig.java @@ -0,0 +1,45 @@ +package com.xpeho.spring_boot_java_random_user.config; + +import com.xpeho.spring_boot_java_random_user.domain.services.RemoteUserService; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; +import com.xpeho.spring_boot_java_random_user.domain.usecases.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +public class UseCaseConfig { + + @Bean + public CreateUserUseCase createUserUseCase(UserService userService) { + return new CreateUserUseCase(userService); + } + + @Bean + public DeleteUserByIdUseCase deleteUserByIdUseCase(UserService userService) { + return new DeleteUserByIdUseCase(userService); + } + + @Bean + public FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase( + UserService userService, + List remoteUserServices) { + return new FetchAndSaveRandomUsersUseCase(userService, remoteUserServices); + } + + @Bean + public FilterUsersUseCase filterUsersUseCase(UserService userService) { + return new FilterUsersUseCase(userService); + } + + @Bean + public GetUserByIdUseCase getUserByIdUseCase(UserService userService) { + return new GetUserByIdUseCase(userService); + } + + @Bean + public UpdateRandomUserUseCase updateRandomUserUseCase(UserService userService) { + return new UpdateRandomUserUseCase(userService); + } +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverter.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverter.java index 5bff94e..5e56915 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverter.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverter.java @@ -2,7 +2,7 @@ import com.xpeho.spring_boot_java_random_user.data.models.api.dummy.DummyUserResultDTO; import com.xpeho.spring_boot_java_random_user.data.models.api.randomuser.RandomUserResultDTO; -import com.xpeho.spring_boot_java_random_user.data.models.database.User; +import com.xpeho.spring_boot_java_random_user.data.models.database.UserDao; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; import org.springframework.stereotype.Service; @@ -10,8 +10,8 @@ @Service public class UserConverter { // Domain -> DAO - public User toDao(UserEntity entity) { - User user = new User(); + public UserDao toDao(UserEntity entity) { + UserDao user = new UserDao(); user.setId(entity.id()); user.setGender(entity.gender()); user.setFirstname(entity.firstname()); @@ -25,7 +25,7 @@ public User toDao(UserEntity entity) { } // DAO -> Domain - public UserEntity toDomain(User user) { + public UserEntity toDomain(UserDao user) { return new UserEntity( user.getId(), user.getGender(), 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/UserDao.java similarity index 97% rename from src/main/java/com/xpeho/spring_boot_java_random_user/data/models/database/User.java rename to src/main/java/com/xpeho/spring_boot_java_random_user/data/models/database/UserDao.java index bdd3a8a..3421503 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/UserDao.java @@ -9,7 +9,7 @@ @Entity @Table(name = "users") -public class User { +public class UserDao { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") @@ -32,7 +32,7 @@ public class User { private String nationality; // Required by JPA - public User() { + public UserDao() { // No initialization needed } @@ -108,3 +108,4 @@ public void setNationality(String nationality) { this.nationality = nationality; } } + 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 76271af..53348d6 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 @@ -1,12 +1,15 @@ package com.xpeho.spring_boot_java_random_user.data.services; 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.models.database.UserDao; 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.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.services.LocalUserService; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import java.util.List; @@ -14,8 +17,7 @@ import java.util.stream.StreamSupport; @Service - -public class UserServiceImpl implements LocalUserService { +public class UserServiceImpl implements UserService { private final UserRepository userRepository; private final UserConverter userConverter; @@ -26,8 +28,8 @@ public UserServiceImpl(UserRepository userRepository, UserConverter userConverte @Override public List saveAll(List users) { - List daoUsers = users.stream().map(userConverter::toDao).toList(); - Iterable saved = userRepository.saveAll(daoUsers); + List daoUsers = users.stream().map(userConverter::toDao).toList(); + Iterable saved = userRepository.saveAll(daoUsers); return StreamSupport.stream(saved.spliterator(), false) .map(userConverter::toDomain) .toList(); @@ -41,7 +43,7 @@ public Optional getById(long id) { @Override public UserEntity save(UserEntity user) { - User savedUser = userRepository.save(userConverter.toDao(user)); + UserDao savedUser = userRepository.save(userConverter.toDao(user)); return userConverter.toDomain(savedUser); } @@ -51,9 +53,13 @@ public void deleteById(long id) { } @Override - public List filterUsers(UserFilter filter) { - return userRepository.findAll(UserSpecifications.byFilter(filter)).stream() + public PaginatedUsers filterUsers(UserFilter filter, int page, int size) { + PageRequest pageable = PageRequest.of(page - 1, size); + Page result = userRepository.findAll(UserSpecifications.byFilter(filter), pageable); + List entities = result.getContent().stream() .map(userConverter::toDomain) .toList(); + int skip = (page - 1) * size; + return new PaginatedUsers(entities, (int) result.getTotalElements(), skip, size); } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/randomuser/RandomUserApiConfig.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/UserApiConfig.java similarity index 52% rename from src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/randomuser/RandomUserApiConfig.java rename to src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/UserApiConfig.java index 882f3ff..70b6fa5 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/randomuser/RandomUserApiConfig.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/UserApiConfig.java @@ -1,5 +1,7 @@ -package com.xpeho.spring_boot_java_random_user.data.sources.api.randomuser; +package com.xpeho.spring_boot_java_random_user.data.sources.api; +import com.xpeho.spring_boot_java_random_user.data.sources.api.dummy.DummyUserApi; +import com.xpeho.spring_boot_java_random_user.data.sources.api.randomuser.RandomUserApi; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -8,7 +10,21 @@ import retrofit2.converter.gson.GsonConverterFactory; @Configuration -public class RandomUserApiConfig { +public class UserApiConfig { + + @Bean(name = "dummyUserRetrofit") + public Retrofit dummyUserRetrofit(Environment env) { + return new Retrofit.Builder() + .baseUrl(env.getRequiredProperty("dummy.api.base-url")) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + } + + @Bean + public DummyUserApi dummyUserApi(@Qualifier("dummyUserRetrofit") Retrofit dummyUserRetrofit) { + return dummyUserRetrofit.create(DummyUserApi.class); + } + @Bean(name = "randomUserRetrofit") public Retrofit randomUserRetrofit(Environment env) { return new Retrofit.Builder() diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/dummy/DummyUserApiConfig.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/dummy/DummyUserApiConfig.java deleted file mode 100644 index 9b1dfa5..0000000 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/dummy/DummyUserApiConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xpeho.spring_boot_java_random_user.data.sources.api.dummy; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -@Configuration -public class DummyUserApiConfig { - @Bean(name = "dummyUserRetrofit") - public Retrofit dummyUserRetrofit(Environment env) { - return new Retrofit.Builder() - .baseUrl(env.getRequiredProperty("dummy.api.base-url")) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - } - - @Bean - public DummyUserApi dummyUserApi(@Qualifier("dummyUserRetrofit") Retrofit dummyUserRetrofit) { - return dummyUserRetrofit.create(DummyUserApi.class); - } -} - 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 b23c856..4d06779 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,8 +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 com.xpeho.spring_boot_java_random_user.data.models.database.UserDao; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -public interface UserRepository extends JpaRepository, JpaSpecificationExecutor { +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 index f4be154..c2aaa8b 100644 --- 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 @@ -1,6 +1,6 @@ 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.data.models.database.UserDao; import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Path; @@ -15,7 +15,7 @@ public final class UserSpecifications { private UserSpecifications() { } - public static Specification byFilter(UserFilter filter) { + public static Specification byFilter(UserFilter filter) { return (user, query, criteriaBuilder) -> { List predicates = new ArrayList<>(); 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/UserService.java similarity index 70% rename from src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/LocalUserService.java rename to src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/UserService.java index 8bb472e..19393fb 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/UserService.java @@ -1,12 +1,13 @@ package com.xpeho.spring_boot_java_random_user.domain.services; +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 java.util.List; import java.util.Optional; -public interface LocalUserService { +public interface UserService { List saveAll(List users); Optional getById(long id); @@ -15,6 +16,7 @@ public interface LocalUserService { void deleteById(long id); - List filterUsers(UserFilter filter); + PaginatedUsers filterUsers(UserFilter filter, int page, int size); } + diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/CreateUserUseCase.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/CreateUserUseCase.java index eada9af..67ee07d 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/CreateUserUseCase.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/CreateUserUseCase.java @@ -1,31 +1,16 @@ 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.UserRequest; -import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; -import org.springframework.stereotype.Service; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; -@Service public class CreateUserUseCase { - private final LocalUserService userService; + private final UserService userService; - public CreateUserUseCase(LocalUserService userService) { + public CreateUserUseCase(UserService userService) { this.userService = userService; } - public UserEntity execute(UserRequest user) { - UserEntity userToCreate = new UserEntity( - null, - user.gender(), - user.firstname(), - user.lastname(), - user.civility(), - user.email(), - user.phone(), - user.picture(), - user.nat() - ); - - return userService.save(userToCreate); + public UserEntity execute(UserEntity user) { + return userService.save(user); } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/DeleteUserByIdUseCase.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/DeleteUserByIdUseCase.java index e120db4..7071cb1 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/DeleteUserByIdUseCase.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/DeleteUserByIdUseCase.java @@ -1,13 +1,11 @@ package com.xpeho.spring_boot_java_random_user.domain.usecases; -import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; -import org.springframework.stereotype.Service; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; -@Service public class DeleteUserByIdUseCase { - private final LocalUserService userService; + private final UserService userService; - public DeleteUserByIdUseCase(LocalUserService userService) { + public DeleteUserByIdUseCase(UserService userService) { this.userService = userService; } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FetchAndSaveRandomUsersUseCase.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FetchAndSaveRandomUsersUseCase.java index 231dd98..bfb4c86 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FetchAndSaveRandomUsersUseCase.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FetchAndSaveRandomUsersUseCase.java @@ -2,9 +2,8 @@ import com.xpeho.spring_boot_java_random_user.domain.entities.PaginatedUsers; import com.xpeho.spring_boot_java_random_user.domain.enums.UserSource; -import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; import com.xpeho.spring_boot_java_random_user.domain.services.RemoteUserService; -import org.springframework.stereotype.Service; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; import java.io.IOException; import java.util.List; @@ -12,14 +11,13 @@ import java.util.function.Function; import java.util.stream.Collectors; -@Service public class FetchAndSaveRandomUsersUseCase { - private final LocalUserService localUserService; + private final UserService userService; private final Map remoteUserServices; - public FetchAndSaveRandomUsersUseCase(LocalUserService localUserService, List remoteUserServices) { - this.localUserService = localUserService; + public FetchAndSaveRandomUsersUseCase(UserService userService, List remoteUserServices) { + this.userService = userService; this.remoteUserServices = remoteUserServices.stream() .collect(Collectors.toMap(RemoteUserService::getSource, Function.identity())); } @@ -30,7 +28,7 @@ public PaginatedUsers execute(int page, int size, UserSource source) throws IOEx throw new IllegalStateException("No remote service configured for source: " + source); } PaginatedUsers response = remoteUserService.fetchUsers(page, size); - localUserService.saveAll(response.data()); + userService.saveAll(response.data()); return response; } } 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 index a28a71d..3123293 100644 --- 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 @@ -1,21 +1,17 @@ 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.PaginatedUsers; 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 com.xpeho.spring_boot_java_random_user.domain.services.UserService; -import java.util.List; - -@Service public class FilterUsersUseCase { - private final LocalUserService userService; + private final UserService userService; - public FilterUsersUseCase(LocalUserService userService) { + public FilterUsersUseCase(UserService userService) { this.userService = userService; } - public List execute(UserFilter filter) { - return userService.filterUsers(filter); + public PaginatedUsers execute(UserFilter filter, int page, int size) { + return userService.filterUsers(filter, page, size); } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCase.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCase.java index 730b3e4..09e1bc1 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCase.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCase.java @@ -2,14 +2,12 @@ import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException; -import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; -import org.springframework.stereotype.Service; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; -@Service public class GetUserByIdUseCase { - private final LocalUserService userService; + private final UserService userService; - public GetUserByIdUseCase(LocalUserService userService) { + public GetUserByIdUseCase(UserService userService) { this.userService = userService; } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserUseCase.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserUseCase.java index 5a17aad..08724c1 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserUseCase.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserUseCase.java @@ -1,20 +1,17 @@ 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.UserRequest; import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException; -import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; -import org.springframework.stereotype.Service; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; -@Service public class UpdateRandomUserUseCase { - private final LocalUserService userService; + private final UserService userService; - public UpdateRandomUserUseCase(LocalUserService userService) { + public UpdateRandomUserUseCase(UserService userService) { this.userService = userService; } - public UserEntity execute(int id, UserRequest user) { + public UserEntity execute(long id, UserEntity user) { UserEntity existingUser = userService.getById(id) .orElseThrow(() -> new UserNotFoundException(id)); 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 abe1376..9790b27 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 @@ -1,21 +1,20 @@ package com.xpeho.spring_boot_java_random_user.presentation.controllers; -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.UserDTO; +import com.xpeho.spring_boot_java_random_user.presentation.dto.UserRequestDTO; import com.xpeho.spring_boot_java_random_user.presentation.dto.UserResponseDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RequestMapping("/random-users") @@ -27,8 +26,6 @@ public interface UserController { summary = "Get random users", description = "Fetches a paginated list of random users from the external API and saves them to the database.", parameters = { - @Parameter(name = "page", description = "Page number", example = "1"), - @Parameter(name = "size", description = "Number of users per page (max 30)", example = "10"), @Parameter(name = "source", description = "External source to use", example = "DUMMY") } ) @@ -36,15 +33,8 @@ public interface UserController { @ApiResponse(responseCode = "500", description = "Internal server error") @ApiResponse(responseCode = "503", description = "External service unavailable") ResponseEntity getRandomUsers( - @RequestParam(defaultValue = "1") - @Min(1) - int page, - @RequestParam(defaultValue = "10") - @Min(1) - @Max(30) - int size, - @RequestParam(defaultValue = "DUMMY") - UserSource source + @ParameterObject @PageableDefault(size = 10, page = 0) Pageable pageable, + @RequestParam(defaultValue = "DUMMY") UserSource source ); @GetMapping("/{id}") @@ -58,47 +48,37 @@ ResponseEntity getRandomUsers( @ApiResponse(responseCode = "200", description = "User successfully found and returned") @ApiResponse(responseCode = "404", description = "The requested user does not exist") @ApiResponse(responseCode = "500", description = "Internal server error") - ResponseEntity getUserById( - @PathVariable - int id - ); + ResponseEntity getUserById(@PathVariable int id); @PutMapping("/{id}") @Operation( summary = "Modify a random user", description = "Giving a random user id, modify the content of the user", parameters = { - @Parameter(name = "id", description = "id of the requested user"), - @Parameter(name = "UserEntity", description = "changeable parameters") + @Parameter(name = "id", description = "id of the requested user") } ) @ApiResponse(responseCode = "200", description = "User successfully modified and saved") @ApiResponse(responseCode = "404", description = "The requested user does not exist") @ApiResponse(responseCode = "500", description = "Internal server error") - ResponseEntity updateRandomUser( - @PathVariable - int id, - @RequestBody - UserRequest user - ); + ResponseEntity updateRandomUser(@PathVariable int id, @RequestBody UserRequestDTO user); @PostMapping("") @Operation( summary = "Create a user", description = "Creates a new user in the database.", parameters = { - @Parameter(name = "UserRequest", description = "User data to persist") + @Parameter(name = "UserRequestDTO", description = "User data to persist") } ) @ApiResponse(responseCode = "201", description = "User successfully created") @ApiResponse(responseCode = "500", description = "Internal server error") - ResponseEntity createUser(@RequestBody UserRequest user); - + ResponseEntity createUser(@RequestBody UserRequestDTO 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.", + description = "Search users by optional filters. Supports pagination via page/size query params.", parameters = { @Parameter(name = "gender", description = "Filter by gender (MALE or FEMALE)"), @Parameter(name = "firstname", description = "Filter by firstname"), @@ -109,16 +89,17 @@ ResponseEntity updateRandomUser( @Parameter(name = "nat", description = "Filter by nationality") } ) - @ApiResponse(responseCode = "200", description = "Filtered list of users") + @ApiResponse(responseCode = "200", description = "Paginated filtered list of users") @ApiResponse(responseCode = "500", description = "Internal server error") - ResponseEntity> filterUsers( + 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 + @RequestParam(required = false) String nat, + @ParameterObject @PageableDefault(size = 10, page = 0) Pageable pageable ); @DeleteMapping("/{id}") @@ -132,8 +113,5 @@ ResponseEntity> filterUsers( @ApiResponse(responseCode = "204", description = "User successfully deleted") @ApiResponse(responseCode = "404", description = "The requested user does not exist") @ApiResponse(responseCode = "500", description = "Internal server error") - void deleteUserById( - @PathVariable - int id - ); + void deleteUserById(@PathVariable int id); } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserDTO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserDTO.java new file mode 100644 index 0000000..d05715c --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserDTO.java @@ -0,0 +1,15 @@ +package com.xpeho.spring_boot_java_random_user.presentation.dto; + +public record UserDTO( + Long id, + String gender, + String firstname, + String lastname, + String civility, + String email, + String phone, + String picture, + String nat +) { +} + diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserRequest.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserRequestDTO.java similarity index 66% rename from src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserRequest.java rename to src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserRequestDTO.java index a923b54..b82fbc2 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserRequest.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserRequestDTO.java @@ -1,6 +1,6 @@ -package com.xpeho.spring_boot_java_random_user.domain.entities; +package com.xpeho.spring_boot_java_random_user.presentation.dto; -public record UserRequest( +public record UserRequestDTO( String gender, String firstname, String lastname, @@ -11,3 +11,4 @@ public record UserRequest( String nat ) { } + diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserResponseDTO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserResponseDTO.java index 06e7084..f61e49c 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserResponseDTO.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserResponseDTO.java @@ -1,8 +1,6 @@ package com.xpeho.spring_boot_java_random_user.presentation.dto; -import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; - import java.util.List; -public record UserResponseDTO(List data, int total, int skip, int limit) { +public record UserResponseDTO(List data, int total, int skip, int limit) { } 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 c6d81c2..c1551c4 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 @@ -3,15 +3,18 @@ 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.UserNotFoundException; import com.xpeho.spring_boot_java_random_user.domain.usecases.*; import com.xpeho.spring_boot_java_random_user.presentation.controllers.UserController; +import com.xpeho.spring_boot_java_random_user.presentation.dto.UserDTO; +import com.xpeho.spring_boot_java_random_user.presentation.dto.UserRequestDTO; import com.xpeho.spring_boot_java_random_user.presentation.dto.UserResponseDTO; +import com.xpeho.spring_boot_java_random_user.presentation.mappers.UserMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -19,7 +22,6 @@ import org.springframework.web.bind.annotation.RestController; import java.io.IOException; -import java.util.List; @Validated @@ -52,13 +54,14 @@ public UserHandler( this.filterUsersUseCase = filterUsersUseCase; } - @Override - public ResponseEntity getRandomUsers(int page, int size, UserSource source) { + public ResponseEntity getRandomUsers(Pageable pageable, UserSource source) { try { + int page = pageable.getPageNumber() + 1; // 0-based → 1-based + int size = pageable.getPageSize(); PaginatedUsers result = fetchAndSaveRandomUsersUseCase.execute(page, size, source); UserResponseDTO response = new UserResponseDTO( - result.data(), + result.data().stream().map(UserMapper::toDTO).toList(), result.total(), result.skip(), result.limit() @@ -71,10 +74,11 @@ public ResponseEntity getRandomUsers(int page, int size, UserSo } @Override - public ResponseEntity updateRandomUser(int id, UserRequest user) { + public ResponseEntity updateRandomUser(int id, UserRequestDTO user) { try { - UserEntity savedUser = updateRandomUserUseCase.execute(id, user); - return ResponseEntity.ok(savedUser); + UserEntity input = toUserEntity(user); + UserEntity savedUser = updateRandomUserUseCase.execute(id, input); + return ResponseEntity.ok(UserMapper.toDTO(savedUser)); } catch (UserNotFoundException e) { logUserNotFound(e); return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); @@ -82,31 +86,40 @@ public ResponseEntity updateRandomUser(int id, UserRequest user) { } @Override - public ResponseEntity getUserById(int id) { + public ResponseEntity getUserById(int id) { try { UserEntity user = getUserByIdUseCase.execute(id); - return ResponseEntity.ok(user); + return ResponseEntity.ok(UserMapper.toDTO(user)); } catch (UserNotFoundException e) { logUserNotFound(e); return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); } } - @Override - public ResponseEntity createUser(@RequestBody UserRequest user) { - UserEntity createdUser = createUserUseCase.execute(user); - return ResponseEntity.status(HttpStatus.CREATED).body(createdUser); + public ResponseEntity createUser(@RequestBody UserRequestDTO user) { + UserEntity input = toUserEntity(user); + UserEntity createdUser = createUserUseCase.execute(input); + return ResponseEntity.status(HttpStatus.CREATED).body(UserMapper.toDTO(createdUser)); } @Override - public ResponseEntity> filterUsers( + public ResponseEntity filterUsers( Gender gender, String firstname, String lastname, - String civility, String email, String phone, String nat + String civility, String email, String phone, String nat, + Pageable pageable ) { UserFilter filter = new UserFilter(gender, firstname, lastname, civility, email, phone, nat); - List users = filterUsersUseCase.execute(filter); - return ResponseEntity.ok(users); + int page = pageable.getPageNumber() + 1; + int size = pageable.getPageSize(); + PaginatedUsers result = filterUsersUseCase.execute(filter, page, size); + UserResponseDTO response = new UserResponseDTO( + result.data().stream().map(UserMapper::toDTO).toList(), + result.total(), + result.skip(), + result.limit() + ); + return ResponseEntity.ok(response); } @Override @@ -118,6 +131,11 @@ public void deleteUserById(int id) { } } + private UserEntity toUserEntity(UserRequestDTO dto) { + return new UserEntity(null, dto.gender(), dto.firstname(), dto.lastname(), + dto.civility(), dto.email(), dto.phone(), dto.picture(), dto.nat()); + } + private void logUserNotFound(UserNotFoundException e) { logger.warn(USER_NOT_FOUND_LOG, e.getMessage()); } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/mappers/UserMapper.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/mappers/UserMapper.java new file mode 100644 index 0000000..251c1ad --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/mappers/UserMapper.java @@ -0,0 +1,25 @@ +package com.xpeho.spring_boot_java_random_user.presentation.mappers; + +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.presentation.dto.UserDTO; + +public class UserMapper { + + private UserMapper() { + } + + public static UserDTO toDTO(UserEntity entity) { + return new UserDTO( + entity.id(), + entity.gender(), + entity.firstname(), + entity.lastname(), + entity.civility(), + entity.email(), + entity.phone(), + entity.picture(), + entity.nat() + ); + } +} + diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java index 8e97b5c..a66baec 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java @@ -5,7 +5,7 @@ import com.xpeho.spring_boot_java_random_user.data.models.api.randomuser.RandomUserPictureDTO; import com.xpeho.spring_boot_java_random_user.data.models.api.randomuser.RandomUserResultDTO; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; -import com.xpeho.spring_boot_java_random_user.data.models.database.User; +import com.xpeho.spring_boot_java_random_user.data.models.database.UserDao; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -104,7 +104,7 @@ void shouldHandleMissingNestedFieldsFromRandomUserApiModel() { @DisplayName("Should convert domain to DB and back without losing data") void shouldConvertDomainToDbAndBackWithoutLosingData() { UserEntity entity = new UserEntity(1L, "male", "John", "Doe", "Mr", "john@doe.com", "1234", "pic.jpg", "FR"); - User dao = converter.toDao(entity); + UserDao dao = converter.toDao(entity); assertEquals(entity.id(), dao.getId()); assertEquals(entity.firstname(), dao.getFirstname()); assertEquals(entity.lastname(), dao.getLastname()); 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 942d99e..f0b6b7a 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 @@ -1,14 +1,18 @@ package com.xpeho.spring_boot_java_random_user.data.services; 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.models.database.UserDao; import com.xpeho.spring_boot_java_random_user.data.sources.database.UserRepository; +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.enums.Gender; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import java.util.Collections; @@ -17,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -36,12 +41,10 @@ void setUp() { @Test @DisplayName("Should return mapped user when id exists") void shouldReturnMappedUserWhenIdExists() { - User dao = new User(); + UserDao dao = new UserDao(); dao.setId(1L); dao.setFirstname("John"); - UserEntity expected = new UserEntity(1L, "male", "John", "Doe", "Mr", "john@doe.com", "1234", "pic.jpg", "FR"); - when(userRepository.findById(1L)).thenReturn(Optional.of(dao)); when(userConverter.toDomain(dao)).thenReturn(expected); @@ -68,14 +71,11 @@ void shouldReturnEmptyOptionalWhenIdDoesNotExist() { @DisplayName("Should save mapped user and return mapped domain entity") void shouldSaveMappedUserAndReturnMappedDomainEntity() { UserEntity input = new UserEntity(3L, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US"); - - User daoToSave = new User(); + UserDao daoToSave = new UserDao(); daoToSave.setFirstname("Alice"); - - User savedDao = new User(); + UserDao savedDao = new UserDao(); savedDao.setId(3L); savedDao.setFirstname("Alice"); - UserEntity expected = new UserEntity(3L, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US"); when(userConverter.toDao(input)).thenReturn(daoToSave); @@ -91,61 +91,58 @@ void shouldSaveMappedUserAndReturnMappedDomainEntity() { } @Test - @DisplayName("Should build a specification and call repository for filtered users") + @DisplayName("Should return paginated users when filtering with gender") void shouldFilterUsersWithGender() { UserFilter filter = new UserFilter(Gender.MALE, "John", null, null, null, null, null); - - User dao = new User(); + UserDao dao = new UserDao(); dao.setId(1L); dao.setFirstname("John"); - UserEntity expected = new UserEntity(1L, "male", "John", "Doe", "Mr", "john@doe.com", "1234", "pic.jpg", "FR"); + Page page = new PageImpl<>(List.of(dao)); - when(userRepository.findAll(org.mockito.ArgumentMatchers.>any())) - .thenReturn(List.of(dao)); + when(userRepository.findAll(any(Specification.class), any(Pageable.class))).thenReturn(page); when(userConverter.toDomain(dao)).thenReturn(expected); - List result = userService.filterUsers(filter); + PaginatedUsers result = userService.filterUsers(filter, 1, 10); - assertEquals(1, result.size()); - assertEquals(expected, result.get(0)); - verify(userRepository).findAll(org.mockito.ArgumentMatchers.>any()); + assertEquals(1, result.data().size()); + assertEquals(expected, result.data().get(0)); + assertEquals(1, result.total()); + verify(userRepository).findAll(any(Specification.class), any(Pageable.class)); verify(userConverter).toDomain(dao); } @Test - @DisplayName("Should call repository when gender filter is null") + @DisplayName("Should return paginated users when filtering with null gender") void shouldFilterUsersWithNullGender() { UserFilter filter = new UserFilter(null, null, "Smith", null, null, null, null); - - User dao = new User(); + UserDao dao = new UserDao(); dao.setId(2L); dao.setLastname("Smith"); - UserEntity expected = new UserEntity(2L, "female", "Alice", "Smith", "Ms", "alice@smith.com", "5678", "pic2.jpg", "US"); + Page page = new PageImpl<>(List.of(dao)); - when(userRepository.findAll(org.mockito.ArgumentMatchers.>any())) - .thenReturn(List.of(dao)); + when(userRepository.findAll(any(Specification.class), any(Pageable.class))).thenReturn(page); when(userConverter.toDomain(dao)).thenReturn(expected); - List result = userService.filterUsers(filter); + PaginatedUsers result = userService.filterUsers(filter, 1, 10); - assertEquals(1, result.size()); - assertEquals(expected, result.get(0)); - verify(userRepository).findAll(org.mockito.ArgumentMatchers.>any()); + assertEquals(1, result.data().size()); + verify(userRepository).findAll(any(Specification.class), any(Pageable.class)); } @Test - @DisplayName("Should return empty list when no users match filter") + @DisplayName("Should return empty paginated result when no users match filter") void shouldReturnEmptyListWhenNoUsersMatchFilter() { UserFilter filter = new UserFilter(Gender.FEMALE, "Unknown", null, null, null, null, null); + Page emptyPage = new PageImpl<>(Collections.emptyList()); - when(userRepository.findAll(org.mockito.ArgumentMatchers.>any())) - .thenReturn(Collections.emptyList()); + when(userRepository.findAll(any(Specification.class), any(Pageable.class))).thenReturn(emptyPage); - List result = userService.filterUsers(filter); + PaginatedUsers result = userService.filterUsers(filter, 1, 10); - assertTrue(result.isEmpty()); - verify(userRepository).findAll(org.mockito.ArgumentMatchers.>any()); + assertTrue(result.data().isEmpty()); + assertEquals(0, result.total()); + verify(userRepository).findAll(any(Specification.class), any(Pageable.class)); } } 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 index cbe3095..f96c5a2 100644 --- 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 @@ -1,6 +1,6 @@ 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.data.models.database.UserDao; import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter; import jakarta.persistence.criteria.*; import org.junit.jupiter.api.BeforeEach; @@ -11,11 +11,10 @@ import static org.mockito.Mockito.*; class UserSpecificationsTest { - private Root user; + private Root user; private CriteriaQuery query; private CriteriaBuilder cb; private Expression lowerExpr; - private Predicate predicate; @BeforeEach @SuppressWarnings("unchecked") @@ -24,7 +23,7 @@ void setUp() { query = mock(CriteriaQuery.class); cb = mock(CriteriaBuilder.class); lowerExpr = mock(Expression.class); - predicate = mock(Predicate.class); + Predicate 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); @@ -38,7 +37,7 @@ void setUp() { void shouldAddLikePredicatesForAllTextFields() { UserFilter filter = new UserFilter(null, "John", "Doe", "Mr", "john@doe.com", "1234", "FR"); - Specification spec = UserSpecifications.byFilter(filter); + Specification spec = UserSpecifications.byFilter(filter); spec.toPredicate(user, query, cb); verify(user).get("firstname"); diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/CreateUserUseCaseTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/CreateUserUseCaseTest.java index 4a99dd5..4f9d894 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/CreateUserUseCaseTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/CreateUserUseCaseTest.java @@ -1,8 +1,7 @@ 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.UserRequest; -import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,33 +12,30 @@ import static org.mockito.Mockito.when; class CreateUserUseCaseTest { - private LocalUserService userService; + private UserService userService; private CreateUserUseCase useCase; @BeforeEach void setUp() { - userService = mock(LocalUserService.class); + userService = mock(UserService.class); useCase = new CreateUserUseCase(userService); } @Test - @DisplayName("Should create a user without keeping the input id") + @DisplayName("Should create a user by passing the UserEntity directly") void shouldCreateUserWithoutKeepingInputId() { - UserRequest payload = new UserRequest( - "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US" + UserEntity input = new UserEntity( + null, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US" ); UserEntity createdUser = new UserEntity( 1L, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US" ); - UserEntity expectedSavedUser = new UserEntity( - null, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US" - ); - when(userService.save(expectedSavedUser)).thenReturn(createdUser); + when(userService.save(input)).thenReturn(createdUser); - UserEntity result = useCase.execute(payload); + UserEntity result = useCase.execute(input); assertEquals(createdUser, result); - verify(userService).save(expectedSavedUser); + verify(userService).save(input); } } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/DeleteUserByIdUseCaseTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/DeleteUserByIdUseCaseTest.java index 73d8000..923804e 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/DeleteUserByIdUseCaseTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/DeleteUserByIdUseCaseTest.java @@ -1,6 +1,6 @@ package com.xpeho.spring_boot_java_random_user.domain.usecases; -import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -8,12 +8,12 @@ import static org.mockito.Mockito.*; class DeleteUserByIdUseCaseTest { - private LocalUserService userService; + private UserService userService; private DeleteUserByIdUseCase useCase; @BeforeEach void setUp() { - userService = mock(LocalUserService.class); + userService = mock(UserService.class); useCase = new DeleteUserByIdUseCase(userService); } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FetchAndSaveRandomUsersUseCaseTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FetchAndSaveRandomUsersUseCaseTest.java index 1b867da..4cf3efc 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FetchAndSaveRandomUsersUseCaseTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FetchAndSaveRandomUsersUseCaseTest.java @@ -3,7 +3,7 @@ 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.enums.UserSource; -import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; import com.xpeho.spring_boot_java_random_user.domain.services.RemoteUserService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -16,14 +16,14 @@ import static org.mockito.Mockito.*; class FetchAndSaveRandomUsersUseCaseTest { - private LocalUserService userService; + private UserService userService; private RemoteUserService dummyRemoteUserService; private RemoteUserService randomRemoteUserService; private FetchAndSaveRandomUsersUseCase useCase; @BeforeEach void setUp() { - userService = mock(LocalUserService.class); + userService = mock(UserService.class); dummyRemoteUserService = mock(RemoteUserService.class); randomRemoteUserService = mock(RemoteUserService.class); when(dummyRemoteUserService.getSource()).thenReturn(UserSource.DUMMY); 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 index 368f69f..aae4138 100644 --- 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 @@ -1,9 +1,10 @@ package com.xpeho.spring_boot_java_random_user.domain.usecases; +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.enums.Gender; -import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -15,55 +16,58 @@ import static org.mockito.Mockito.*; class FilterUsersUseCaseTest { - private LocalUserService userService; + private UserService userService; private FilterUsersUseCase useCase; @BeforeEach void setUp() { - userService = mock(LocalUserService.class); + userService = mock(UserService.class); useCase = new FilterUsersUseCase(userService); } @Test - @DisplayName("Should return filtered users matching the filter") + @DisplayName("Should return paginated filtered users matching the filter") void shouldReturnFilteredUsers() { UserFilter filter = new UserFilter(Gender.MALE, "John", null, null, null, null, null); - List expected = List.of( + List users = List.of( new UserEntity(1L, "male", "John", "Doe", "Mr", "john@example.com", "0600000000", "http://pic.jpg", "FR") ); - when(userService.filterUsers(filter)).thenReturn(expected); + PaginatedUsers expected = new PaginatedUsers(users, 1, 0, 10); + when(userService.filterUsers(filter, 1, 10)).thenReturn(expected); - List result = useCase.execute(filter); + PaginatedUsers result = useCase.execute(filter, 1, 10); assertEquals(expected, result); - verify(userService).filterUsers(filter); + verify(userService).filterUsers(filter, 1, 10); } @Test - @DisplayName("Should return empty list when no users match the filter") + @DisplayName("Should return empty paginated result 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()); + PaginatedUsers empty = new PaginatedUsers(Collections.emptyList(), 0, 0, 10); + when(userService.filterUsers(filter, 1, 10)).thenReturn(empty); - List result = useCase.execute(filter); + PaginatedUsers result = useCase.execute(filter, 1, 10); - assertTrue(result.isEmpty()); - verify(userService).filterUsers(filter); + assertTrue(result.data().isEmpty()); + verify(userService).filterUsers(filter, 1, 10); } @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( + List users = List.of( new UserEntity(5L, "female", "Alice", "Smith", "Ms", "alice@example.com", "0611111111", "http://pic2.jpg", "US") ); - when(userService.filterUsers(filter)).thenReturn(expected); + PaginatedUsers expected = new PaginatedUsers(users, 1, 0, 10); + when(userService.filterUsers(filter, 1, 10)).thenReturn(expected); - List result = useCase.execute(filter); + PaginatedUsers result = useCase.execute(filter, 1, 10); assertEquals(expected, result); - verify(userService, times(1)).filterUsers(filter); + verify(userService, times(1)).filterUsers(filter, 1, 10); verifyNoMoreInteractions(userService); } } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCaseTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCaseTest.java index cbbf0c1..f22956c 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCaseTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCaseTest.java @@ -2,7 +2,7 @@ import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException; -import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,12 +13,12 @@ import static org.mockito.Mockito.*; class GetUserByIdUseCaseTest { - private LocalUserService userService; + private UserService userService; private GetUserByIdUseCase useCase; @BeforeEach void setUp() { - userService = mock(LocalUserService.class); + userService = mock(UserService.class); useCase = new GetUserByIdUseCase(userService); } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserUseCaseTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserUseCaseTest.java index 1d8a5a8..c92772a 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserUseCaseTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserUseCaseTest.java @@ -1,9 +1,8 @@ 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.UserRequest; import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException; -import com.xpeho.spring_boot_java_random_user.domain.services.LocalUserService; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,12 +18,12 @@ import static org.mockito.Mockito.when; class UpdateRandomUserUseCaseTest { - private LocalUserService userService; + private UserService userService; private UpdateRandomUserUseCase useCase; @BeforeEach void setUp() { - userService = mock(LocalUserService.class); + userService = mock(UserService.class); useCase = new UpdateRandomUserUseCase(userService); } @@ -34,39 +33,35 @@ void shouldUpdateExistingUserAndPreserveItsId() { UserEntity existingUser = new UserEntity( 42L, "male", "John", "Doe", "Mr", "john@doe.com", "1234", "pic.jpg", "FR" ); - UserRequest request = new UserRequest( - "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US" + UserEntity input = new UserEntity( + null, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US" ); UserEntity savedUser = new UserEntity( 42L, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US" ); when(userService.getById(42)).thenReturn(Optional.of(existingUser)); - when(userService.save(new UserEntity( - 42L, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US" - ))).thenReturn(savedUser); + when(userService.save(savedUser)).thenReturn(savedUser); - UserEntity result = useCase.execute(42, request); + UserEntity result = useCase.execute(42, input); assertEquals(savedUser, result); verify(userService).getById(42); - verify(userService).save(new UserEntity( - 42L, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US" - )); + verify(userService).save(savedUser); } @Test @DisplayName("Should throw when updating a user that does not exist") void shouldThrowWhenUserDoesNotExist() { - UserRequest request = new UserRequest( - "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US" + UserEntity input = new UserEntity( + null, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US" ); when(userService.getById(99)).thenReturn(Optional.empty()); UserNotFoundException exception = assertThrows( UserNotFoundException.class, - () -> useCase.execute(99, request) + () -> useCase.execute(99, input) ); assertEquals("User not found with id: 99", exception.getMessage()); diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserGetByIdContainerTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserGetByIdContainerTest.java index 1468bd9..87cb552 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserGetByIdContainerTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserGetByIdContainerTest.java @@ -1,8 +1,8 @@ package com.xpeho.spring_boot_java_random_user.presentation; -import com.xpeho.spring_boot_java_random_user.data.models.database.User; +import com.xpeho.spring_boot_java_random_user.data.models.database.UserDao; 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.presentation.dto.UserDTO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -60,7 +60,7 @@ void setUp() { @Test @DisplayName("GET /random-users/{id} should return 200 with persisted user") void shouldReturnUserByIdWhenUserExists() { - User user = new User(); + UserDao user = new UserDao(); user.setGender("female"); user.setFirstname("Jane"); user.setLastname("Doe"); @@ -70,11 +70,11 @@ void shouldReturnUserByIdWhenUserExists() { user.setPicture("https://example.com/jane.jpg"); user.setNationality("FR"); - User saved = userRepository.saveAndFlush(user); + UserDao saved = userRepository.saveAndFlush(user); - ResponseEntity response = restTemplate.withBasicAuth(TEST_USERNAME, TEST_PASSWORD).getForEntity( + ResponseEntity response = restTemplate.withBasicAuth(TEST_USERNAME, TEST_PASSWORD).getForEntity( "/random-users/{id}", - UserEntity.class, + UserDTO.class, saved.getId() ); @@ -88,9 +88,9 @@ void shouldReturnUserByIdWhenUserExists() { @Test @DisplayName("GET /random-users/{id} should return 404 when user does not exist") void shouldReturnNotFoundWhenUserDoesNotExist() { - ResponseEntity response = restTemplate.withBasicAuth(TEST_USERNAME, TEST_PASSWORD).getForEntity( + ResponseEntity response = restTemplate.withBasicAuth(TEST_USERNAME, TEST_PASSWORD).getForEntity( "/random-users/{id}", - UserEntity.class, + UserDTO.class, -1 ); 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 3ff8b59..b6d51bf 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 @@ -3,16 +3,20 @@ 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.UserNotFoundException; import com.xpeho.spring_boot_java_random_user.domain.usecases.*; -import com.xpeho.spring_boot_java_random_user.presentation.handlers.UserHandler; +import com.xpeho.spring_boot_java_random_user.presentation.dto.UserDTO; +import com.xpeho.spring_boot_java_random_user.presentation.dto.UserRequestDTO; import com.xpeho.spring_boot_java_random_user.presentation.dto.UserResponseDTO; +import com.xpeho.spring_boot_java_random_user.presentation.handlers.UserHandler; +import com.xpeho.spring_boot_java_random_user.presentation.mappers.UserMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -46,37 +50,34 @@ void setUp() { @Test @DisplayName("Should return 200 and paged users when getRandomUsers succeeds") void shouldReturnOkWhenGetRandomUsersSucceeds() throws IOException { - int page = 1; - int size = 10; + Pageable pageable = PageRequest.of(0, 10); List users = List.of( new UserEntity(1L, "male", "John", "Doe", "Mr", "john@example.com", "0600000000", "pic.jpg", "FR") ); PaginatedUsers paginatedUsers = new PaginatedUsers(users, 50, 0, 10); - when(fetchAndSaveRandomUsersUseCase.execute(page, size, UserSource.DUMMY)).thenReturn(paginatedUsers); + when(fetchAndSaveRandomUsersUseCase.execute(1, 10, UserSource.DUMMY)).thenReturn(paginatedUsers); - ResponseEntity response = userHandler.getRandomUsers(page, size, UserSource.DUMMY); + ResponseEntity response = userHandler.getRandomUsers(pageable, UserSource.DUMMY); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); - assertEquals(users, response.getBody().data()); + assertEquals(users.stream().map(UserMapper::toDTO).toList(), response.getBody().data()); assertEquals(50, response.getBody().total()); assertEquals(0, response.getBody().skip()); assertEquals(10, response.getBody().limit()); - verify(fetchAndSaveRandomUsersUseCase, times(1)).execute(page, size, UserSource.DUMMY); + verify(fetchAndSaveRandomUsersUseCase, times(1)).execute(1, 10, UserSource.DUMMY); } @Test @DisplayName("Should return 500 when getRandomUsers throws IOException") void shouldReturnInternalServerErrorWhenGetRandomUsersFails() throws IOException { - int page = 1; - int size = 10; - when(fetchAndSaveRandomUsersUseCase.execute(page, size, UserSource.RANDOM_USER)).thenThrow(new IOException("downstream unavailable")); + Pageable pageable = PageRequest.of(0, 10); + when(fetchAndSaveRandomUsersUseCase.execute(1, 10, UserSource.RANDOM_USER)).thenThrow(new IOException("downstream unavailable")); - ResponseEntity response = userHandler.getRandomUsers(page, size, UserSource.RANDOM_USER); + ResponseEntity response = userHandler.getRandomUsers(pageable, UserSource.RANDOM_USER); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); assertNull(response.getBody()); - verify(fetchAndSaveRandomUsersUseCase, times(1)).execute(page, size, UserSource.RANDOM_USER); } @Test @@ -85,10 +86,10 @@ void shouldReturnOkWhenGetUserByIdSucceeds() { UserEntity user = new UserEntity(42L, "female", "Alice", "Smith", "Ms", "alice@example.com", "0611111111", "alice.jpg", "US"); when(getUserByIdUseCase.execute(42)).thenReturn(user); - ResponseEntity response = userHandler.getUserById(42); + ResponseEntity response = userHandler.getUserById(42); assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(user, response.getBody()); + assertEquals(UserMapper.toDTO(user), response.getBody()); verify(getUserByIdUseCase, times(1)).execute(42); } @@ -97,62 +98,60 @@ void shouldReturnOkWhenGetUserByIdSucceeds() { void shouldReturnNotFoundWhenGetUserByIdFails() { when(getUserByIdUseCase.execute(99)).thenThrow(new UserNotFoundException(99)); - ResponseEntity response = userHandler.getUserById(99); + ResponseEntity response = userHandler.getUserById(99); assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); assertNull(response.getBody()); - verify(getUserByIdUseCase, times(1)).execute(99); } @Test @DisplayName("Should return 200 and updated user when updateRandomUser succeeds") void shouldReturnOkWhenUpdateRandomUserSucceeds() { - UserRequest request = new UserRequest("female", "Jane", "Doe", "Ms", "jane@example.com", "0622222222", "jane.jpg", "FR"); + UserRequestDTO request = new UserRequestDTO("female", "Jane", "Doe", "Ms", "jane@example.com", "0622222222", "jane.jpg", "FR"); + UserEntity input = new UserEntity(null, "female", "Jane", "Doe", "Ms", "jane@example.com", "0622222222", "jane.jpg", "FR"); UserEntity updated = new UserEntity(7L, "female", "Jane", "Doe", "Ms", "jane@example.com", "0622222222", "jane.jpg", "FR"); - when(updateRandomUserUseCase.execute(7, request)).thenReturn(updated); + when(updateRandomUserUseCase.execute(7, input)).thenReturn(updated); - ResponseEntity response = userHandler.updateRandomUser(7, request); + ResponseEntity response = userHandler.updateRandomUser(7, request); assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(updated, response.getBody()); - verify(updateRandomUserUseCase, times(1)).execute(7, request); + assertEquals(UserMapper.toDTO(updated), response.getBody()); + verify(updateRandomUserUseCase, times(1)).execute(7, input); } @Test @DisplayName("Should return 404 when updateRandomUser throws UserNotFoundException") void shouldReturnNotFoundWhenUpdateRandomUserFails() { - UserRequest request = new UserRequest("male", "Bob", "Brown", "Mr", "bob@example.com", "0633333333", "bob.jpg", "DE"); - when(updateRandomUserUseCase.execute(123, request)).thenThrow(new UserNotFoundException(123)); + UserRequestDTO request = new UserRequestDTO("male", "Bob", "Brown", "Mr", "bob@example.com", "0633333333", "bob.jpg", "DE"); + UserEntity input = new UserEntity(null, "male", "Bob", "Brown", "Mr", "bob@example.com", "0633333333", "bob.jpg", "DE"); + when(updateRandomUserUseCase.execute(123, input)).thenThrow(new UserNotFoundException(123)); - ResponseEntity response = userHandler.updateRandomUser(123, request); + ResponseEntity response = userHandler.updateRandomUser(123, request); assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); assertNull(response.getBody()); - verify(updateRandomUserUseCase, times(1)).execute(123, request); } @Test @DisplayName("Should return 201 and created user when createUser succeeds") void shouldReturnCreatedWhenCreateUserSucceeds() { - UserRequest request = new UserRequest("female", "Emma", "Stone", "Ms", "emma@example.com", "0644444444", "emma.jpg", "FR"); + UserRequestDTO request = new UserRequestDTO("female", "Emma", "Stone", "Ms", "emma@example.com", "0644444444", "emma.jpg", "FR"); + UserEntity input = new UserEntity(null, "female", "Emma", "Stone", "Ms", "emma@example.com", "0644444444", "emma.jpg", "FR"); UserEntity created = new UserEntity(10L, "female", "Emma", "Stone", "Ms", "emma@example.com", "0644444444", "emma.jpg", "FR"); - when(createUserUseCase.execute(request)).thenReturn(created); + when(createUserUseCase.execute(input)).thenReturn(created); - ResponseEntity response = userHandler.createUser(request); + ResponseEntity response = userHandler.createUser(request); assertEquals(HttpStatus.CREATED, response.getStatusCode()); - assertEquals(created, response.getBody()); - verify(createUserUseCase, times(1)).execute(request); + assertEquals(UserMapper.toDTO(created), response.getBody()); + verify(createUserUseCase, times(1)).execute(input); } @Test @DisplayName("Should return 204 when deleteUserById succeeds") void shouldReturnNoContentWhenDeleteUserByIdSucceeds() { - int userId = 42; - - userHandler.deleteUserById(userId); - - verify(deleteUserUseCase, times(1)).execute(userId); + userHandler.deleteUserById(42); + verify(deleteUserUseCase, times(1)).execute(42); } @Test @@ -160,41 +159,42 @@ void shouldReturnNoContentWhenDeleteUserByIdSucceeds() { void shouldLogWarningWhenDeleteUserByIdFails() { int userId = 123; doThrow(new UserNotFoundException(userId)).when(deleteUserUseCase).execute(userId); - userHandler.deleteUserById(userId); - verify(deleteUserUseCase, times(1)).execute(userId); } @Test @DisplayName("Should return 200 and filtered users when filterUsers succeeds") void shouldReturnOkWhenFilterUsersSucceeds() { + Pageable pageable = PageRequest.of(0, 10); 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); + PaginatedUsers paginatedUsers = new PaginatedUsers(users, 1, 0, 10); + when(filterUsersUseCase.execute(filter, 1, 10)).thenReturn(paginatedUsers); - ResponseEntity> response = userHandler.filterUsers(Gender.MALE, null, null, null, null, null, "FR"); + ResponseEntity response = userHandler.filterUsers(Gender.MALE, null, null, null, null, null, "FR", pageable); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); - assertEquals(1, response.getBody().size()); - assertEquals(users, response.getBody()); - verify(filterUsersUseCase, times(1)).execute(filter); + assertEquals(1, response.getBody().data().size()); + assertEquals(users.stream().map(UserMapper::toDTO).toList(), response.getBody().data()); + verify(filterUsersUseCase, times(1)).execute(filter, 1, 10); } @Test @DisplayName("Should return 200 and empty list when no users match filter") void shouldReturnOkWithEmptyListWhenNoUsersMatchFilter() { + Pageable pageable = PageRequest.of(0, 10); UserFilter filter = new UserFilter(null, "NonExistent", null, null, null, null, null); - when(filterUsersUseCase.execute(filter)).thenReturn(List.of()); + PaginatedUsers emptyResult = new PaginatedUsers(List.of(), 0, 0, 10); + when(filterUsersUseCase.execute(filter, 1, 10)).thenReturn(emptyResult); - ResponseEntity> response = userHandler.filterUsers(null, "NonExistent", null, null, null, null, null); + ResponseEntity response = userHandler.filterUsers(null, "NonExistent", null, null, null, null, null, pageable); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); - assertTrue(response.getBody().isEmpty()); - verify(filterUsersUseCase, times(1)).execute(filter); + assertTrue(response.getBody().data().isEmpty()); } } diff --git a/src/test/java/feature/CucumberIntegrationTest.java b/src/test/java/features/CucumberIntegrationTest.java similarity index 96% rename from src/test/java/feature/CucumberIntegrationTest.java rename to src/test/java/features/CucumberIntegrationTest.java index 88a7029..ec1ddaa 100644 --- a/src/test/java/feature/CucumberIntegrationTest.java +++ b/src/test/java/features/CucumberIntegrationTest.java @@ -1,4 +1,4 @@ -package feature; +package features; import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.IncludeEngines; @@ -11,7 +11,7 @@ @Suite @IncludeEngines("cucumber") @SelectClasspathResource("features") -@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "feature") +@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "features") @ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty") public class CucumberIntegrationTest { diff --git a/src/test/java/feature/CucumberTypeConfig.java b/src/test/java/features/CucumberTypeConfig.java similarity index 86% rename from src/test/java/feature/CucumberTypeConfig.java rename to src/test/java/features/CucumberTypeConfig.java index c89ab6d..e539219 100644 --- a/src/test/java/feature/CucumberTypeConfig.java +++ b/src/test/java/features/CucumberTypeConfig.java @@ -1,6 +1,6 @@ -package feature; +package features; -import com.xpeho.spring_boot_java_random_user.domain.entities.UserRequest; +import com.xpeho.spring_boot_java_random_user.presentation.dto.UserRequestDTO; import io.cucumber.java.DataTableType; import java.util.List; @@ -33,12 +33,12 @@ public FieldAssertion fieldAssertion(List row) { } /** - * Convertit une ligne de DataTable (Map) en UserRequest. + * Convertit une ligne de DataTable (Map) en UserRequestDTO. * Utilisable dans les .feature avec des tables à en-têtes. */ @DataTableType - public UserRequest userRequest(Map row) { - return new UserRequest( + public UserRequestDTO userRequest(Map row) { + return new UserRequestDTO( row.get("gender"), row.get("firstname"), row.get("lastname"), diff --git a/src/test/java/feature/SpringIntegrationTest.java b/src/test/java/features/SpringIntegrationTest.java similarity index 99% rename from src/test/java/feature/SpringIntegrationTest.java rename to src/test/java/features/SpringIntegrationTest.java index a3d8d66..66a6495 100644 --- a/src/test/java/feature/SpringIntegrationTest.java +++ b/src/test/java/features/SpringIntegrationTest.java @@ -1,4 +1,4 @@ -package feature; +package features; import com.xpeho.spring_boot_java_random_user.SpringBootJavaRandomUserApplication; import io.cucumber.spring.CucumberContextConfiguration; diff --git a/src/test/java/feature/StepDefinition.java b/src/test/java/features/StepDefinition.java similarity index 94% rename from src/test/java/feature/StepDefinition.java rename to src/test/java/features/StepDefinition.java index afb3443..c2e7e66 100644 --- a/src/test/java/feature/StepDefinition.java +++ b/src/test/java/features/StepDefinition.java @@ -1,9 +1,9 @@ -package feature; +package features; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.xpeho.spring_boot_java_random_user.domain.entities.UserRequest; -import feature.CucumberTypeConfig.FieldAssertion; +import com.xpeho.spring_boot_java_random_user.presentation.dto.UserRequestDTO; +import features.CucumberTypeConfig.FieldAssertion; import io.cucumber.java.en.And; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; @@ -18,12 +18,12 @@ public class StepDefinition extends SpringIntegrationTest { private final ObjectMapper objectMapper = new ObjectMapper(); - private UserRequest payload; + private UserRequestDTO payload; private Long createdUserId; @Given("a valid user payload for creation") public void aValidUserPayloadForCreation() { - payload = new UserRequest( + payload = new UserRequestDTO( "female", "Emma", "Stone", @@ -102,7 +102,7 @@ public void theClientCallToDeleteTheCreatedUser() { @Given("a valid user payload for update") public void aValidUserPayloadForUpdate() { - payload = new UserRequest( + payload = new UserRequestDTO( "male", "John", "Doe",