Skip to content

Commit

Permalink
feat: find products
Browse files Browse the repository at this point in the history
  • Loading branch information
waltercrdz committed Jan 14, 2025
1 parent 07eb4d3 commit 7a8c2ab
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 22 deletions.
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ services:
condition: service_healthy

db:
image: postgres:latest
image: postgres:16.6-alpine3.21
environment:
POSTGRES_DB: ecommerce
POSTGRES_USER: ecommerce
Expand Down
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,43 +24,58 @@ public class ErrorHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ErrorHandler.class);

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ApiError> 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<ApiError> 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<ApiError> 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<ApiError> 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<ApiError> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
);
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}

0 comments on commit 7a8c2ab

Please sign in to comment.