From e98147dd44abb31fbd9a0fe4f52d969c08090ad7 Mon Sep 17 00:00:00 2001 From: Martho Evan Date: Tue, 5 May 2026 13:21:47 +0200 Subject: [PATCH] fix(archi): clean archi of the project --- README.md | 6 +- mvnw.cmd | 2 +- pom.xml | 2 +- .../data/converters/UserConverter.java | 46 ++++++------- .../database/{User.java => UserDAO.java} | 4 +- .../data/services/UserServiceImpl.java | 51 ++++++++++---- .../sources/api}/RemoteUserService.java | 2 +- .../data/sources/api/UserApiConfig.java | 67 +++++++++++++++++++ .../sources/api/dummy/DummyUserApiConfig.java | 25 ------- .../api/dummy/DummyUserServiceImpl.java | 2 +- .../api/randomuser/RandomUserApiConfig.java | 25 ------- .../api/randomuser/RandomUserServiceImpl.java | 2 +- .../data/sources/database/UserRepository.java | 4 +- .../sources/database/UserSpecifications.java | 4 +- .../exceptions/UserNotFoundException.java | 2 +- .../domain/services/LocalUserService.java | 20 ------ .../domain/services/UserService.java | 28 ++++++++ .../domain/usecases/CreateUserUseCase.java | 10 ++- .../usecases/DeleteUserByIdUseCase.java | 6 +- .../FetchAndSaveRandomUsersUseCase.java | 24 ++----- .../domain/usecases/FilterUsersUseCase.java | 14 ++-- .../domain/usecases/GetUserByIdUseCase.java | 6 +- .../usecases/UpdateRandomUserUseCase.java | 25 ++++--- .../controllers/UserController.java | 34 +++++----- .../presentation/dto/PaginatedRequest.java | 11 +++ .../dto}/UserRequest.java | 3 +- .../exceptions/GlobalExceptionHandler.java | 2 +- .../presentation/handlers/UserHandler.java | 33 +++++++-- src/main/resources/application.properties | 6 +- ...ootJavaRandomUserDAOApplicationTests.java} | 2 +- ...terTest.java => UserDAOConverterTest.java} | 6 +- ...est.java => DummyUserDAOResponseTest.java} | 2 +- ...st.java => DummyUserDTOResultDAOTest.java} | 2 +- ...est.java => RandomUserDTONameDAOTest.java} | 2 +- ....java => RandomUserDTOPictureDAOTest.java} | 2 +- ...java => RandomUserDTOResponseDAOTest.java} | 2 +- ...t.java => RandomUserDTOResultDAOTest.java} | 2 +- ...lTest.java => UserDAOServiceImplTest.java} | 59 +++++++++------- ....java => DummyUserDAOServiceImplTest.java} | 2 +- ...java => RandomUserDAOServiceImplTest.java} | 2 +- ...st.java => UserDAOSpecificationsTest.java} | 8 +-- ...est.java => CreateUserDAOUseCaseTest.java} | 13 ++-- ...java => DeleteUserDAOByIdUseCaseTest.java} | 8 +-- .../FetchAndSaveRandomUsersUseCaseTest.java | 43 +++++------- .../usecases/FilterUsersUseCaseTest.java | 39 ++++++----- ...st.java => GetUserDAOByIdUseCaseTest.java} | 8 +-- ...va => UpdateRandomUserDAOUseCaseTest.java} | 23 +++---- ....java => UserDAOGetByIdContainerTest.java} | 24 +++---- ...ndlerTest.java => UserDAOHandlerTest.java} | 44 +++++++----- .../CucumberIntegrationTest.java | 4 +- .../CucumberTypeConfig.java | 18 ++--- .../SpringIntegrationTest.java | 2 +- .../{feature => features}/StepDefinition.java | 6 +- .../resources/application-test.properties | 4 +- 54 files changed, 435 insertions(+), 358 deletions(-) rename src/main/java/com/xpeho/spring_boot_java_random_user/data/models/database/{User.java => UserDAO.java} (98%) rename src/main/java/com/xpeho/spring_boot_java_random_user/{domain/services => data/sources/api}/RemoteUserService.java (83%) create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/UserApiConfig.java delete mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/dummy/DummyUserApiConfig.java delete mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/randomuser/RandomUserApiConfig.java delete mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/LocalUserService.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/UserService.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/PaginatedRequest.java rename src/main/java/com/xpeho/spring_boot_java_random_user/{domain/entities => presentation/dto}/UserRequest.java (76%) rename src/test/java/com/xpeho/spring_boot_java_random_user/{SpringBootJavaRandomUserApplicationTests.java => SpringBootJavaRandomUserDAOApplicationTests.java} (94%) rename src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/{UserConverterTest.java => UserDAOConverterTest.java} (98%) rename src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/dummy/{DummyUserResponseTest.java => DummyUserDAOResponseTest.java} (96%) rename src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/dummy/{DummyUserResultDTOTest.java => DummyUserDTOResultDAOTest.java} (97%) rename src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/{RandomUserNameDTOTest.java => RandomUserDTONameDAOTest.java} (96%) rename src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/{RandomUserPictureDTOTest.java => RandomUserDTOPictureDAOTest.java} (95%) rename src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/{RandomUserResponseDTOTest.java => RandomUserDTOResponseDAOTest.java} (96%) rename src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/{RandomUserResultDTOTest.java => RandomUserDTOResultDAOTest.java} (97%) rename src/test/java/com/xpeho/spring_boot_java_random_user/data/services/{UserServiceImplTest.java => UserDAOServiceImplTest.java} (69%) rename src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/{DummyUserServiceImplTest.java => DummyUserDAOServiceImplTest.java} (99%) rename src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/{RandomUserServiceImplTest.java => RandomUserDAOServiceImplTest.java} (99%) rename src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/database/{UserSpecificationsTest.java => UserDAOSpecificationsTest.java} (92%) rename src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/{CreateUserUseCaseTest.java => CreateUserDAOUseCaseTest.java} (75%) rename src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/{DeleteUserByIdUseCaseTest.java => DeleteUserDAOByIdUseCaseTest.java} (88%) rename src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/{GetUserByIdUseCaseTest.java => GetUserDAOByIdUseCaseTest.java} (92%) rename src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/{UpdateRandomUserUseCaseTest.java => UpdateRandomUserDAOUseCaseTest.java} (75%) rename src/test/java/com/xpeho/spring_boot_java_random_user/presentation/{UserGetByIdContainerTest.java => UserDAOGetByIdContainerTest.java} (87%) rename src/test/java/com/xpeho/spring_boot_java_random_user/presentation/{UserHandlerTest.java => UserDAOHandlerTest.java} (78%) rename src/test/java/{feature => features}/CucumberIntegrationTest.java (96%) rename src/test/java/{feature => features}/CucumberTypeConfig.java (66%) rename src/test/java/{feature => features}/SpringIntegrationTest.java (99%) rename src/test/java/{feature => features}/StepDefinition.java (97%) diff --git a/README.md b/README.md index 09356b1..75c301b 100644 --- a/README.md +++ b/README.md @@ -254,10 +254,10 @@ curl -X PUT "http://localhost:8080/random-users/1" \ PostgreSQL ``` -### Domain Service Ports +### Service Architecture -- `LocalUserService`: local persistence operations (save, read, delete) backed by PostgreSQL. -- `RemoteUserService`: external user source contract used by use cases. +- `UserService`: domain port for local persistence operations (save, read, delete) backed by PostgreSQL. Implemented by `UserServiceImpl` in the data layer. +- `RemoteUserService`: data-layer interface for external API adapters. Implemented by `DummyUserServiceImpl` and `RandomUserServiceImpl`. ### External Source Adapter diff --git a/mvnw.cmd b/mvnw.cmd index 92450f9..14aeffb 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -23,7 +23,7 @@ @REM @REM Optional ENV vars @REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_USERNAME/MVNW_PASSWORD - userDAO and password for downloading maven @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output @REM ---------------------------------------------------------------------------- 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/data/converters/UserConverter.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverter.java index 5bff94e..748dac6 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,32 +10,32 @@ @Service public class UserConverter { // Domain -> DAO - public User toDao(UserEntity entity) { - User user = new User(); - user.setId(entity.id()); - user.setGender(entity.gender()); - user.setFirstname(entity.firstname()); - user.setLastname(entity.lastname()); - user.setCivility(entity.civility()); - user.setEmail(entity.email()); - user.setPhone(entity.phone()); - user.setPicture(entity.picture()); - user.setNationality(entity.nat()); - return user; + public UserDAO toDao(UserEntity entity) { + UserDAO userDAO = new UserDAO(); + userDAO.setId(entity.id()); + userDAO.setGender(entity.gender()); + userDAO.setFirstname(entity.firstname()); + userDAO.setLastname(entity.lastname()); + userDAO.setCivility(entity.civility()); + userDAO.setEmail(entity.email()); + userDAO.setPhone(entity.phone()); + userDAO.setPicture(entity.picture()); + userDAO.setNationality(entity.nat()); + return userDAO; } // DAO -> Domain - public UserEntity toDomain(User user) { + public UserEntity toDomain(UserDAO userDAO) { return new UserEntity( - user.getId(), - user.getGender(), - user.getFirstname(), - user.getLastname(), - user.getCivility(), - user.getEmail(), - user.getPhone(), - user.getPicture(), - user.getNationality() + userDAO.getId(), + userDAO.getGender(), + userDAO.getFirstname(), + userDAO.getLastname(), + userDAO.getCivility(), + userDAO.getEmail(), + userDAO.getPhone(), + userDAO.getPicture(), + userDAO.getNationality() ); } 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 98% 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..5199914 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 } 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..c42e272 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,33 +1,48 @@ 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.api.RemoteUserService; 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.enums.UserSource; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.StreamSupport; @Service - -public class UserServiceImpl implements LocalUserService { +public class UserServiceImpl implements UserService { private final UserRepository userRepository; private final UserConverter userConverter; + private final Map remoteUserServices; - public UserServiceImpl(UserRepository userRepository, UserConverter userConverter) { + public UserServiceImpl( + UserRepository userRepository, + UserConverter userConverter, + List remoteUserServices + ) { this.userRepository = userRepository; this.userConverter = userConverter; + this.remoteUserServices = remoteUserServices.stream() + .collect(Collectors.toMap(RemoteUserService::getSource, Function.identity())); } @Override public List saveAll(List users) { - List daoUsers = users.stream().map(userConverter::toDao).toList(); - Iterable saved = userRepository.saveAll(daoUsers); + List userDAOs = users.stream().map(userConverter::toDao).toList(); + Iterable saved = userRepository.saveAll(userDAOs); return StreamSupport.stream(saved.spliterator(), false) .map(userConverter::toDomain) .toList(); @@ -41,8 +56,8 @@ public Optional getById(long id) { @Override public UserEntity save(UserEntity user) { - User savedUser = userRepository.save(userConverter.toDao(user)); - return userConverter.toDomain(savedUser); + UserDAO savedUserDAO = userRepository.save(userConverter.toDao(user)); + return userConverter.toDomain(savedUserDAO); } @Override @@ -51,9 +66,19 @@ public void deleteById(long id) { } @Override - public List filterUsers(UserFilter filter) { - return userRepository.findAll(UserSpecifications.byFilter(filter)).stream() - .map(userConverter::toDomain) - .toList(); + public Page filterUsers(UserFilter filter, Pageable pageable) { + return userRepository.findAll(UserSpecifications.byFilter(filter), pageable) + .map(userConverter::toDomain); + } + + @Override + public PaginatedUsers fetchAndSaveUsers(int page, int size, UserSource source) throws IOException { + RemoteUserService remoteService = remoteUserServices.get(source); + if (remoteService == null) { + throw new IllegalStateException("No remote service configured for source: " + source); + } + PaginatedUsers response = remoteService.fetchUsers(page, size); + saveAll(response.data()); + return response; } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/RemoteUserService.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RemoteUserService.java similarity index 83% rename from src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/RemoteUserService.java rename to src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RemoteUserService.java index 0945997..40ab8f5 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/RemoteUserService.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RemoteUserService.java @@ -1,4 +1,4 @@ -package com.xpeho.spring_boot_java_random_user.domain.services; +package com.xpeho.spring_boot_java_random_user.data.sources.api; import com.xpeho.spring_boot_java_random_user.domain.entities.PaginatedUsers; import com.xpeho.spring_boot_java_random_user.domain.enums.UserSource; diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/UserApiConfig.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/UserApiConfig.java new file mode 100644 index 0000000..58fa87f --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/UserApiConfig.java @@ -0,0 +1,67 @@ +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 jakarta.annotation.PreDestroy; +import okhttp3.OkHttpClient; +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 UserApiConfig { + + private OkHttpClient okHttpClient; + + /** + * Single shared OkHttpClient intentionally reused by both Retrofit instances (dummyUserRetrofit and randomUserRetrofit). + * Sharing a single client allows both APIs to benefit from a common connection pool, thread pool, + * and keep-alive settings, reducing resource consumption. + * If the two APIs ever require distinct timeouts or interceptors, separate clients should be created. + * The {@link jakarta.annotation.PreDestroy} hook ensures the client is shut down cleanly on application stop. + */ + @Bean + public OkHttpClient okHttpClient() { + okHttpClient = new OkHttpClient.Builder().build(); + return okHttpClient; + } + + @Bean(name = "dummyUserRetrofit") + public Retrofit dummyUserRetrofit(OkHttpClient okHttpClient, Environment env) { + return new Retrofit.Builder() + .baseUrl(env.getRequiredProperty("dummy.api.base-url")) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + } + + @Bean + public DummyUserApi dummyUserApi(@Qualifier("dummyUserRetrofit") Retrofit dummyUserRetrofit) { + return dummyUserRetrofit.create(DummyUserApi.class); + } + + @Bean(name = "randomUserRetrofit") + public Retrofit randomUserRetrofit(OkHttpClient okHttpClient, Environment env) { + return new Retrofit.Builder() + .baseUrl(env.getRequiredProperty("randomuser.api.base-url")) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + } + + @Bean + public RandomUserApi randomUserApi(@Qualifier("randomUserRetrofit") Retrofit randomUserRetrofit) { + return randomUserRetrofit.create(RandomUserApi.class); + } + + @PreDestroy + public void destroy() { + if (okHttpClient != null) { + okHttpClient.dispatcher().executorService().shutdown(); + okHttpClient.connectionPool().evictAll(); + } + } +} 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/api/dummy/DummyUserServiceImpl.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/dummy/DummyUserServiceImpl.java index a81423e..b250351 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/dummy/DummyUserServiceImpl.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/dummy/DummyUserServiceImpl.java @@ -6,7 +6,7 @@ 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.entities.UserEntity; -import com.xpeho.spring_boot_java_random_user.domain.services.RemoteUserService; +import com.xpeho.spring_boot_java_random_user.data.sources.api.RemoteUserService; import org.springframework.stereotype.Service; import retrofit2.Response; 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/randomuser/RandomUserApiConfig.java deleted file mode 100644 index 882f3ff..0000000 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/randomuser/RandomUserApiConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.xpeho.spring_boot_java_random_user.data.sources.api.randomuser; - -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 RandomUserApiConfig { - @Bean(name = "randomUserRetrofit") - public Retrofit randomUserRetrofit(Environment env) { - return new Retrofit.Builder() - .baseUrl(env.getRequiredProperty("randomuser.api.base-url")) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - } - - @Bean - public RandomUserApi randomUserApi(@Qualifier("randomUserRetrofit") Retrofit randomUserRetrofit) { - return randomUserRetrofit.create(RandomUserApi.class); - } -} - diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/randomuser/RandomUserServiceImpl.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/randomuser/RandomUserServiceImpl.java index b327df9..463a62f 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/randomuser/RandomUserServiceImpl.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/randomuser/RandomUserServiceImpl.java @@ -6,7 +6,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.RemoteUserService; +import com.xpeho.spring_boot_java_random_user.data.sources.api.RemoteUserService; import org.springframework.stereotype.Service; import retrofit2.Response; 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..fffc289 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..f5bb1d0 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/exceptions/UserNotFoundException.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/exceptions/UserNotFoundException.java index e53e627..bdf9f53 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/exceptions/UserNotFoundException.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/exceptions/UserNotFoundException.java @@ -2,6 +2,6 @@ public class UserNotFoundException extends RuntimeException { public UserNotFoundException(long id) { - super("User not found with id: " + id); + super("UserDAO not found with id: " + id); } } 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 deleted file mode 100644 index 8bb472e..0000000 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/LocalUserService.java +++ /dev/null @@ -1,20 +0,0 @@ -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; - -public interface LocalUserService { - List saveAll(List users); - - Optional getById(long id); - - 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/services/UserService.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/UserService.java new file mode 100644 index 0000000..a2a3c1f --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/UserService.java @@ -0,0 +1,28 @@ +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 com.xpeho.spring_boot_java_random_user.domain.enums.UserSource; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.io.IOException; +import java.util.List; +import java.util.Optional; + +public interface UserService { + + List saveAll(List users); + + Optional getById(long id); + + UserEntity save(UserEntity user); + + void deleteById(long id); + + Page filterUsers(UserFilter filter, Pageable pageable); + + PaginatedUsers fetchAndSaveUsers(int page, int size, UserSource source) throws IOException; +} + 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..79683b0 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,19 +1,18 @@ 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.springframework.stereotype.Service; @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) { + public UserEntity execute(UserEntity user) { UserEntity userToCreate = new UserEntity( null, user.gender(), @@ -25,7 +24,6 @@ public UserEntity execute(UserRequest user) { user.picture(), user.nat() ); - return userService.save(userToCreate); } } 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..c6bbbf8 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,13 @@ 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.springframework.stereotype.Service; @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..1922301 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,35 +2,21 @@ 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 com.xpeho.spring_boot_java_random_user.domain.services.UserService; import org.springframework.stereotype.Service; import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; @Service public class FetchAndSaveRandomUsersUseCase { - private final LocalUserService localUserService; - private final Map remoteUserServices; + private final UserService userService; - public FetchAndSaveRandomUsersUseCase(LocalUserService localUserService, List remoteUserServices) { - this.localUserService = localUserService; - this.remoteUserServices = remoteUserServices.stream() - .collect(Collectors.toMap(RemoteUserService::getSource, Function.identity())); + public FetchAndSaveRandomUsersUseCase(UserService userService) { + this.userService = userService; } public PaginatedUsers execute(int page, int size, UserSource source) throws IOException { - RemoteUserService remoteUserService = remoteUserServices.get(source); - if (remoteUserService == null) { - throw new IllegalStateException("No remote service configured for source: " + source); - } - PaginatedUsers response = remoteUserService.fetchUsers(page, size); - localUserService.saveAll(response.data()); - return response; + return userService.fetchAndSaveUsers(page, size, source); } } 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..4a6c40c 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 @@ -2,20 +2,20 @@ 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.Pageable; import org.springframework.stereotype.Service; -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 Page execute(UserFilter filter, Pageable pageable) { + return userService.filterUsers(filter, pageable); } } 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..c95ea00 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,14 @@ 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.springframework.stereotype.Service; @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..17e9523 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,33 +1,32 @@ 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.springframework.stereotype.Service; @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 newData) { UserEntity existingUser = userService.getById(id) .orElseThrow(() -> new UserNotFoundException(id)); UserEntity updatedUser = new UserEntity( existingUser.id(), - user.gender(), - user.firstname(), - user.lastname(), - user.civility(), - user.email(), - user.phone(), - user.picture(), - user.nat() + newData.gender(), + newData.firstname(), + newData.lastname(), + newData.civility(), + newData.email(), + newData.phone(), + newData.picture(), + newData.nat() ); return userService.save(updatedUser); 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..bd129e9 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,9 +1,9 @@ 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.UserRequest; 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; @@ -11,15 +11,13 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; +import org.springframework.data.domain.Page; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.List; - - @RequestMapping("/random-users") -@Tag(name = "User", description = "Endpoints for random user generation") +@Tag(name = "UserDAO", description = "Endpoints for random user generation") public interface UserController { @GetMapping("") @@ -55,7 +53,7 @@ ResponseEntity getRandomUsers( @Parameter(name = "id", description = "id of the requested user") } ) - @ApiResponse(responseCode = "200", description = "User successfully found and returned") + @ApiResponse(responseCode = "200", description = "UserDAO successfully found and returned") @ApiResponse(responseCode = "404", description = "The requested user does not exist") @ApiResponse(responseCode = "500", description = "Internal server error") ResponseEntity getUserById( @@ -69,10 +67,10 @@ ResponseEntity getUserById( 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 = "UserRequest", description = "changeable parameters") } ) - @ApiResponse(responseCode = "200", description = "User successfully modified and saved") + @ApiResponse(responseCode = "200", description = "UserDAO successfully modified and saved") @ApiResponse(responseCode = "404", description = "The requested user does not exist") @ApiResponse(responseCode = "500", description = "Internal server error") ResponseEntity updateRandomUser( @@ -87,10 +85,10 @@ ResponseEntity updateRandomUser( summary = "Create a user", description = "Creates a new user in the database.", parameters = { - @Parameter(name = "UserRequest", description = "User data to persist") + @Parameter(name = "UserRequest", description = "UserDAO data to persist") } ) - @ApiResponse(responseCode = "201", description = "User successfully created") + @ApiResponse(responseCode = "201", description = "UserDAO successfully created") @ApiResponse(responseCode = "500", description = "Internal server error") ResponseEntity createUser(@RequestBody UserRequest user); @@ -98,7 +96,7 @@ ResponseEntity updateRandomUser( @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 on gender, firstname, lastname, civility, email, phone and nationality. All filters are case-insensitive and support partial matching. Supports pagination via page and size parameters.", parameters = { @Parameter(name = "gender", description = "Filter by gender (MALE or FEMALE)"), @Parameter(name = "firstname", description = "Filter by firstname"), @@ -106,19 +104,23 @@ ResponseEntity updateRandomUser( @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") + @Parameter(name = "nat", description = "Filter by nationality"), + @Parameter(name = "page", description = "Page index (0-based)", example = "0"), + @Parameter(name = "size", description = "Page size", example = "20") } ) - @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, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size ); @DeleteMapping("/{id}") @@ -129,7 +131,7 @@ ResponseEntity> filterUsers( @Parameter(name = "id", description = "id of the requested user") } ) - @ApiResponse(responseCode = "204", description = "User successfully deleted") + @ApiResponse(responseCode = "204", description = "UserDAO successfully deleted") @ApiResponse(responseCode = "404", description = "The requested user does not exist") @ApiResponse(responseCode = "500", description = "Internal server error") void deleteUserById( diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/PaginatedRequest.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/PaginatedRequest.java new file mode 100644 index 0000000..a1cd851 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/PaginatedRequest.java @@ -0,0 +1,11 @@ +package com.xpeho.spring_boot_java_random_user.presentation.dto; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +public record PaginatedRequest(int page, int size) { + public Pageable toPageable() { + return PageRequest.of(page, size); + } +} + 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/UserRequest.java similarity index 76% 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/UserRequest.java index a923b54..5136d54 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/UserRequest.java @@ -1,4 +1,4 @@ -package com.xpeho.spring_boot_java_random_user.domain.entities; +package com.xpeho.spring_boot_java_random_user.presentation.dto; public record UserRequest( String gender, @@ -11,3 +11,4 @@ public record UserRequest( String nat ) { } + diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/exceptions/GlobalExceptionHandler.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/exceptions/GlobalExceptionHandler.java index 2429896..92d3f45 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/exceptions/GlobalExceptionHandler.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/exceptions/GlobalExceptionHandler.java @@ -40,7 +40,7 @@ public ResponseEntity handleMethodArgumentTypeMismatch(MethodArgu @ExceptionHandler(UserNotFoundException.class) public ResponseEntity handleUserNotFoundException(UserNotFoundException ex) { - logger.warn("User not found: {}", ex.getMessage()); + logger.warn("UserDAO not found: {}", ex.getMessage()); return buildErrorResponse("USER_NOT_FOUND", ex.getMessage(), HttpStatus.NOT_FOUND); } 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..9959b71 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,17 @@ 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.PaginatedRequest; +import com.xpeho.spring_boot_java_random_user.presentation.dto.UserRequest; import com.xpeho.spring_boot_java_random_user.presentation.dto.UserResponseDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -19,7 +21,6 @@ import org.springframework.web.bind.annotation.RestController; import java.io.IOException; -import java.util.List; @Validated @@ -73,7 +74,8 @@ public ResponseEntity getRandomUsers(int page, int size, UserSo @Override public ResponseEntity updateRandomUser(int id, UserRequest user) { try { - UserEntity savedUser = updateRandomUserUseCase.execute(id, user); + UserEntity userEntity = toEntity(user); + UserEntity savedUser = updateRandomUserUseCase.execute(id, userEntity); return ResponseEntity.ok(savedUser); } catch (UserNotFoundException e) { logUserNotFound(e); @@ -95,17 +97,20 @@ public ResponseEntity getUserById(int id) { @Override public ResponseEntity createUser(@RequestBody UserRequest user) { - UserEntity createdUser = createUserUseCase.execute(user); + UserEntity userEntity = toEntity(user); + UserEntity createdUser = createUserUseCase.execute(userEntity); return ResponseEntity.status(HttpStatus.CREATED).body(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, + int page, int size ) { UserFilter filter = new UserFilter(gender, firstname, lastname, civility, email, phone, nat); - List users = filterUsersUseCase.execute(filter); + PaginatedRequest pagination = new PaginatedRequest(page, size); + Page users = filterUsersUseCase.execute(filter, pagination.toPageable()); return ResponseEntity.ok(users); } @@ -118,6 +123,20 @@ public void deleteUserById(int id) { } } + private UserEntity toEntity(UserRequest user) { + return new UserEntity( + null, + user.gender(), + user.firstname(), + user.lastname(), + user.civility(), + user.email(), + user.phone(), + user.picture(), + user.nat() + ); + } + private void logUserNotFound(UserNotFoundException e) { logger.warn(USER_NOT_FOUND_LOG, e.getMessage()); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 62f00f1..464b1ac 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,8 +3,8 @@ spring.application.name=spring_boot_java_random_user # Security app.security.admin.username=${APP_SECURITY_ADMIN_USER} app.security.admin.password=${APP_SECURITY_ADMIN_PASSWORD} -app.security.user.username=${APP_SECURITY_USER} -app.security.user.password=${APP_SECURITY_PASSWORD} +app.security.userDAO.username=${APP_SECURITY_USER} +app.security.userDAO.password=${APP_SECURITY_PASSWORD} app.security.test.username=${APP_SECURITY_TEST_USER} app.security.test.password=${APP_SECURITY_TEST_PASSWORD} @@ -14,7 +14,7 @@ springdoc.swagger-ui.path=/api # URL for Dummy API dummy.api.base-url=https://dummyjson.com/ -# URL for random user API +# URL for random userDAO API randomuser.api.base-url=https://randomuser.me/api/ diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/SpringBootJavaRandomUserApplicationTests.java b/src/test/java/com/xpeho/spring_boot_java_random_user/SpringBootJavaRandomUserDAOApplicationTests.java similarity index 94% rename from src/test/java/com/xpeho/spring_boot_java_random_user/SpringBootJavaRandomUserApplicationTests.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/SpringBootJavaRandomUserDAOApplicationTests.java index 073dbe8..c37da02 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/SpringBootJavaRandomUserApplicationTests.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/SpringBootJavaRandomUserDAOApplicationTests.java @@ -13,7 +13,7 @@ @SpringBootTest @ActiveProfiles("test") -class SpringBootJavaRandomUserApplicationTests { +class SpringBootJavaRandomUserDAOApplicationTests { @Autowired ApplicationContext applicationContext; 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/UserDAOConverterTest.java similarity index 98% rename from src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserDAOConverterTest.java index 8e97b5c..21c0b76 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/UserDAOConverterTest.java @@ -4,14 +4,14 @@ import com.xpeho.spring_boot_java_random_user.data.models.api.randomuser.RandomUserNameDTO; 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.data.models.database.UserDAO; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; -import com.xpeho.spring_boot_java_random_user.data.models.database.User; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; -class UserConverterTest { +class UserDAOConverterTest { private final UserConverter converter = new UserConverter(); @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/models/api/dummy/DummyUserResponseTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/dummy/DummyUserDAOResponseTest.java similarity index 96% rename from src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/dummy/DummyUserResponseTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/dummy/DummyUserDAOResponseTest.java index 3eda125..616e208 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/dummy/DummyUserResponseTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/dummy/DummyUserDAOResponseTest.java @@ -5,7 +5,7 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.*; -class DummyUserResponseTest { +class DummyUserDAOResponseTest { @Test @DisplayName("Should store and return users list") void shouldStoreAndReturnUsersList() { diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/dummy/DummyUserResultDTOTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/dummy/DummyUserDTOResultDAOTest.java similarity index 97% rename from src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/dummy/DummyUserResultDTOTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/dummy/DummyUserDTOResultDAOTest.java index a4eff1d..bb8b8e9 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/dummy/DummyUserResultDTOTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/dummy/DummyUserDTOResultDAOTest.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; -class DummyUserResultDTOTest { +class DummyUserDTOResultDAOTest { @Test @DisplayName("Should store and return all user fields") void shouldStoreAndReturnAllUserFields() { diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserNameDTOTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserDTONameDAOTest.java similarity index 96% rename from src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserNameDTOTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserDTONameDAOTest.java index 8ae05d8..d21c936 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserNameDTOTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserDTONameDAOTest.java @@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -class RandomUserNameDTOTest { +class RandomUserDTONameDAOTest { @Test @DisplayName("Should store and return all name fields") void shouldStoreAndReturnAllNameFields() { diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserPictureDTOTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserDTOPictureDAOTest.java similarity index 95% rename from src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserPictureDTOTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserDTOPictureDAOTest.java index 6c47d24..0800f82 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserPictureDTOTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserDTOPictureDAOTest.java @@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -class RandomUserPictureDTOTest { +class RandomUserDTOPictureDAOTest { @Test @DisplayName("Should store and return medium picture URL") void shouldStoreAndReturnMediumPictureUrl() { diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserResponseDTOTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserDTOResponseDAOTest.java similarity index 96% rename from src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserResponseDTOTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserDTOResponseDAOTest.java index 0cb1af7..60f9880 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserResponseDTOTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserDTOResponseDAOTest.java @@ -8,7 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -class RandomUserResponseDTOTest { +class RandomUserDTOResponseDAOTest { @Test @DisplayName("Should store and return results list") void shouldStoreAndReturnResultsList() { diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserResultDTOTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserDTOResultDAOTest.java similarity index 97% rename from src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserResultDTOTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserDTOResultDAOTest.java index fee3333..509f61c 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserResultDTOTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/randomuser/RandomUserDTOResultDAOTest.java @@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -class RandomUserResultDTOTest { +class RandomUserDTOResultDAOTest { @Test @DisplayName("Should store and return all user fields") void shouldStoreAndReturnAllUserFields() { 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/UserDAOServiceImplTest.java similarity index 69% rename from src/test/java/com/xpeho/spring_boot_java_random_user/data/services/UserServiceImplTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/data/services/UserDAOServiceImplTest.java index 942d99e..d8a0f10 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/UserDAOServiceImplTest.java @@ -1,7 +1,7 @@ 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.UserEntity; import com.xpeho.spring_boot_java_random_user.domain.entities.UserFilter; @@ -9,6 +9,10 @@ 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.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import java.util.Collections; @@ -17,11 +21,13 @@ 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.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -class UserServiceImplTest { +class UserDAOServiceImplTest { private UserRepository userRepository; private UserConverter userConverter; private UserServiceImpl userService; @@ -30,13 +36,13 @@ class UserServiceImplTest { void setUp() { userRepository = mock(UserRepository.class); userConverter = mock(UserConverter.class); - userService = new UserServiceImpl(userRepository, userConverter); + userService = new UserServiceImpl(userRepository, userConverter, List.of()); } @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"); @@ -69,10 +75,10 @@ void shouldReturnEmptyOptionalWhenIdDoesNotExist() { 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"); @@ -94,22 +100,23 @@ void shouldSaveMappedUserAndReturnMappedDomainEntity() { @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); + Pageable pageable = PageRequest.of(0, 20); - 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 daoPage = new PageImpl<>(List.of(dao), pageable, 1); - when(userRepository.findAll(org.mockito.ArgumentMatchers.>any())) - .thenReturn(List.of(dao)); + when(userRepository.findAll(any(Specification.class), eq(pageable))).thenReturn(daoPage); when(userConverter.toDomain(dao)).thenReturn(expected); - List result = userService.filterUsers(filter); + Page result = userService.filterUsers(filter, pageable); - assertEquals(1, result.size()); - assertEquals(expected, result.get(0)); - verify(userRepository).findAll(org.mockito.ArgumentMatchers.>any()); + assertEquals(1, result.getTotalElements()); + assertEquals(expected, result.getContent().get(0)); + verify(userRepository).findAll(any(Specification.class), eq(pageable)); verify(userConverter).toDomain(dao); } @@ -117,35 +124,37 @@ void shouldFilterUsersWithGender() { @DisplayName("Should call repository when gender filter is null") void shouldFilterUsersWithNullGender() { UserFilter filter = new UserFilter(null, null, "Smith", null, null, null, null); + Pageable pageable = PageRequest.of(0, 20); - 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 daoPage = new PageImpl<>(List.of(dao), pageable, 1); - when(userRepository.findAll(org.mockito.ArgumentMatchers.>any())) - .thenReturn(List.of(dao)); + when(userRepository.findAll(any(Specification.class), eq(pageable))).thenReturn(daoPage); when(userConverter.toDomain(dao)).thenReturn(expected); - List result = userService.filterUsers(filter); + Page result = userService.filterUsers(filter, pageable); - assertEquals(1, result.size()); - assertEquals(expected, result.get(0)); - verify(userRepository).findAll(org.mockito.ArgumentMatchers.>any()); + assertEquals(1, result.getTotalElements()); + assertEquals(expected, result.getContent().get(0)); + verify(userRepository).findAll(any(Specification.class), eq(pageable)); } @Test - @DisplayName("Should return empty list when no users match filter") + @DisplayName("Should return empty page when no users match filter") void shouldReturnEmptyListWhenNoUsersMatchFilter() { UserFilter filter = new UserFilter(Gender.FEMALE, "Unknown", null, null, null, null, null); + Pageable pageable = PageRequest.of(0, 20); + Page emptyPage = new PageImpl<>(Collections.emptyList(), pageable, 0); - when(userRepository.findAll(org.mockito.ArgumentMatchers.>any())) - .thenReturn(Collections.emptyList()); + when(userRepository.findAll(any(Specification.class), eq(pageable))).thenReturn(emptyPage); - List result = userService.filterUsers(filter); + Page result = userService.filterUsers(filter, pageable); assertTrue(result.isEmpty()); - verify(userRepository).findAll(org.mockito.ArgumentMatchers.>any()); + verify(userRepository).findAll(any(Specification.class), eq(pageable)); } } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/DummyUserServiceImplTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/DummyUserDAOServiceImplTest.java similarity index 99% rename from src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/DummyUserServiceImplTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/DummyUserDAOServiceImplTest.java index bb357df..cdd4aa1 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/DummyUserServiceImplTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/DummyUserDAOServiceImplTest.java @@ -20,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -class DummyUserServiceImplTest { +class DummyUserDAOServiceImplTest { private DummyUserApi dummyUserApi; private UserConverter userConverter; private DummyUserServiceImpl service; diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserServiceImplTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserDAOServiceImplTest.java similarity index 99% rename from src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserServiceImplTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserDAOServiceImplTest.java index 6f75460..c5b0053 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserServiceImplTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserDAOServiceImplTest.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class RandomUserServiceImplTest { +class RandomUserDAOServiceImplTest { private RandomUserApi randomUserApi; private UserConverter userConverter; private RandomUserServiceImpl service; 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/UserDAOSpecificationsTest.java similarity index 92% rename from src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserSpecificationsTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserDAOSpecificationsTest.java index cbe3095..ab0d468 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/UserDAOSpecificationsTest.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; @@ -10,8 +10,8 @@ import static org.mockito.Mockito.*; -class UserSpecificationsTest { - private Root user; +class UserDAOSpecificationsTest { + private Root user; private CriteriaQuery query; private CriteriaBuilder cb; private Expression lowerExpr; @@ -38,7 +38,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/CreateUserDAOUseCaseTest.java similarity index 75% rename from src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/CreateUserUseCaseTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/CreateUserDAOUseCaseTest.java index 4a99dd5..8bddb24 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/CreateUserDAOUseCaseTest.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; @@ -12,21 +11,21 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -class CreateUserUseCaseTest { - private LocalUserService userService; +class CreateUserDAOUseCaseTest { + 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") void shouldCreateUserWithoutKeepingInputId() { - UserRequest payload = new UserRequest( - "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US" + UserEntity payload = new UserEntity( + 99L, "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" 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/DeleteUserDAOByIdUseCaseTest.java similarity index 88% rename from src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/DeleteUserByIdUseCaseTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/DeleteUserDAOByIdUseCaseTest.java index 73d8000..3ef8fb7 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/DeleteUserDAOByIdUseCaseTest.java @@ -1,19 +1,19 @@ 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; import static org.mockito.Mockito.*; -class DeleteUserByIdUseCaseTest { - private LocalUserService userService; +class DeleteUserDAOByIdUseCaseTest { + 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..9181630 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,8 +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.RemoteUserService; +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; @@ -16,19 +15,13 @@ import static org.mockito.Mockito.*; class FetchAndSaveRandomUsersUseCaseTest { - private LocalUserService userService; - private RemoteUserService dummyRemoteUserService; - private RemoteUserService randomRemoteUserService; + private UserService userService; private FetchAndSaveRandomUsersUseCase useCase; @BeforeEach void setUp() { - userService = mock(LocalUserService.class); - dummyRemoteUserService = mock(RemoteUserService.class); - randomRemoteUserService = mock(RemoteUserService.class); - when(dummyRemoteUserService.getSource()).thenReturn(UserSource.DUMMY); - when(randomRemoteUserService.getSource()).thenReturn(UserSource.RANDOM_USER); - useCase = new FetchAndSaveRandomUsersUseCase(userService, List.of(dummyRemoteUserService, randomRemoteUserService)); + userService = mock(UserService.class); + useCase = new FetchAndSaveRandomUsersUseCase(userService); } @Test @@ -36,25 +29,20 @@ void setUp() { void shouldFetchUsersFromApiAndSaveThem() throws IOException { int page = 1; int size = 10; - int total = 50; - int skip = 0; - int limit = 10; List fetched = List.of(new UserEntity( 1L, "male", "John", "Doe", "Mr", "john@doe.com", "1234", "pic.jpg", "FR" )); - PaginatedUsers paginatedUsers = new PaginatedUsers(fetched, total, skip, limit); - when(dummyRemoteUserService.fetchUsers(page, size)).thenReturn(paginatedUsers); - when(userService.saveAll(fetched)).thenReturn(fetched); + PaginatedUsers paginatedUsers = new PaginatedUsers(fetched, 50, 0, 10); + when(userService.fetchAndSaveUsers(page, size, UserSource.DUMMY)).thenReturn(paginatedUsers); PaginatedUsers result = useCase.execute(page, size, UserSource.DUMMY); assertEquals(paginatedUsers, result); assertEquals(fetched, result.data()); - assertEquals(total, result.total()); - assertEquals(skip, result.skip()); - assertEquals(limit, result.limit()); - verify(dummyRemoteUserService).fetchUsers(page, size); - verify(userService).saveAll(fetched); + assertEquals(50, result.total()); + assertEquals(0, result.skip()); + assertEquals(10, result.limit()); + verify(userService).fetchAndSaveUsers(page, size, UserSource.DUMMY); } @Test @@ -62,7 +50,8 @@ void shouldFetchUsersFromApiAndSaveThem() throws IOException { void shouldPropagateIOExceptionWhenApiFails() throws IOException { int page = 1; int size = 10; - when(randomRemoteUserService.fetchUsers(page, size)).thenThrow(new IOException("API error")); + when(userService.fetchAndSaveUsers(page, size, UserSource.RANDOM_USER)).thenThrow(new IOException("API error")); + IOException ex = assertThrows(IOException.class, () -> useCase.execute(page, size, UserSource.RANDOM_USER)); assertEquals("API error", ex.getMessage()); } @@ -72,15 +61,13 @@ void shouldPropagateIOExceptionWhenApiFails() throws IOException { void shouldHandleEmptyUsersListGracefully() throws IOException { int page = 1; int size = 10; - int total = 0; - PaginatedUsers paginatedUsers = new PaginatedUsers(List.of(), total, 0, 10); - when(dummyRemoteUserService.fetchUsers(page, size)).thenReturn(paginatedUsers); - when(userService.saveAll(List.of())).thenReturn(List.of()); + PaginatedUsers paginatedUsers = new PaginatedUsers(List.of(), 0, 0, 10); + when(userService.fetchAndSaveUsers(page, size, UserSource.DUMMY)).thenReturn(paginatedUsers); PaginatedUsers result = useCase.execute(page, size, UserSource.DUMMY); assertEquals(paginatedUsers, result); assertTrue(result.data().isEmpty()); - assertEquals(total, result.total()); + assertEquals(0, result.total()); } } 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..1470e54 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 @@ -3,10 +3,14 @@ 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; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import java.util.Collections; import java.util.List; @@ -15,55 +19,60 @@ import static org.mockito.Mockito.*; class FilterUsersUseCaseTest { - private LocalUserService userService; + private UserService userService; private FilterUsersUseCase useCase; + private Pageable pageable; @BeforeEach void setUp() { - userService = mock(LocalUserService.class); + userService = mock(UserService.class); useCase = new FilterUsersUseCase(userService); + pageable = PageRequest.of(0, 20); } @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( + List content = List.of( new UserEntity(1L, "male", "John", "Doe", "Mr", "john@example.com", "0600000000", "http://pic.jpg", "FR") ); - when(userService.filterUsers(filter)).thenReturn(expected); + Page expected = new PageImpl<>(content, pageable, 1); + when(userService.filterUsers(filter, pageable)).thenReturn(expected); - List result = useCase.execute(filter); + Page result = useCase.execute(filter, pageable); assertEquals(expected, result); - verify(userService).filterUsers(filter); + verify(userService).filterUsers(filter, pageable); } @Test - @DisplayName("Should return empty list when no users match the filter") + @DisplayName("Should return empty page 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()); + Page empty = new PageImpl<>(Collections.emptyList(), pageable, 0); + when(userService.filterUsers(filter, pageable)).thenReturn(empty); - List result = useCase.execute(filter); + Page result = useCase.execute(filter, pageable); assertTrue(result.isEmpty()); - verify(userService).filterUsers(filter); + verify(userService).filterUsers(filter, pageable); } @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 content = List.of( new UserEntity(5L, "female", "Alice", "Smith", "Ms", "alice@example.com", "0611111111", "http://pic2.jpg", "US") ); - when(userService.filterUsers(filter)).thenReturn(expected); + Page expected = new PageImpl<>(content, pageable, 1); + when(userService.filterUsers(filter, pageable)).thenReturn(expected); - List result = useCase.execute(filter); + Page result = useCase.execute(filter, pageable); assertEquals(expected, result); - verify(userService, times(1)).filterUsers(filter); + verify(userService, times(1)).filterUsers(filter, pageable); 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/GetUserDAOByIdUseCaseTest.java similarity index 92% rename from src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCaseTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserDAOByIdUseCaseTest.java index cbbf0c1..854ee42 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/GetUserDAOByIdUseCaseTest.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; @@ -12,13 +12,13 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -class GetUserByIdUseCaseTest { - private LocalUserService userService; +class GetUserDAOByIdUseCaseTest { + 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/UpdateRandomUserDAOUseCaseTest.java similarity index 75% rename from src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserUseCaseTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserDAOUseCaseTest.java index 1d8a5a8..bd1d776 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/UpdateRandomUserDAOUseCaseTest.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; @@ -18,13 +17,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -class UpdateRandomUserUseCaseTest { - private LocalUserService userService; +class UpdateRandomUserDAOUseCaseTest { + private UserService userService; private UpdateRandomUserUseCase useCase; @BeforeEach void setUp() { - userService = mock(LocalUserService.class); + userService = mock(UserService.class); useCase = new UpdateRandomUserUseCase(userService); } @@ -34,8 +33,8 @@ 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 newData = 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" @@ -46,7 +45,7 @@ void shouldUpdateExistingUserAndPreserveItsId() { 42L, "female", "Alice", "Smith", "Mrs", "alice@smith.com", "5678", "new-pic.jpg", "US" ))).thenReturn(savedUser); - UserEntity result = useCase.execute(42, request); + UserEntity result = useCase.execute(42, newData); assertEquals(savedUser, result); verify(userService).getById(42); @@ -58,18 +57,18 @@ void shouldUpdateExistingUserAndPreserveItsId() { @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 newData = 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, newData) ); - assertEquals("User not found with id: 99", exception.getMessage()); + assertEquals("UserDAO not found with id: 99", exception.getMessage()); verify(userService).getById(99); verify(userService, never()).save(any(UserEntity.class)); } 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/UserDAOGetByIdContainerTest.java similarity index 87% rename from src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserGetByIdContainerTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserDAOGetByIdContainerTest.java index 1468bd9..a4e927a 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/UserDAOGetByIdContainerTest.java @@ -1,6 +1,6 @@ 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 org.junit.jupiter.api.BeforeEach; @@ -37,7 +37,7 @@ "logging.level.com.zaxxer.hikari.pool.PoolBase=ERROR" } ) -class UserGetByIdContainerTest { +class UserDAOGetByIdContainerTest { private static final String TEST_USERNAME = "testuser"; private static final String TEST_PASSWORD = "testpass"; @@ -60,17 +60,17 @@ void setUp() { @Test @DisplayName("GET /random-users/{id} should return 200 with persisted user") void shouldReturnUserByIdWhenUserExists() { - User user = new User(); - user.setGender("female"); - user.setFirstname("Jane"); - user.setLastname("Doe"); - user.setCivility("Ms"); - user.setEmail("jane.doe@example.com"); - user.setPhone("0600000000"); - user.setPicture("https://example.com/jane.jpg"); - user.setNationality("FR"); + UserDAO userDAO = new UserDAO(); + userDAO.setGender("female"); + userDAO.setFirstname("Jane"); + userDAO.setLastname("Doe"); + userDAO.setCivility("Ms"); + userDAO.setEmail("jane.doe@example.com"); + userDAO.setPhone("0600000000"); + userDAO.setPicture("https://example.com/jane.jpg"); + userDAO.setNationality("FR"); - User saved = userRepository.saveAndFlush(user); + UserDAO saved = userRepository.saveAndFlush(userDAO); ResponseEntity response = restTemplate.withBasicAuth(TEST_USERNAME, TEST_PASSWORD).getForEntity( "/random-users/{id}", 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/UserDAOHandlerTest.java similarity index 78% rename from src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserHandlerTest.java rename to src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserDAOHandlerTest.java index 3ff8b59..6f8c19d 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/UserDAOHandlerTest.java @@ -3,16 +3,19 @@ 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.dto.UserRequest; import com.xpeho.spring_boot_java_random_user.presentation.handlers.UserHandler; import com.xpeho.spring_boot_java_random_user.presentation.dto.UserResponseDTO; 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.PageRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -22,7 +25,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -class UserHandlerTest { +class UserDAOHandlerTest { private FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase; private UpdateRandomUserUseCase updateRandomUserUseCase; @@ -109,40 +112,43 @@ void shouldReturnNotFoundWhenGetUserByIdFails() { void shouldReturnOkWhenUpdateRandomUserSucceeds() { UserRequest request = new UserRequest("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); + UserEntity expectedEntity = new UserEntity(null, "female", "Jane", "Doe", "Ms", "jane@example.com", "0622222222", "jane.jpg", "FR"); + when(updateRandomUserUseCase.execute(7, expectedEntity)).thenReturn(updated); ResponseEntity response = userHandler.updateRandomUser(7, request); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(updated, response.getBody()); - verify(updateRandomUserUseCase, times(1)).execute(7, request); + verify(updateRandomUserUseCase, times(1)).execute(7, expectedEntity); } @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)); + UserEntity expectedEntity = new UserEntity(null, "male", "Bob", "Brown", "Mr", "bob@example.com", "0633333333", "bob.jpg", "DE"); + when(updateRandomUserUseCase.execute(123, expectedEntity)).thenThrow(new UserNotFoundException(123)); ResponseEntity response = userHandler.updateRandomUser(123, request); assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); assertNull(response.getBody()); - verify(updateRandomUserUseCase, times(1)).execute(123, request); + verify(updateRandomUserUseCase, times(1)).execute(123, expectedEntity); } @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"); + UserEntity expectedEntity = 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(expectedEntity)).thenReturn(created); ResponseEntity response = userHandler.createUser(request); assertEquals(HttpStatus.CREATED, response.getStatusCode()); assertEquals(created, response.getBody()); - verify(createUserUseCase, times(1)).execute(request); + verify(createUserUseCase, times(1)).execute(expectedEntity); } @Test @@ -167,34 +173,36 @@ void shouldLogWarningWhenDeleteUserByIdFails() { } @Test - @DisplayName("Should return 200 and filtered users when filterUsers succeeds") + @DisplayName("Should return 200 and filtered page 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); + Page page = new PageImpl<>(users, PageRequest.of(0, 20), 1); + when(filterUsersUseCase.execute(filter, PageRequest.of(0, 20))).thenReturn(page); - ResponseEntity> response = userHandler.filterUsers(Gender.MALE, null, null, null, null, null, "FR"); + ResponseEntity> response = userHandler.filterUsers(Gender.MALE, null, null, null, null, null, "FR", 0, 20); 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().getTotalElements()); + assertEquals(users, response.getBody().getContent()); + verify(filterUsersUseCase, times(1)).execute(filter, PageRequest.of(0, 20)); } @Test - @DisplayName("Should return 200 and empty list when no users match filter") + @DisplayName("Should return 200 and empty page 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()); + Page emptyPage = new PageImpl<>(List.of(), PageRequest.of(0, 20), 0); + when(filterUsersUseCase.execute(filter, PageRequest.of(0, 20))).thenReturn(emptyPage); - ResponseEntity> response = userHandler.filterUsers(null, "NonExistent", null, null, null, null, null); + ResponseEntity> response = userHandler.filterUsers(null, "NonExistent", null, null, null, null, null, 0, 20); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); assertTrue(response.getBody().isEmpty()); - verify(filterUsersUseCase, times(1)).execute(filter); + verify(filterUsersUseCase, times(1)).execute(filter, PageRequest.of(0, 20)); } } 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 66% rename from src/test/java/feature/CucumberTypeConfig.java rename to src/test/java/features/CucumberTypeConfig.java index c89ab6d..85a34d8 100644 --- a/src/test/java/feature/CucumberTypeConfig.java +++ b/src/test/java/features/CucumberTypeConfig.java @@ -1,25 +1,25 @@ -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.UserRequest; import io.cucumber.java.DataTableType; import java.util.List; import java.util.Map; /** - * Cucumber converters — transforment automatiquement les données - * des fichiers .feature en objets Java typés. + * Cucumber converters — automatically transform data + * from .feature files into typed Java objects. */ public class CucumberTypeConfig { /** - * Représente une ligne de vérification field/expected dans une DataTable. + * Represents a field/expected verification row in a DataTable. */ public record FieldAssertion(String field, String expected) {} /** - * Convertit chaque ligne de la DataTable (2 colonnes sans en-tête) - * en un objet FieldAssertion. + * Converts each DataTable row (2 columns without header) + * into a FieldAssertion object. */ @DataTableType public FieldAssertion fieldAssertion(List row) { @@ -33,8 +33,8 @@ public FieldAssertion fieldAssertion(List row) { } /** - * Convertit une ligne de DataTable (Map) en UserRequest. - * Utilisable dans les .feature avec des tables à en-têtes. + * Converts a DataTable row (Map) into a UserRequest. + * Usable in .feature files with header-based tables. */ @DataTableType public UserRequest userRequest(Map row) { 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 97% rename from src/test/java/feature/StepDefinition.java rename to src/test/java/features/StepDefinition.java index afb3443..6a6303a 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.UserRequest; +import features.CucumberTypeConfig.FieldAssertion; import io.cucumber.java.en.And; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties index 45e847f..5c8b097 100644 --- a/src/test/resources/application-test.properties +++ b/src/test/resources/application-test.properties @@ -4,8 +4,8 @@ spring.datasource.username=${H2_USERNAME:myusername} spring.datasource.password=${H2_PASSWORD:mypassword} app.security.admin.username=testadmin app.security.admin.password=testadminpass -app.security.user.username=testuser -app.security.user.password=testpass +app.security.userDAO.username=testuser +app.security.userDAO.password=testpass app.security.test.username=testviewer app.security.test.password=testviewerpass spring.sql.init.mode=never