diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/exceptions/GlobalExceptionHandler.java b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/exceptions/GlobalExceptionHandler.java index 0e2c14a..2429896 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/exceptions/GlobalExceptionHandler.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/presentation/exceptions/GlobalExceptionHandler.java @@ -2,6 +2,7 @@ import com.xpeho.spring_boot_java_random_user.domain.exceptions.InvalidPaginationException; import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException; +import jakarta.validation.ConstraintViolationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -14,6 +15,16 @@ public class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex) { + String message = ex.getConstraintViolations().stream() + .map(v -> v.getPropertyPath() + ": " + v.getMessage()) + .findFirst() + .orElse(ex.getMessage()); + logger.warn("Constraint violation: {}", message); + return buildErrorResponse("INVALID_PAGINATION", message, HttpStatus.BAD_REQUEST); + } + @ExceptionHandler(InvalidPaginationException.class) public ResponseEntity handleInvalidPaginationException(InvalidPaginationException ex) { logger.warn("Invalid pagination request: {}", ex.getMessage()); 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 9872731..ae2f80e 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 @@ -6,7 +6,6 @@ import com.xpeho.spring_boot_java_random_user.domain.entities.UserRequest; import com.xpeho.spring_boot_java_random_user.domain.enums.Gender; import com.xpeho.spring_boot_java_random_user.domain.enums.UserSource; -import com.xpeho.spring_boot_java_random_user.domain.exceptions.InvalidPaginationException; import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException; import com.xpeho.spring_boot_java_random_user.domain.usecases.*; import com.xpeho.spring_boot_java_random_user.presentation.controllers.UserController; @@ -15,6 +14,7 @@ import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @@ -22,6 +22,7 @@ import java.util.List; +@Validated @RestController public class UserHandler implements UserController { @@ -54,12 +55,6 @@ public UserHandler( @Override public ResponseEntity getRandomUsers(int page, int size, UserSource source) { - if (page < 1) { - throw new InvalidPaginationException("Page must be greater than or equal to 1. Requested: " + page); - } - if (size < 1 || size > 30) { - throw new InvalidPaginationException("Page size must be between 1 and 30. Requested: " + size); - } try { PaginatedUsers result = fetchAndSaveRandomUsersUseCase.execute(page, size, source); UserResponseDTO response = new UserResponseDTO( @@ -70,7 +65,7 @@ public ResponseEntity getRandomUsers(int page, int size, UserSo ); return ResponseEntity.ok(response); } catch (IOException e) { - logger.error("Error fetching random users: {}", e.getMessage(), e); + logger.error("Error fetching random users", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @@ -124,6 +119,6 @@ public void deleteUserById(int id) { } private void logUserNotFound(UserNotFoundException e) { - logger.warn(USER_NOT_FOUND_LOG, e.getMessage(), e); + logger.warn(USER_NOT_FOUND_LOG, e); } } diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserHandlerTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserHandlerTest.java index 9dcd49d..3ff8b59 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserHandlerTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/UserHandlerTest.java @@ -6,7 +6,6 @@ import com.xpeho.spring_boot_java_random_user.domain.entities.UserRequest; import com.xpeho.spring_boot_java_random_user.domain.enums.Gender; import com.xpeho.spring_boot_java_random_user.domain.enums.UserSource; -import com.xpeho.spring_boot_java_random_user.domain.exceptions.InvalidPaginationException; import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException; import com.xpeho.spring_boot_java_random_user.domain.usecases.*; import com.xpeho.spring_boot_java_random_user.presentation.handlers.UserHandler; @@ -14,8 +13,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -82,19 +79,6 @@ void shouldReturnInternalServerErrorWhenGetRandomUsersFails() throws IOException verify(fetchAndSaveRandomUsersUseCase, times(1)).execute(page, size, UserSource.RANDOM_USER); } - @ParameterizedTest - @CsvSource({ - "1, 31", - "0, 10", - "1, 0" - }) - @DisplayName("Should throw InvalidPaginationException for invalid pagination inputs") - void shouldThrowInvalidPaginationExceptionForInvalidPaginationInputs(int page, int size) throws IOException { - assertThrows(InvalidPaginationException.class, () -> userHandler.getRandomUsers(page, size, UserSource.DUMMY)); - verify(fetchAndSaveRandomUsersUseCase, never()).execute(page, size, UserSource.DUMMY); - } - - @Test @DisplayName("Should return 200 and user when getUserById succeeds") void shouldReturnOkWhenGetUserByIdSucceeds() { diff --git a/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/exceptions/GlobalExceptionHandlerTest.java b/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/exceptions/GlobalExceptionHandlerTest.java index 757c1d2..2c27fa2 100644 --- a/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/exceptions/GlobalExceptionHandlerTest.java +++ b/src/test/java/com/xpeho/spring_boot_java_random_user/presentation/exceptions/GlobalExceptionHandlerTest.java @@ -3,18 +3,42 @@ import com.xpeho.spring_boot_java_random_user.domain.exceptions.InvalidPaginationException; import com.xpeho.spring_boot_java_random_user.domain.exceptions.UserNotFoundException; import com.xpeho.spring_boot_java_random_user.domain.enums.UserSource; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import java.util.Set; + import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class GlobalExceptionHandlerTest { private final GlobalExceptionHandler handler = new GlobalExceptionHandler(); + @Test + @DisplayName("Should return 400 BAD_REQUEST when ConstraintViolationException is thrown") + void shouldReturnBadRequestWhenConstraintViolationException() { + ConstraintViolation violation = mock(ConstraintViolation.class); + when(violation.getPropertyPath()).thenReturn(mock(jakarta.validation.Path.class)); + when(violation.getPropertyPath().toString()).thenReturn("size"); + when(violation.getMessage()).thenReturn("must be less than or equal to 30"); + + ConstraintViolationException ex = new ConstraintViolationException(Set.of(violation)); + ResponseEntity response = handler.handleConstraintViolationException(ex); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals("INVALID_PAGINATION", response.getBody().error()); + assertEquals(400, response.getBody().status()); + assertTrue(response.getBody().message().contains("must be less than or equal to 30")); + } + @Test @DisplayName("Should return 400 BAD_REQUEST when InvalidPaginationException is thrown") void shouldReturnBadRequestWhenInvalidPaginationException() { diff --git a/src/test/java/feature/StepDefinition.java b/src/test/java/feature/StepDefinition.java index a4a4793..7d0a4cb 100644 --- a/src/test/java/feature/StepDefinition.java +++ b/src/test/java/feature/StepDefinition.java @@ -78,4 +78,28 @@ public void theClientCallToGetTheCreatedUser() { assertNotNull(createdUserId, "No user was created before this step"); executeGet("/random-users/" + createdUserId); } + + @When("the client call to GET \\/random-users") + public void theClientCallToGetRandomUsers() { + executeGet("/random-users"); + } + + @When("the client call to GET \\/random-users with page {int} and size {int}") + public void theClientCallToGetRandomUsersWithPageAndSize(int page, int size) { + executeGet("/random-users?page=" + page + "&size=" + size); + } + + @And("the response contains a list of users") + public void theResponseContainsAListOfUsers() throws Exception { + JsonNode body = objectMapper.readTree(latestResponse.getBody()); + assertNotNull(body.get("data")); + assertTrue(body.get("data").isArray()); + } + + @And("the response contains {int} users") + public void theResponseContainsUsers(int expectedSize) throws Exception { + JsonNode body = objectMapper.readTree(latestResponse.getBody()); + assertNotNull(body.get("data")); + assertEquals(expectedSize, body.get("data").size()); + } } diff --git a/src/test/resources/features/get_random_users.feature b/src/test/resources/features/get_random_users.feature new file mode 100644 index 0000000..9dbf778 --- /dev/null +++ b/src/test/resources/features/get_random_users.feature @@ -0,0 +1,15 @@ +Feature: GET /random-users endpoint + + Scenario: Fetch random users with default parameters + When the client call to GET /random-users + Then the response status should be 200 + And the response contains a list of users + + Scenario: Fetch random users with custom page and size + When the client call to GET /random-users with page 1 and size 5 + Then the response status should be 200 + And the response contains 5 users + + Scenario: Fetch random users with size above maximum + When the client call to GET /random-users with page 1 and size 31 + Then the response status should be 400