diff --git a/README.md b/README.md
index f4cb016..8ca11f4 100644
--- a/README.md
+++ b/README.md
@@ -1,311 +1,561 @@
-# spring_boot_java_random_user
+# Spring Boot Java Random User API
-Spring Boot REST API that consumes the public [Random User](https://randomuser.me/) API and persists data in a PostgreSQL database.
-Interactive API documentation is available via **Swagger UI / OpenAPI**.
+
+
+[](https://www.oracle.com/java/)
+[](https://spring.io/projects/spring-boot)
+[](https://www.postgresql.org/)
+[](LICENSE)
+
+A modern Spring Boot REST API that consumes the public [Random User](https://randomuser.me/) API and persists data in a PostgreSQL database. Includes interactive API documentation via **Swagger UI / OpenAPI**.
+
+[Features](#-features) โข [Quick Start](#-quick-start) โข [API Endpoints](#-api-endpoints) โข [Testing](#-testing) โข [Architecture](#-architecture)
+
+
+
+---
+
+## ๐ Table of Contents
+
+1. [Features](#-features)
+2. [Tech Stack](#-tech-stack)
+3. [Prerequisites](#-prerequisites)
+4. [Quick Start](#-quick-start)
+5. [Configuration](#-configuration)
+6. [Running the Project](#-running-the-project)
+7. [API Documentation](#-api-documentation)
+8. [Testing](#-testing)
+9. [Architecture](#-architecture)
+10. [Database Schema](#-database-schema)
+11. [Monitoring](#-monitoring)
+12. [Quality & CI/CD](#-quality--cicd)
+13. [Troubleshooting](#-troubleshooting)
+14. [Todo](#-todo)
+
+---
+
+## โจ Features
+
+- โ
**RESTful API** - Fetch and manage random users
+- โ
**PostgreSQL Integration** - Persist data with Spring Data JDBC
+- โ
**OpenAPI/Swagger** - Interactive API documentation at `/api`
+- โ
**Docker Support** - Easy setup with Docker Compose
+- โ
**Health Monitoring** - Spring Actuator endpoints
+- โ
**Code Quality** - SonarQube integration via GitHub Actions
+- โ
**Test Coverage** - JUnit 5 + Mockito + JaCoCo
+- โ
**BDD Testing** - Cucumber support (in progress)
+- โ
**Environment Management** - dotenv-java for secure configuration
---
## ๐ ๏ธ Tech Stack
-| Technology | Version |
-|---|---|
-| Java | 25 |
-| Spring Boot | 4.0.3 |
-| Spring Web MVC | (managed by Boot) |
-| Spring Data JDBC | (managed by Boot) |
-| Spring Actuator | (managed by Boot) |
-| springdoc-openapi | 3.0.1 |
-| PostgreSQL | 15 |
-| dotenv-java | 5.2.2 |
-| Docker / Docker Compose | - |
-| Maven | Wrapper included |
+| Component | Version | Purpose |
+|-----------|---------|---------|
+| **Java** | 25 | Language |
+| **Spring Boot** | 4.0.3 | Framework |
+| **Spring Web MVC** | (auto) | REST API |
+| **Spring Data JDBC** | (auto) | Database access |
+| **Spring Actuator** | (auto) | Monitoring |
+| **PostgreSQL** | 15 | Database |
+| **H2** | (test) | In-memory DB for tests |
+| **springdoc-openapi** | 3.0.1 | OpenAPI/Swagger |
+| **dotenv-java** | 5.2.2 | Environment config |
+| **Docker Compose** | - | Local infrastructure |
+| **Maven** | Wrapper | Build tool |
+| **JUnit 5** | (auto) | Testing framework |
+| **Mockito** | (auto) | Mocking |
+| **JaCoCo** | 0.8.14 | Code coverage |
---
-## ๐ Prerequisites
+## ๐ฆ Prerequisites
-- **Java 25+**
-- **Maven** (or use the included `./mvnw` wrapper)
-- **Docker Desktop**
+- **Java 25+** (or compatible JDK)
+- **Maven 3.8+** (included wrapper available)
+- **Docker Desktop** (for PostgreSQL)
+- **Git** (for version control)
---
-## โ๏ธ Configuration
+## ๐ Quick Start
+
+### 1. Clone the repository
-### 1. Environment variables
+```bash
+git clone https://github.com/XPEHO/spring_boot_java_random_user.git
+cd spring_boot_java_random_user
+```
-Copy the template and fill in the values:
+### 2. Set up environment variables
```bash
cp .env.template .env
+# Edit .env with your PostgreSQL credentials
```
-`.env` content:
+### 3. Start PostgreSQL
-```env
-POSTGRES_USER={POSTGRES_USER}
-POSTGRES_PASSWORD={POSTGRES_PASSWORD}
-POSTGRES_DB={POSTGRES_DB}
-POSTGRES_PORT={POSTGRES_PORT}
+```bash
+docker-compose up -d
```
-> โ ๏ธ The `.env` file is git-ignored. Never commit it.
-
-### 2. Test properties
+### 4. Run the application
```bash
-cp src/test/resources/application-test.properties.template src/test/resources/application-test.properties
+./mvnw spring-boot:run
```
-> โ ๏ธ This file is also git-ignored.
-But for credentials and local overrides, use:
+### 5. Access the API
-```
-src/main/resources/application-local.properties
-```
+- **Swagger UI**: http://localhost:8080/api
+- **Health Check**: http://localhost:8080/actuator/health
-### 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
+## โ๏ธ Configuration
+
+### Environment Setup
+
+#### .env file (git-ignored)
+
+```bash
+cp .env.template .env
```
-### Local credentials & security
+**Content:**
+```env
+POSTGRES_USER=your_user
+POSTGRES_PASSWORD=your_password
+POSTGRES_DB=your_database
+POSTGRES_PORT=5432
+```
-To avoid exposing database credentials in source code, create a `.env` file at the project root.
+#### Local Profile (application-local.properties)
-Then, in `src/main/resources/application-local.properties`:
+For local development with different credentials:
```properties
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
```
-Both `.env` and `application-local.properties` are in `.gitignore` (already set).
+Set `SPRING_PROFILES_ACTIVE=local` in your IDE run configuration.
-To activate the local profile in IntelliJ or VS Code, add this environment variable to your run configuration:
+#### Test Profile (application-test.properties)
-```
-SPRING_PROFILES_ACTIVE=local
-```
+H2 in-memory database for unit tests (auto-configured, no setup needed).
-This way, each developer can use their own credentials without risk of leaking them to GitHub.
+### Default Configuration
+**application.properties** includes:
-> โ ๏ธ **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
-> ```
+```properties
+spring.application.name=spring_boot_java_random_user
+spring.datasource.url=jdbc:postgresql://localhost:${POSTGRES_PORT}/${POSTGRES_DB}
+spring.datasource.driver-class-name=org.postgresql.Driver
+springdoc.swagger-ui.path=/api
+randomuser.api.base-url=https://dummyjson.com/
+```
---
-## ๐ Running the project
-
-### Start the database
-
-```bash
-docker-compose up -d
-```
+## ๐ Running the Project
-### With the Maven Wrapper (recommended)
+### Development Mode
```bash
+# Using Maven wrapper (recommended)
./mvnw spring-boot:run
-```
-
-### With Maven installed
-```bash
+# Or with Maven installed
mvn spring-boot:run
```
-### Build and run the JAR
+### Production Mode
```bash
+# Build JAR
./mvnw clean package
+
+# Run JAR
java -jar target/spring_boot_java_random_user-0.0.1-SNAPSHOT.jar
```
-The application will be available at: http://localhost:8080
+### Docker Mode
+
+```bash
+# Build Docker image
+docker build -t xpeho/spring-boot-random-user .
+
+# Run container
+docker run -p 8080:8080 --env-file .env xpeho/spring-boot-random-user
+```
+
+Application will be available at: **http://localhost:8080**
---
-## ๐ API Documentation (Swagger UI)
+## ๐ API Documentation
+
+### Interactive Swagger UI
```
http://localhost:8080/api
```
-Raw OpenAPI specification (JSON):
+### OpenAPI JSON Specification
```
http://localhost:8080/v3/api-docs
```
-### Available endpoints
+### Available Endpoints
-#### Generate and persist random users
+#### ๐ต GET /random-users
+
+Fetch and persist random users from the external API.
```http
GET /random-users?count=500
```
-#### Update an existing user
+**Parameters:**
+- `count` (optional): Number of users to fetch (default: 500, max: 5000)
+
+**Response:** `200 OK`
+```json
+[
+ {
+ "id": 1,
+ "gender": "male",
+ "firstname": "John",
+ "lastname": "Doe",
+ "civility": "Mr",
+ "email": "john.doe@example.com",
+ "phone": "+1 234 567 8900",
+ "picture": "https://example.com/pic.jpg",
+ "nat": "US"
+ }
+]
+```
+
+#### ๐ข GET /random-users/{id}
+
+Retrieve a specific user by ID.
```http
-PUT /random-users/{id}
-Content-Type: application/json
+GET /random-users/1
```
-Example body:
+**Response:** `200 OK` or `404 Not Found`
+#### ๐ก PUT /random-users/{id}
+
+Update an existing user's information.
+
+```http
+PUT /random-users/1
+Content-Type: application/json
+```
+
+**Request Body:**
```json
{
"gender": "female",
- "firstname": "Albert",
- "lastname": "Bing",
- "civility": "Mrs",
- "email": "albert.bing@example.com",
- "phone": "123456789",
+ "firstname": "Jane",
+ "lastname": "Smith",
+ "civility": "Ms",
+ "email": "jane.smith@example.com",
+ "phone": "+1 987 654 3210",
"picture": "pic.jpg",
"nat": "FR"
}
```
-Responses:
-- `200` if the user was updated successfully
-- `404` if the user id does not exist
+**Responses:**
+- `200 OK` - User updated successfully
+- `404 Not Found` - User not found
---
-## ๐ Monitoring (Actuator)
+## ๐งช Testing
+
+### Run All Tests
+```bash
+./mvnw verify
```
-http://localhost:8080/actuator
-http://localhost:8080/actuator/health
+
+Test execution lifecycle:
+1. **pre-integration-test** โ Docker Compose starts PostgreSQL
+2. **integration-test** โ Unit and integration tests run
+3. **post-integration-test** โ Docker Compose stops
+
+### Run Specific Test Suite
+
+```bash
+# Unit tests only
+./mvnw test
+
+# Integration tests only
+./mvnw failsafe:integration-test
+
+# Tests with specific tag (Cucumber)
+./mvnw test -Dcucumber.filter.tags="@smoke"
+```
+
+### Test Coverage Report
+
+```bash
+./mvnw clean verify
```
+Generated reports:
+- **HTML Report**: `target/site/jacoco/index.html`
+- **XML Report**: `target/site/jacoco/jacoco.xml` (used by SonarQube)
+
+### Test Framework Details
+
+- **Framework**: JUnit 5
+- **Mocking**: Mockito
+- **BDD**: Cucumber (planned)
+- **Coverage**: JaCoCo
+
---
-## ๐งช Tests
+## ๐๏ธ Architecture
-The Maven plugin starts and stops Docker Compose automatically during tests:
+### Project Structure
-```bash
-./mvnw verify
+```
+spring_boot_java_random_user/
+โโโ src/
+โ โโโ main/
+โ โ โโโ java/com/xpeho/spring_boot_java_random_user/
+โ โ โ โโโ SpringBootJavaRandomUserApplication.java โ Entry point
+โ โ โ โโโ config/ โ Spring configuration
+โ โ โ โโโ data/ โ Data layer
+โ โ โ โ โโโ converters/ โ DTO/Entity conversion
+โ โ โ โ โโโ models/ โ Data models
+โ โ โ โ โโโ services/ โ Data services
+โ โ โ โ โโโ sources/ โ API & DB sources
+โ โ โ โโโ domain/ โ Business logic
+โ โ โ โ โโโ entities/ โ Domain entities
+โ โ โ โ โโโ exceptions/ โ Custom exceptions
+โ โ โ โ โโโ services/ โ Business services
+โ โ โ โ โโโ usecases/ โ Use cases
+โ โ โ โโโ presentation/ โ API layer
+โ โ โ โโโ controllers/ โ REST controllers
+โ โ โ โโโ handlers/ โ Exception handlers
+โ โ โโโ resources/
+โ โ โโโ application.properties โ Main config
+โ โ โโโ application-local.properties โ Local overrides
+โ โ โโโ schema.sql โ DB schema
+โ โ โโโ static/ โ Static assets
+โ โโโ test/
+โ โโโ java/ โ Test code
+โ โโโ resources/
+โ โโโ application-test.properties โ Test config
+โ โโโ features/ โ Cucumber features
+โโโ .github/
+โ โโโ workflows/
+โ โโโ sonar.yaml โ SonarQube CI/CD
+โ โโโ ISSUE_TEMPLATE/ โ GitHub templates
+โโโ docker-compose.yml โ Local infrastructure
+โโโ pom.xml โ Maven config
+โโโ .env.template โ Env variables template
+โโโ .gitignore โ Git ignore rules
+โโโ README.md โ This file
```
-Execution cycle:
-1. `pre-integration-test` โ `docker-compose up` (PostgreSQL starts)
-2. `integration-test` โ tests run
-3. `post-integration-test` โ `docker-compose down` (PostgreSQL stops)
+### Layered Architecture
-> **Prerequisite**: Docker must be installed and running.
+```
+Presentation Layer (Controllers)
+ โ
+Domain Layer (Use Cases, Services)
+ โ
+Data Layer (Repositories, Converters)
+ โ
+Database (PostgreSQL)
+```
---
-## ๐ Code Quality (SonarQube)
+## ๐๏ธ Database Schema
+
+### Users Table
+
+| Column | Type | Constraints | Description |
+|--------|------|-------------|-------------|
+| `id` | SERIAL | PRIMARY KEY | Auto-incremented identifier |
+| `gender` | VARCHAR(20) | - | Gender |
+| `firstname` | VARCHAR(100) | - | First name |
+| `lastname` | VARCHAR(100) | - | Last name |
+| `civility` | VARCHAR(20) | - | Title (Mr, Ms, Mrs, etc.) |
+| `email` | VARCHAR(255) | - | Email address |
+| `phone` | VARCHAR(50) | - | Phone number |
+| `picture` | VARCHAR(500) | - | Avatar/picture URL |
+| `nat` | VARCHAR(10) | - | Nationality code |
+
+### DDL
+
+Auto-generated on startup via `schema.sql`:
+
+```sql
+DROP TABLE IF EXISTS "users";
+
+CREATE TABLE IF NOT EXISTS "users" (
+ id SERIAL PRIMARY KEY,
+ gender VARCHAR(20),
+ firstname VARCHAR(100),
+ lastname VARCHAR(100),
+ civility VARCHAR(20),
+ email VARCHAR(255),
+ phone VARCHAR(50),
+ picture VARCHAR(500),
+ nationality VARCHAR(10)
+);
+```
-A GitHub Actions workflow is configured in `.github/workflows/sonar.yaml`.
+---
-### Workflow triggers
+## ๐ Monitoring
-- `push` on all branches
-- `pull_request`
+### Health Check
-### What it runs
+```
+http://localhost:8080/actuator/health
+```
-- Java 25 setup (Temurin)
-- Maven build + tests + SonarQube analysis:
+### Actuator Endpoints
-```bash
-./mvnw clean verify sonar:sonar -Dsonar.qualitygate.wait=true
```
+http://localhost:8080/actuator
+http://localhost:8080/actuator/health
+http://localhost:8080/actuator/metrics
+http://localhost:8080/actuator/beans
+```
+
+### Metrics
-### Required GitHub Secrets
+- JVM metrics
+- HTTP metrics
+- Database connection pool metrics
-| Secret | Description |
-| `APPLICATION_TEST_PROPERTIES` (Base64-encoded `application-test.properties` content)
-| `SONAR_TOKEN` | SonarQube authentication token |
-| `SONAR_HOST_URL` | SonarQube instance URL |
-| `POSTGRES_USER` | PostgreSQL user |
-| `POSTGRES_PASSWORD` | PostgreSQL password |
-| `POSTGRES_DB` | Database name |
-| `POSTGRES_PORT` | PostgreSQL port |
+---
-> `GITHUB_TOKEN` is provided automatically by GitHub Actions.
+## ๐ Quality & CI/CD
-### JaCoCo coverage report (local)
+### Local SonarQube Analysis
```bash
-./mvnw clean verify
+./mvnw clean verify sonar:sonar
```
-Generated reports:
-- HTML: `target/site/jacoco/index.html`
-- XML (used by SonarQube): `target/site/jacoco/jacoco.xml`
+### GitHub Actions Workflows
+
+#### ๐ฆ SonarQube Analysis (.github/workflows/sonar.yaml)
+
+**Triggers:**
+- Push to any branch
+- Pull requests
+
+**What it does:**
+1. Sets up Java 25
+2. Runs Maven tests with code coverage
+3. Uploads results to SonarQube
+4. Waits for quality gate
+
+**Required Secrets:**
+- `SONAR_TOKEN` - SonarQube authentication
+- `SONAR_HOST_URL` - SonarQube instance URL
+- `POSTGRES_USER` - DB user
+- `POSTGRES_PASSWORD` - DB password
+- `POSTGRES_DB` - DB name
+- `POSTGRES_PORT` - DB port
---
-## ๐ Project structure
+## โ Troubleshooting
+
+### DataSource Configuration Error
+**Error:**
```
-src/
-โโโ main/
-โ โโโ java/com/xpeho/spring_boot_java_random_user/
-โ โ โโโ SpringBootJavaRandomUserApplication.java โ Entry point, loads .env
-โ โโโ resources/
-โ โโโ application.properties โ Spring configuration
-โ โโโ schema.sql โ Database schema
-โโโ test/
- โโโ java/com/xpeho/spring_boot_java_random_user/
- โ โโโ SpringBootJavaRandomUserApplicationTests.java
- โโโ resources/
- โโโ application-test.properties โ Test config (git-ignored)
- โโโ application-test.properties.template โ Template to copy
-.env โ Environment variables (git-ignored)
-.env.template โ Template to copy
-docker-compose.yml โ Local PostgreSQL
+Failed to configure a DataSource: 'url' attribute is not specified
+and no embedded datasource could be configured.
```
----
+**Solution:**
+Ensure `spring.datasource.url` is set in `application.properties` or environment variables.
+
+### PostgreSQL Connection Failed
+
+**Error:**
+```
+org.postgresql.util.PSQLException: Connection to localhost:5432 refused
+```
+
+**Solution:**
+```bash
+# Check if Docker is running
+docker ps
+
+# Start PostgreSQL
+docker-compose up -d
+
+# Verify connection
+psql -U postgres -h localhost
+```
+
+### Tests Fail with H2
+
+**Error:**
+```
+Circular placeholder reference in application-test.properties
+```
-## ๐๏ธ Database
+**Solution:**
+Use the corrected `application-test.properties` with H2 configuration:
+```properties
+spring.datasource.url=jdbc:h2:mem:testdb
+spring.datasource.driver-class-name=org.h2.Driver
+```
-### Schema
+### Java Version Not Supported
-The `user` table is created automatically on startup via `schema.sql`:
+**Error:**
+```
+error: release version 25 not supported
+```
-| Column | Type | Description |
-|---|---|---|
-| `id` | SERIAL PK | Auto-incremented identifier |
-| `gender` | VARCHAR(20) | Gender |
-| `firstname` | VARCHAR(100) | First name (`name.first`) |
-| `lastname` | VARCHAR(100) | Last name (`name.last`) |
-| `civility` | VARCHAR(20) | Title (`name.title`) |
-| `email` | VARCHAR(255) | Email address |
-| `phone` | VARCHAR(50) | Phone number |
-| `picture` | VARCHAR(500) | Medium picture URL |
-| `nationality` | VARCHAR(10) | Nationality |
+**Solution:**
+- Install Java 25 or later
+- Update Maven compiler plugin version in `pom.xml`
---
## ๐ External API
-This project consumes the public **Random User Generator** API:
+This project integrates with **Random User Generator API**:
-- **URL**: [https://randomuser.me/api/](https://randomuser.me/api/)
-- **Documentation**: [https://randomuser.me/documentation](https://randomuser.me/documentation)
+- **URL**: https://randomuser.me/api/
+- **Documentation**: https://randomuser.me/documentation
+- **Features**:
+ - Generate random users
+ - Filter by nationality
+ - Customize response fields
+
+---
+
+## ๐ Future Enhancements
+
+- [ ] [Delete endpoint - DELETE /random-users/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/10)
+- [ ] [Create endpoint - POST /random-users](https://github.com/XPEHO/spring_boot_java_random_user/issues/11)
+- [ ] [Liquibase database migrations](setup-docs/liquibase-guide.md)
+- [ ] [Cucumber BDD testing suite](setup-docs/cucumber-guide-english.md)
---
@@ -313,14 +563,53 @@ 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)
-- [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)
-- [X] [Add this endpoint put /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/9)
+- [x] [Add this endpoint get /user/random](https://github.com/XPEHO/spring_boot_java_random_user/issues/5)
+- [x] [Add this endpoint get /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/8)
+- [x] [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)
---
+## ๐ฅ Contributing
+
+Contributions are welcome! Please follow these steps:
+
+1. Fork the repository
+2. Create a feature branch (`git checkout -b feature/amazing-feature`)
+3. Commit changes (`git commit -m 'Add amazing feature'`)
+4. Push to branch (`git push origin feature/amazing-feature`)
+5. Open a Pull Request
+
+---
+
+## ๐ License
+
+This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.
+
+---
+
## ๐ค Author
-Project developed by **XPEHO**.
+**XPEHO** - [GitHub Organization](https://github.com/XPEHO)
+
+---
+
+## ๐ Useful Links
+
+- [Spring Boot Documentation](https://spring.io/projects/spring-boot)
+- [Random User API Docs](https://randomuser.me/documentation)
+- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
+- [Docker Documentation](https://docs.docker.com/)
+- [SonarQube Documentation](https://docs.sonarqube.org/)
+
+---
+
+
+
+**Made with โค๏ธ by XPEHO**
+
+โญ If you found this helpful, please consider starring the repository!
+
+
+
diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCase.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCase.java
new file mode 100644
index 0000000..4f92988
--- /dev/null
+++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCase.java
@@ -0,0 +1,20 @@
+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.exceptions.UserNotFoundException;
+import com.xpeho.spring_boot_java_random_user.domain.services.UserService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class GetUserByIdUseCase {
+ private final UserService userService;
+
+ public GetUserByIdUseCase(UserService userService) {
+ this.userService = userService;
+ }
+
+ public UserEntity execute(long id) {
+ return userService.getById(id)
+ .orElseThrow(() -> new UserNotFoundException(id));
+ }
+}
diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserUseCase.java b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserUseCase.java
index 26a73f5..df84352 100644
--- a/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserUseCase.java
+++ b/src/main/java/com/xpeho/spring_boot_java_random_user/domain/usecases/UpdateRandomUserUseCase.java
@@ -16,18 +16,18 @@ public UpdateRandomUserUseCase(UserService userService) {
public UserEntity execute(int id, UserRequest user) {
UserEntity existingUser = userService.getById(id)
- .orElseThrow(() -> new UserNotFoundException(id));
+ .orElseThrow(() -> new UserNotFoundException(id));
UserEntity updatedUser = new UserEntity(
- existingUser.id(),
- user.gender(),
- user.firstname(),
- user.lastname(),
- user.civility(),
- user.email(),
- user.phone(),
- user.picture(),
- user.nat()
+ existingUser.id(),
+ user.gender(),
+ user.firstname(),
+ user.lastname(),
+ user.civility(),
+ user.email(),
+ user.phone(),
+ user.picture(),
+ user.nat()
);
return userService.save(updatedUser);
diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/controllers/UserController.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/controllers/UserController.java
index 622f434..c8ad32a 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
@@ -41,6 +41,23 @@ ResponseEntity> getRandomUsers(
@Max(5000)
int count
);
+
+ @GetMapping("/{id}")
+ @Operation(
+ summary = "Get user by id",
+ description = "Given a user by id, return the user if it exists in the database",
+ parameters = {
+ @Parameter(name = "id", description = "id of the requested user")
+ }
+ )
+ @ApiResponse(responseCode = "200", description = "User successfully found and returned")
+ @ApiResponse(responseCode = "404", description = "The requested user does not exist")
+ @ApiResponse(responseCode = "500", description = "Internal server error")
+ ResponseEntity getUserById(
+ @PathVariable
+ int id
+ );
+
@PutMapping("/{id}")
@Operation(
summary = "Modify a random user",
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 49a6203..6bb260f 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
@@ -4,6 +4,7 @@
import com.xpeho.spring_boot_java_random_user.domain.entities.UserRequest;
import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException;
import com.xpeho.spring_boot_java_random_user.domain.usecases.FetchAndSaveRandomUsersUseCase;
+import com.xpeho.spring_boot_java_random_user.domain.usecases.GetUserByIdUseCase;
import com.xpeho.spring_boot_java_random_user.domain.usecases.UpdateRandomUserUseCase;
import com.xpeho.spring_boot_java_random_user.presentation.controllers.UserController;
import org.slf4j.Logger;
@@ -24,13 +25,16 @@ public class UserHandler implements UserController {
private final FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase;
private final UpdateRandomUserUseCase updateRandomUserUseCase;
+ private final GetUserByIdUseCase getUserByIdUseCase;
public UserHandler(
FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase,
- UpdateRandomUserUseCase updateRandomUserUseCase
+ UpdateRandomUserUseCase updateRandomUserUseCase,
+ GetUserByIdUseCase getUserByIdUseCase
) {
this.fetchAndSaveRandomUsersUseCase = fetchAndSaveRandomUsersUseCase;
this.updateRandomUserUseCase = updateRandomUserUseCase;
+ this.getUserByIdUseCase = getUserByIdUseCase;
}
@Override
@@ -55,4 +59,15 @@ public ResponseEntity updateRandomUser(int id, UserRequest user) {
}
}
+ @Override
+ public ResponseEntity getUserById(int id) {
+ try {
+ UserEntity user = getUserByIdUseCase.execute(id);
+ return ResponseEntity.ok(user);
+ } catch (UserNotFoundException e) {
+ logger.warn("warning: the requested user does not exist : {}", e.getMessage(), e);
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
+ }
+ }
+
}
diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCaseTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCaseTest.java
new file mode 100644
index 0000000..f22956c
--- /dev/null
+++ b/src/test/java/com/xpeho/spring_boot_java_random_user/domain/usecases/GetUserByIdUseCaseTest.java
@@ -0,0 +1,73 @@
+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.exceptions.UserNotFoundException;
+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.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class GetUserByIdUseCaseTest {
+ private UserService userService;
+ private GetUserByIdUseCase useCase;
+
+ @BeforeEach
+ void setUp() {
+ userService = mock(UserService.class);
+ useCase = new GetUserByIdUseCase(userService);
+ }
+
+ @Test
+ @DisplayName("Should return user when user exists")
+ void shouldReturnUserWhenFound() {
+ UserEntity expected = new UserEntity(1L, "male", "John", "Doe", "Mr", "john@example.com", "0600000000", "http://pic.jpg", "FR");
+ when(userService.getById(1L)).thenReturn(Optional.of(expected));
+
+ UserEntity result = useCase.execute(1L);
+
+ assertEquals(expected, result);
+ verify(userService).getById(1L);
+ }
+
+ @Test
+ @DisplayName("Should throw UserNotFoundException when user does not exist")
+ void shouldThrowUserNotFoundExceptionWhenUserDoesNotExist() {
+ when(userService.getById(99L)).thenReturn(Optional.empty());
+
+ UserNotFoundException exception = assertThrows(
+ UserNotFoundException.class,
+ () -> useCase.execute(99L)
+ );
+
+ assertTrue(exception.getMessage().contains("99"));
+ verify(userService).getById(99L);
+ }
+
+ @Test
+ @DisplayName("Should call userService exactly once with the given id")
+ void shouldCallUserServiceOnce() {
+ UserEntity user = new UserEntity(5L, "female", "Alice", "Smith", "Ms", "alice@example.com", "0611111111", "http://pic2.jpg", "US");
+ when(userService.getById(5L)).thenReturn(Optional.of(user));
+
+ useCase.execute(5L);
+
+ verify(userService, times(1)).getById(5L);
+ verifyNoMoreInteractions(userService);
+ }
+
+ @Test
+ @DisplayName("Should not call userService with a different id")
+ void shouldNotCallUserServiceWithDifferentId() {
+ UserEntity user = new UserEntity(3L, "male", "Bob", "Brown", "Mr", "bob@example.com", "0622222222", "http://pic3.jpg", "DE");
+ when(userService.getById(3L)).thenReturn(Optional.of(user));
+
+ useCase.execute(3L);
+
+ verify(userService, never()).getById(42L);
+ }
+}
diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserHandlerTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserHandlerTest.java
new file mode 100644
index 0000000..8debcad
--- /dev/null
+++ b/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserHandlerTest.java
@@ -0,0 +1,120 @@
+package com.xpeho.spring_boot_java_random_user.presentation;
+
+import com.xpeho.spring_boot_java_random_user.domain.entities.UserEntity;
+import com.xpeho.spring_boot_java_random_user.domain.entities.UserRequest;
+import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException;
+import com.xpeho.spring_boot_java_random_user.domain.usecases.FetchAndSaveRandomUsersUseCase;
+import com.xpeho.spring_boot_java_random_user.domain.usecases.GetUserByIdUseCase;
+import com.xpeho.spring_boot_java_random_user.domain.usecases.UpdateRandomUserUseCase;
+import com.xpeho.spring_boot_java_random_user.presentation.handlers.UserHandler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class UserHandlerTest {
+
+ private FetchAndSaveRandomUsersUseCase fetchAndSaveRandomUsersUseCase;
+ private UpdateRandomUserUseCase updateRandomUserUseCase;
+ private GetUserByIdUseCase getUserByIdUseCase;
+ private UserHandler userHandler;
+
+ @BeforeEach
+ void setUp() {
+ fetchAndSaveRandomUsersUseCase = mock(FetchAndSaveRandomUsersUseCase.class);
+ updateRandomUserUseCase = mock(UpdateRandomUserUseCase.class);
+ getUserByIdUseCase = mock(GetUserByIdUseCase.class);
+ userHandler = new UserHandler(fetchAndSaveRandomUsersUseCase, updateRandomUserUseCase, getUserByIdUseCase);
+ }
+
+ @Test
+ @DisplayName("Should return 200 and users when getRandomUsers succeeds")
+ void shouldReturnOkWhenGetRandomUsersSucceeds() throws IOException {
+ List users = List.of(
+ new UserEntity(1L, "male", "John", "Doe", "Mr", "john@example.com", "0600000000", "pic.jpg", "FR")
+ );
+ when(fetchAndSaveRandomUsersUseCase.execute(2)).thenReturn(users);
+
+ ResponseEntity> response = userHandler.getRandomUsers(2);
+
+ assertEquals(HttpStatus.OK, response.getStatusCode());
+ assertEquals(users, response.getBody());
+ verify(fetchAndSaveRandomUsersUseCase, times(1)).execute(2);
+ }
+
+ @Test
+ @DisplayName("Should return 500 and empty list when getRandomUsers throws IOException")
+ void shouldReturnInternalServerErrorWhenGetRandomUsersFails() throws IOException {
+ when(fetchAndSaveRandomUsersUseCase.execute(5)).thenThrow(new IOException("downstream unavailable"));
+
+ ResponseEntity> response = userHandler.getRandomUsers(5);
+
+ assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
+ assertTrue(response.getBody() != null && response.getBody().isEmpty());
+ verify(fetchAndSaveRandomUsersUseCase, times(1)).execute(5);
+ }
+
+ @Test
+ @DisplayName("Should return 200 and user when getUserById succeeds")
+ void shouldReturnOkWhenGetUserByIdSucceeds() {
+ UserEntity user = new UserEntity(42L, "female", "Alice", "Smith", "Ms", "alice@example.com", "0611111111", "alice.jpg", "US");
+ when(getUserByIdUseCase.execute(42)).thenReturn(user);
+
+ ResponseEntity response = userHandler.getUserById(42);
+
+ assertEquals(HttpStatus.OK, response.getStatusCode());
+ assertEquals(user, response.getBody());
+ verify(getUserByIdUseCase, times(1)).execute(42);
+ }
+
+ @Test
+ @DisplayName("Should return 404 when getUserById throws UserNotFoundException")
+ void shouldReturnNotFoundWhenGetUserByIdFails() {
+ when(getUserByIdUseCase.execute(99)).thenThrow(new UserNotFoundException(99));
+
+ ResponseEntity response = userHandler.getUserById(99);
+
+ assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
+ assertNull(response.getBody());
+ verify(getUserByIdUseCase, times(1)).execute(99);
+ }
+
+ @Test
+ @DisplayName("Should return 200 and updated user when updateRandomUser succeeds")
+ void shouldReturnOkWhenUpdateRandomUserSucceeds() {
+ UserRequest request = new UserRequest("female", "Jane", "Doe", "Ms", "jane@example.com", "0622222222", "jane.jpg", "FR");
+ UserEntity updated = new UserEntity(7L, "female", "Jane", "Doe", "Ms", "jane@example.com", "0622222222", "jane.jpg", "FR");
+ when(updateRandomUserUseCase.execute(7, request)).thenReturn(updated);
+
+ ResponseEntity response = userHandler.updateRandomUser(7, request);
+
+ assertEquals(HttpStatus.OK, response.getStatusCode());
+ assertEquals(updated, response.getBody());
+ verify(updateRandomUserUseCase, times(1)).execute(7, request);
+ }
+
+ @Test
+ @DisplayName("Should return 404 when updateRandomUser throws UserNotFoundException")
+ void shouldReturnNotFoundWhenUpdateRandomUserFails() {
+ UserRequest request = new UserRequest("male", "Bob", "Brown", "Mr", "bob@example.com", "0633333333", "bob.jpg", "DE");
+ when(updateRandomUserUseCase.execute(123, request)).thenThrow(new UserNotFoundException(123));
+
+ ResponseEntity response = userHandler.updateRandomUser(123, request);
+
+ assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
+ assertNull(response.getBody());
+ verify(updateRandomUserUseCase, times(1)).execute(123, request);
+ }
+}
diff --git a/src/test/resources/application-test.properties.template b/src/test/resources/application-test.properties.template
index 7b598cc..5b6f5b1 100644
--- a/src/test/resources/application-test.properties.template
+++ b/src/test/resources/application-test.properties.template
@@ -1,6 +1,6 @@
POSTGRES_USER=your_postgres_user
POSTGRES_PASSWORD=your_postgres_password
POSTGRES_DB=your_postgres_db
-POSTGRES_PORT=5433
+POSTGRES_PORT=your_postgres_port