diff --git a/pom.xml b/pom.xml
index f74e5f3..0e6568c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -120,6 +120,18 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.12.1
+
+ ${java.version}
+ ${java.version}
+
+ -parameters
+
+
+
diff --git a/src/main/java/dev/waltercrdz/api/ecommerce/orders/infrastructure/in/error/ErrorHandler.java b/src/main/java/dev/waltercrdz/api/ecommerce/orders/infrastructure/in/error/ErrorHandler.java
index 28cec7a..6b928a4 100644
--- a/src/main/java/dev/waltercrdz/api/ecommerce/orders/infrastructure/in/error/ErrorHandler.java
+++ b/src/main/java/dev/waltercrdz/api/ecommerce/orders/infrastructure/in/error/ErrorHandler.java
@@ -7,6 +7,10 @@
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+import org.springframework.web.servlet.NoHandlerFoundException;
import dev.waltercrdz.api.ecommerce.shared.domain.exception.DatabaseConnectionException;
import dev.waltercrdz.api.ecommerce.shared.domain.exception.ErrorCode;
@@ -20,43 +24,58 @@ public class ErrorHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ErrorHandler.class);
@ExceptionHandler(IllegalArgumentException.class)
- public ResponseEntity handleIllegalArgumentException(IllegalArgumentException e) {
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ @ResponseBody
+ public ApiError handleIllegalArgumentException(IllegalArgumentException e) {
LOGGER.error("Illegal argument: ", e);
- return ResponseEntity
- .status(HttpStatus.BAD_REQUEST)
- .body(ApiError.fromException(ErrorCode.ILLEGAL_ARGUMENT, e));
+ return ApiError.fromException(ErrorCode.ILLEGAL_ARGUMENT, e);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
- public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ @ResponseBody
+ public ApiError handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
LOGGER.error("Argument Not Valid: ", e);
- return ResponseEntity
- .status(HttpStatus.BAD_REQUEST)
- .body(ApiError.fromException(ErrorCode.ILLEGAL_ARGUMENT, e));
+ return ApiError.fromException(ErrorCode.ILLEGAL_ARGUMENT, e);
+ }
+
+ @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ @ResponseBody
+ public ApiError handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
+ LOGGER.error("Argument Not Valid: ", e);
+ return ApiError.fromException(ErrorCode.ILLEGAL_ARGUMENT, e);
}
@ExceptionHandler(DatabaseConnectionException.class)
- public ResponseEntity handleDatabaseConnectionException(DatabaseConnectionException e) {
+ @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
+ @ResponseBody
+ public ApiError handleDatabaseConnectionException(DatabaseConnectionException e) {
LOGGER.error("Database connection: ", e);
- return ResponseEntity
- .status(HttpStatus.SERVICE_UNAVAILABLE)
- .body(ApiError.fromDomainException(e));
+ return ApiError.fromDomainException(e);
}
@ExceptionHandler(ProductNotFoundException.class)
- public ResponseEntity handleProductNotFoundException(ProductNotFoundException e) {
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ @ResponseBody
+ public ApiError handleProductNotFoundException(ProductNotFoundException e) {
LOGGER.error("Product Not Found: ", e);
- return ResponseEntity
- .status(HttpStatus.BAD_REQUEST)
- .body(ApiError.fromDomainException(e));
+ return ApiError.fromDomainException(e);
}
@ExceptionHandler(NotEnoughStockException.class)
- public ResponseEntity handleNotEnoughStockException(NotEnoughStockException e) {
+ @ResponseStatus(HttpStatus.CONFLICT)
+ @ResponseBody
+ public ApiError handleNotEnoughStockException(NotEnoughStockException e) {
LOGGER.error("Insufficient Stock: ", e);
- return ResponseEntity
- .status(HttpStatus.CONFLICT)
- .body(ApiError.fromDomainException(e));
+ return ApiError.fromDomainException(e);
+ }
+
+ @ExceptionHandler(NoHandlerFoundException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ @ResponseBody
+ public ApiError handleNoHandlerFoundException(NoHandlerFoundException e) {
+ return ApiError.fromException(ErrorCode.NOT_FOUND, e);
}
@ExceptionHandler(Exception.class)
diff --git a/src/main/java/dev/waltercrdz/api/ecommerce/products/infrastructure/in/controller/ProductController.java b/src/main/java/dev/waltercrdz/api/ecommerce/products/infrastructure/in/controller/ProductController.java
index 7cbfb89..dc8453d 100644
--- a/src/main/java/dev/waltercrdz/api/ecommerce/products/infrastructure/in/controller/ProductController.java
+++ b/src/main/java/dev/waltercrdz/api/ecommerce/products/infrastructure/in/controller/ProductController.java
@@ -5,6 +5,7 @@
import java.util.stream.Collectors;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -23,7 +24,7 @@ public ProductController(ProductFinder finder) {
}
@GetMapping("/{id}")
- public ProductResponse getProduct(UUID id) {
+ public ProductResponse getProduct(@PathVariable UUID id) {
final var product = this.finder.find(id);
return ProductMapper.toResponse(product);
}
diff --git a/src/main/java/dev/waltercrdz/api/ecommerce/shared/domain/exception/ErrorCode.java b/src/main/java/dev/waltercrdz/api/ecommerce/shared/domain/exception/ErrorCode.java
index 1b71890..dc497be 100644
--- a/src/main/java/dev/waltercrdz/api/ecommerce/shared/domain/exception/ErrorCode.java
+++ b/src/main/java/dev/waltercrdz/api/ecommerce/shared/domain/exception/ErrorCode.java
@@ -6,6 +6,7 @@ public enum ErrorCode {
NOT_ENOUGH_STOCK("NOT_ENOUGH_STOCK"),
ILLEGAL_ARGUMENT("ILLEGAL_ARGUMENT"),
MISSING_PARAMETER("MISSING_PARAMETER"),
+ NOT_FOUND("NOT_FOUND"),
INTERNAL_SERVER_ERROR("INTERNAL_SERVER_ERROR");
private final String code;
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index afd1b29..66c5976 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -10,6 +10,9 @@ aws:
spring:
application:
name: e-commerce
+ web:
+ resources:
+ add-mappings: false
datasource:
url: ${DB_URL}
driver-class-name: org.postgresql.Driver
diff --git a/src/test/java/dev/waltercrdz/api/ecommerce/orders/infrastructure/utils/ProductMother.java b/src/test/java/dev/waltercrdz/api/ecommerce/orders/infrastructure/utils/ProductMother.java
new file mode 100644
index 0000000..99a813a
--- /dev/null
+++ b/src/test/java/dev/waltercrdz/api/ecommerce/orders/infrastructure/utils/ProductMother.java
@@ -0,0 +1,27 @@
+package dev.waltercrdz.api.ecommerce.orders.infrastructure.utils;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import dev.waltercrdz.api.ecommerce.products.infrastructure.in.dto.ProductResponse;
+
+public class ProductMother {
+
+ public static final String INVALID_PRODUCT_ID = "invalid_product_id";
+ public static final UUID NON_EXISTENT_PRODUCT_ID = UUID.fromString("f7b3b3b4-3b1b-4b3b-8b3b-3b1b3b1b3b1f");
+ public static final UUID PRODUCT_ID_1 = UUID.fromString("f7b3b3b4-3b1b-4b3b-8b3b-3b1b3b1b3b1b");
+
+ // ('f7b3b3b4-3b1b-4b3b-8b3b-3b1b3b1b3b1b', 'Product 1', 'Description 1', 10.00, 10),
+ // ('f7b3b3b4-3b1b-4b3b-8b3b-3b1b3b1b3b1c', 'Product 2', 'Description 2', 20.00, 0),
+ // ('f7b3b3b4-3b1b-4b3b-8b3b-3b1b3b1b3b1d', 'Product 3', 'Description 3', 30.00, 5)
+
+ public static ProductResponse createProduct1() {
+ return new ProductResponse(
+ PRODUCT_ID_1,
+ "Product 1",
+ "Description 1",
+ BigDecimal.valueOf(10.00),
+ 10
+ );
+ }
+}
diff --git a/src/test/java/dev/waltercrdz/api/ecommerce/products/infrastructure/in/controller/ProductControllerTest.java b/src/test/java/dev/waltercrdz/api/ecommerce/products/infrastructure/in/controller/ProductControllerTest.java
new file mode 100644
index 0000000..b70888d
--- /dev/null
+++ b/src/test/java/dev/waltercrdz/api/ecommerce/products/infrastructure/in/controller/ProductControllerTest.java
@@ -0,0 +1,66 @@
+package dev.waltercrdz.api.ecommerce.products.infrastructure.in.controller;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.springframework.http.MediaType;
+
+import dev.waltercrdz.api.ecommerce.orders.infrastructure.configuration.TestConfig;
+import dev.waltercrdz.api.ecommerce.orders.infrastructure.utils.ProductMother;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@AutoConfigureMockMvc
+@Import(TestConfig.class)
+public class ProductControllerTest {
+
+ private static final String URI_PRODUCT = "/products";
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Test
+ void shouldReturnAllProducts() throws Exception {
+ mockMvc.perform(get(URI_PRODUCT)
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$").isArray())
+ .andExpect(jsonPath("$.length()").value(3));
+ }
+
+ @Test
+ void shouldReturnProduct_whenValidProductIdIsProvided() throws Exception {
+ final var expectedProductResult = ProductMother.createProduct1();
+ mockMvc.perform(get(URI_PRODUCT + "/" + expectedProductResult.id().toString())
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.id").value(expectedProductResult.id().toString()))
+ .andExpect(jsonPath("$.name").value(expectedProductResult.name()))
+ .andExpect(jsonPath("$.description").value(expectedProductResult.description()))
+ .andExpect(jsonPath("$.price").value(expectedProductResult.price().toString()))
+ .andExpect(jsonPath("$.stock").value(expectedProductResult.stock()));
+ }
+
+ @Test
+ void shouldReturnNotFoundError_whenNonExistentProductIdIsProvided() throws Exception {
+ mockMvc.perform(get(URI_PRODUCT + "/" + ProductMother.NON_EXISTENT_PRODUCT_ID.toString())
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ void shouldReturnBadRequestError_whenInvalidProductIdIsProvided() throws Exception {
+ mockMvc.perform(get(URI_PRODUCT + "/" + ProductMother.INVALID_PRODUCT_ID)
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(status().isBadRequest());
+ }
+}