From 6ba4df2b6791768277227d1b7c26a955681e6023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Leb=C3=A8gue?= Date: Fri, 27 Feb 2026 10:38:03 +0100 Subject: [PATCH 01/11] feat(getRandomUser): endpoint get Random user --- README.md | 10 ++--- docker-compose.yaml | 26 +++++++++++ pom.xml | 6 ++- .../data/converters/UserConverter.java | 21 +++++++++ .../data/models/RandomUserInfoDAO.java | 8 ++++ .../data/models/RandomUserNameDAO.java | 7 +++ .../data/models/RandomUserPictureDAO.java | 5 +++ .../data/models/RandomUserResponseDAO.java | 6 +++ .../data/models/RandomUserResultDAO.java | 10 +++++ .../data/repositories/UserRepository.java | 7 +++ .../data/services/UserServiceImpl.java | 24 ++++++++++ .../data/sources/RandomUserApi.java | 11 +++++ .../sources/RandomUserRetrofitClient.java | 27 ++++++++++++ .../domain/entities/UserEntity.java | 36 +++++++++++++++ .../domain/services/UserService.java | 8 ++++ .../FetchAndSaveRandomUsersUseCase.java | 44 +++++++++++++++++++ .../controllers/UserController.java | 35 +++++++++++++++ .../presentation/handlers/UserHandler.java | 29 ++++++++++++ .../resources/application-local.properties | 10 +++++ 19 files changed, 324 insertions(+), 6 deletions(-) create mode 100644 docker-compose.yaml create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverter.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserInfoDAO.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserNameDAO.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserPictureDAO.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResponseDAO.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResultDAO.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/repositories/UserRepository.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/services/UserServiceImpl.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserApi.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserRetrofitClient.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserEntity.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/domain/usecases/FetchAndSaveRandomUsersUseCase.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/presentation/controllers/UserController.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/presentation/handlers/UserHandler.java create mode 100644 src/main/resources/application-local.properties diff --git a/README.md b/README.md index 7e0284b..79267ed 100644 --- a/README.md +++ b/README.md @@ -231,11 +231,11 @@ This project consumes the public **Random User Generator** API: - [x] [Add Sonarqube in the project](https://github.com/XPEHO/spring_boot_java_random_user/issues/2) - [x] [Add PostgreSQL database with docker](https://github.com/XPEHO/spring_boot_java_random_user/issues/6) -- [ ] [Add this endpoint GET /user/random](https://github.com/XPEHO/spring_boot_java_random_user/issues/5) -- [ ] [Add this endpoint GET /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/8) -- [ ] [Add this endpoint PUT /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/9) -- [ ] [Add this endpoint DELETE /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/10) -- [ ] [Add this endpoint POST /user](https://github.com/XPEHO/spring_boot_java_random_user/issues/11) +- [X] [Add this endpoint get /user/random](https://github.com/XPEHO/spring_boot_java_random_user/issues/5) +- [ ] [Add this endpoint get /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/8) +- [ ] [Add this endpoint put /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/9) +- [ ] [Add this endpoint delete /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/10) +- [ ] [Add this endpoint post /user](https://github.com/XPEHO/spring_boot_java_random_user/issues/11) --- diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..7ec4aaa --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,26 @@ +services: + postgres: + image: postgres:15-alpine + container_name: xpeho_postgres + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: xpeho_db + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql + # INIT DATA + # - ./src/main/resources/data.sql:/docker-entrypoint-initdb.d/02-data.sql + networks: + - xpeho_network + +volumes: + postgres_data: + driver: local + +networks: + xpeho_network: + driver: bridge + diff --git a/pom.xml b/pom.xml index 20b9212..89f4820 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,11 @@ org.springframework.boot spring-boot-starter-actuator + + com.squareup.retrofit2 + converter-gson + 2.9.0 + org.springframework.boot spring-boot-starter-data-jdbc @@ -50,7 +55,6 @@ org.springframework.boot spring-boot-starter-webmvc - org.springdoc springdoc-openapi-starter-webmvc-ui 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 new file mode 100644 index 0000000..6112c2b --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverter.java @@ -0,0 +1,21 @@ +package com.xpeho.spring_boot_java_random_user.data.converters; + +import com.xpeho.spring_boot_java_random_user.data.models.RandomUserResultDAO; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import org.springframework.stereotype.Service; + +@Service +public class UserConverter { + public UserEntity modelToEntity(RandomUserResultDAO model) { + UserEntity entity = new UserEntity(); + entity.setGender(model.gender); + entity.setFirstname(model.name.first); + entity.setLastname(model.name.last); + entity.setCivility(model.name.title); + entity.setEmail(model.email); + entity.setPhone(model.phone); + entity.setPicture(model.picture.medium); + entity.setNat(model.nat); + return entity; + } +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserInfoDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserInfoDAO.java new file mode 100644 index 0000000..f1ffe1a --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserInfoDAO.java @@ -0,0 +1,8 @@ +package com.xpeho.spring_boot_java_random_user.data.models; + +public class RandomUserInfoDAO { + public String seed; + public int results; + public int page; + public String version; +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserNameDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserNameDAO.java new file mode 100644 index 0000000..a3298ad --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserNameDAO.java @@ -0,0 +1,7 @@ +package com.xpeho.spring_boot_java_random_user.data.models; + +public class RandomUserNameDAO { + public String title; + public String first; + public String last; +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserPictureDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserPictureDAO.java new file mode 100644 index 0000000..514da2d --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserPictureDAO.java @@ -0,0 +1,5 @@ +package com.xpeho.spring_boot_java_random_user.data.models; + +public class RandomUserPictureDAO { + public String medium; +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResponseDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResponseDAO.java new file mode 100644 index 0000000..19e70a3 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResponseDAO.java @@ -0,0 +1,6 @@ +package com.xpeho.spring_boot_java_random_user.data.models; + +public class RandomUserResponseDAO { + public RandomUserResultDAO[] results; + public RandomUserInfoDAO info; +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResultDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResultDAO.java new file mode 100644 index 0000000..5db847b --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResultDAO.java @@ -0,0 +1,10 @@ +package com.xpeho.spring_boot_java_random_user.data.models; + +public class RandomUserResultDAO { + public String gender; + public RandomUserNameDAO name; + public String email; + public String phone; + public RandomUserPictureDAO picture; + public String nat; +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/repositories/UserRepository.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/repositories/UserRepository.java new file mode 100644 index 0000000..6357784 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/repositories/UserRepository.java @@ -0,0 +1,7 @@ +package com.xpeho.spring_boot_java_random_user.data.repositories; + +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { +} 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 new file mode 100644 index 0000000..47374ed --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/services/UserServiceImpl.java @@ -0,0 +1,24 @@ +package com.xpeho.spring_boot_java_random_user.data.services; + +import com.xpeho.spring_boot_java_random_user.data.repositories.UserRepository; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + + public UserServiceImpl(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public List saveAll(List users) { + Iterable saved = userRepository.saveAll(users); + return saved instanceof List ? (List) saved : + java.util.stream.StreamSupport.stream(saved.spliterator(), false).toList(); + } +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserApi.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserApi.java new file mode 100644 index 0000000..f0eb6f3 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserApi.java @@ -0,0 +1,11 @@ +package com.xpeho.spring_boot_java_random_user.data.sources; + +import com.xpeho.spring_boot_java_random_user.data.models.RandomUserResponseDAO; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Query; + +public interface RandomUserApi { + @GET("/api/") + Call getRandomUsers(@Query("results") int results); +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserRetrofitClient.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserRetrofitClient.java new file mode 100644 index 0000000..98591e6 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserRetrofitClient.java @@ -0,0 +1,27 @@ +package com.xpeho.spring_boot_java_random_user.data.sources; + +import okhttp3.OkHttpClient; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +@Component +public class RandomUserRetrofitClient { + private final RandomUserApi apiInstance; + + public RandomUserRetrofitClient(Environment env) { + String baseUrl = env.getProperty("randomuser.api.base-url", "https://randomuser.me/"); + OkHttpClient client = new OkHttpClient.Builder().build(); + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(GsonConverterFactory.create()) + .client(client) + .build(); + this.apiInstance = retrofit.create(RandomUserApi.class); + } + + public RandomUserApi getApi() { + return apiInstance; + } +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserEntity.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserEntity.java new file mode 100644 index 0000000..ebe1a60 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserEntity.java @@ -0,0 +1,36 @@ +package com.xpeho.spring_boot_java_random_user.domain.entities; + +import org.springframework.data.annotation.Id; + +public class UserEntity { + @Id + private Long id; + private String gender; + private String firstname; + private String lastname; + private String civility; + private String email; + private String phone; + private String picture; + private String nat; + + // Getters and setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getGender() { return gender; } + public void setGender(String gender) { this.gender = gender; } + public String getFirstname() { return firstname; } + public void setFirstname(String firstname) { this.firstname = firstname; } + public String getLastname() { return lastname; } + public void setLastname(String lastname) { this.lastname = lastname; } + public String getCivility() { return civility; } + public void setCivility(String civility) { this.civility = civility; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } + public String getPicture() { return picture; } + public void setPicture(String picture) { this.picture = picture; } + public String getNat() { return nat; } + public void setNat(String nat) { this.nat = nat; } +} 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..2462da7 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/UserService.java @@ -0,0 +1,8 @@ +package com.xpeho.spring_boot_java_random_user.domain.services; + +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import java.util.List; + +public interface UserService { + List saveAll(List users); +} 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 new file mode 100644 index 0000000..b8b1437 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FetchAndSaveRandomUsersUseCase.java @@ -0,0 +1,44 @@ +package com.xpeho.spring_boot_java_random_user.domain.usecases; + +import com.xpeho.spring_boot_java_random_user.data.models.RandomUserResponseDAO; +import com.xpeho.spring_boot_java_random_user.data.sources.RandomUserRetrofitClient; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; +import com.xpeho.spring_boot_java_random_user.data.converters.UserConverter; +import org.springframework.stereotype.Service; +import retrofit2.Response; + +import java.io.IOException; +import java.util.List; +import java.util.Arrays; + +@Service +public class FetchAndSaveRandomUsersUseCase { + + private final UserService userService; + private final UserConverter userConverter; + private final RandomUserRetrofitClient randomUserRetrofitClient; + + public FetchAndSaveRandomUsersUseCase(UserService userService, UserConverter userConverter, RandomUserRetrofitClient randomUserRetrofitClient) { + this.userService = userService; + this.userConverter = userConverter; + this.randomUserRetrofitClient = randomUserRetrofitClient; + } + + public List execute(int count) throws IOException { + Response response = randomUserRetrofitClient.getApi() + .getRandomUsers(count) + .execute(); + + if (!response.isSuccessful() || response.body() == null) { + throw new IOException("Failed to fetch users: " + response.code()); + } + + // Utilisation des Streams pour la transformation + List users = Arrays.stream(response.body().results) + .map(userConverter::modelToEntity) + .toList(); + + return userService.saveAll(users); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..122b785 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/controllers/UserController.java @@ -0,0 +1,35 @@ +package com.xpeho.spring_boot_java_random_user.presentation.controllers; + +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +import java.util.List; + +@RequestMapping("/user") +@Tag(name = "User", description = "Endpoints for random user generation") +public interface UserController { + + @GetMapping("/random") + @Operation( + summary = "Get random users", + description = "Fetches a list of random users from the external API and saves them to the database.", + parameters = { + @Parameter(name = "count", description = "Number of users to generate", example = "500") + } + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "List of users successfully fetched and saved"), + @ApiResponse(responseCode = "500", description = "Internal server error") + }) + ResponseEntity> getRandomUsers(@RequestParam(defaultValue = "500") int count); +} 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 new file mode 100644 index 0000000..717adf8 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/handlers/UserHandler.java @@ -0,0 +1,29 @@ +package com.xpeho.spring_boot_java_random_user.presentation.handlers; + +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.domain.usecases.FetchAndSaveRandomUsersUseCase; +import com.xpeho.spring_boot_java_random_user.presentation.controllers.UserController; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.List; + +@RestController +public class UserHandler implements UserController { + private final FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase; + + public UserHandler(FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase) { + this.fetchAndSaveRandomUsersUseCase = fetchAndSaveRandomUsersUseCase; + } + + @Override + public ResponseEntity> getRandomUsers(int count) { + try { + List users = fetchAndSaveRandomUsersUseCase.execute(count); + return ResponseEntity.ok(users); + } catch (IOException e) { + return ResponseEntity.internalServerError().build(); + } + } +} diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties new file mode 100644 index 0000000..521de86 --- /dev/null +++ b/src/main/resources/application-local.properties @@ -0,0 +1,10 @@ + +spring.application.name=spring_boot_java_random_user +# Swagger UI custom path +springdoc.swagger-ui.path=/api +# URL for Random User API +randomuser.api.base-url=https://randomuser.me/ + +spring.datasource.url=jdbc:postgresql://localhost:5432/xpeho_db +spring.datasource.username=${DB_USER} +spring.datasource.password=${DB_PASSWORD} \ No newline at end of file From 8fecac6b4d59ba5299da3424d40d1a4c292e8b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Leb=C3=A8gue?= Date: Fri, 27 Feb 2026 14:25:33 +0100 Subject: [PATCH 02/11] feat(issues): apply issues --- .gitignore | 2 + README.md | 48 ++++++++++ pom.xml | 2 +- .../data/converters/UserConverter.java | 77 +++++++++++++--- .../data/models/RandomUserInfoDAO.java | 8 -- .../data/models/RandomUserResponseDAO.java | 6 -- .../models/{ => api}/RandomUserNameDAO.java | 2 +- .../data/models/api/RandomUserPictureDAO.java | 5 ++ .../data/models/api/RandomUserResponse.java | 5 ++ .../models/{ => api}/RandomUserResultDAO.java | 2 +- .../data/models/db/User.java | 35 ++++++++ .../data/repositories/UserRepository.java | 7 -- .../data/services/UserServiceImpl.java | 19 ++-- .../data/sources/RandomUserApi.java | 11 --- .../data/sources/api/RandomUserApi.java | 11 +++ .../RandomUserApiConfig.java} | 22 ++--- .../sources/api/RandomUserProviderImpl.java | 34 +++++++ .../data/sources/database/UserRepository.java | 7 ++ .../domain/entities/UserEntity.java | 45 +++------- .../domain/services/RandomUserProvider.java | 10 +++ .../FetchAndSaveRandomUsersUseCase.java | 29 ++---- .../controllers/UserController.java | 20 +++-- .../presentation/dto/UserDTO.java | 13 +++ .../presentation/handlers/UserHandler.java | 35 ++++++-- .../resources/application-local.properties | 6 +- .../data/converters/UserConverterTest.java | 88 +++++++++++++++++++ .../FetchAndSaveRandomUsersUseCaseTest.java | 63 +++++++++++++ 27 files changed, 472 insertions(+), 140 deletions(-) delete mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserInfoDAO.java delete mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResponseDAO.java rename src/main/java/com/xpeho/spring_boot_java_random_user/data/models/{ => api}/RandomUserNameDAO.java (63%) create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAO.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponse.java rename src/main/java/com/xpeho/spring_boot_java_random_user/data/models/{ => api}/RandomUserResultDAO.java (76%) create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java delete mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/repositories/UserRepository.java delete mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserApi.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApi.java rename src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/{RandomUserRetrofitClient.java => api/RandomUserApiConfig.java} (51%) create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImpl.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserRepository.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/RandomUserProvider.java create mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserDTO.java create mode 100644 src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java create mode 100644 src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FetchAndSaveRandomUsersUseCaseTest.java diff --git a/.gitignore b/.gitignore index 83d80fc..3532613 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +application-local.properties +.env HELP.md target/ .mvn/wrapper/maven-wrapper.jar diff --git a/README.md b/README.md index 79267ed..289eae4 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,54 @@ cp src/test/resources/application-test.properties.template src/test/resources/ap ``` > ⚠️ This file is also git-ignored. +But for credentials and local overrides, use: + +``` +src/main/resources/application-local.properties +``` + +### Example for application.properties + +```properties +spring.application.name=spring_boot_java_random_user +springdoc.swagger-ui.path=/api +spring.datasource.url=jdbc:postgresql://localhost:5432/ +spring.datasource.driver-class-name=org.postgresql.Driver +``` + +### Local credentials & security + +To avoid exposing database credentials in source code, create a `.env` file at the project root. + +Then, in `src/main/resources/application-local.properties`: + +```properties +spring.datasource.username=${DB_USER} +spring.datasource.password=${DB_PASSWORD} +``` + +Both `.env` and `application-local.properties` are in `.gitignore` (already set). + +To activate the local profile in IntelliJ or VS Code, add this environment variable to your run configuration: + +``` +SPRING_PROFILES_ACTIVE=local +``` + +This way, each developer can use their own credentials without risk of leaking them to GitHub. + + +> ⚠️ **Common startup error**: +> ``` +> Failed to configure a DataSource: 'url' attribute is not specified +> and no embedded datasource could be configured. +> Reason: Failed to determine a suitable driver class +> ``` +> This error occurs because the PostgreSQL driver is present in the dependencies but the datasource URL is not configured. +> **Fix**: either add the `spring.datasource.*` properties above, or exclude the DataSource auto-configuration if no database is needed: +> ```properties +> spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration +> ``` --- diff --git a/pom.xml b/pom.xml index 89f4820..e2a01c1 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ com.squareup.retrofit2 converter-gson - 2.9.0 + 2.11.0 org.springframework.boot 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 6112c2b..c72a93f 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 @@ -1,21 +1,72 @@ package com.xpeho.spring_boot_java_random_user.data.converters; - -import com.xpeho.spring_boot_java_random_user.data.models.RandomUserResultDAO; +import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserResultDAO; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.data.models.db.User; import org.springframework.stereotype.Service; +import com.xpeho.spring_boot_java_random_user.presentation.dto.UserDTO; + @Service public class UserConverter { - public UserEntity modelToEntity(RandomUserResultDAO model) { - UserEntity entity = new UserEntity(); - entity.setGender(model.gender); - entity.setFirstname(model.name.first); - entity.setLastname(model.name.last); - entity.setCivility(model.name.title); - entity.setEmail(model.email); - entity.setPhone(model.phone); - entity.setPicture(model.picture.medium); - entity.setNat(model.nat); - return entity; + // Domain -> DAO + public User toDao(UserEntity entity) { + User user = new User(); + user.setId(entity.id()); + user.setFirstName(entity.firstname()); + user.setLastName(entity.lastname()); + user.setEmail(entity.email()); + user.setPictureUrl(entity.picture()); + return user; + } + // DAO -> Domain + public UserEntity toDomain(User user) { + return new UserEntity( + user.getId(), + // gender + null, + user.getFirstName(), + user.getLastName(), + // civility + null, + user.getEmail(), + // phone + null, + user.getPictureUrl(), + // nat + null + ); + } + // API -> Domain + public UserEntity fromApiModel(RandomUserResultDAO model) { + String firstName = model.name != null ? model.name.first : null; + String lastName = model.name != null ? model.name.last : null; + String civility = model.name != null ? model.name.title : null; + String picture = model.picture != null ? model.picture.medium : null; + return new UserEntity( + null, + model.gender, + firstName, + lastName, + civility, + model.email, + model.phone, + picture, + model.nat + ); + } + + // Domain -> DTO + public UserDTO toDto(UserEntity entity) { + return new UserDTO( + entity.id(), + entity.gender(), + entity.firstname(), + entity.lastname(), + entity.civility(), + entity.email(), + entity.phone(), + entity.picture(), + entity.nat() + ); } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserInfoDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserInfoDAO.java deleted file mode 100644 index f1ffe1a..0000000 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserInfoDAO.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.xpeho.spring_boot_java_random_user.data.models; - -public class RandomUserInfoDAO { - public String seed; - public int results; - public int page; - public String version; -} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResponseDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResponseDAO.java deleted file mode 100644 index 19e70a3..0000000 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResponseDAO.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.xpeho.spring_boot_java_random_user.data.models; - -public class RandomUserResponseDAO { - public RandomUserResultDAO[] results; - public RandomUserInfoDAO info; -} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserNameDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAO.java similarity index 63% rename from src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserNameDAO.java rename to src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAO.java index a3298ad..6e3f7ef 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserNameDAO.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAO.java @@ -1,4 +1,4 @@ -package com.xpeho.spring_boot_java_random_user.data.models; +package com.xpeho.spring_boot_java_random_user.data.models.api; public class RandomUserNameDAO { public String title; diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAO.java new file mode 100644 index 0000000..f10e990 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAO.java @@ -0,0 +1,5 @@ +package com.xpeho.spring_boot_java_random_user.data.models.api; + +public class RandomUserPictureDAO { + public String medium; +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponse.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponse.java new file mode 100644 index 0000000..b91b373 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponse.java @@ -0,0 +1,5 @@ +package com.xpeho.spring_boot_java_random_user.data.models.api; + +public class RandomUserResponse { + public RandomUserResultDAO[] results; +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResultDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAO.java similarity index 76% rename from src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResultDAO.java rename to src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAO.java index 5db847b..1b848fd 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserResultDAO.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAO.java @@ -1,4 +1,4 @@ -package com.xpeho.spring_boot_java_random_user.data.models; +package com.xpeho.spring_boot_java_random_user.data.models.api; public class RandomUserResultDAO { public String gender; diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java new file mode 100644 index 0000000..53f5716 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java @@ -0,0 +1,35 @@ +package com.xpeho.spring_boot_java_random_user.data.models.db; + +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Table; + +@Table("users") +public class User { + @Id + private Long id; + private String firstName; + private String lastName; + private String email; + private String pictureUrl; + + public User() {} + + public User(Long id, String firstName, String lastName, String email, String pictureUrl) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.pictureUrl = pictureUrl; + } + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getFirstName() { return firstName; } + public void setFirstName(String firstName) { this.firstName = firstName; } + public String getLastName() { return lastName; } + public void setLastName(String lastName) { this.lastName = lastName; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getPictureUrl() { return pictureUrl; } + public void setPictureUrl(String pictureUrl) { this.pictureUrl = pictureUrl; } +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/repositories/UserRepository.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/repositories/UserRepository.java deleted file mode 100644 index 6357784..0000000 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/repositories/UserRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.xpeho.spring_boot_java_random_user.data.repositories; - -import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; -import org.springframework.data.repository.CrudRepository; - -public interface UserRepository extends CrudRepository { -} 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 47374ed..d33de19 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,24 +1,33 @@ package com.xpeho.spring_boot_java_random_user.data.services; -import com.xpeho.spring_boot_java_random_user.data.repositories.UserRepository; +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.services.UserService; +import com.xpeho.spring_boot_java_random_user.data.models.db.User; +import com.xpeho.spring_boot_java_random_user.data.converters.UserConverter; import org.springframework.stereotype.Service; import java.util.List; +import java.util.stream.StreamSupport; +import java.util.stream.Collectors; @Service + public class UserServiceImpl implements UserService { private final UserRepository userRepository; + private final UserConverter userConverter; - public UserServiceImpl(UserRepository userRepository) { + public UserServiceImpl(UserRepository userRepository, UserConverter userConverter) { this.userRepository = userRepository; + this.userConverter = userConverter; } @Override public List saveAll(List users) { - Iterable saved = userRepository.saveAll(users); - return saved instanceof List ? (List) saved : - java.util.stream.StreamSupport.stream(saved.spliterator(), false).toList(); + List daoUsers = users.stream().map(userConverter::toDao).toList(); + Iterable saved = userRepository.saveAll(daoUsers); + return StreamSupport.stream(saved.spliterator(), false) + .map(userConverter::toDomain) + .collect(Collectors.toList()); } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserApi.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserApi.java deleted file mode 100644 index f0eb6f3..0000000 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserApi.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.xpeho.spring_boot_java_random_user.data.sources; - -import com.xpeho.spring_boot_java_random_user.data.models.RandomUserResponseDAO; -import retrofit2.Call; -import retrofit2.http.GET; -import retrofit2.http.Query; - -public interface RandomUserApi { - @GET("/api/") - Call getRandomUsers(@Query("results") int results); -} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApi.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApi.java new file mode 100644 index 0000000..3faf096 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApi.java @@ -0,0 +1,11 @@ +package com.xpeho.spring_boot_java_random_user.data.sources.api; + +import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserResponse; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Query; + +public interface RandomUserApi { + @GET("/api/") + Call getRandomUsers(@Query("results") int results); +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserRetrofitClient.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApiConfig.java similarity index 51% rename from src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserRetrofitClient.java rename to src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApiConfig.java index 98591e6..f0aa109 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/RandomUserRetrofitClient.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApiConfig.java @@ -1,27 +1,27 @@ -package com.xpeho.spring_boot_java_random_user.data.sources; +package com.xpeho.spring_boot_java_random_user.data.sources.api; import okhttp3.OkHttpClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; -import org.springframework.stereotype.Component; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; -@Component -public class RandomUserRetrofitClient { - private final RandomUserApi apiInstance; - - public RandomUserRetrofitClient(Environment env) { +@Configuration +public class RandomUserApiConfig { + @Bean + public Retrofit randomUserRetrofit(Environment env) { String baseUrl = env.getProperty("randomuser.api.base-url", "https://randomuser.me/"); OkHttpClient client = new OkHttpClient.Builder().build(); - Retrofit retrofit = new Retrofit.Builder() + return new Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create()) .client(client) .build(); - this.apiInstance = retrofit.create(RandomUserApi.class); } - public RandomUserApi getApi() { - return apiInstance; + @Bean + public RandomUserApi randomUserApi(Retrofit randomUserRetrofit) { + return randomUserRetrofit.create(RandomUserApi.class); } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImpl.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImpl.java new file mode 100644 index 0000000..de1d2a1 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImpl.java @@ -0,0 +1,34 @@ +package com.xpeho.spring_boot_java_random_user.data.sources.api; + +import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserResponse; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.domain.services.RandomUserProvider; +import com.xpeho.spring_boot_java_random_user.data.converters.UserConverter; +import org.springframework.stereotype.Service; +import retrofit2.Response; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +@Service +public class RandomUserProviderImpl implements RandomUserProvider { + private final RandomUserApi randomUserApi; + private final UserConverter userConverter; + + public RandomUserProviderImpl(RandomUserApi randomUserApi, UserConverter userConverter) { + this.randomUserApi = randomUserApi; + this.userConverter = userConverter; + } + + @Override + public List fetchRandomUsers(int count) throws IOException { + Response response = randomUserApi.getRandomUsers(count).execute(); + if (!response.isSuccessful() || response.body() == null) { + throw new IOException("Failed to fetch users: " + response.code()); + } + return Arrays.stream(response.body().results) + .map(userConverter::fromApiModel) + .toList(); + } +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserRepository.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserRepository.java new file mode 100644 index 0000000..11a75bb --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/database/UserRepository.java @@ -0,0 +1,7 @@ +package com.xpeho.spring_boot_java_random_user.data.sources.database; + +import com.xpeho.spring_boot_java_random_user.data.models.db.User; +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { +} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserEntity.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserEntity.java index ebe1a60..557327c 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserEntity.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/entities/UserEntity.java @@ -1,36 +1,13 @@ package com.xpeho.spring_boot_java_random_user.domain.entities; -import org.springframework.data.annotation.Id; - -public class UserEntity { - @Id - private Long id; - private String gender; - private String firstname; - private String lastname; - private String civility; - private String email; - private String phone; - private String picture; - private String nat; - - // Getters and setters - public Long getId() { return id; } - public void setId(Long id) { this.id = id; } - public String getGender() { return gender; } - public void setGender(String gender) { this.gender = gender; } - public String getFirstname() { return firstname; } - public void setFirstname(String firstname) { this.firstname = firstname; } - public String getLastname() { return lastname; } - public void setLastname(String lastname) { this.lastname = lastname; } - public String getCivility() { return civility; } - public void setCivility(String civility) { this.civility = civility; } - public String getEmail() { return email; } - public void setEmail(String email) { this.email = email; } - public String getPhone() { return phone; } - public void setPhone(String phone) { this.phone = phone; } - public String getPicture() { return picture; } - public void setPicture(String picture) { this.picture = picture; } - public String getNat() { return nat; } - public void setNat(String nat) { this.nat = nat; } -} +public record UserEntity( + Long id, + String gender, + String firstname, + String lastname, + String civility, + String email, + String phone, + String picture, + String nat +) {} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/RandomUserProvider.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/RandomUserProvider.java new file mode 100644 index 0000000..6a785b9 --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/services/RandomUserProvider.java @@ -0,0 +1,10 @@ +package com.xpeho.spring_boot_java_random_user.domain.services; + +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import java.io.IOException; +import java.util.List; + +public interface RandomUserProvider { + List fetchRandomUsers(int count) throws IOException; + +} 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 b8b1437..f9a41ac 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 @@ -1,44 +1,25 @@ package com.xpeho.spring_boot_java_random_user.domain.usecases; -import com.xpeho.spring_boot_java_random_user.data.models.RandomUserResponseDAO; -import com.xpeho.spring_boot_java_random_user.data.sources.RandomUserRetrofitClient; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; import com.xpeho.spring_boot_java_random_user.domain.services.UserService; -import com.xpeho.spring_boot_java_random_user.data.converters.UserConverter; +import com.xpeho.spring_boot_java_random_user.domain.services.RandomUserProvider; import org.springframework.stereotype.Service; -import retrofit2.Response; - import java.io.IOException; import java.util.List; -import java.util.Arrays; @Service public class FetchAndSaveRandomUsersUseCase { private final UserService userService; - private final UserConverter userConverter; - private final RandomUserRetrofitClient randomUserRetrofitClient; + private final RandomUserProvider randomUserProvider; - public FetchAndSaveRandomUsersUseCase(UserService userService, UserConverter userConverter, RandomUserRetrofitClient randomUserRetrofitClient) { + public FetchAndSaveRandomUsersUseCase(UserService userService, RandomUserProvider randomUserProvider) { this.userService = userService; - this.userConverter = userConverter; - this.randomUserRetrofitClient = randomUserRetrofitClient; + this.randomUserProvider = randomUserProvider; } public List execute(int count) throws IOException { - Response response = randomUserRetrofitClient.getApi() - .getRandomUsers(count) - .execute(); - - if (!response.isSuccessful() || response.body() == null) { - throw new IOException("Failed to fetch users: " + response.code()); - } - - // Utilisation des Streams pour la transformation - List users = Arrays.stream(response.body().results) - .map(userConverter::modelToEntity) - .toList(); - + List users = randomUserProvider.fetchRandomUsers(count); return userService.saveAll(users); } } \ No newline at end of file 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 122b785..ef017ff 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,7 +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.presentation.dto.UserDTO; import org.springframework.http.ResponseEntity; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -15,21 +17,27 @@ import java.util.List; -@RequestMapping("/user") +@RequestMapping("/random-users") @Tag(name = "User", description = "Endpoints for random user generation") public interface UserController { - @GetMapping("/random") + @GetMapping("") @Operation( summary = "Get random users", description = "Fetches a list of random users from the external API and saves them to the database.", parameters = { - @Parameter(name = "count", description = "Number of users to generate", example = "500") + @Parameter(name = "count", description = "Number of users to generate (max 5000)", example = "500") } ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "List of users successfully fetched and saved"), - @ApiResponse(responseCode = "500", description = "Internal server error") + @ApiResponse(responseCode = "500", description = "Internal server error"), + @ApiResponse(responseCode = "503", description = "External service unavailable") }) - ResponseEntity> getRandomUsers(@RequestParam(defaultValue = "500") int count); + ResponseEntity getRandomUsers( + @RequestParam(defaultValue = "500") + @Min(1) + @Max(5000) + int count + ); } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserDTO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserDTO.java new file mode 100644 index 0000000..f23f4ac --- /dev/null +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserDTO.java @@ -0,0 +1,13 @@ +package com.xpeho.spring_boot_java_random_user.presentation.dto; + +public record UserDTO( + Long id, + String gender, + String firstname, + String lastname, + String civility, + String email, + String phone, + String picture, + String nat +) {} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/handlers/UserHandler.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/handlers/UserHandler.java index 717adf8..5a103f3 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 @@ -1,29 +1,46 @@ package com.xpeho.spring_boot_java_random_user.presentation.handlers; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.presentation.dto.UserDTO; +import com.xpeho.spring_boot_java_random_user.data.converters.UserConverter; import com.xpeho.spring_boot_java_random_user.domain.usecases.FetchAndSaveRandomUsersUseCase; import com.xpeho.spring_boot_java_random_user.presentation.controllers.UserController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.util.List; +import java.util.Map; @RestController public class UserHandler implements UserController { + + private static final Logger logger = LoggerFactory.getLogger(UserHandler.class); + private final FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase; + private final UserConverter userConverter; - public UserHandler(FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase) { + public UserHandler(FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase, UserConverter userConverter) { this.fetchAndSaveRandomUsersUseCase = fetchAndSaveRandomUsersUseCase; + this.userConverter = userConverter; } @Override - public ResponseEntity> getRandomUsers(int count) { - try { - List users = fetchAndSaveRandomUsersUseCase.execute(count); - return ResponseEntity.ok(users); - } catch (IOException e) { - return ResponseEntity.internalServerError().build(); - } + public ResponseEntity getRandomUsers(int count) { + try { + List users = fetchAndSaveRandomUsersUseCase.execute(count); + List dtos = users.stream().map(userConverter::toDto).toList(); + return ResponseEntity.ok(dtos); + } catch (IOException e) { + logger.error("Error fetching random users: {}", e.getMessage(), e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Map.of( + "error", "External API Error", + "message", "Impossible to fetch random users from the external API. Please try again later." + )); + } } -} +} \ No newline at end of file diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 521de86..1a69b90 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -5,6 +5,6 @@ springdoc.swagger-ui.path=/api # URL for Random User API randomuser.api.base-url=https://randomuser.me/ -spring.datasource.url=jdbc:postgresql://localhost:5432/xpeho_db -spring.datasource.username=${DB_USER} -spring.datasource.password=${DB_PASSWORD} \ No newline at end of file +spring.datasource.url=jdbc:postgresql://localhost:${POSTGRES_PORT}/${POSTGRES_DB} +spring.datasource.username=${POSTGRES_USER} +spring.datasource.password=${POSTGRES_PASSWORD} \ No newline at end of file diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java new file mode 100644 index 0000000..26fb8f1 --- /dev/null +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java @@ -0,0 +1,88 @@ +package com.xpeho.spring_boot_java_random_user.data.converters; + +import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserResultDAO; +import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserNameDAO; +import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserPictureDAO; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import com.xpeho.spring_boot_java_random_user.data.models.db.User; +import com.xpeho.spring_boot_java_random_user.presentation.dto.UserDTO; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class UserConverterTest { + private final UserConverter converter = new UserConverter(); + + @Test + void fromApiModel_fullData() { + RandomUserNameDAO name = new RandomUserNameDAO(); + name.first = "John"; + name.last = "Doe"; + name.title = "Mr"; + RandomUserPictureDAO picture = new RandomUserPictureDAO(); + picture.medium = "pic.jpg"; + RandomUserResultDAO api = new RandomUserResultDAO(); + api.gender = "male"; + api.name = name; + api.email = "john@doe.com"; + api.phone = "1234"; + api.picture = picture; + api.nat = "FR"; + UserEntity entity = converter.fromApiModel(api); + assertNull(entity.id()); + assertEquals("male", entity.gender()); + assertEquals("John", entity.firstname()); + assertEquals("Doe", entity.lastname()); + assertEquals("Mr", entity.civility()); + assertEquals("john@doe.com", entity.email()); + assertEquals("1234", entity.phone()); + assertEquals("pic.jpg", entity.picture()); + assertEquals("FR", entity.nat()); + } + + @Test + void fromApiModel_nullNameAndPicture() { + RandomUserResultDAO api = new RandomUserResultDAO(); + api.gender = "female"; + api.name = null; + api.email = "jane@doe.com"; + api.phone = "5678"; + api.picture = null; + api.nat = "US"; + UserEntity entity = converter.fromApiModel(api); + assertNull(entity.firstname()); + assertNull(entity.lastname()); + assertNull(entity.civility()); + assertNull(entity.picture()); + assertEquals("female", entity.gender()); + assertEquals("jane@doe.com", entity.email()); + assertEquals("5678", entity.phone()); + assertEquals("US", entity.nat()); + } + + @Test + void toDto_and_toDao_and_toDomain() { + UserEntity entity = new UserEntity(1L, "male", "John", "Doe", "Mr", "john@doe.com", "1234", "pic.jpg", "FR"); + UserDTO dto = converter.toDto(entity); + assertEquals(entity.id(), dto.id()); + assertEquals(entity.firstname(), dto.firstname()); + assertEquals(entity.lastname(), dto.lastname()); + assertEquals(entity.civility(), dto.civility()); + assertEquals(entity.email(), dto.email()); + assertEquals(entity.phone(), dto.phone()); + assertEquals(entity.picture(), dto.picture()); + assertEquals(entity.nat(), dto.nat()); + User dao = converter.toDao(entity); + assertEquals(entity.id(), dao.getId()); + assertEquals(entity.firstname(), dao.getFirstName()); + assertEquals(entity.lastname(), dao.getLastName()); + assertEquals(entity.email(), dao.getEmail()); + assertEquals(entity.picture(), dao.getPictureUrl()); + UserEntity back = converter.toDomain(dao); + assertEquals(entity.id(), back.id()); + assertEquals(entity.firstname(), back.firstname()); + assertEquals(entity.lastname(), back.lastname()); + assertEquals(entity.email(), back.email()); + assertEquals(entity.picture(), back.picture()); + } +} 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 new file mode 100644 index 0000000..a09e0a3 --- /dev/null +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/FetchAndSaveRandomUsersUseCaseTest.java @@ -0,0 +1,63 @@ +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.services.RandomUserProvider; +import com.xpeho.spring_boot_java_random_user.domain.services.UserService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class FetchAndSaveRandomUsersUseCaseTest { + private UserService userService; + private RandomUserProvider randomUserProvider; + private FetchAndSaveRandomUsersUseCase useCase; + + @BeforeEach + void setUp() { + userService = mock(UserService.class); + randomUserProvider = mock(RandomUserProvider.class); + useCase = new FetchAndSaveRandomUsersUseCase(userService, randomUserProvider); + } + + @Test + void testSuccessfulApiResponse() throws IOException { + List fetched = List.of(new UserEntity( + 1L, "male", "John", "Doe", "Mr", "john@doe.com", "1234", "pic.jpg", "FR" + )); when(randomUserProvider.fetchRandomUsers(2)).thenReturn(fetched); + when(userService.saveAll(fetched)).thenReturn(fetched); + List result = useCase.execute(2); + assertEquals(fetched, result); + verify(randomUserProvider).fetchRandomUsers(2); + verify(userService).saveAll(fetched); + } + + @Test + void testApiThrowsIOException() throws IOException { + when(randomUserProvider.fetchRandomUsers(1)).thenThrow(new IOException("API error")); + IOException ex = assertThrows(IOException.class, () -> useCase.execute(1)); + assertEquals("API error", ex.getMessage()); + } + + @Test + void testNullResponse() throws IOException { + when(randomUserProvider.fetchRandomUsers(1)).thenReturn(null); + when(userService.saveAll(null)).thenReturn(null); + List result = useCase.execute(1); + assertNull(result); + } + + @Test + void testEmptyResultArray() throws IOException { + when(randomUserProvider.fetchRandomUsers(0)).thenReturn(Collections.emptyList()); + when(userService.saveAll(Collections.emptyList())).thenReturn(Collections.emptyList()); + List result = useCase.execute(0); + assertTrue(result.isEmpty()); + } +} From 69771e88550d9e9eefa239aa495fda9980786c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Leb=C3=A8gue?= Date: Fri, 27 Feb 2026 15:41:11 +0100 Subject: [PATCH 03/11] feat(converters): refactor UserConverter and update DAO classes for better encapsulation --- .../data/converters/UserConverter.java | 16 +++++----- .../data/models/RandomUserPictureDAO.java | 10 +++++- .../data/models/api/RandomUserNameDAO.java | 13 ++++++-- .../data/models/api/RandomUserPictureDAO.java | 10 +++++- .../data/models/api/RandomUserResultDAO.java | 25 +++++++++++---- .../data/services/UserServiceImpl.java | 4 +-- .../controllers/UserController.java | 14 ++++---- .../presentation/handlers/UserHandler.java | 9 +++--- .../data/converters/UserConverterTest.java | 32 +++++++++---------- 9 files changed, 83 insertions(+), 50 deletions(-) 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 c72a93f..1ebaf33 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 @@ -38,20 +38,20 @@ public UserEntity toDomain(User user) { } // API -> Domain public UserEntity fromApiModel(RandomUserResultDAO model) { - String firstName = model.name != null ? model.name.first : null; - String lastName = model.name != null ? model.name.last : null; - String civility = model.name != null ? model.name.title : null; - String picture = model.picture != null ? model.picture.medium : null; + String firstName = model.getName() != null ? model.getName().getFirst() : null; + String lastName = model.getName() != null ? model.getName().getLast() : null; + String civility = model.getName() != null ? model.getName().getTitle() : null; + String picture = model.getPicture() != null ? model.getPicture().getMedium() : null; return new UserEntity( null, - model.gender, + model.getGender(), firstName, lastName, civility, - model.email, - model.phone, + model.getEmail(), + model.getPhone(), picture, - model.nat + model.getNat() ); } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserPictureDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserPictureDAO.java index 514da2d..2e9c567 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserPictureDAO.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserPictureDAO.java @@ -1,5 +1,13 @@ package com.xpeho.spring_boot_java_random_user.data.models; public class RandomUserPictureDAO { - public String medium; + private String medium; + + public String getMedium() { + return medium; + } + + public void setMedium(String medium) { + this.medium = medium; + } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAO.java index 6e3f7ef..a095b9c 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAO.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAO.java @@ -1,7 +1,14 @@ package com.xpeho.spring_boot_java_random_user.data.models.api; public class RandomUserNameDAO { - public String title; - public String first; - public String last; + private String title; + private String first; + private String last; + + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + public String getFirst() { return first; } + public void setFirst(String first) { this.first = first; } + public String getLast() { return last; } + public void setLast(String last) { this.last = last; } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAO.java index f10e990..9096ddb 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAO.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAO.java @@ -1,5 +1,13 @@ package com.xpeho.spring_boot_java_random_user.data.models.api; public class RandomUserPictureDAO { - public String medium; + private String medium; + + public String getMedium() { + return medium; + } + + public void setMedium(String medium) { + this.medium = medium; + } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAO.java index 1b848fd..15e964b 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAO.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAO.java @@ -1,10 +1,23 @@ package com.xpeho.spring_boot_java_random_user.data.models.api; public class RandomUserResultDAO { - public String gender; - public RandomUserNameDAO name; - public String email; - public String phone; - public RandomUserPictureDAO picture; - public String nat; + private String gender; + private RandomUserNameDAO name; + private String email; + private String phone; + private RandomUserPictureDAO picture; + private String nat; + + public String getGender() { return gender; } + public void setGender(String gender) { this.gender = gender; } + public RandomUserNameDAO getName() { return name; } + public void setName(RandomUserNameDAO name) { this.name = name; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } + public RandomUserPictureDAO getPicture() { return picture; } + public void setPicture(RandomUserPictureDAO picture) { this.picture = picture; } + public String getNat() { return nat; } + public void setNat(String nat) { this.nat = nat; } } 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 d33de19..25493c9 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 @@ -27,7 +27,7 @@ public List saveAll(List users) { List daoUsers = users.stream().map(userConverter::toDao).toList(); Iterable saved = userRepository.saveAll(daoUsers); return StreamSupport.stream(saved.spliterator(), false) - .map(userConverter::toDomain) - .collect(Collectors.toList()); + .map(userConverter::toDomain) + .toList(); } } 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 ef017ff..c0048d9 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,7 +1,8 @@ package com.xpeho.spring_boot_java_random_user.presentation.controllers; -import com.xpeho.spring_boot_java_random_user.presentation.dto.UserDTO; import org.springframework.http.ResponseEntity; +import java.util.List; +import com.xpeho.spring_boot_java_random_user.presentation.dto.UserDTO; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import org.springframework.web.bind.annotation.GetMapping; @@ -15,7 +16,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; @RequestMapping("/random-users") @Tag(name = "User", description = "Endpoints for random user generation") @@ -29,12 +29,10 @@ public interface UserController { @Parameter(name = "count", description = "Number of users to generate (max 5000)", example = "500") } ) - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "List of users successfully fetched and saved"), - @ApiResponse(responseCode = "500", description = "Internal server error"), - @ApiResponse(responseCode = "503", description = "External service unavailable") - }) - ResponseEntity getRandomUsers( + @ApiResponse(responseCode = "200", description = "List of users successfully fetched and saved") + @ApiResponse(responseCode = "500", description = "Internal server error") + @ApiResponse(responseCode = "503", description = "External service unavailable") + ResponseEntity> getRandomUsers( @RequestParam(defaultValue = "500") @Min(1) @Max(5000) 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 5a103f3..6a17265 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 @@ -15,6 +15,8 @@ import java.util.List; import java.util.Map; +import static java.util.Collections.emptyList; + @RestController public class UserHandler implements UserController { @@ -29,7 +31,7 @@ public UserHandler(FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase } @Override - public ResponseEntity getRandomUsers(int count) { + public ResponseEntity> getRandomUsers(int count) { try { List users = fetchAndSaveRandomUsersUseCase.execute(count); List dtos = users.stream().map(userConverter::toDto).toList(); @@ -37,10 +39,7 @@ public ResponseEntity getRandomUsers(int count) { } catch (IOException e) { logger.error("Error fetching random users: {}", e.getMessage(), e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of( - "error", "External API Error", - "message", "Impossible to fetch random users from the external API. Please try again later." - )); + .body(emptyList()); } } } \ No newline at end of file diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java index 26fb8f1..630a57b 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java @@ -16,18 +16,18 @@ class UserConverterTest { @Test void fromApiModel_fullData() { RandomUserNameDAO name = new RandomUserNameDAO(); - name.first = "John"; - name.last = "Doe"; - name.title = "Mr"; + name.setFirst("John"); + name.setLast("Doe"); + name.setTitle("Mr"); RandomUserPictureDAO picture = new RandomUserPictureDAO(); - picture.medium = "pic.jpg"; + picture.setMedium("pic.jpg"); RandomUserResultDAO api = new RandomUserResultDAO(); - api.gender = "male"; - api.name = name; - api.email = "john@doe.com"; - api.phone = "1234"; - api.picture = picture; - api.nat = "FR"; + api.setGender("male"); + api.setName(name); + api.setEmail("john@doe.com"); + api.setPhone("1234"); + api.setPicture(picture); + api.setNat("FR"); UserEntity entity = converter.fromApiModel(api); assertNull(entity.id()); assertEquals("male", entity.gender()); @@ -43,12 +43,12 @@ void fromApiModel_fullData() { @Test void fromApiModel_nullNameAndPicture() { RandomUserResultDAO api = new RandomUserResultDAO(); - api.gender = "female"; - api.name = null; - api.email = "jane@doe.com"; - api.phone = "5678"; - api.picture = null; - api.nat = "US"; + api.setGender("female"); + api.setName(null); + api.setEmail("jane@doe.com"); + api.setPhone("5678"); + api.setPicture(null); + api.setNat("US"); UserEntity entity = converter.fromApiModel(api); assertNull(entity.firstname()); assertNull(entity.lastname()); From 06ba29ab9c5ac0a3c51eeed396cf8caf99cdcf5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Leb=C3=A8gue?= Date: Fri, 27 Feb 2026 15:47:03 +0100 Subject: [PATCH 04/11] feat(api): encapsulate results in RandomUserResponse and update related methods --- .../data/models/api/RandomUserResponse.java | 10 +++++++++- .../data/services/UserServiceImpl.java | 1 - .../data/sources/api/RandomUserProviderImpl.java | 6 +++--- .../presentation/controllers/UserController.java | 1 - .../presentation/handlers/UserHandler.java | 1 - .../usecases/FetchAndSaveRandomUsersUseCaseTest.java | 1 - 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponse.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponse.java index b91b373..18f61ed 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponse.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponse.java @@ -1,5 +1,13 @@ package com.xpeho.spring_boot_java_random_user.data.models.api; public class RandomUserResponse { - public RandomUserResultDAO[] results; + private RandomUserResultDAO[] results; + + public RandomUserResultDAO[] getResults() { + return results; + } + + public void setResults(RandomUserResultDAO[] results) { + this.results = results; + } } 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 25493c9..3dae4e3 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 @@ -9,7 +9,6 @@ import java.util.List; import java.util.stream.StreamSupport; -import java.util.stream.Collectors; @Service diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImpl.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImpl.java index de1d2a1..6d4d648 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImpl.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImpl.java @@ -27,8 +27,8 @@ public List fetchRandomUsers(int count) throws IOException { if (!response.isSuccessful() || response.body() == null) { throw new IOException("Failed to fetch users: " + response.code()); } - return Arrays.stream(response.body().results) - .map(userConverter::fromApiModel) - .toList(); + return Arrays.stream(response.body().getResults()) + .map(userConverter::fromApiModel) + .toList(); } } 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 c0048d9..a0f5cce 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 @@ -13,7 +13,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; 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 6a17265..e9d2ec7 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 @@ -13,7 +13,6 @@ import java.io.IOException; import java.util.List; -import java.util.Map; import static java.util.Collections.emptyList; 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 a09e0a3..c3d1bea 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 @@ -5,7 +5,6 @@ import com.xpeho.spring_boot_java_random_user.domain.services.UserService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import java.io.IOException; import java.util.Collections; From 5bdb66c88a9459a6ef4fa75cbf7c62a13e5f1b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Leb=C3=A8gue?= Date: Fri, 27 Feb 2026 15:55:38 +0100 Subject: [PATCH 05/11] feat(tests): add unit tests --- .../models/api/RandomUserNameDAOTest.java | 24 +++++++ .../models/api/RandomUserPictureDAOTest.java | 18 +++++ .../models/api/RandomUserResponseTest.java | 22 ++++++ .../models/api/RandomUserResultDAOTest.java | 35 ++++++++++ .../api/RandomUserProviderImplTest.java | 67 +++++++++++++++++++ 5 files changed, 166 insertions(+) create mode 100644 src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAOTest.java create mode 100644 src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAOTest.java create mode 100644 src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponseTest.java create mode 100644 src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAOTest.java create mode 100644 src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImplTest.java diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAOTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAOTest.java new file mode 100644 index 0000000..ea8c3d0 --- /dev/null +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAOTest.java @@ -0,0 +1,24 @@ +package com.xpeho.spring_boot_java_random_user.data.models.api; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class RandomUserNameDAOTest { + @Test + void testGetSetAllFields() { + RandomUserNameDAO name = new RandomUserNameDAO(); + name.setFirst("John"); + name.setLast("Doe"); + name.setTitle("Mr"); + assertEquals("John", name.getFirst()); + assertEquals("Doe", name.getLast()); + assertEquals("Mr", name.getTitle()); + } + @Test + void testDefaultFieldsAreNull() { + RandomUserNameDAO name = new RandomUserNameDAO(); + assertNull(name.getFirst()); + assertNull(name.getLast()); + assertNull(name.getTitle()); + } +} diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAOTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAOTest.java new file mode 100644 index 0000000..398b8f1 --- /dev/null +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAOTest.java @@ -0,0 +1,18 @@ +package com.xpeho.spring_boot_java_random_user.data.models.api; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class RandomUserPictureDAOTest { + @Test + void testGetSetMedium() { + RandomUserPictureDAO picture = new RandomUserPictureDAO(); + picture.setMedium("pic.jpg"); + assertEquals("pic.jpg", picture.getMedium()); + } + @Test + void testDefaultMediumIsNull() { + RandomUserPictureDAO picture = new RandomUserPictureDAO(); + assertNull(picture.getMedium()); + } +} diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponseTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponseTest.java new file mode 100644 index 0000000..42f8ecf --- /dev/null +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponseTest.java @@ -0,0 +1,22 @@ +package com.xpeho.spring_boot_java_random_user.data.models.api; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class RandomUserResponseTest { + @Test + void testGetSetResults() { + RandomUserResponse response = new RandomUserResponse(); + RandomUserResultDAO[] arr = new RandomUserResultDAO[2]; + arr[0] = new RandomUserResultDAO(); + arr[1] = new RandomUserResultDAO(); + response.setResults(arr); + assertArrayEquals(arr, response.getResults()); + } + + @Test + void testDefaultResultsIsNull() { + RandomUserResponse response = new RandomUserResponse(); + assertNull(response.getResults()); + } +} diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAOTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAOTest.java new file mode 100644 index 0000000..3f5f725 --- /dev/null +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAOTest.java @@ -0,0 +1,35 @@ +package com.xpeho.spring_boot_java_random_user.data.models.api; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class RandomUserResultDAOTest { + @Test + void testGetSetAllFields() { + RandomUserResultDAO result = new RandomUserResultDAO(); + result.setGender("male"); + RandomUserNameDAO name = new RandomUserNameDAO(); + result.setName(name); + result.setEmail("john@doe.com"); + result.setPhone("1234"); + RandomUserPictureDAO picture = new RandomUserPictureDAO(); + result.setPicture(picture); + result.setNat("FR"); + assertEquals("male", result.getGender()); + assertSame(name, result.getName()); + assertEquals("john@doe.com", result.getEmail()); + assertEquals("1234", result.getPhone()); + assertSame(picture, result.getPicture()); + assertEquals("FR", result.getNat()); + } + @Test + void testDefaultFieldsAreNull() { + RandomUserResultDAO result = new RandomUserResultDAO(); + assertNull(result.getGender()); + assertNull(result.getName()); + assertNull(result.getEmail()); + assertNull(result.getPhone()); + assertNull(result.getPicture()); + assertNull(result.getNat()); + } +} diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImplTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImplTest.java new file mode 100644 index 0000000..4616ec4 --- /dev/null +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImplTest.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.converters.UserConverter; +import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserResponse; +import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserResultDAO; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import retrofit2.Call; +import retrofit2.Response; + +import java.io.IOException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class RandomUserProviderImplTest { + private RandomUserApi randomUserApi; + private UserConverter userConverter; + private RandomUserProviderImpl provider; + private Call call; + + @BeforeEach + void setUp() { + randomUserApi = mock(RandomUserApi.class); + userConverter = mock(UserConverter.class); + call = mock(Call.class); + provider = new RandomUserProviderImpl(randomUserApi, userConverter); + } + + @Test + void fetchRandomUsers_success() throws IOException { + int count = 2; + RandomUserResponse responseObj = new RandomUserResponse(); + RandomUserResultDAO dao1 = new RandomUserResultDAO(); + RandomUserResultDAO dao2 = new RandomUserResultDAO(); + responseObj.setResults(new RandomUserResultDAO[]{dao1, dao2}); + UserEntity entity1 = new UserEntity(null, "a", "b", "c", "d", "e", "f", "g", "h"); + UserEntity entity2 = new UserEntity(null, "i", "j", "k", "l", "m", "n", "o", "p"); + when(randomUserApi.getRandomUsers(count)).thenReturn(call); + when(call.execute()).thenReturn(Response.success(responseObj)); + when(userConverter.fromApiModel(dao1)).thenReturn(entity1); + when(userConverter.fromApiModel(dao2)).thenReturn(entity2); + + List result = provider.fetchRandomUsers(count); + assertEquals(2, result.size()); + assertEquals(entity1, result.get(0)); + assertEquals(entity2, result.get(1)); + } + + @Test + void fetchRandomUsers_unsuccessfulResponse_throwsIOException() throws IOException { + int count = 1; + when(randomUserApi.getRandomUsers(count)).thenReturn(call); + when(call.execute()).thenReturn(Response.error(500, okhttp3.ResponseBody.create(null, "error"))); + assertThrows(IOException.class, () -> provider.fetchRandomUsers(count)); + } + + @Test + void fetchRandomUsers_nullBody_throwsIOException() throws IOException { + int count = 1; + when(randomUserApi.getRandomUsers(count)).thenReturn(call); + when(call.execute()).thenReturn(Response.success(null)); + assertThrows(IOException.class, () -> provider.fetchRandomUsers(count)); + } +} From 5339826977f0498a7ba2a159a3c632a4fb51b660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Leb=C3=A8gue?= Date: Mon, 2 Mar 2026 16:07:53 +0100 Subject: [PATCH 06/11] feat(api): change data url for api --- docker-compose.yaml | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 docker-compose.yaml diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 7ec4aaa..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,26 +0,0 @@ -services: - postgres: - image: postgres:15-alpine - container_name: xpeho_postgres - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: xpeho_db - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - - ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql - # INIT DATA - # - ./src/main/resources/data.sql:/docker-entrypoint-initdb.d/02-data.sql - networks: - - xpeho_network - -volumes: - postgres_data: - driver: local - -networks: - xpeho_network: - driver: bridge - From 86b3bace760e3fc03782abb2f9672e5ec6cc5d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Leb=C3=A8gue?= Date: Mon, 2 Mar 2026 16:08:25 +0100 Subject: [PATCH 07/11] feat(api): update user model and API response structure for better data handling --- .../data/converters/UserConverter.java | 42 +++++++++---------- .../data/models/api/RandomUserResponse.java | 14 ++++--- .../data/models/api/RandomUserResultDAO.java | 18 ++++---- .../data/models/db/User.java | 41 ++++++++++-------- .../data/sources/api/RandomUserApi.java | 4 +- .../data/sources/api/RandomUserApiConfig.java | 2 +- .../sources/api/RandomUserProviderImpl.java | 8 +++- .../resources/application-local.properties | 2 +- src/main/resources/application.properties | 3 ++ src/main/resources/schema.sql | 4 +- ...ingBootJavaRandomUserApplicationTests.java | 7 +++- .../data/converters/UserConverterTest.java | 27 ++++-------- .../models/api/RandomUserResponseTest.java | 15 ++++--- .../models/api/RandomUserResultDAOTest.java | 21 +++++----- .../api/RandomUserProviderImplTest.java | 2 +- 15 files changed, 106 insertions(+), 104 deletions(-) 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 1ebaf33..7caec7e 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 @@ -12,46 +12,42 @@ public class UserConverter { public User toDao(UserEntity entity) { User user = new User(); user.setId(entity.id()); - user.setFirstName(entity.firstname()); - user.setLastName(entity.lastname()); + user.setGender(entity.gender()); + user.setFirstname(entity.firstname()); + user.setLastname(entity.lastname()); + user.setCivility(entity.civility()); user.setEmail(entity.email()); - user.setPictureUrl(entity.picture()); + user.setPhone(entity.phone()); + user.setPicture(entity.picture()); + user.setNationality(entity.nat()); return user; } // DAO -> Domain public UserEntity toDomain(User user) { return new UserEntity( user.getId(), - // gender - null, - user.getFirstName(), - user.getLastName(), - // civility - null, + user.getGender(), + user.getFirstname(), + user.getLastname(), + user.getCivility(), user.getEmail(), - // phone - null, - user.getPictureUrl(), - // nat - null + user.getPhone(), + user.getPicture(), + user.getNationality() ); } // API -> Domain public UserEntity fromApiModel(RandomUserResultDAO model) { - String firstName = model.getName() != null ? model.getName().getFirst() : null; - String lastName = model.getName() != null ? model.getName().getLast() : null; - String civility = model.getName() != null ? model.getName().getTitle() : null; - String picture = model.getPicture() != null ? model.getPicture().getMedium() : null; return new UserEntity( null, model.getGender(), - firstName, - lastName, - civility, + model.getFirstName(), + model.getLastName(), + null, model.getEmail(), model.getPhone(), - picture, - model.getNat() + model.getImage(), + null ); } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponse.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponse.java index 18f61ed..603922a 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponse.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponse.java @@ -1,13 +1,17 @@ package com.xpeho.spring_boot_java_random_user.data.models.api; +import com.google.gson.annotations.SerializedName; +import java.util.List; + public class RandomUserResponse { - private RandomUserResultDAO[] results; + @SerializedName("users") + private List users; - public RandomUserResultDAO[] getResults() { - return results; + public List getUsers() { + return users; } - public void setResults(RandomUserResultDAO[] results) { - this.results = results; + public void setUsers(List users) { + this.users = users; } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAO.java index 15e964b..7d79a46 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAO.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAO.java @@ -2,22 +2,22 @@ public class RandomUserResultDAO { private String gender; - private RandomUserNameDAO name; + private String firstName; + private String lastName; private String email; private String phone; - private RandomUserPictureDAO picture; - private String nat; + private String image; public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } - public RandomUserNameDAO getName() { return name; } - public void setName(RandomUserNameDAO name) { this.name = name; } + public String getFirstName() { return firstName; } + public void setFirstName(String firstName) { this.firstName = firstName; } + public String getLastName() { return lastName; } + public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } - public RandomUserPictureDAO getPicture() { return picture; } - public void setPicture(RandomUserPictureDAO picture) { this.picture = picture; } - public String getNat() { return nat; } - public void setNat(String nat) { this.nat = nat; } + public String getImage() { return image; } + public void setImage(String image) { this.image = image; } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java index 53f5716..5bef6ba 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java @@ -1,35 +1,42 @@ package com.xpeho.spring_boot_java_random_user.data.models.db; import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Table; @Table("users") public class User { @Id private Long id; - private String firstName; - private String lastName; + private String gender; + @Column("firstname") + private String firstname; + @Column("lastname") + private String lastname; + private String civility; private String email; - private String pictureUrl; + private String phone; + private String picture; + private String nationality; public User() {} - public User(Long id, String firstName, String lastName, String email, String pictureUrl) { - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - this.email = email; - this.pictureUrl = pictureUrl; - } - public Long getId() { return id; } public void setId(Long id) { this.id = id; } - public String getFirstName() { return firstName; } - public void setFirstName(String firstName) { this.firstName = firstName; } - public String getLastName() { return lastName; } - public void setLastName(String lastName) { this.lastName = lastName; } + public String getGender() { return gender; } + public void setGender(String gender) { this.gender = gender; } + public String getFirstname() { return firstname; } + public void setFirstname(String firstname) { this.firstname = firstname; } + public String getLastname() { return lastname; } + public void setLastname(String lastname) { this.lastname = lastname; } + public String getCivility() { return civility; } + public void setCivility(String civility) { this.civility = civility; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } - public String getPictureUrl() { return pictureUrl; } - public void setPictureUrl(String pictureUrl) { this.pictureUrl = pictureUrl; } + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } + public String getPicture() { return picture; } + public void setPicture(String picture) { this.picture = picture; } + public String getNationality() { return nationality; } + public void setNationality(String nationality) { this.nationality = nationality; } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApi.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApi.java index 3faf096..78eb496 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApi.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApi.java @@ -6,6 +6,6 @@ import retrofit2.http.Query; public interface RandomUserApi { - @GET("/api/") - Call getRandomUsers(@Query("results") int results); + @GET("/users") + Call getRandomUsers(@Query("limit") int limit); } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApiConfig.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApiConfig.java index f0aa109..da0c2f6 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApiConfig.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserApiConfig.java @@ -11,7 +11,7 @@ public class RandomUserApiConfig { @Bean public Retrofit randomUserRetrofit(Environment env) { - String baseUrl = env.getProperty("randomuser.api.base-url", "https://randomuser.me/"); + String baseUrl = env.getProperty("randomuser.api.base-url", "https://dummyjson.com/"); OkHttpClient client = new OkHttpClient.Builder().build(); return new Retrofit.Builder() .baseUrl(baseUrl) diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImpl.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImpl.java index 6d4d648..e30a5ea 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImpl.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImpl.java @@ -1,6 +1,7 @@ package com.xpeho.spring_boot_java_random_user.data.sources.api; import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserResponse; +import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserResultDAO; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; import com.xpeho.spring_boot_java_random_user.domain.services.RandomUserProvider; import com.xpeho.spring_boot_java_random_user.data.converters.UserConverter; @@ -8,7 +9,6 @@ import retrofit2.Response; import java.io.IOException; -import java.util.Arrays; import java.util.List; @Service @@ -27,7 +27,11 @@ public List fetchRandomUsers(int count) throws IOException { if (!response.isSuccessful() || response.body() == null) { throw new IOException("Failed to fetch users: " + response.code()); } - return Arrays.stream(response.body().getResults()) + List users = response.body().getUsers(); + if (users == null) { + throw new IOException("Failed to parse users from response"); + } + return users.stream() .map(userConverter::fromApiModel) .toList(); } diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 1a69b90..2602ffc 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -3,7 +3,7 @@ spring.application.name=spring_boot_java_random_user # Swagger UI custom path springdoc.swagger-ui.path=/api # URL for Random User API -randomuser.api.base-url=https://randomuser.me/ +randomuser.api.base-url=https://dummyjson.com/ spring.datasource.url=jdbc:postgresql://localhost:${POSTGRES_PORT}/${POSTGRES_DB} spring.datasource.username=${POSTGRES_USER} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index df2d4f1..ce63678 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,6 +3,9 @@ spring.application.name=spring_boot_java_random_user # Swagger UI custom path springdoc.swagger-ui.path=/api +# URL for Random User API +randomuser.api.base-url=https://dummyjson.com/ + # Database configuration spring.datasource.url=jdbc:postgresql://localhost:${POSTGRES_PORT}/${POSTGRES_DB} spring.datasource.username=${POSTGRES_USER} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 2140b5c..9ccd248 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,6 +1,6 @@ -DROP TABLE IF EXISTS "user"; +DROP TABLE IF EXISTS "users"; -CREATE TABLE IF NOT EXISTS "user" +CREATE TABLE IF NOT EXISTS "users" ( id SERIAL PRIMARY KEY, gender VARCHAR(20), 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/SpringBootJavaRandomUserApplicationTests.java index 21bcb10..1790fc3 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/SpringBootJavaRandomUserApplicationTests.java @@ -1,13 +1,16 @@ package com.xpeho.spring_boot_java_random_user; +import com.xpeho.spring_boot_java_random_user.data.sources.database.UserRepository; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; -@SpringBootTest @ActiveProfiles("test") class SpringBootJavaRandomUserApplicationTests { + @MockitoBean + UserRepository userRepository; + @Test void contextLoads() { } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java index 630a57b..bc97c5d 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java @@ -15,49 +15,36 @@ class UserConverterTest { @Test void fromApiModel_fullData() { - RandomUserNameDAO name = new RandomUserNameDAO(); - name.setFirst("John"); - name.setLast("Doe"); - name.setTitle("Mr"); - RandomUserPictureDAO picture = new RandomUserPictureDAO(); - picture.setMedium("pic.jpg"); RandomUserResultDAO api = new RandomUserResultDAO(); api.setGender("male"); - api.setName(name); + api.setFirstName("John"); + api.setLastName("Doe"); api.setEmail("john@doe.com"); api.setPhone("1234"); - api.setPicture(picture); - api.setNat("FR"); + api.setImage("pic.jpg"); UserEntity entity = converter.fromApiModel(api); assertNull(entity.id()); assertEquals("male", entity.gender()); assertEquals("John", entity.firstname()); assertEquals("Doe", entity.lastname()); - assertEquals("Mr", entity.civility()); assertEquals("john@doe.com", entity.email()); assertEquals("1234", entity.phone()); assertEquals("pic.jpg", entity.picture()); - assertEquals("FR", entity.nat()); } @Test - void fromApiModel_nullNameAndPicture() { + void fromApiModel_nullFields() { RandomUserResultDAO api = new RandomUserResultDAO(); api.setGender("female"); - api.setName(null); api.setEmail("jane@doe.com"); api.setPhone("5678"); - api.setPicture(null); - api.setNat("US"); UserEntity entity = converter.fromApiModel(api); assertNull(entity.firstname()); assertNull(entity.lastname()); - assertNull(entity.civility()); assertNull(entity.picture()); assertEquals("female", entity.gender()); assertEquals("jane@doe.com", entity.email()); assertEquals("5678", entity.phone()); - assertEquals("US", entity.nat()); } @Test @@ -74,10 +61,10 @@ void toDto_and_toDao_and_toDomain() { assertEquals(entity.nat(), dto.nat()); User dao = converter.toDao(entity); assertEquals(entity.id(), dao.getId()); - assertEquals(entity.firstname(), dao.getFirstName()); - assertEquals(entity.lastname(), dao.getLastName()); + assertEquals(entity.firstname(), dao.getFirstname()); + assertEquals(entity.lastname(), dao.getLastname()); assertEquals(entity.email(), dao.getEmail()); - assertEquals(entity.picture(), dao.getPictureUrl()); + assertEquals(entity.picture(), dao.getPicture()); UserEntity back = converter.toDomain(dao); assertEquals(entity.id(), back.id()); assertEquals(entity.firstname(), back.firstname()); diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponseTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponseTest.java index 42f8ecf..15e4437 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponseTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponseTest.java @@ -1,22 +1,21 @@ package com.xpeho.spring_boot_java_random_user.data.models.api; import org.junit.jupiter.api.Test; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; class RandomUserResponseTest { @Test - void testGetSetResults() { + void testGetSetUsers() { RandomUserResponse response = new RandomUserResponse(); - RandomUserResultDAO[] arr = new RandomUserResultDAO[2]; - arr[0] = new RandomUserResultDAO(); - arr[1] = new RandomUserResultDAO(); - response.setResults(arr); - assertArrayEquals(arr, response.getResults()); + List users = List.of(new RandomUserResultDAO(), new RandomUserResultDAO()); + response.setUsers(users); + assertEquals(users, response.getUsers()); } @Test - void testDefaultResultsIsNull() { + void testDefaultUsersIsNull() { RandomUserResponse response = new RandomUserResponse(); - assertNull(response.getResults()); + assertNull(response.getUsers()); } } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAOTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAOTest.java index 3f5f725..878d579 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAOTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAOTest.java @@ -8,28 +8,27 @@ class RandomUserResultDAOTest { void testGetSetAllFields() { RandomUserResultDAO result = new RandomUserResultDAO(); result.setGender("male"); - RandomUserNameDAO name = new RandomUserNameDAO(); - result.setName(name); + result.setFirstName("John"); + result.setLastName("Doe"); result.setEmail("john@doe.com"); result.setPhone("1234"); - RandomUserPictureDAO picture = new RandomUserPictureDAO(); - result.setPicture(picture); - result.setNat("FR"); + result.setImage("pic.jpg"); assertEquals("male", result.getGender()); - assertSame(name, result.getName()); + assertEquals("John", result.getFirstName()); + assertEquals("Doe", result.getLastName()); assertEquals("john@doe.com", result.getEmail()); assertEquals("1234", result.getPhone()); - assertSame(picture, result.getPicture()); - assertEquals("FR", result.getNat()); + assertEquals("pic.jpg", result.getImage()); } + @Test void testDefaultFieldsAreNull() { RandomUserResultDAO result = new RandomUserResultDAO(); assertNull(result.getGender()); - assertNull(result.getName()); + assertNull(result.getFirstName()); + assertNull(result.getLastName()); assertNull(result.getEmail()); assertNull(result.getPhone()); - assertNull(result.getPicture()); - assertNull(result.getNat()); + assertNull(result.getImage()); } } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImplTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImplTest.java index 4616ec4..eef5413 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImplTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImplTest.java @@ -35,7 +35,7 @@ void fetchRandomUsers_success() throws IOException { RandomUserResponse responseObj = new RandomUserResponse(); RandomUserResultDAO dao1 = new RandomUserResultDAO(); RandomUserResultDAO dao2 = new RandomUserResultDAO(); - responseObj.setResults(new RandomUserResultDAO[]{dao1, dao2}); + responseObj.setUsers(List.of(dao1, dao2)); UserEntity entity1 = new UserEntity(null, "a", "b", "c", "d", "e", "f", "g", "h"); UserEntity entity2 = new UserEntity(null, "i", "j", "k", "l", "m", "n", "o", "p"); when(randomUserApi.getRandomUsers(count)).thenReturn(call); From ef40e333699a23ed84151fa7a60dc32aa1889022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Leb=C3=A8gue?= Date: Mon, 2 Mar 2026 16:24:14 +0100 Subject: [PATCH 08/11] feat(tests): enhance SpringBootJavaRandomUserApplicationTests with application context validation --- .../data/models/db/User.java | 1 + .../SpringBootJavaRandomUserApplicationTests.java | 10 ++++++++++ .../data/converters/UserConverterTest.java | 2 -- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java index 5bef6ba..f0c7203 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java @@ -19,6 +19,7 @@ public class User { private String picture; private String nationality; + // Required by Spring Data JDBC to instantiate the entity via reflection public User() {} public Long getId() { return id; } 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/SpringBootJavaRandomUserApplicationTests.java index 1790fc3..2d3e234 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/SpringBootJavaRandomUserApplicationTests.java @@ -2,17 +2,27 @@ import com.xpeho.spring_boot_java_random_user.data.sources.database.UserRepository; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest @ActiveProfiles("test") class SpringBootJavaRandomUserApplicationTests { + @Autowired + ApplicationContext applicationContext; + @MockitoBean UserRepository userRepository; @Test void contextLoads() { + assertThat(applicationContext).isNotNull(); } } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java index bc97c5d..efdbbcb 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java @@ -1,8 +1,6 @@ package com.xpeho.spring_boot_java_random_user.data.converters; import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserResultDAO; -import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserNameDAO; -import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserPictureDAO; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; import com.xpeho.spring_boot_java_random_user.data.models.db.User; import com.xpeho.spring_boot_java_random_user.presentation.dto.UserDTO; From 216a8f8e8abf667f86723c06fee51b43d41eea87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Leb=C3=A8gue?= Date: Mon, 2 Mar 2026 16:26:34 +0100 Subject: [PATCH 09/11] feat(models): add comment to User constructor for clarity on field initialization --- .../spring_boot_java_random_user/data/models/db/User.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java index f0c7203..7351350 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/db/User.java @@ -20,7 +20,9 @@ public class User { private String nationality; // Required by Spring Data JDBC to instantiate the entity via reflection - public User() {} + public User() { + // No initialization needed; fields are populated by Spring Data JDBC after instantiation + } public Long getId() { return id; } public void setId(Long id) { this.id = id; } From b57fd2286209eaefad32ab0084f61278cdc12cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Leb=C3=A8gue?= Date: Tue, 3 Mar 2026 10:38:57 +0100 Subject: [PATCH 10/11] feat(api): refactor user handling by removing DTO and updating controller to use UserEntity directly --- .../data/converters/UserConverter.java | 16 ---------------- .../data/models/RandomUserPictureDAO.java | 13 ------------- .../presentation/controllers/UserController.java | 4 ++-- .../presentation/dto/UserDTO.java | 13 ------------- .../presentation/handlers/UserHandler.java | 11 +++-------- .../data/converters/UserConverterTest.java | 12 +----------- 6 files changed, 6 insertions(+), 63 deletions(-) delete mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserPictureDAO.java delete mode 100644 src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserDTO.java 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 7caec7e..92161c8 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 @@ -3,7 +3,6 @@ import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; import com.xpeho.spring_boot_java_random_user.data.models.db.User; import org.springframework.stereotype.Service; -import com.xpeho.spring_boot_java_random_user.presentation.dto.UserDTO; @Service @@ -50,19 +49,4 @@ public UserEntity fromApiModel(RandomUserResultDAO model) { null ); } - - // Domain -> DTO - public UserDTO toDto(UserEntity entity) { - return new UserDTO( - entity.id(), - entity.gender(), - entity.firstname(), - entity.lastname(), - entity.civility(), - entity.email(), - entity.phone(), - entity.picture(), - entity.nat() - ); - } } diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserPictureDAO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserPictureDAO.java deleted file mode 100644 index 2e9c567..0000000 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/data/models/RandomUserPictureDAO.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.xpeho.spring_boot_java_random_user.data.models; - -public class RandomUserPictureDAO { - private String medium; - - public String getMedium() { - return medium; - } - - public void setMedium(String medium) { - this.medium = medium; - } -} 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 a0f5cce..530f82d 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/controllers/UserController.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/controllers/UserController.java @@ -2,7 +2,7 @@ import org.springframework.http.ResponseEntity; import java.util.List; -import com.xpeho.spring_boot_java_random_user.presentation.dto.UserDTO; +import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import org.springframework.web.bind.annotation.GetMapping; @@ -31,7 +31,7 @@ public interface UserController { @ApiResponse(responseCode = "200", description = "List of users successfully fetched and saved") @ApiResponse(responseCode = "500", description = "Internal server error") @ApiResponse(responseCode = "503", description = "External service unavailable") - ResponseEntity> getRandomUsers( + ResponseEntity> getRandomUsers( @RequestParam(defaultValue = "500") @Min(1) @Max(5000) diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserDTO.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserDTO.java deleted file mode 100644 index f23f4ac..0000000 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/dto/UserDTO.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.xpeho.spring_boot_java_random_user.presentation.dto; - -public record UserDTO( - Long id, - String gender, - String firstname, - String lastname, - String civility, - String email, - String phone, - String picture, - String nat -) {} diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/handlers/UserHandler.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/handlers/UserHandler.java index e9d2ec7..50f860a 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 @@ -1,8 +1,6 @@ package com.xpeho.spring_boot_java_random_user.presentation.handlers; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; -import com.xpeho.spring_boot_java_random_user.presentation.dto.UserDTO; -import com.xpeho.spring_boot_java_random_user.data.converters.UserConverter; import com.xpeho.spring_boot_java_random_user.domain.usecases.FetchAndSaveRandomUsersUseCase; import com.xpeho.spring_boot_java_random_user.presentation.controllers.UserController; import org.slf4j.Logger; @@ -22,19 +20,16 @@ public class UserHandler implements UserController { private static final Logger logger = LoggerFactory.getLogger(UserHandler.class); private final FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase; - private final UserConverter userConverter; - public UserHandler(FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase, UserConverter userConverter) { + public UserHandler(FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase) { this.fetchAndSaveRandomUsersUseCase = fetchAndSaveRandomUsersUseCase; - this.userConverter = userConverter; } @Override - public ResponseEntity> getRandomUsers(int count) { + public ResponseEntity> getRandomUsers(int count) { try { List users = fetchAndSaveRandomUsersUseCase.execute(count); - List dtos = users.stream().map(userConverter::toDto).toList(); - return ResponseEntity.ok(dtos); + return ResponseEntity.ok(users); } catch (IOException e) { logger.error("Error fetching random users: {}", e.getMessage(), e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java index efdbbcb..97e80db 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java @@ -3,7 +3,6 @@ import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserResultDAO; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; import com.xpeho.spring_boot_java_random_user.data.models.db.User; -import com.xpeho.spring_boot_java_random_user.presentation.dto.UserDTO; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -46,17 +45,8 @@ void fromApiModel_nullFields() { } @Test - void toDto_and_toDao_and_toDomain() { + void toDao_and_toDomain() { UserEntity entity = new UserEntity(1L, "male", "John", "Doe", "Mr", "john@doe.com", "1234", "pic.jpg", "FR"); - UserDTO dto = converter.toDto(entity); - assertEquals(entity.id(), dto.id()); - assertEquals(entity.firstname(), dto.firstname()); - assertEquals(entity.lastname(), dto.lastname()); - assertEquals(entity.civility(), dto.civility()); - assertEquals(entity.email(), dto.email()); - assertEquals(entity.phone(), dto.phone()); - assertEquals(entity.picture(), dto.picture()); - assertEquals(entity.nat(), dto.nat()); User dao = converter.toDao(entity); assertEquals(entity.id(), dao.getId()); assertEquals(entity.firstname(), dao.getFirstname()); From 89f6860660b2851858f929b096c2ee89d4f01405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Leb=C3=A8gue?= Date: Tue, 3 Mar 2026 10:44:56 +0100 Subject: [PATCH 11/11] feat(tests): enhance test method names with descriptive display annotations for clarity --- .../SpringBootJavaRandomUserApplicationTests.java | 4 +++- .../data/converters/UserConverterTest.java | 10 +++++++--- .../data/models/api/RandomUserNameDAOTest.java | 7 +++++-- .../data/models/api/RandomUserPictureDAOTest.java | 7 +++++-- .../data/models/api/RandomUserResponseTest.java | 7 +++++-- .../data/models/api/RandomUserResultDAOTest.java | 7 +++++-- .../sources/api/RandomUserProviderImplTest.java | 10 +++++++--- .../FetchAndSaveRandomUsersUseCaseTest.java | 13 +++++++++---- 8 files changed, 46 insertions(+), 19 deletions(-) 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/SpringBootJavaRandomUserApplicationTests.java index 2d3e234..073dbe8 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/SpringBootJavaRandomUserApplicationTests.java @@ -1,6 +1,7 @@ package com.xpeho.spring_boot_java_random_user; import com.xpeho.spring_boot_java_random_user.data.sources.database.UserRepository; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -21,7 +22,8 @@ class SpringBootJavaRandomUserApplicationTests { UserRepository userRepository; @Test - void contextLoads() { + @DisplayName("Spring context should load successfully") + void springContextShouldLoadSuccessfully() { assertThat(applicationContext).isNotNull(); } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java index 97e80db..e2afd04 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/converters/UserConverterTest.java @@ -3,6 +3,7 @@ import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserResultDAO; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; import com.xpeho.spring_boot_java_random_user.data.models.db.User; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -11,7 +12,8 @@ class UserConverterTest { private final UserConverter converter = new UserConverter(); @Test - void fromApiModel_fullData() { + @DisplayName("Should convert API model to domain entity with all fields") + void shouldConvertApiModelToDomainWithAllFields() { RandomUserResultDAO api = new RandomUserResultDAO(); api.setGender("male"); api.setFirstName("John"); @@ -30,7 +32,8 @@ void fromApiModel_fullData() { } @Test - void fromApiModel_nullFields() { + @DisplayName("Should handle missing fields when converting API model") + void shouldHandleMissingFieldsFromApiModel() { RandomUserResultDAO api = new RandomUserResultDAO(); api.setGender("female"); api.setEmail("jane@doe.com"); @@ -45,7 +48,8 @@ void fromApiModel_nullFields() { } @Test - void toDao_and_toDomain() { + @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); assertEquals(entity.id(), dao.getId()); diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAOTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAOTest.java index ea8c3d0..63bc65a 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAOTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserNameDAOTest.java @@ -1,11 +1,13 @@ package com.xpeho.spring_boot_java_random_user.data.models.api; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class RandomUserNameDAOTest { @Test - void testGetSetAllFields() { + @DisplayName("Should store and return all name fields") + void shouldStoreAndReturnAllNameFields() { RandomUserNameDAO name = new RandomUserNameDAO(); name.setFirst("John"); name.setLast("Doe"); @@ -15,7 +17,8 @@ void testGetSetAllFields() { assertEquals("Mr", name.getTitle()); } @Test - void testDefaultFieldsAreNull() { + @DisplayName("Should have null fields by default") + void shouldHaveNullFieldsByDefault() { RandomUserNameDAO name = new RandomUserNameDAO(); assertNull(name.getFirst()); assertNull(name.getLast()); diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAOTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAOTest.java index 398b8f1..8ea3498 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAOTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserPictureDAOTest.java @@ -1,17 +1,20 @@ package com.xpeho.spring_boot_java_random_user.data.models.api; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class RandomUserPictureDAOTest { @Test - void testGetSetMedium() { + @DisplayName("Should store and return medium picture URL") + void shouldStoreAndReturnMediumPictureUrl() { RandomUserPictureDAO picture = new RandomUserPictureDAO(); picture.setMedium("pic.jpg"); assertEquals("pic.jpg", picture.getMedium()); } @Test - void testDefaultMediumIsNull() { + @DisplayName("Should have null medium by default") + void shouldHaveNullMediumByDefault() { RandomUserPictureDAO picture = new RandomUserPictureDAO(); assertNull(picture.getMedium()); } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponseTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponseTest.java index 15e4437..6fbbe64 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponseTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResponseTest.java @@ -1,12 +1,14 @@ package com.xpeho.spring_boot_java_random_user.data.models.api; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.List; import static org.junit.jupiter.api.Assertions.*; class RandomUserResponseTest { @Test - void testGetSetUsers() { + @DisplayName("Should store and return users list") + void shouldStoreAndReturnUsersList() { RandomUserResponse response = new RandomUserResponse(); List users = List.of(new RandomUserResultDAO(), new RandomUserResultDAO()); response.setUsers(users); @@ -14,7 +16,8 @@ void testGetSetUsers() { } @Test - void testDefaultUsersIsNull() { + @DisplayName("Should have null users list by default") + void shouldHaveNullUsersListByDefault() { RandomUserResponse response = new RandomUserResponse(); assertNull(response.getUsers()); } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAOTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAOTest.java index 878d579..1ba34ba 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAOTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/models/api/RandomUserResultDAOTest.java @@ -1,11 +1,13 @@ package com.xpeho.spring_boot_java_random_user.data.models.api; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class RandomUserResultDAOTest { @Test - void testGetSetAllFields() { + @DisplayName("Should store and return all user fields") + void shouldStoreAndReturnAllUserFields() { RandomUserResultDAO result = new RandomUserResultDAO(); result.setGender("male"); result.setFirstName("John"); @@ -22,7 +24,8 @@ void testGetSetAllFields() { } @Test - void testDefaultFieldsAreNull() { + @DisplayName("Should have null fields by default") + void shouldHaveNullFieldsByDefault() { RandomUserResultDAO result = new RandomUserResultDAO(); assertNull(result.getGender()); assertNull(result.getFirstName()); diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImplTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImplTest.java index eef5413..aa6cccf 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImplTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/data/sources/api/RandomUserProviderImplTest.java @@ -5,6 +5,7 @@ import com.xpeho.spring_boot_java_random_user.data.models.api.RandomUserResultDAO; import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import retrofit2.Call; import retrofit2.Response; @@ -30,7 +31,8 @@ void setUp() { } @Test - void fetchRandomUsers_success() throws IOException { + @DisplayName("Should fetch and convert users successfully") + void shouldFetchAndConvertUsersSuccessfully() throws IOException { int count = 2; RandomUserResponse responseObj = new RandomUserResponse(); RandomUserResultDAO dao1 = new RandomUserResultDAO(); @@ -50,7 +52,8 @@ void fetchRandomUsers_success() throws IOException { } @Test - void fetchRandomUsers_unsuccessfulResponse_throwsIOException() throws IOException { + @DisplayName("Should throw IOException when API returns error") + void shouldThrowIOExceptionWhenApiReturnsError() throws IOException { int count = 1; when(randomUserApi.getRandomUsers(count)).thenReturn(call); when(call.execute()).thenReturn(Response.error(500, okhttp3.ResponseBody.create(null, "error"))); @@ -58,7 +61,8 @@ void fetchRandomUsers_unsuccessfulResponse_throwsIOException() throws IOExceptio } @Test - void fetchRandomUsers_nullBody_throwsIOException() throws IOException { + @DisplayName("Should throw IOException when response body is null") + void shouldThrowIOExceptionWhenResponseBodyIsNull() throws IOException { int count = 1; when(randomUserApi.getRandomUsers(count)).thenReturn(call); when(call.execute()).thenReturn(Response.success(null)); 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 c3d1bea..2648b0c 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 @@ -4,6 +4,7 @@ import com.xpeho.spring_boot_java_random_user.domain.services.RandomUserProvider; 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 java.io.IOException; @@ -26,7 +27,8 @@ void setUp() { } @Test - void testSuccessfulApiResponse() throws IOException { + @DisplayName("Should fetch users from API and save them") + void shouldFetchUsersFromApiAndSaveThem() throws IOException { List fetched = List.of(new UserEntity( 1L, "male", "John", "Doe", "Mr", "john@doe.com", "1234", "pic.jpg", "FR" )); when(randomUserProvider.fetchRandomUsers(2)).thenReturn(fetched); @@ -38,14 +40,16 @@ void testSuccessfulApiResponse() throws IOException { } @Test - void testApiThrowsIOException() throws IOException { + @DisplayName("Should propagate IOException when API fails") + void shouldPropagateIOExceptionWhenApiFails() throws IOException { when(randomUserProvider.fetchRandomUsers(1)).thenThrow(new IOException("API error")); IOException ex = assertThrows(IOException.class, () -> useCase.execute(1)); assertEquals("API error", ex.getMessage()); } @Test - void testNullResponse() throws IOException { + @DisplayName("Should handle null response gracefully") + void shouldHandleNullResponseGracefully() throws IOException { when(randomUserProvider.fetchRandomUsers(1)).thenReturn(null); when(userService.saveAll(null)).thenReturn(null); List result = useCase.execute(1); @@ -53,7 +57,8 @@ void testNullResponse() throws IOException { } @Test - void testEmptyResultArray() throws IOException { + @DisplayName("Should return empty list when no users found") + void shouldReturnEmptyListWhenNoUsersFound() throws IOException { when(randomUserProvider.fetchRandomUsers(0)).thenReturn(Collections.emptyList()); when(userService.saveAll(Collections.emptyList())).thenReturn(Collections.emptyList()); List result = useCase.execute(0);