diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml
index 41372ec..7c20938 100644
--- a/.github/workflows/cd.yaml
+++ b/.github/workflows/cd.yaml
@@ -21,7 +21,7 @@ jobs:
contents: read
strategy:
matrix:
- service: [retail-data-services, cart-service]
+ service: [product-api, cart-service]
steps:
- name: Checkout
@@ -56,7 +56,7 @@ jobs:
packages: write
strategy:
matrix:
- service: [retail-data-services, cart-service]
+ service: [product-api, cart-service]
steps:
- name: Checkout
diff --git a/README.md b/README.md
index 3b6818c..56ce565 100644
--- a/README.md
+++ b/README.md
@@ -6,10 +6,10 @@ A collection of containerized microservices used for technical interviews. Candi
## Services
-| Service | Port | Description |
-| -------------------- | ---- | ----------------------------------------------------------------------------------------------------------------- |
-| retail-data-services | 8080 | Read-only REST API serving synthetic product catalog, pricing, and inventory availability |
-| cart-service | 8081 | Shopping cart REST API with CRUD operations. Calls retail-data-services at runtime for item and price enrichment. |
+| Service | Port | Description |
+| ------------ | ---- | -------------------------------------------------------------------------------------------------------- |
+| product-api | 8080 | Read-only REST API serving synthetic product catalog, pricing, and inventory availability |
+| cart-service | 8081 | Shopping cart REST API with CRUD operations. Calls product-api at runtime for item and price enrichment. |
**Note:** All data returned by these services is mocked/sample data intended for interviewing purposes only. It does not represent real or production retail data.
@@ -27,8 +27,8 @@ docker compose up
The services will be available at:
-- retail-data-services:
-- cart-service:
+- product-api:
+- cart-service:
To stop the services:
@@ -38,148 +38,148 @@ docker compose down
### Option 2: docker run (individual services)
-Build and run each service separately. Note that cart-service depends on retail-data-services, so retail-data-services must be running first.
+Build and run each service separately. Note that cart-service depends on product-api, so product-api must be running first.
```sh
# Build both JARs
./gradlew clean build
-# Build and run retail-data-services
-docker build -t retail-data-services retail-data-services/
-docker run -d -p 8080:8080 --name data retail-data-services
+# Build and run product-api
+docker build -t product-api product-api/
+docker run -d -p 8080:8080 --name product-api product-api
# Build and run cart-service
docker build -t cart-service cart-service/
-docker run -p 8081:8081 --name cart --link data:data cart-service
+docker run -p 8081:8081 --name cart --link product-api:product-api cart-service
```
### OpenAPI specs
Both services expose Swagger UI and OpenAPI docs:
-| Service | Swagger UI | API docs |
-| -------------------- | --------------------------------------------------------------------- | -------------------------------------------------------- |
-| retail-data-services | | |
-| cart-service | | |
+| Service | Swagger UI | API docs |
+| ------------ | --------------------------------------------- | -------------------------------- |
+| product-api | | |
+| cart-service | | |
HTTP request files for use with IntelliJ or VS Code are available at:
-- `retail-data-services/retail-data-services.http`
+- `product-api/product-api.http`
- `cart-service/cart-service.http`
-## retail-data-services endpoints
+## product-api endpoints
### Get price
-**`GET /retail_data_services/v1/prices/{id}`**
+**`GET /v1/prices/{id}`**
```sh
-curl -X GET "http://localhost:8080/retail_data_services/v1/prices/123456"
+curl -X GET "http://localhost:8080/v1/prices/123456"
```
### Get item
-**`GET /retail_data_services/v1/items/{id}`**
+**`GET /v1/items/{id}`**
```sh
-curl -X GET "http://localhost:8080/retail_data_services/v1/items/123456"
+curl -X GET "http://localhost:8080/v1/items/123456"
```
### List items
-**`GET /retail_data_services/v1/items`**
+**`GET /v1/items`**
Supports filtering by `small_description` query parameter.
```sh
-curl -X GET "http://localhost:8080/retail_data_services/v1/items"
-curl -X GET "http://localhost:8080/retail_data_services/v1/items?small_description=jersey"
+curl -X GET "http://localhost:8080/v1/items"
+curl -X GET "http://localhost:8080/v1/items?small_description=jersey"
```
### Get availability
-**`GET /retail_data_services/v1/availability/{id}`**
+**`GET /v1/availability/{id}`**
```sh
-curl -X GET "http://localhost:8080/retail_data_services/v1/availability/123456"
+curl -X GET "http://localhost:8080/v1/availability/123456"
```
## cart-service endpoints
-cart-service depends on retail-data-services at runtime. When a cart is read, the service calls retail-data-services over HTTP to enrich each line item with product details and pricing. It then calculates taxes (by product category) and delivery charges.
+cart-service depends on product-api at runtime. When a cart is read, the service calls product-api over HTTP to enrich each line item with product details and pricing. It then calculates taxes (by product category) and delivery charges.
### Get cart
-**`GET /cart/v1/carts/{id}`**
+**`GET /v1/carts/{id}`**
```sh
-curl 'http://localhost:8081/cart/v1/carts/100' -i -X GET
+curl 'http://localhost:8081/v1/carts/100' -i -X GET
```
### Create cart
-**`POST /cart/v1/carts`**
+**`POST /v1/carts`**
-Request body: array of objects with `tcin` (string) and `quantity` (integer).
+Request body: array of objects with `item_id` (string) and `quantity` (integer).
```sh
-curl 'http://localhost:8081/cart/v1/carts' -i -X POST \
+curl 'http://localhost:8081/v1/carts' -i -X POST \
-H 'Content-Type: application/json' \
-d '[
- {"tcin" : "123456", "quantity": 1},
- {"tcin" : "789123", "quantity": 2}
+ {"item_id" : "123456", "quantity": 1},
+ {"item_id" : "789123", "quantity": 2}
]'
```
### Add item to cart
-**`POST /cart/v1/carts/{id}/items`**
+**`POST /v1/carts/{id}/items`**
```sh
-curl 'http://localhost:8081/cart/v1/carts/100/items' -i -X POST \
+curl 'http://localhost:8081/v1/carts/100/items' -i -X POST \
-H 'Content-Type: application/json' \
- -d '{"tcin" : "456788", "quantity": 2}'
+ -d '{"item_id" : "456788", "quantity": 2}'
```
### Update item quantity
-**`PATCH /cart/v1/carts/{id}/items/{tcin}`**
+**`PATCH /v1/carts/{id}/items/{item_id}`**
```sh
-curl 'http://localhost:8081/cart/v1/carts/100/items/456788' -i -X PATCH \
+curl 'http://localhost:8081/v1/carts/100/items/456788' -i -X PATCH \
-H 'Content-Type: application/json' \
-d '{"quantity": 3}'
```
### Remove item from cart
-**`DELETE /cart/v1/carts/{id}/items/{tcin}`**
+**`DELETE /v1/carts/{id}/items/{item_id}`**
Removing the last item from a cart also removes the cart.
```sh
-curl 'http://localhost:8081/cart/v1/carts/100/items/456788' -i -X DELETE
+curl 'http://localhost:8081/v1/carts/100/items/456788' -i -X DELETE
```
## Customizing data
-You can customize the data returned by retail-data-services by creating your own CSV files and mounting them into the container. See [retail-data-services/data-formats.md](retail-data-services/data-formats.md) for details.
+You can customize the data returned by product-api by creating your own CSV files and mounting them into the container. See [product-api/data-formats.md](product-api/data-formats.md) for details.
## Induced behaviors (latency and failure simulation)
Both services support configurable induced behaviors that simulate latency and failures. By setting the `DEFAULT_BEHAVIOR` environment variable, you can run the same APIs in different modes (normal, slow, or randomly failing) without changing any code.
-See [retail-data-services/induced_behaviors.md](retail-data-services/induced_behaviors.md) for available modes, environment variables, and usage examples.
+See [product-api/induced_behaviors.md](product-api/induced_behaviors.md) for available modes, environment variables, and usage examples.
## Performance benchmarking
-A startup time benchmarking script is available at `retail-data-services/scripts/benchmark-startup.sh`. See `retail-data-services/scripts/README.md` for usage details.
+A startup time benchmarking script is available at `product-api/scripts/benchmark-startup.sh`. See `product-api/scripts/README.md` for usage details.
## Project structure
```txt
tech-case-studies/
- retail-data-services/ # Read-only data API (port 8080)
+ product-api/ # Read-only data API (port 8080)
src/
Dockerfile
build.gradle.kts
@@ -192,3 +192,4 @@ tech-case-studies/
settings.gradle.kts # Multi-project includes
gradle/libs.versions.toml # Shared dependency versions
```
+
diff --git a/cart-service/src/main/java/com/target/retail/cart/controller/CartController.java b/cart-service/src/main/java/com/target/retail/cart/controller/CartController.java
index 61db348..9e4eb89 100644
--- a/cart-service/src/main/java/com/target/retail/cart/controller/CartController.java
+++ b/cart-service/src/main/java/com/target/retail/cart/controller/CartController.java
@@ -4,7 +4,6 @@
import com.target.retail.cart.controller.dto.CartResponse;
import com.target.retail.cart.controller.dto.UpdateItemRequest;
import com.target.retail.cart.model.Cart;
-import com.target.retail.cart.model.CartLineItem;
import com.target.retail.cart.service.CartService;
import com.target.retail.cart.service.behavior.Behaviors;
import io.swagger.v3.oas.annotations.Operation;
@@ -21,7 +20,7 @@
import java.util.stream.Collectors;
@RestController
-@RequestMapping("/cart/v1")
+@RequestMapping("/v1")
public class CartController {
private CartService cartService;
@@ -39,18 +38,18 @@ public CartController(CartService cartService, Behaviors behaviors) {
@ApiResponse(responseCode = "200", description = "Successfully created cart",
content = {@Content(mediaType = "application/json",
schema = @Schema(implementation = CartResponse.class))}),
- @ApiResponse(responseCode = "400", description = "Invalid request due to duplicate TCINs",
+ @ApiResponse(responseCode = "400", description = "Invalid request due to duplicate item IDs",
content = @Content)
})
@PostMapping("/carts")
public ResponseEntity createCart(@RequestBody List addItems) {
- if (addItems.stream().map(AddItemRequest::tcin).distinct().count() != addItems.size()) {
+ if (addItems.stream().map(AddItemRequest::itemId).distinct().count() != addItems.size()) {
return ResponseEntity.badRequest().build();
}
Map itemsInCart = addItems.stream()
- .collect(Collectors.toMap(AddItemRequest::tcin, AddItemRequest::quantity));
+ .collect(Collectors.toMap(AddItemRequest::itemId, AddItemRequest::quantity));
String cartId = cartService.createCart(itemsInCart);
@@ -83,12 +82,9 @@ public ResponseEntity getCart(@PathVariable String id) {
@ApiResponse(responseCode = "404", description = "Cart not found",
content = @Content)
})
- @DeleteMapping("/carts/{id}/items/{tcin}")
- public ResponseEntity removeItemFromCart(@PathVariable String id, @PathVariable String tcin) {
- if(cartService.getCart(id).isEmpty()) {
- return ResponseEntity.notFound().build();
- }
- cartService.removeItem(id, tcin);
+ @DeleteMapping("/carts/{id}/items/{itemId}")
+ public ResponseEntity removeItemFromCart(@PathVariable String id, @PathVariable String itemId) {
+ cartService.removeItem(id, itemId);
if (cartService.getCart(id).isEmpty()) {
return ResponseEntity.noContent().build();
} else {
@@ -106,10 +102,7 @@ public ResponseEntity removeItemFromCart(@PathVariable String id,
})
@PostMapping("/carts/{id}/items")
public ResponseEntity addItem(@PathVariable String id, @RequestBody AddItemRequest addItemRequest) {
- if(cartService.getCart(id).isEmpty()) {
- return ResponseEntity.notFound().build();
- }
- cartService.addItem(id, addItemRequest.tcin(), addItemRequest.quantity());
+ cartService.addItem(id, addItemRequest.itemId(), addItemRequest.quantity());
return getCart(id);
}
@@ -121,18 +114,10 @@ public ResponseEntity addItem(@PathVariable String id, @RequestBod
@ApiResponse(responseCode = "404", description = "Cart or item not found",
content = @Content)
})
- @PatchMapping("/carts/{id}/items/{tcin}")
- public ResponseEntity updateItem(@PathVariable String id, @PathVariable String tcin, @RequestBody UpdateItemRequest updateItemRequest) {
-
- Optional cartLineItem = cartService.getCart(id)
- .flatMap( it -> it.findByTcin(tcin));
- if(cartLineItem.isEmpty()) {
- return ResponseEntity.notFound().build();
- }
-
- cartService.updateCartItem(id, tcin, updateItemRequest.quantity());
+ @PatchMapping("/carts/{id}/items/{itemId}")
+ public ResponseEntity updateItem(@PathVariable String id, @PathVariable String itemId, @RequestBody UpdateItemRequest updateItemRequest) {
+ cartService.updateCartItem(id, itemId, updateItemRequest.quantity());
return getCart(id);
-
}
}
diff --git a/cart-service/src/main/java/com/target/retail/cart/controller/GlobalExceptionHandler.java b/cart-service/src/main/java/com/target/retail/cart/controller/GlobalExceptionHandler.java
new file mode 100644
index 0000000..6eb2923
--- /dev/null
+++ b/cart-service/src/main/java/com/target/retail/cart/controller/GlobalExceptionHandler.java
@@ -0,0 +1,39 @@
+package com.target.retail.cart.controller;
+
+import com.target.retail.cart.controller.dto.ErrorResponse;
+import com.target.retail.cart.data.DataException;
+import com.target.retail.cart.exception.CartLineItemNotFoundException;
+import com.target.retail.cart.exception.CartNotFoundException;
+import com.target.retail.cart.exception.InducedFailureException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler({CartNotFoundException.class, CartLineItemNotFoundException.class})
+ public ResponseEntity handleNotFound(RuntimeException ex) {
+ return ResponseEntity.status(HttpStatus.NOT_FOUND)
+ .body(new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage()));
+ }
+
+ @ExceptionHandler(InducedFailureException.class)
+ public ResponseEntity handleInducedFailure(InducedFailureException ex) {
+ return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
+ .body(new ErrorResponse(HttpStatus.SERVICE_UNAVAILABLE.value(), ex.getMessage()));
+ }
+
+ @ExceptionHandler(DataException.class)
+ public ResponseEntity handleDataException(DataException ex) {
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An internal data error occurred"));
+ }
+
+ @ExceptionHandler(RuntimeException.class)
+ public ResponseEntity handleRuntimeException(RuntimeException ex) {
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An unexpected error occurred"));
+ }
+}
diff --git a/cart-service/src/main/java/com/target/retail/cart/controller/dto/AddItemRequest.java b/cart-service/src/main/java/com/target/retail/cart/controller/dto/AddItemRequest.java
index 86b7575..696afd8 100644
--- a/cart-service/src/main/java/com/target/retail/cart/controller/dto/AddItemRequest.java
+++ b/cart-service/src/main/java/com/target/retail/cart/controller/dto/AddItemRequest.java
@@ -1,4 +1,8 @@
package com.target.retail.cart.controller.dto;
-public record AddItemRequest(String tcin, Integer quantity) {
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+public record AddItemRequest(String itemId, Integer quantity) {
}
diff --git a/cart-service/src/main/java/com/target/retail/cart/controller/dto/CartResponse.java b/cart-service/src/main/java/com/target/retail/cart/controller/dto/CartResponse.java
index d765adf..d3fbb6c 100644
--- a/cart-service/src/main/java/com/target/retail/cart/controller/dto/CartResponse.java
+++ b/cart-service/src/main/java/com/target/retail/cart/controller/dto/CartResponse.java
@@ -41,7 +41,7 @@ private String formatNumber(BigDecimal bigDecimal) {
}
- public record ItemResponse(String tcin, String title, String description, String brand, String merchClass, Integer quantity, PriceResponse price, ImageResponse imageData) {
+ public record ItemResponse(String itemId, String title, String description, String brand, Integer merchClass, Integer quantity, PriceResponse price, ImageResponse imageData) {
public record PriceResponse(BigDecimal regular, BigDecimal sale) {}
@@ -50,9 +50,9 @@ public record ImageResponse(String primary, String alternate, String baseUrl) {}
public static CartResponse from(Cart cart) {
- List items = cart.cartLineItems().stream().map(it -> new ItemResponse(it.item().tcin(),
+ List items = cart.cartLineItems().stream().map(it -> new ItemResponse(it.item().itemId(),
it.item().title(), it.item().description(), it.item().brand(), it.item().merchClass(),
- it.quantity(), new ItemResponse.PriceResponse(it.price().regularPrice(), it.price().salePrice().orElse(null)), new ItemResponse.ImageResponse(it.item().primaryImage(), it.item().alternateImage(), it.item().baseUrl()))).toList();
+ it.quantity(), new ItemResponse.PriceResponse(it.price().regular(), it.price().sale().orElse(null)), new ItemResponse.ImageResponse(it.item().primary(), it.item().alternate(), it.item().baseUrl()))).toList();
return new CartResponse(cart.id(), items, cart.subTotal(), cart.deliveryCharges(), cart.totalTax(), cart.getTotal(), cart.createdOn(), cart.updatedOn());
}
diff --git a/cart-service/src/main/java/com/target/retail/cart/controller/dto/ErrorResponse.java b/cart-service/src/main/java/com/target/retail/cart/controller/dto/ErrorResponse.java
new file mode 100644
index 0000000..70f177c
--- /dev/null
+++ b/cart-service/src/main/java/com/target/retail/cart/controller/dto/ErrorResponse.java
@@ -0,0 +1,4 @@
+package com.target.retail.cart.controller.dto;
+
+public record ErrorResponse(int status, String message) {
+}
diff --git a/cart-service/src/main/java/com/target/retail/cart/exception/CartLineItemNotFoundException.java b/cart-service/src/main/java/com/target/retail/cart/exception/CartLineItemNotFoundException.java
new file mode 100644
index 0000000..e7e9ca9
--- /dev/null
+++ b/cart-service/src/main/java/com/target/retail/cart/exception/CartLineItemNotFoundException.java
@@ -0,0 +1,7 @@
+package com.target.retail.cart.exception;
+
+public class CartLineItemNotFoundException extends RuntimeException {
+ public CartLineItemNotFoundException(String itemId) {
+ super("No cart line found for item id " + itemId);
+ }
+}
diff --git a/cart-service/src/main/java/com/target/retail/cart/exception/CartNotFoundException.java b/cart-service/src/main/java/com/target/retail/cart/exception/CartNotFoundException.java
new file mode 100644
index 0000000..a08c805
--- /dev/null
+++ b/cart-service/src/main/java/com/target/retail/cart/exception/CartNotFoundException.java
@@ -0,0 +1,7 @@
+package com.target.retail.cart.exception;
+
+public class CartNotFoundException extends RuntimeException {
+ public CartNotFoundException(String cartId) {
+ super("No cart found with id " + cartId);
+ }
+}
diff --git a/cart-service/src/main/java/com/target/retail/cart/exception/InducedFailureException.java b/cart-service/src/main/java/com/target/retail/cart/exception/InducedFailureException.java
new file mode 100644
index 0000000..2b0e1ec
--- /dev/null
+++ b/cart-service/src/main/java/com/target/retail/cart/exception/InducedFailureException.java
@@ -0,0 +1,7 @@
+package com.target.retail.cart.exception;
+
+public class InducedFailureException extends RuntimeException {
+ public InducedFailureException(String message) {
+ super(message);
+ }
+}
diff --git a/cart-service/src/main/java/com/target/retail/cart/model/Cart.java b/cart-service/src/main/java/com/target/retail/cart/model/Cart.java
index 71919ea..7c86f0e 100644
--- a/cart-service/src/main/java/com/target/retail/cart/model/Cart.java
+++ b/cart-service/src/main/java/com/target/retail/cart/model/Cart.java
@@ -24,7 +24,7 @@ public ZonedDateTime updatedOn() {
return cartLineItems().stream().map(CartLineItem::updatedOn).max(ZonedDateTime::compareTo).orElse(ZonedDateTime.now());
}
- public Optional findByTcin(String tcin) {
- return cartLineItems.stream().filter(it -> it.item().tcin().equals(tcin)).findFirst();
+ public Optional findByItemId(String itemId) {
+ return cartLineItems.stream().filter(it -> it.item().itemId().equals(itemId)).findFirst();
}
}
diff --git a/cart-service/src/main/java/com/target/retail/cart/model/Item.java b/cart-service/src/main/java/com/target/retail/cart/model/Item.java
index 4ab7fab..47cfb58 100644
--- a/cart-service/src/main/java/com/target/retail/cart/model/Item.java
+++ b/cart-service/src/main/java/com/target/retail/cart/model/Item.java
@@ -1,14 +1,14 @@
package com.target.retail.cart.model;
public record Item(
- String tcin,
+ String itemId,
String title,
String description,
String brand,
String category,
- String merchClass,
- String primaryImage,
- String alternateImage,
+ Integer merchClass,
+ String primary,
+ String alternate,
String baseUrl
) {
}
diff --git a/cart-service/src/main/java/com/target/retail/cart/model/Price.java b/cart-service/src/main/java/com/target/retail/cart/model/Price.java
index c7ede55..02f12e0 100644
--- a/cart-service/src/main/java/com/target/retail/cart/model/Price.java
+++ b/cart-service/src/main/java/com/target/retail/cart/model/Price.java
@@ -3,9 +3,9 @@
import java.math.BigDecimal;
import java.util.Optional;
-public record Price(String tcin, BigDecimal regularPrice, Optional salePrice) {
+public record Price(String itemId, BigDecimal regular, Optional sale) {
public BigDecimal getCurrentPrice(){
- return salePrice.orElse(regularPrice);
+ return sale.orElse(regular);
}
}
diff --git a/cart-service/src/main/java/com/target/retail/cart/model/StoredCartLine.java b/cart-service/src/main/java/com/target/retail/cart/model/StoredCartLine.java
index cae0d73..8342e5d 100644
--- a/cart-service/src/main/java/com/target/retail/cart/model/StoredCartLine.java
+++ b/cart-service/src/main/java/com/target/retail/cart/model/StoredCartLine.java
@@ -9,9 +9,9 @@
import java.time.ZonedDateTime;
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
-@JsonPropertyOrder({ "lineId", "cartId", "tcin", "quantity", "createdOn", "updatedOn" })
+@JsonPropertyOrder({ "lineId", "cartId", "itemId", "quantity", "createdOn", "updatedOn" })
@JsonIgnoreProperties(ignoreUnknown = true)
-public record StoredCartLine(String lineId, String cartId, String tcin, Integer quantity, ZonedDateTime createdOn, ZonedDateTime updatedOn) implements Identifiable {
+public record StoredCartLine(String lineId, String cartId, String itemId, Integer quantity, ZonedDateTime createdOn, ZonedDateTime updatedOn) implements Identifiable {
public String getId() {
return lineId();
}
diff --git a/cart-service/src/main/java/com/target/retail/cart/service/CartService.java b/cart-service/src/main/java/com/target/retail/cart/service/CartService.java
index b4c200d..83d8e23 100644
--- a/cart-service/src/main/java/com/target/retail/cart/service/CartService.java
+++ b/cart-service/src/main/java/com/target/retail/cart/service/CartService.java
@@ -1,6 +1,8 @@
package com.target.retail.cart.service;
import com.target.retail.cart.data.CartDatabase;
+import com.target.retail.cart.exception.CartLineItemNotFoundException;
+import com.target.retail.cart.exception.CartNotFoundException;
import com.target.retail.cart.service.client.dto.ItemApiResponse;
import com.target.retail.cart.service.client.dto.PriceApiResponse;
import com.target.retail.cart.model.StoredCartLine;
@@ -82,21 +84,21 @@ public Optional getCart(String cartId) {
}
- public void removeItem(String cartId, String tcin) {
+ public void removeItem(String cartId, String itemId) {
Optional cart = getCart(cartId);
if (cart.isEmpty()) {
- throw new RuntimeException("No cart found with id " + cartId);
+ throw new CartNotFoundException(cartId);
}
List updatedItems = cart.get().cartLineItems().stream()
- .filter(it -> !it.item().tcin().equals(tcin))
+ .filter(it -> !it.item().itemId().equals(itemId))
.collect(Collectors.toList());
List storedCartLines = updatedItems.stream()
.map(it ->
new StoredCartLine(it.lineItemId(),
cart.get().id(),
- it.item().tcin(),
+ it.item().itemId(),
it.quantity(),
it.createdOn(),
it.updatedOn()))
@@ -104,41 +106,41 @@ public void removeItem(String cartId, String tcin) {
cartDatabase.updateCart(cartId, storedCartLines);
}
- public void addItem(String cartId, String tcin, Integer quantity) {
+ public void addItem(String cartId, String itemId, Integer quantity) {
List storedCartLines = new ArrayList<>(cartDatabase.getCart(cartId));
if (storedCartLines.isEmpty()) {
- throw new RuntimeException("No cart found with id " + cartId);
+ throw new CartNotFoundException(cartId);
}
- Optional storedCartLineForTcin = storedCartLines.stream().filter( it -> it.tcin().equals(tcin)).findFirst();
+ Optional storedCartLineForItemId = storedCartLines.stream().filter(it -> it.itemId().equals(itemId)).findFirst();
Integer quantityForNewStoredLine = quantity;
- if(storedCartLineForTcin.isPresent()) {
- quantityForNewStoredLine += storedCartLineForTcin.get().quantity() ;
- storedCartLines.remove(storedCartLineForTcin.get());
+ if(storedCartLineForItemId.isPresent()) {
+ quantityForNewStoredLine += storedCartLineForItemId.get().quantity();
+ storedCartLines.remove(storedCartLineForItemId.get());
}
StoredCartLine storedCartLine =
- new StoredCartLine(storedCartLineForTcin.map(StoredCartLine::lineId).orElseGet(() -> cartId + "-" + tcin),
- cartId, tcin, quantityForNewStoredLine, ZonedDateTime.now(), ZonedDateTime.now());
+ new StoredCartLine(storedCartLineForItemId.map(StoredCartLine::lineId).orElseGet(() -> cartId + "-" + itemId),
+ cartId, itemId, quantityForNewStoredLine, ZonedDateTime.now(), ZonedDateTime.now());
storedCartLines.add(storedCartLine);
cartDatabase.updateCart(cartId, storedCartLines);
}
- public void updateCartItem(String cartId, String tcin, Integer quantity) {
+ public void updateCartItem(String cartId, String itemId, Integer quantity) {
List storedCartLines = new ArrayList<>(cartDatabase.getCart(cartId));
- Optional storedCartLineForTcin = storedCartLines.stream().filter( it -> it.tcin().equals(tcin)).findFirst();
- if(storedCartLineForTcin.isPresent()) {
- storedCartLines.remove(storedCartLineForTcin.get());
+ Optional storedCartLineForItemId = storedCartLines.stream().filter(it -> it.itemId().equals(itemId)).findFirst();
+ if(storedCartLineForItemId.isPresent()) {
+ storedCartLines.remove(storedCartLineForItemId.get());
if(quantity > 0) {
StoredCartLine storedCartLine =
- new StoredCartLine(storedCartLineForTcin.get().lineId(),
- cartId, tcin, quantity, ZonedDateTime.now(), ZonedDateTime.now());
+ new StoredCartLine(storedCartLineForItemId.get().lineId(),
+ cartId, itemId, quantity, ZonedDateTime.now(), ZonedDateTime.now());
storedCartLines.add(storedCartLine);
}
cartDatabase.updateCart(cartId, storedCartLines);
} else {
- throw new RuntimeException("No cart line found for tcin "+tcin);
+ throw new CartLineItemNotFoundException(itemId);
}
}
@@ -148,36 +150,36 @@ private BigDecimal calculateDeliveryCharge(List cartLineItemList)
return deliveryChargeCalculator.calculateDeliveryCharges(itemMap);
}
private CartLineItem assembleCartLineItem(StoredCartLine scl) {
- Item item = getItem(scl.tcin());
- Price price = getPriceForItem(scl.tcin());
+ Item item = getItem(scl.itemId());
+ Price price = getPriceForItem(scl.itemId());
return new CartLineItem(scl.lineId(), item, scl.quantity(), price, scl.createdOn(), scl.updatedOn());
}
- private Price getPriceForItem(String tcin) {
- PriceApiResponse priceResponse = priceApiClient.getPricing(tcin);
+ private Price getPriceForItem(String itemId) {
+ PriceApiResponse priceResponse = priceApiClient.getPricing(itemId);
if(priceResponse.priceType().equals("SALE")) {
- return new Price(tcin, priceResponse.regularPrice(), Optional.of(priceResponse.salePrice()));
+ return new Price(itemId, priceResponse.regular(), Optional.of(priceResponse.sale()));
} else {
- return new Price(tcin, priceResponse.regularPrice(), Optional.empty());
+ return new Price(itemId, priceResponse.regular(), Optional.empty());
}
}
- private Item getItem(String tcin) {
- ItemApiResponse itemApiResponse = itemApiClient.getItem(tcin);
+ private Item getItem(String itemId) {
+ ItemApiResponse itemApiResponse = itemApiClient.getItem(itemId);
return new Item(itemApiResponse.itemId(),
itemApiResponse.smallDescription(),
itemApiResponse.longDescription(),
itemApiResponse.brandName(),
itemApiResponse.category(),
- itemApiResponse.merchClass().toString(),
- itemApiResponse.imageData().primaryImage(),
- itemApiResponse.imageData().alternateImage(),
+ itemApiResponse.merchClass(),
+ itemApiResponse.imageData().primary(),
+ itemApiResponse.imageData().alternate(),
itemApiResponse.imageData().baseUrl());
}
diff --git a/cart-service/src/main/java/com/target/retail/cart/service/behavior/Behaviors.java b/cart-service/src/main/java/com/target/retail/cart/service/behavior/Behaviors.java
index ad13d9e..58f1b08 100644
--- a/cart-service/src/main/java/com/target/retail/cart/service/behavior/Behaviors.java
+++ b/cart-service/src/main/java/com/target/retail/cart/service/behavior/Behaviors.java
@@ -1,5 +1,6 @@
package com.target.retail.cart.service.behavior;
+import com.target.retail.cart.exception.InducedFailureException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -76,7 +77,7 @@ private T callWithNoInducedBehavior(Supplier supplier) {
private T callWithRandomFailures(Supplier supplier) {
if(random.nextDouble() < failingBehaviorFailureRate) {
- throw new RuntimeException("Failure to call the service. Please try later");
+ throw new InducedFailureException("Failure to call the service. Please try later");
}
return supplier.get();
}
diff --git a/cart-service/src/main/java/com/target/retail/cart/service/client/ItemApiClient.java b/cart-service/src/main/java/com/target/retail/cart/service/client/ItemApiClient.java
index c33fa8f..d4f7639 100644
--- a/cart-service/src/main/java/com/target/retail/cart/service/client/ItemApiClient.java
+++ b/cart-service/src/main/java/com/target/retail/cart/service/client/ItemApiClient.java
@@ -14,9 +14,9 @@ public ItemApiClient(@Value("${clients.item-service.base-url}") String baseUrl)
this.restClient = RestClient.builder().baseUrl(baseUrl).build();
}
- public ItemApiResponse getItem(String tcin) {
+ public ItemApiResponse getItem(String itemId) {
return restClient.get()
- .uri("/items/{item_id}", tcin)
+ .uri("/items/{itemId}", itemId)
.retrieve()
.body(ItemApiResponse.class);
}
diff --git a/cart-service/src/main/java/com/target/retail/cart/service/client/PriceApiClient.java b/cart-service/src/main/java/com/target/retail/cart/service/client/PriceApiClient.java
index 1fdbff3..6e964b8 100644
--- a/cart-service/src/main/java/com/target/retail/cart/service/client/PriceApiClient.java
+++ b/cart-service/src/main/java/com/target/retail/cart/service/client/PriceApiClient.java
@@ -14,9 +14,9 @@ public PriceApiClient(@Value("${clients.price-service.base-url}") String baseUrl
this.restClient = RestClient.builder().baseUrl(baseUrl).build();
}
- public PriceApiResponse getPricing(String tcin) {
+ public PriceApiResponse getPricing(String itemId) {
return restClient.get()
- .uri("/prices/{item_id}", tcin)
+ .uri("/prices/{itemId}", itemId)
.retrieve()
.body(PriceApiResponse.class);
}
diff --git a/cart-service/src/main/java/com/target/retail/cart/service/client/dto/ItemApiResponse.java b/cart-service/src/main/java/com/target/retail/cart/service/client/dto/ItemApiResponse.java
index 14e7ab1..6167f65 100644
--- a/cart-service/src/main/java/com/target/retail/cart/service/client/dto/ItemApiResponse.java
+++ b/cart-service/src/main/java/com/target/retail/cart/service/client/dto/ItemApiResponse.java
@@ -16,7 +16,7 @@ public record ItemApiResponse(String itemId,
ImageData imageData) {
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
- public record ImageData(String primaryImage, String alternateImage, String baseUrl) {
+ public record ImageData(String primary, String alternate, String baseUrl) {
}
}
diff --git a/cart-service/src/main/java/com/target/retail/cart/service/client/dto/PriceApiResponse.java b/cart-service/src/main/java/com/target/retail/cart/service/client/dto/PriceApiResponse.java
index 4435064..26821b2 100644
--- a/cart-service/src/main/java/com/target/retail/cart/service/client/dto/PriceApiResponse.java
+++ b/cart-service/src/main/java/com/target/retail/cart/service/client/dto/PriceApiResponse.java
@@ -6,4 +6,4 @@
import java.math.BigDecimal;
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
-public record PriceApiResponse(String productId, BigDecimal regularPrice, BigDecimal salePrice, String priceType) {}
+public record PriceApiResponse(String itemId, BigDecimal regular, BigDecimal sale, String priceType) {}
diff --git a/cart-service/src/main/resources/application.yml b/cart-service/src/main/resources/application.yml
index f3e084f..8c46796 100644
--- a/cart-service/src/main/resources/application.yml
+++ b/cart-service/src/main/resources/application.yml
@@ -12,9 +12,9 @@ cart:
clients:
price-service:
- base-url: http://data:8080/retail_data_services/v1
+ base-url: http://product-api:8080/v1
item-service:
- base-url: http://data:8080/retail_data_services/v1
+ base-url: http://product-api:8080/v1
taxes:
defaultRate: 7.23
diff --git a/cart-service/src/main/resources/data/100.csv b/cart-service/src/main/resources/data/100.csv
index 7253158..c39aa30 100644
--- a/cart-service/src/main/resources/data/100.csv
+++ b/cart-service/src/main/resources/data/100.csv
@@ -1,4 +1,4 @@
-lineId,cartId,tcin,quantity,createdOn,updatedOn
+lineId,cartId,itemId,quantity,createdOn,updatedOn
10001,100,123456,1,2025-03-16T00:14:45.73Z,2025-03-16T00:15:45.73Z
10002,100,789123,11,2025-03-14T00:11:45.73Z,2025-03-14T00:15:45.73Z
10003,100,456788,3,2025-03-13T00:15:45.73Z,2025-03-15T00:15:45.73Z
diff --git a/cart-service/src/main/resources/data/101.csv b/cart-service/src/main/resources/data/101.csv
index cce9242..0b48cf1 100644
--- a/cart-service/src/main/resources/data/101.csv
+++ b/cart-service/src/main/resources/data/101.csv
@@ -1,4 +1,4 @@
-lineId,cartId,tcin,quantity,createdOn,updatedOn
+lineId,cartId,itemId,quantity,createdOn,updatedOn
10004,101,123456,6,2025-03-10T01:15:45.73Z,2025-03-13T00:10:45.73Z
10005,101,456788,3,2025-03-11T02:15:45.73Z,2025-03-14T00:16:45.73Z
10006,101,987612,30,2025-03-12T03:15:45.73Z,2025-03-15T00:11:45.73Z
diff --git a/cart-service/src/test/java/com/target/retail/cart/controller/CartControllerTest.java b/cart-service/src/test/java/com/target/retail/cart/controller/CartControllerTest.java
index a387807..c596c45 100644
--- a/cart-service/src/test/java/com/target/retail/cart/controller/CartControllerTest.java
+++ b/cart-service/src/test/java/com/target/retail/cart/controller/CartControllerTest.java
@@ -12,11 +12,13 @@
import com.target.retail.cart.service.behavior.InducedBehavior;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import com.target.retail.cart.exception.CartNotFoundException;
+import com.target.retail.cart.exception.CartLineItemNotFoundException;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.springframework.http.ResponseEntity;
+import org.springframework.http.ResponseEntity;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.util.List;
@@ -25,6 +27,7 @@
import java.util.function.Supplier;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
public class CartControllerTest {
@@ -118,7 +121,6 @@ public void testRemoveLastItemFromCart() {
// Mock behavior
when(cartService.getCart("cart123"))
- .thenReturn(Optional.of(mockCart))
.thenReturn(Optional.empty());
when(behaviors.getConfiguredBehavior()).thenReturn(createInducedBehavior());
@@ -131,22 +133,17 @@ public void testRemoveLastItemFromCart() {
@Test
public void testRemoveItemFromCartWhenCartDoesNotExist() {
- // Mock behavior
- when(cartService.getCart("cart123")).thenReturn(Optional.empty());
when(behaviors.getConfiguredBehavior()).thenReturn(createInducedBehavior());
+ org.mockito.Mockito.doThrow(new CartNotFoundException("cart123")).when(cartService).removeItem("cart123", "item1");
- // Execute the method
- ResponseEntity response = cartController.removeItemFromCart("cart123", "item1");
-
- // Verify the response
- assertEquals(ResponseEntity.notFound().build(), response);
+ assertThrows(CartNotFoundException.class, () -> cartController.removeItemFromCart("cart123", "item1"));
}
@Test
public void testUpdateCartItem() {
// Mock data
String cartId = "cart123";
- String tcin = "item1";
+ String itemId = "item1";
int newQuantity = 5;
Cart mockCart = new Cart(
cartId,
@@ -160,7 +157,7 @@ public void testUpdateCartItem() {
when(behaviors.getConfiguredBehavior()).thenReturn(createInducedBehavior());
// Execute the method
- ResponseEntity response = cartController.updateItem(cartId, tcin, new UpdateItemRequest(newQuantity));
+ ResponseEntity response = cartController.updateItem(cartId, itemId, new UpdateItemRequest(newQuantity));
// Verify the response
assertEquals(ResponseEntity.ok(CartResponse.from(mockCart)), response);
@@ -168,44 +165,26 @@ public void testUpdateCartItem() {
@Test
public void testUpdateCartItemWhenCartDoesNotExist() {
- // Mock data
String cartId = "cart123";
- String tcin = "item1";
+ String itemId = "item1";
int newQuantity = 5;
- // Mock behavior
- when(cartService.getCart(cartId)).thenReturn(Optional.empty());
when(behaviors.getConfiguredBehavior()).thenReturn(createInducedBehavior());
+ org.mockito.Mockito.doThrow(new CartNotFoundException(cartId)).when(cartService).updateCartItem(cartId, itemId, newQuantity);
- // Execute the method
- ResponseEntity response = cartController.updateItem(cartId, tcin, new UpdateItemRequest(newQuantity));
-
- // Verify the response
- assertEquals(ResponseEntity.notFound().build(), response);
+ assertThrows(CartNotFoundException.class, () -> cartController.updateItem(cartId, itemId, new UpdateItemRequest(newQuantity)));
}
@Test
- public void testUpdateCartItemWhenTcinNotFoundInCart() {
- // Mock data
+ public void testUpdateCartItemWhenItemIdNotFoundInCart() {
String cartId = "cart123";
- String tcin = "missingItemId";
+ String itemId = "missingItemId";
int newQuantity = 5;
- Cart mockCart = new Cart(
- cartId,
- new BigDecimal("10"),
- new BigDecimal("10"),
- List.of(newCartLineItem())
- );
- // Mock behavior
- when(cartService.getCart(cartId)).thenReturn(Optional.of(mockCart));
when(behaviors.getConfiguredBehavior()).thenReturn(createInducedBehavior());
+ org.mockito.Mockito.doThrow(new CartLineItemNotFoundException(itemId)).when(cartService).updateCartItem(cartId, itemId, newQuantity);
- // Execute the method
- ResponseEntity response = cartController.updateItem(cartId, tcin, new UpdateItemRequest(newQuantity));
-
- // Verify the response
- assertEquals(ResponseEntity.notFound().build(), response);
+ assertThrows(CartLineItemNotFoundException.class, () -> cartController.updateItem(cartId, itemId, new UpdateItemRequest(newQuantity)));
}
@Test
@@ -240,7 +219,7 @@ public void testCreateCartWithDuplicateTcins() {
// Mock data
List addItems = List.of(
new AddItemRequest("item1", 2),
- new AddItemRequest("item1", 3) // Duplicate tcin
+ new AddItemRequest("item1", 3) // Duplicate item ID
);
// Execute the method
@@ -266,7 +245,7 @@ private CartLineItem newCartLineItem(String suffix) {
String itemId = "item%s".formatted(suffix);
return new CartLineItem(lineId,
- new Item(itemId, "Small Description", "Long Description", "Brand", "Category", "Class", "primaryImage", "alternateImage", "baseUrl"), 1, new Price(itemId, new BigDecimal("10"), Optional.empty()), ZonedDateTime.now(), ZonedDateTime.now());
+ new Item(itemId, "Small Description", "Long Description", "Brand", "Category", 12, "primary", "alternate", "baseUrl"), 1, new Price(itemId, new BigDecimal("10"), Optional.empty()), ZonedDateTime.now(), ZonedDateTime.now());
}
private CartLineItem newCartLineItem() {
diff --git a/cart-service/src/test/java/com/target/retail/cart/controller/dto/CartResponseSerializationTest.java b/cart-service/src/test/java/com/target/retail/cart/controller/dto/CartResponseSerializationTest.java
index e7efc99..057484a 100644
--- a/cart-service/src/test/java/com/target/retail/cart/controller/dto/CartResponseSerializationTest.java
+++ b/cart-service/src/test/java/com/target/retail/cart/controller/dto/CartResponseSerializationTest.java
@@ -33,7 +33,7 @@ public void timestampsSerializeAsIso8601Strings() throws Exception {
new BigDecimal("5.00"),
List.of(new CartLineItem(
"1",
- new Item("12345", "Title", "Description", "Brand", "APPAREL", "MerchClass",
+ new Item("12345", "Title", "Description", "Brand", "APPAREL", 12,
"primary", "alternate", "baseUrl"),
1,
new Price("12345", new BigDecimal("10.00"), Optional.empty()),
diff --git a/cart-service/src/test/java/com/target/retail/cart/controller/dto/CartResponseTest.java b/cart-service/src/test/java/com/target/retail/cart/controller/dto/CartResponseTest.java
index 6d1bc4a..203d123 100644
--- a/cart-service/src/test/java/com/target/retail/cart/controller/dto/CartResponseTest.java
+++ b/cart-service/src/test/java/com/target/retail/cart/controller/dto/CartResponseTest.java
@@ -23,7 +23,7 @@ public void testFrom() {
"cart123",
new BigDecimal("10"),
new BigDecimal("10"),
- List.of(new CartLineItem("1",new Item("12345", "Item Title", "Item Description", "Brand", "Category", "MerchClass", "primaryImage", "alternateImage", "baseUrl"), 2, new Price("12345", new BigDecimal("8.00"), Optional.of(new BigDecimal("6.00"))),
+ List.of(new CartLineItem("1",new Item("12345", "Item Title", "Item Description", "Brand", "Category", 12, "primary", "alternate", "baseUrl"), 2, new Price("12345", new BigDecimal("8.00"), Optional.of(new BigDecimal("6.00"))),
ZonedDateTime.now(),
ZonedDateTime.now())));
@@ -43,16 +43,16 @@ public void testFrom() {
assertEquals(cart.cartLineItems().size(), cartResponse.items().size());
CartLineItem cartLineItem = cart.cartLineItems().get(0);
CartResponse.ItemResponse itemResponse = cartResponse.items().get(0);
- assertEquals(cartLineItem.item().tcin(), itemResponse.tcin());
+ assertEquals(cartLineItem.item().itemId(), itemResponse.itemId());
assertEquals(cartLineItem.item().title(), itemResponse.title());
assertEquals(cartLineItem.item().description(), itemResponse.description());
assertEquals(cartLineItem.item().brand(), itemResponse.brand());
assertEquals(cartLineItem.item().merchClass(), itemResponse.merchClass());
assertEquals(cartLineItem.quantity(), itemResponse.quantity());
- assertEquals(cartLineItem.price().regularPrice(), itemResponse.price().regular());
- assertEquals(cartLineItem.price().salePrice().orElse(null), itemResponse.price().sale());
- assertEquals(cartLineItem.item().primaryImage(), itemResponse.imageData().primary());
- assertEquals(cartLineItem.item().alternateImage(), itemResponse.imageData().alternate());
+ assertEquals(cartLineItem.price().regular(), itemResponse.price().regular());
+ assertEquals(cartLineItem.price().sale().orElse(null), itemResponse.price().sale());
+ assertEquals(cartLineItem.item().primary(), itemResponse.imageData().primary());
+ assertEquals(cartLineItem.item().alternate(), itemResponse.imageData().alternate());
assertEquals(cartLineItem.item().baseUrl(), itemResponse.imageData().baseUrl());
}
@@ -64,7 +64,7 @@ public void testFormattedPricesInCartResponse() {
new BigDecimal("5.25"),
List.of(new CartLineItem(
"1",
- new Item("12345", "Item Title", "Item Description", "Brand", "Category", "MerchClass", "primaryImage", "alternateImage", "baseUrl"),
+ new Item("12345", "Item Title", "Item Description", "Brand", "Category", 12, "primary", "alternate", "baseUrl"),
2,
new Price("12345", new BigDecimal("8.00"), Optional.of(new BigDecimal("6.50"))),
ZonedDateTime.now(),
diff --git a/cart-service/src/test/java/com/target/retail/cart/data/CartDatabaseTest.java b/cart-service/src/test/java/com/target/retail/cart/data/CartDatabaseTest.java
index 6802163..cf1bc8a 100644
--- a/cart-service/src/test/java/com/target/retail/cart/data/CartDatabaseTest.java
+++ b/cart-service/src/test/java/com/target/retail/cart/data/CartDatabaseTest.java
@@ -21,14 +21,14 @@ public class CartDatabaseTest {
private static final String cartsDataPath = "./carts-test/";
private static final String testDataForCart1 = """
- lineId,cartId,tcin,quantity,createdOn,updatedOn
+ lineId,cartId,itemId,quantity,createdOn,updatedOn
10001,cart-1,987612,10,2025-03-10T00:14:45.73Z,2025-03-16T00:15:45.73Z
10002,cart-1,789123,12,2025-03-11T00:11:45.73Z,2025-03-14T00:15:45.73Z
10003,cart-1,456788,1,2025-03-09T00:15:45.73Z,2025-03-15T00:15:45.73Z
""" ;
private static final String testDataForCart2 = """
- lineId,cartId,tcin,quantity,createdOn,updatedOn
+ lineId,cartId,itemId,quantity,createdOn,updatedOn
10004,cart-2,987612,1,2025-03-16T00:14:45.73Z,2025-03-16T00:15:45.73Z
10005,cart-2,789123,2,2025-03-14T00:11:45.73Z,2025-03-14T00:15:45.73Z
10006,cart-2,456788,3,2025-03-13T00:15:45.73Z,2025-03-15T00:15:45.73Z
diff --git a/cart-service/src/test/java/com/target/retail/cart/service/CartServiceTest.java b/cart-service/src/test/java/com/target/retail/cart/service/CartServiceTest.java
index e3bda68..1dba664 100644
--- a/cart-service/src/test/java/com/target/retail/cart/service/CartServiceTest.java
+++ b/cart-service/src/test/java/com/target/retail/cart/service/CartServiceTest.java
@@ -1,6 +1,7 @@
package com.target.retail.cart.service;
import com.target.retail.cart.data.CartDatabase;
+import com.target.retail.cart.exception.CartLineItemNotFoundException;
import com.target.retail.cart.model.Cart;
import com.target.retail.cart.model.CartLineItem;
import com.target.retail.cart.model.Item;
@@ -50,18 +51,18 @@ public void setUp() {
public void testGetCart() {
// Mock data
String cartId = "123";
- String tcin = "456";
+ String itemId = "456";
String deliveryZip = "78910";
- StoredCartLine storedCartLine = new StoredCartLine("1", cartId, tcin, 2, ZonedDateTime.now(), ZonedDateTime.now());
+ StoredCartLine storedCartLine = new StoredCartLine("1", cartId, itemId, 2, ZonedDateTime.now(), ZonedDateTime.now());
List storedCartLines = List.of(storedCartLine);
- Item item = new Item(tcin, "Short description", "Long description", "Brand", "Category", "MerchClass", "PrimaryImage", "AlternateImage", "BaseUrl");
- Price price = new Price(tcin, BigDecimal.valueOf(10.00), Optional.of(BigDecimal.valueOf(8.00)));
+ Item item = new Item(itemId, "Short description", "Long description", "Brand", "Category", 12, "PrimaryImage", "AlternateImage", "BaseUrl");
+ Price price = new Price(itemId, BigDecimal.valueOf(10.00), Optional.of(BigDecimal.valueOf(8.00)));
CartLineItem cartLineItem = new CartLineItem("1", item, 2, price, ZonedDateTime.now(), ZonedDateTime.now());
when(cartDatabase.getCart(cartId)).thenReturn(storedCartLines);
- when(itemApiClient.getItem(anyString())).thenReturn(new ItemApiResponse(tcin, "Short description", "Long description", "Brand", 5, "ONLINE", "BAR12345", "BRAND", 21, new ItemApiResponse.ImageData("PrimaryImage", "AlternateImage", "BaseUrl")));
- when(priceApiClient.getPricing(anyString())).thenReturn(new PriceApiResponse(tcin, BigDecimal.valueOf(10.00), BigDecimal.valueOf(8.00), "SALE"));
+ when(itemApiClient.getItem(anyString())).thenReturn(new ItemApiResponse(itemId, "Short description", "Long description", "Brand", 5, "ONLINE", "BAR12345", "BRAND", 21, new ItemApiResponse.ImageData("PrimaryImage", "AlternateImage", "BaseUrl")));
+ when(priceApiClient.getPricing(anyString())).thenReturn(new PriceApiResponse(itemId, BigDecimal.valueOf(10.00), BigDecimal.valueOf(8.00), "SALE"));
when(taxCalculator.calculateTax(Mockito.any(BigDecimal.class), Mockito.anyString())).thenReturn(BigDecimal.valueOf(1.00));
when(deliveryChargeCalculator.calculateDeliveryCharges(Mockito.anyMap())).thenReturn(BigDecimal.valueOf(5.00));
@@ -74,31 +75,31 @@ public void testGetCart() {
assertEquals(BigDecimal.valueOf(1.00), cart.get().totalTax());
assertEquals(BigDecimal.valueOf(5.00), cart.get().deliveryCharges());
assertEquals(1, cart.get().cartLineItems().size());
- assertEquals(cartLineItem.item().tcin(), cart.get().cartLineItems().get(0).item().tcin());
+ assertEquals(cartLineItem.item().itemId(), cart.get().cartLineItems().get(0).item().itemId());
}
@Test
public void testRemoveItem() {
// Mock data
String cartId = "123";
- String tcin1 = "456";
- String tcin2 = "123";
+ String itemId1 = "456";
+ String itemId2 = "123";
String deliveryZip = "78910";
ZonedDateTime now = ZonedDateTime.now();
- StoredCartLine storedCartLine1 = new StoredCartLine("1", cartId, tcin1, 2, now, now);
- StoredCartLine storedCartLine2 = new StoredCartLine("2", cartId, tcin2, 10, now, now);
+ StoredCartLine storedCartLine1 = new StoredCartLine("1", cartId, itemId1, 2, now, now);
+ StoredCartLine storedCartLine2 = new StoredCartLine("2", cartId, itemId2, 10, now, now);
List storedCartLines = List.of(storedCartLine1, storedCartLine2);
- Item item = new Item(tcin1, "Short description", "Long description", "Brand", "Category", "MerchClass", "PrimaryImage", "AlternateImage", "BaseUrl");
- Price price = new Price(tcin1, BigDecimal.valueOf(10.00), Optional.of(BigDecimal.valueOf(8.00)));
+ Item item = new Item(itemId1, "Short description", "Long description", "Brand", "Category", 12, "PrimaryImage", "AlternateImage", "BaseUrl");
+ Price price = new Price(itemId1, BigDecimal.valueOf(10.00), Optional.of(BigDecimal.valueOf(8.00)));
CartLineItem cartLineItem = new CartLineItem("1", item, 2, price, ZonedDateTime.now(), ZonedDateTime.now());
when(cartDatabase.getCart(cartId)).thenReturn(storedCartLines);
- when(itemApiClient.getItem(tcin1)).thenReturn(new ItemApiResponse(tcin1, "Short description1", "Long description1", "Brand1", 5, "ONLINE", "BAR12345", "BRAND", 21, new ItemApiResponse.ImageData("PrimaryImage", "AlternateImage", "BaseUrl")));
- when(itemApiClient.getItem(tcin2)).thenReturn(new ItemApiResponse(tcin2, "Short description2", "Long description2", "Brand2", 5, "ONLINE", "BAR12345", "BRAND", 1, new ItemApiResponse.ImageData("PrimaryImage", "AlternateImage", "BaseUrl")));
+ when(itemApiClient.getItem(itemId1)).thenReturn(new ItemApiResponse(itemId1, "Short description1", "Long description1", "Brand1", 5, "ONLINE", "BAR12345", "BRAND", 21, new ItemApiResponse.ImageData("PrimaryImage", "AlternateImage", "BaseUrl")));
+ when(itemApiClient.getItem(itemId2)).thenReturn(new ItemApiResponse(itemId2, "Short description2", "Long description2", "Brand2", 5, "ONLINE", "BAR12345", "BRAND", 1, new ItemApiResponse.ImageData("PrimaryImage", "AlternateImage", "BaseUrl")));
- when(priceApiClient.getPricing(anyString())).thenReturn(new PriceApiResponse(tcin1, BigDecimal.valueOf(10.00), BigDecimal.valueOf(8.00), "SALE"));
+ when(priceApiClient.getPricing(anyString())).thenReturn(new PriceApiResponse(itemId1, BigDecimal.valueOf(10.00), BigDecimal.valueOf(8.00), "SALE"));
when(taxCalculator.calculateTax(Mockito.any(BigDecimal.class), Mockito.anyString())).thenReturn(BigDecimal.valueOf(1.00));
when(deliveryChargeCalculator.calculateDeliveryCharges(Mockito.anyMap())).thenReturn(BigDecimal.valueOf(5.00));
@@ -107,68 +108,68 @@ public void testRemoveItem() {
assertEquals(2, cart.get().cartLineItems().size());
// Call the method to remove item from cart
- cartService.removeItem(cartId, tcin1);
+ cartService.removeItem(cartId, itemId1);
// verify the call to updateCart
- Mockito.verify(cartDatabase).updateCart(cartId, List.of( new StoredCartLine("2", cartId, tcin2, 10, now, now)));
+ Mockito.verify(cartDatabase).updateCart(cartId, List.of( new StoredCartLine("2", cartId, itemId2, 10, now, now)));
}
@Test
- public void testAddItem_CartHasOneLineBeforeAddingNewOneAndTCINisNew() {
+ public void testAddItem_CartHasOneLineBeforeAddingNewOneAndItemIdIsNew() {
// Mock data
String cartId = "123";
- String tcin = "456";
- String existingTcin = "789";
+ String itemId = "456";
+ String existingItemId = "789";
Integer quantity = 2;
ZonedDateTime now = ZonedDateTime.now();
List storedCartLines = List.of(
- new StoredCartLine("1", cartId, existingTcin, 1, now, now)
+ new StoredCartLine("1", cartId, existingItemId, 1, now, now)
);
when(cartDatabase.getCart(cartId)).thenReturn(storedCartLines);
// Call the method to add item to cart
- cartService.addItem(cartId, tcin, quantity);
+ cartService.addItem(cartId, itemId, quantity);
// Verify the call to updateCart
Mockito.verify(cartDatabase).updateCart(eq(cartId), argThat(list ->
list.size() == 2 &&
list.get(0).getId().equals("1") &&
list.get(0).cartId().equals(cartId) &&
- list.get(0).tcin().equals(existingTcin) &&
+ list.get(0).itemId().equals(existingItemId) &&
list.get(0).quantity() == 1 &&
- list.get(1).getId().equals(cartId+"-"+tcin) &&
+ list.get(1).getId().equals(cartId+"-"+itemId) &&
list.get(1).cartId().equals(cartId) &&
- list.get(1).tcin().equals(tcin) &&
+ list.get(1).itemId().equals(itemId) &&
Objects.equals(list.get(1).quantity(), quantity)
));
}
@Test
- public void testAddItem_CartHasExistingTCIN_QuantitiesUpdated() {
+ public void testAddItem_CartHasExistingItemId_QuantitiesUpdated() {
// Mock data
String cartId = "123";
- String tcin = "456";
+ String itemId = "456";
Integer existingQuantity = 2;
Integer additionalQuantity = 3;
ZonedDateTime now = ZonedDateTime.now();
List storedCartLines = List.of(
- new StoredCartLine("1", cartId, tcin, existingQuantity, now, now)
+ new StoredCartLine("1", cartId, itemId, existingQuantity, now, now)
);
when(cartDatabase.getCart(cartId)).thenReturn(storedCartLines);
// Call the method to add item to cart
- cartService.addItem(cartId, tcin, additionalQuantity);
+ cartService.addItem(cartId, itemId, additionalQuantity);
// Verify the call to updateCart
Mockito.verify(cartDatabase).updateCart(eq(cartId), argThat(list ->
list.size() == 1 &&
list.get(0).getId().equals("1") &&
list.get(0).cartId().equals(cartId) &&
- list.get(0).tcin().equals(tcin) &&
+ list.get(0).itemId().equals(itemId) &&
list.get(0).quantity() == (existingQuantity + additionalQuantity)
));
}
@@ -178,23 +179,23 @@ public void testAddItem_CartHasExistingTCIN_QuantitiesUpdated() {
public void testUpdateCartItem_ItemFoundInCart() {
// Mock data
String cartId = "123";
- String tcin = "456";
+ String itemId = "456";
Integer newQuantity = 5;
ZonedDateTime now = ZonedDateTime.now();
- StoredCartLine storedCartLine = new StoredCartLine("1", cartId, tcin, 2, now, now);
+ StoredCartLine storedCartLine = new StoredCartLine("1", cartId, itemId, 2, now, now);
List storedCartLines = List.of(storedCartLine);
when(cartDatabase.getCart(cartId)).thenReturn(storedCartLines);
// Call the method to update the cart item
- cartService.updateCartItem(cartId, tcin, newQuantity);
+ cartService.updateCartItem(cartId, itemId, newQuantity);
// Verify the call to updateCart
Mockito.verify(cartDatabase).updateCart(eq(cartId), argThat(list ->
list.size() == 1 &&
list.get(0).getId().equals("1") &&
list.get(0).cartId().equals(cartId) &&
- list.get(0).tcin().equals(tcin) &&
+ list.get(0).itemId().equals(itemId) &&
Objects.equals(list.get(0).quantity(), newQuantity)
));
}
@@ -203,47 +204,47 @@ public void testUpdateCartItem_ItemFoundInCart() {
public void testUpdateCartItem_ItemNotFoundInCart() {
// Mock data
String cartId = "123";
- String tcin = "456";
+ String itemId = "456";
Integer newQuantity = 5;
// Mock an empty cart or a cart without the specified TCIN
when(cartDatabase.getCart(cartId)).thenReturn(List.of());
- // Assert that the method throws a RuntimeException
- RuntimeException exception = org.junit.jupiter.api.Assertions.assertThrows(
- RuntimeException.class,
- () -> cartService.updateCartItem(cartId, tcin, newQuantity)
+ // Assert that the method throws a CartLineItemNotFoundException
+ CartLineItemNotFoundException exception = org.junit.jupiter.api.Assertions.assertThrows(
+ CartLineItemNotFoundException.class,
+ () -> cartService.updateCartItem(cartId, itemId, newQuantity)
);
// Verify the exception message
- assertEquals("No cart line found for tcin " + tcin, exception.getMessage());
+ assertEquals("No cart line found for item id " + itemId, exception.getMessage());
}
@Test
public void testUpdateCartItem_QuantityZero_ItemRemoved() {
// Mock data
String cartId = "123";
- String tcinToRemove = "456";
- String tcinToKeep = "789";
+ String itemIdToRemove = "456";
+ String itemIdToKeep = "789";
Integer quantityToRemove = 0;
Integer quantityToKeep = 5;
ZonedDateTime now = ZonedDateTime.now();
- StoredCartLine lineToRemove = new StoredCartLine("1", cartId, tcinToRemove, 2, now, now);
- StoredCartLine lineToKeep = new StoredCartLine("2", cartId, tcinToKeep, quantityToKeep, now, now);
+ StoredCartLine lineToRemove = new StoredCartLine("1", cartId, itemIdToRemove, 2, now, now);
+ StoredCartLine lineToKeep = new StoredCartLine("2", cartId, itemIdToKeep, quantityToKeep, now, now);
List storedCartLines = List.of(lineToRemove, lineToKeep);
when(cartDatabase.getCart(cartId)).thenReturn(storedCartLines);
// Call the method to update the cart item
- cartService.updateCartItem(cartId, tcinToRemove, quantityToRemove);
+ cartService.updateCartItem(cartId, itemIdToRemove, quantityToRemove);
// Verify the call to updateCart
Mockito.verify(cartDatabase).updateCart(eq(cartId), argThat(list ->
list.size() == 1 &&
list.get(0).getId().equals("2") &&
list.get(0).cartId().equals(cartId) &&
- list.get(0).tcin().equals(tcinToKeep) &&
+ list.get(0).itemId().equals(itemIdToKeep) &&
Objects.equals(list.get(0).quantity(), quantityToKeep)
));
}
@@ -252,8 +253,8 @@ public void testUpdateCartItem_QuantityZero_ItemRemoved() {
public void testCreateCart() {
// Mock data
String cartId = "123";
- String tcin1 = "456";
- String tcin2 = "789";
+ String itemId1 = "456";
+ String itemId2 = "789";
Integer quantity1 = 2;
Integer quantity2 = 3;
@@ -261,19 +262,19 @@ public void testCreateCart() {
when(cartDatabase.newCartId()).thenReturn(cartId);
// Call the method to create a cart
- cartService.createCart(Map.of(tcin1, quantity1, tcin2, quantity2));
+ cartService.createCart(Map.of(itemId1, quantity1, itemId2, quantity2));
// Verify the call to updateCart
Mockito.verify(cartDatabase).updateCart(eq(cartId), argThat(list ->
list.size() == 2 &&
list.stream().anyMatch(line ->
line.cartId().equals(cartId) &&
- line.tcin().equals(tcin1) &&
+ line.itemId().equals(itemId1) &&
Objects.equals(line.quantity(), quantity1)
) &&
list.stream().anyMatch(line ->
line.cartId().equals(cartId) &&
- line.tcin().equals(tcin2) &&
+ line.itemId().equals(itemId2) &&
Objects.equals(line.quantity(), quantity2)
)
));
diff --git a/cart-service/src/test/resources/application.yml b/cart-service/src/test/resources/application.yml
index 98e1e94..57d5c02 100644
--- a/cart-service/src/test/resources/application.yml
+++ b/cart-service/src/test/resources/application.yml
@@ -3,9 +3,9 @@ cart:
clients:
price-service:
- base-url: http://data:8080/retail_data_services/v1
+ base-url: http://product-api:8080/v1
item-service:
- base-url: http://data:8080/retail_data_services/v1
+ base-url: http://product-api:8080/v1
taxes:
defaultRate: 7.23
diff --git a/docker-compose.yml b/docker-compose.yml
index 9b59cf0..a97c0d1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,12 +1,12 @@
services:
- data:
+ product-api:
build:
- context: retail-data-services
+ context: product-api
dockerfile: Dockerfile
ports:
- "8080:8080"
healthcheck:
- test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
+ test: ["CMD", "curl", "-f", "http://localhost:8080/v1/health"]
interval: 5s
timeout: 3s
retries: 3
@@ -18,5 +18,5 @@ services:
ports:
- "8081:8081"
depends_on:
- data:
+ product-api:
condition: service_healthy
diff --git a/retail-data-services/.dockerignore b/product-api/.dockerignore
similarity index 100%
rename from retail-data-services/.dockerignore
rename to product-api/.dockerignore
diff --git a/retail-data-services/Dockerfile b/product-api/Dockerfile
similarity index 86%
rename from retail-data-services/Dockerfile
rename to product-api/Dockerfile
index 3a66aea..885a5a4 100644
--- a/retail-data-services/Dockerfile
+++ b/product-api/Dockerfile
@@ -4,7 +4,7 @@ FROM eclipse-temurin:17-jre@sha256:3443191793fb27f7df8bab42821d149eec9e280ab28e8
WORKDIR /app
# Copy the current directory contents into the container at /app
-COPY build/libs/retail-data-services.jar /app/retail-data-services.jar
+COPY build/libs/product-api.jar /app/product-api.jar
# Create the /data directory
RUN mkdir /data
@@ -25,4 +25,4 @@ ENTRYPOINT ["java", \
"-XX:TieredStopAtLevel=1", \
"-XX:+UseSerialGC", \
"-Xss256k", \
- "-jar", "/app/retail-data-services.jar"]
+ "-jar", "/app/product-api.jar"]
diff --git a/retail-data-services/api-spec/retail_data_services-v1.yaml b/product-api/api-spec/product-api-v1.yaml
similarity index 97%
rename from retail-data-services/api-spec/retail_data_services-v1.yaml
rename to product-api/api-spec/product-api-v1.yaml
index 3e13644..0e630c0 100644
--- a/retail-data-services/api-spec/retail_data_services-v1.yaml
+++ b/product-api/api-spec/product-api-v1.yaml
@@ -1,10 +1,10 @@
openapi: 3.1.0
info:
- title: Retail Data Services API
- description: API for managing retail data services.
+ title: Product API
+ description: Read-only API for product catalog, pricing, and availability.
version: "1.0"
servers:
-- url: http://localhost:8080/retail_data_services/v1
+- url: http://localhost:8080/v1
description: Generated server url
paths:
/prices/{id}:
diff --git a/retail-data-services/build.gradle.kts b/product-api/build.gradle.kts
similarity index 82%
rename from retail-data-services/build.gradle.kts
rename to product-api/build.gradle.kts
index e493388..8d294c8 100644
--- a/retail-data-services/build.gradle.kts
+++ b/product-api/build.gradle.kts
@@ -1,9 +1,9 @@
application {
- mainClass.set("com.target.retail.data.services.Main")
+ mainClass.set("com.target.retail.product.Main")
}
tasks.named("bootJar") {
- archiveFileName.set("retail-data-services.jar")
+ archiveFileName.set("product-api.jar")
}
dependencies {
diff --git a/retail-data-services/data-formats.md b/product-api/data-formats.md
similarity index 100%
rename from retail-data-services/data-formats.md
rename to product-api/data-formats.md
diff --git a/retail-data-services/induced_behaviors.md b/product-api/induced_behaviors.md
similarity index 83%
rename from retail-data-services/induced_behaviors.md
rename to product-api/induced_behaviors.md
index 88a2dbe..69f0922 100644
--- a/retail-data-services/induced_behaviors.md
+++ b/product-api/induced_behaviors.md
@@ -24,7 +24,7 @@ If `DEFAULT_BEHAVIOR` is set to anything else, the service will fail when it tri
Assumptions in the examples:
-- Container image name: `retail-data-services`
+- Container image name: `product-api`
- Container port: `8080` (mapped to different host ports as needed)
## NORMAL behavior
@@ -43,7 +43,6 @@ Conceptually, controllers/services call into a function that builds the API resp
- You can omit `DEFAULT_BEHAVIOR` entirely (it defaults to `NORMAL`), or
- Set it explicitly:
-
- `DEFAULT_BEHAVIOR=NORMAL`
There are no additional tuning environment variables for this behavior.
@@ -55,7 +54,7 @@ Implicit default (no env var):
```bash
docker run --rm \
-p 8080:8080 \
- retail-data-services
+ product-api
```
Explicitly set `NORMAL`:
@@ -64,7 +63,7 @@ Explicitly set `NORMAL`:
docker run --rm \
-p 8080:8080 \
-e DEFAULT_BEHAVIOR=NORMAL \
- retail-data-services
+ product-api
```
**Run with `docker-compose`**
@@ -73,8 +72,8 @@ Example service definition:
```yaml
services:
- retail-data-services-normal:
- image: retail-data-services
+ product-api-normal:
+ image: product-api
ports:
- "8080:8080"
environment:
@@ -129,16 +128,16 @@ Tune how slow API responses are:
**Run with `docker run`**
-Use default delay settings (around 1–10 seconds before each API response is generated):
+Use default delay settings (around 1 to 10 seconds before each API response is generated):
```bash
docker run --rm \
-p 8080:8080 \
-e DEFAULT_BEHAVIOR=SLOW_RESPONSE \
- retail-data-services
+ product-api
```
-Override delays for a milder slowdown (roughly 0.5–2 seconds added before generating each response):
+Override delays for a milder slowdown (roughly 0.5 to 2 seconds added before generating each response):
```bash
docker run --rm \
@@ -146,10 +145,10 @@ docker run --rm \
-e DEFAULT_BEHAVIOR=SLOW_RESPONSE \
-e BEHAVIORS_SLOW_RESPONSE_MIN_DELAY_MS=500 \
-e BEHAVIORS_SLOW_RESPONSE_MAX_DELAY_MS=2000 \
- retail-data-services
+ product-api
```
-Override for a heavier slowdown (roughly 3–8 seconds before generating each response):
+Override for a heavier slowdown (roughly 3 to 8 seconds before generating each response):
```bash
docker run --rm \
@@ -157,7 +156,7 @@ docker run --rm \
-e DEFAULT_BEHAVIOR=SLOW_RESPONSE \
-e BEHAVIORS_SLOW_RESPONSE_MIN_DELAY_MS=3000 \
-e BEHAVIORS_SLOW_RESPONSE_MAX_DELAY_MS=8000 \
- retail-data-services
+ product-api
```
**Run with `docker-compose`**
@@ -166,8 +165,8 @@ Mild slowdown example (host port 8081):
```yaml
services:
- retail-data-services-slow:
- image: retail-data-services
+ product-api-slow:
+ image: product-api
ports:
- "8081:8080"
environment:
@@ -180,8 +179,8 @@ Heavier slowdown variant:
```yaml
services:
- retail-data-services-slow-heavy:
- image: retail-data-services
+ product-api-slow-heavy:
+ image: product-api
ports:
- "8083:8080"
environment:
@@ -206,10 +205,10 @@ services:
- For each wrapped API call, this behavior:
1. Draws a random number between 0.0 and 1.0.
2. Compares it to a configured failure rate.
- 3. If the random number is less than the failure rate, it throws an exception instead of generating the API response.
+ 3. If the random number is less than the failure rate, it throws an `InducedFailureException` instead of generating the API response.
4. Otherwise, it runs the normal logic that builds the API response and returns that response.
-This causes a configurable fraction of requests to fail before a response is generated, usually surfacing as HTTP 5xx errors (depending on the global exception handling) instead of the normal API payload.
+This causes a configurable fraction of requests to fail before a response is generated. The `GlobalExceptionHandler` maps the resulting `InducedFailureException` to HTTP 503 Service Unavailable instead of returning the normal API payload.
### Configuration and Docker usage
@@ -234,7 +233,7 @@ Use the default failure rate (~5% of API responses fail):
docker run --rm \
-p 8080:8080 \
-e DEFAULT_BEHAVIOR=RANDOM_FAILURES \
- retail-data-services
+ product-api
```
Increase the failure rate to about 25% of API calls:
@@ -244,7 +243,7 @@ docker run --rm \
-p 8080:8080 \
-e DEFAULT_BEHAVIOR=RANDOM_FAILURES \
-e BEHAVIORS_RANDOM_FAILING_FAILURE_RATE=0.25 \
- retail-data-services
+ product-api
```
Use a very aggressive 50% failure rate (half of the API calls fail):
@@ -254,17 +253,17 @@ docker run --rm \
-p 8080:8080 \
-e DEFAULT_BEHAVIOR=RANDOM_FAILURES \
-e BEHAVIORS_RANDOM_FAILING_FAILURE_RATE=0.5 \
- retail-data-services
+ product-api
```
**Run with `docker-compose`**
-Light flakiness (~10–20% of API calls) on host port 8082:
+Light flakiness (~10 to 20% of API calls) on host port 8082:
```yaml
services:
- retail-data-services-flaky:
- image: retail-data-services
+ product-api-flaky:
+ image: product-api
ports:
- "8082:8080"
environment:
@@ -276,8 +275,8 @@ More aggressive flakiness (~50% of API calls):
```yaml
services:
- retail-data-services-flaky-heavy:
- image: retail-data-services
+ product-api-flaky-heavy:
+ image: product-api
ports:
- "8084:8080"
environment:
@@ -302,32 +301,33 @@ services:
- Wait, then generate and return the response (SLOW_RESPONSE), or
- Fail early without generating a response at all (RANDOM_FAILURES).
-By setting only environment variables when you start the container, you can run the same API code in any of these modes. Endpoints that don’t use the behavior wrapper will continue to generate responses normally.
+By setting only environment variables when you start the container, you can run the same API code in any of these modes. Endpoints that don't use the behavior wrapper will continue to generate responses normally.
## Use cases and tips
-- **UI latency testing**
+- **UI latency testing**
Point your UI or client at a `SLOW_RESPONSE` instance to observe loading indicators, spinners, and timeouts when API responses are delayed.
-- **Resilience and retry testing**
- Use a `RANDOM_FAILURES` instance with a moderate failure rate (e.g., 0.1–0.3) to exercise retries, backoff strategies, and error handling when API responses sometimes fail outright.
+- **Resilience and retry testing**
+ Use a `RANDOM_FAILURES` instance with a moderate failure rate (e.g., 0.1 to 0.3) to exercise retries, backoff strategies, and error handling when API responses sometimes fail outright.
-- **Baseline vs. stressed comparison**
+- **Baseline vs. stressed comparison**
Run `NORMAL` and one induced-behavior instance in parallel (on different ports) to compare metrics, logs, and user experience between normal and stressed API behavior.
-- **Safety tips**
- - Very high failure rates or very long delays can make the API appear "down." Start with conservative values and ramp up.
+- **Safety tips**
+ - Very high failure rates or very long delays can make the API appear "down." Start with conservative values and ramp up.
- The chosen `DEFAULT_BEHAVIOR` applies process-wide. To run different modes simultaneously, start multiple containers with different environment settings.
## References
For deeper technical details, see:
-- `src/main/java/com/target/retail/data/services/service/behavior/InducedBehavior.java`
+- `src/main/java/com/target/retail/product/service/behavior/InducedBehavior.java`
Functional interface that wraps a `Supplier` representing the API-response generation logic and lets behaviors inject cross-cutting effects.
-- `src/main/java/com/target/retail/data/services/service/behavior/BehaviorType.java`
+- `src/main/java/com/target/retail/product/service/behavior/BehaviorType.java`
Enum declaring the supported behavior types (`NORMAL`, `SLOW_RESPONSE`, `RANDOM_FAILURES`).
-- `src/main/java/com/target/retail/data/services/service/behavior/Behaviors.java`
+- `src/main/java/com/target/retail/product/service/behavior/Behaviors.java`
Component that reads configuration from environment variables (including `DEFAULT_BEHAVIOR`, failure rate, and delay bounds), wires behavior lambdas into a map, and exposes the configured behavior used to wrap API-response generation.
+
diff --git a/retail-data-services/retail-data-services.http b/product-api/product-api.http
similarity index 100%
rename from retail-data-services/retail-data-services.http
rename to product-api/product-api.http
diff --git a/retail-data-services/scripts/README.md b/product-api/scripts/README.md
similarity index 89%
rename from retail-data-services/scripts/README.md
rename to product-api/scripts/README.md
index b0d4acf..e9fb7b2 100644
--- a/retail-data-services/scripts/README.md
+++ b/product-api/scripts/README.md
@@ -2,7 +2,7 @@
## benchmark-startup.sh
-Measures Docker container startup time for retail-data-services.
+Measures Docker container startup time for product-api.
### Usage
diff --git a/retail-data-services/scripts/benchmark-startup.sh b/product-api/scripts/benchmark-startup.sh
similarity index 92%
rename from retail-data-services/scripts/benchmark-startup.sh
rename to product-api/scripts/benchmark-startup.sh
index ea7e2de..a005418 100755
--- a/retail-data-services/scripts/benchmark-startup.sh
+++ b/product-api/scripts/benchmark-startup.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# ============================================================================
-# Retail Data Services - Docker Startup Time Benchmark Script
+# Product API - Docker Startup Time Benchmark Script
# ============================================================================
# Measures Docker container startup time by building the image locally and
# running multiple test iterations.
@@ -16,9 +16,9 @@
set -e
# Configuration
-IMAGE_NAME="retail-data-services:benchmark"
-CONTAINER_PREFIX="retail-benchmark"
-HEALTH_ENDPOINT="http://localhost:8080/retail_data_services/v1/health"
+IMAGE_NAME="product-api:benchmark"
+CONTAINER_PREFIX="product-api-benchmark"
+HEALTH_ENDPOINT="http://localhost:8080/v1/health"
DEFAULT_ITERATIONS=3
HEALTH_TIMEOUT=60
WAIT_BETWEEN_ITERATIONS=2
@@ -75,7 +75,7 @@ cleanup_container() {
check_port_available() {
local port=$1
- if lsof -Pi :$port -sTCP:LISTEN -t >/dev/null 2>&1 ; then
+ if lsof -Pi :"$port" -sTCP:LISTEN -t >/dev/null 2>&1 ; then
return 1
else
return 0
@@ -88,7 +88,7 @@ calculate_stats() {
eval "local -a arr=(\"\${${array_name}[@]}\")"
local count=${#arr[@]}
- if [ $count -eq 0 ]; then
+ if [ "$count" -eq 0 ]; then
echo "N/A"
return
fi
@@ -128,11 +128,11 @@ calculate_stats() {
# Main Benchmark Logic
# ============================================================================
-print_header "Retail Data Services Startup Benchmark"
+print_header "Product API Startup Benchmark"
# Check if JAR exists
-if [ ! -f "build/libs/retail-data-services.jar" ]; then
- print_error "JAR file not found: build/libs/retail-data-services.jar"
+if [ ! -f "build/libs/product-api.jar" ]; then
+ print_error "JAR file not found: build/libs/product-api.jar"
print_info "Please run: ./gradlew clean build"
exit 1
fi
@@ -146,7 +146,7 @@ if ! check_port_available $PORT; then
print_error "Both ports 8080 and 8081 are in use. Please free up a port."
exit 1
fi
- HEALTH_ENDPOINT="http://localhost:8081/retail_data_services/v1/health"
+ HEALTH_ENDPOINT="http://localhost:8081/v1/health"
fi
print_info "Using port: $PORT"
@@ -172,7 +172,7 @@ print_info "Running $ITERATIONS benchmark iterations..."
echo ""
# Run benchmark iterations
-for i in $(seq 1 $ITERATIONS); do
+for i in $(seq 1 "$ITERATIONS"); do
echo "----------------------------------------"
echo "Iteration $i/$ITERATIONS"
echo "----------------------------------------"
@@ -250,7 +250,7 @@ for i in $(seq 1 $ITERATIONS); do
cleanup_container "$container_name"
# Wait between iterations
- if [ $i -lt $ITERATIONS ]; then
+ if [ "$i" -lt "$ITERATIONS" ]; then
sleep $WAIT_BETWEEN_ITERATIONS
fi
@@ -282,7 +282,7 @@ results_file="${script_dir}/benchmark-results-${timestamp}.txt"
{
echo "==========================================="
- echo "Retail Data Services Startup Benchmark"
+ echo "Product API Startup Benchmark"
echo "Timestamp: $(date)"
echo "==========================================="
echo ""
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/Main.java b/product-api/src/main/java/com/target/retail/product/Main.java
similarity index 86%
rename from retail-data-services/src/main/java/com/target/retail/data/services/Main.java
rename to product-api/src/main/java/com/target/retail/product/Main.java
index b4cdc48..4bacb0c 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/Main.java
+++ b/product-api/src/main/java/com/target/retail/product/Main.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services;
+package com.target.retail.product;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/config/CorsGlobalConfig.java b/product-api/src/main/java/com/target/retail/product/config/CorsGlobalConfig.java
similarity index 95%
rename from retail-data-services/src/main/java/com/target/retail/data/services/config/CorsGlobalConfig.java
rename to product-api/src/main/java/com/target/retail/product/config/CorsGlobalConfig.java
index 5867a4b..ffc1f4b 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/config/CorsGlobalConfig.java
+++ b/product-api/src/main/java/com/target/retail/product/config/CorsGlobalConfig.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.config;
+package com.target.retail.product.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/config/CorsProperties.java b/product-api/src/main/java/com/target/retail/product/config/CorsProperties.java
similarity index 96%
rename from retail-data-services/src/main/java/com/target/retail/data/services/config/CorsProperties.java
rename to product-api/src/main/java/com/target/retail/product/config/CorsProperties.java
index ad1a437..4406688 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/config/CorsProperties.java
+++ b/product-api/src/main/java/com/target/retail/product/config/CorsProperties.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.config;
+package com.target.retail.product.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/config/OpenApiConfig.java b/product-api/src/main/java/com/target/retail/product/config/OpenApiConfig.java
similarity index 67%
rename from retail-data-services/src/main/java/com/target/retail/data/services/config/OpenApiConfig.java
rename to product-api/src/main/java/com/target/retail/product/config/OpenApiConfig.java
index ae0d02f..3d3c452 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/config/OpenApiConfig.java
+++ b/product-api/src/main/java/com/target/retail/product/config/OpenApiConfig.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.config;
+package com.target.retail.product.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
@@ -12,9 +12,9 @@ public class OpenApiConfig {
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
- .title("Retail Data Services API")
+ .title("Product API")
.version("1.0")
- .description("API for managing retail data services."));
+ .description("Read-only API for product catalog, pricing, and availability."));
}
}
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/controller/AvailabilityController.java b/product-api/src/main/java/com/target/retail/product/controller/AvailabilityController.java
similarity index 81%
rename from retail-data-services/src/main/java/com/target/retail/data/services/controller/AvailabilityController.java
rename to product-api/src/main/java/com/target/retail/product/controller/AvailabilityController.java
index d1622ab..ccc5fb4 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/controller/AvailabilityController.java
+++ b/product-api/src/main/java/com/target/retail/product/controller/AvailabilityController.java
@@ -1,9 +1,10 @@
-package com.target.retail.data.services.controller;
+package com.target.retail.product.controller;
-import com.target.retail.data.services.dto.AvailabilityResponse;
-import com.target.retail.data.services.model.ItemAvailability;
-import com.target.retail.data.services.service.AvailabilityService;
-import com.target.retail.data.services.service.behavior.Behaviors;
+import com.target.retail.product.dto.AvailabilityResponse;
+import com.target.retail.product.exception.AvailabilityNotFoundException;
+import com.target.retail.product.model.ItemAvailability;
+import com.target.retail.product.service.AvailabilityService;
+import com.target.retail.product.service.behavior.Behaviors;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -18,6 +19,7 @@
import java.util.Optional;
@RestController
+@RequestMapping("/v1")
public class AvailabilityController {
private final AvailabilityService availabilityService;
@@ -43,7 +45,7 @@ public ResponseEntity getAvailability(@PathVariable String
return behaviors.getConfiguredBehavior().execute(() ->
itemAvailability.map(it ->
ResponseEntity.ok(new AvailabilityResponse(it)))
- .orElseGet(() -> ResponseEntity.notFound().build())
+ .orElseThrow(() -> new AvailabilityNotFoundException(id))
);
}
diff --git a/product-api/src/main/java/com/target/retail/product/controller/GlobalExceptionHandler.java b/product-api/src/main/java/com/target/retail/product/controller/GlobalExceptionHandler.java
new file mode 100644
index 0000000..122e322
--- /dev/null
+++ b/product-api/src/main/java/com/target/retail/product/controller/GlobalExceptionHandler.java
@@ -0,0 +1,40 @@
+package com.target.retail.product.controller;
+
+import com.target.retail.product.data.DataException;
+import com.target.retail.product.dto.ErrorResponse;
+import com.target.retail.product.exception.AvailabilityNotFoundException;
+import com.target.retail.product.exception.InducedFailureException;
+import com.target.retail.product.exception.ItemNotFoundException;
+import com.target.retail.product.exception.PriceNotFoundException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler({ItemNotFoundException.class, PriceNotFoundException.class, AvailabilityNotFoundException.class})
+ public ResponseEntity handleNotFound(RuntimeException ex) {
+ return ResponseEntity.status(HttpStatus.NOT_FOUND)
+ .body(new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage()));
+ }
+
+ @ExceptionHandler(InducedFailureException.class)
+ public ResponseEntity handleInducedFailure(InducedFailureException ex) {
+ return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
+ .body(new ErrorResponse(HttpStatus.SERVICE_UNAVAILABLE.value(), ex.getMessage()));
+ }
+
+ @ExceptionHandler(DataException.class)
+ public ResponseEntity handleDataException(DataException ex) {
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An internal data error occurred"));
+ }
+
+ @ExceptionHandler(RuntimeException.class)
+ public ResponseEntity handleRuntimeException(RuntimeException ex) {
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body(new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An unexpected error occurred"));
+ }
+}
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/controller/HealthController.java b/product-api/src/main/java/com/target/retail/product/controller/HealthController.java
similarity index 91%
rename from retail-data-services/src/main/java/com/target/retail/data/services/controller/HealthController.java
rename to product-api/src/main/java/com/target/retail/product/controller/HealthController.java
index ecbd2e4..88d2c94 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/controller/HealthController.java
+++ b/product-api/src/main/java/com/target/retail/product/controller/HealthController.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.controller;
+package com.target.retail.product.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -8,6 +8,7 @@
import org.springframework.web.bind.annotation.RestController;
@RestController
+@RequestMapping("/v1")
public class HealthController {
@Operation(summary = "Get health status", description = "Returns the health status of the application.")
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/controller/ItemController.java b/product-api/src/main/java/com/target/retail/product/controller/ItemController.java
similarity index 86%
rename from retail-data-services/src/main/java/com/target/retail/data/services/controller/ItemController.java
rename to product-api/src/main/java/com/target/retail/product/controller/ItemController.java
index 1137ef7..84466d0 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/controller/ItemController.java
+++ b/product-api/src/main/java/com/target/retail/product/controller/ItemController.java
@@ -1,10 +1,11 @@
-package com.target.retail.data.services.controller;
+package com.target.retail.product.controller;
-import com.target.retail.data.services.dto.ItemResponse;
-import com.target.retail.data.services.dto.PaginatedResponse;
-import com.target.retail.data.services.model.Item;
-import com.target.retail.data.services.service.ItemService;
-import com.target.retail.data.services.service.behavior.Behaviors;
+import com.target.retail.product.dto.ItemResponse;
+import com.target.retail.product.dto.PaginatedResponse;
+import com.target.retail.product.exception.ItemNotFoundException;
+import com.target.retail.product.model.Item;
+import com.target.retail.product.service.ItemService;
+import com.target.retail.product.service.behavior.Behaviors;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
@@ -18,6 +19,7 @@
import java.util.Optional;
@RestController
+@RequestMapping("/v1")
public class ItemController {
private final ItemService itemService;
@@ -35,7 +37,9 @@ public ItemController(ItemService itemService, Behaviors behaviors) {
@GetMapping("/items/{id}")
public ResponseEntity getItem(@PathVariable String id) {
Optional- item = itemService.getItem(id);
- return behaviors.getConfiguredBehavior().execute(() -> item.map(it -> ResponseEntity.ok(new ItemResponse(it))).orElseGet(() -> ResponseEntity.notFound().build()));
+ return behaviors.getConfiguredBehavior().execute(() -> item
+ .map(it -> ResponseEntity.ok(new ItemResponse(it)))
+ .orElseThrow(() -> new ItemNotFoundException(id)));
}
@Operation(summary = "Get all products with pagination", description = "Returns a paginated list of products with optional filtering.")
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/controller/PriceController.java b/product-api/src/main/java/com/target/retail/product/controller/PriceController.java
similarity index 77%
rename from retail-data-services/src/main/java/com/target/retail/data/services/controller/PriceController.java
rename to product-api/src/main/java/com/target/retail/product/controller/PriceController.java
index e88d79b..e9b93db 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/controller/PriceController.java
+++ b/product-api/src/main/java/com/target/retail/product/controller/PriceController.java
@@ -1,9 +1,10 @@
-package com.target.retail.data.services.controller;
+package com.target.retail.product.controller;
-import com.target.retail.data.services.dto.PriceResponse;
-import com.target.retail.data.services.model.ItemPrice;
-import com.target.retail.data.services.service.PriceService;
-import com.target.retail.data.services.service.behavior.Behaviors;
+import com.target.retail.product.dto.PriceResponse;
+import com.target.retail.product.exception.PriceNotFoundException;
+import com.target.retail.product.model.ItemPrice;
+import com.target.retail.product.service.PriceService;
+import com.target.retail.product.service.behavior.Behaviors;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -16,6 +17,7 @@
import java.util.Optional;
@RestController
+@RequestMapping("/v1")
public class PriceController {
private final PriceService priceService;
@@ -32,7 +34,7 @@ public PriceController(PriceService priceService, Behaviors behaviors) {
@GetMapping("/prices/{id}")
public ResponseEntity getPrice(@PathVariable String id) {
Optional itemPrice = priceService.getPrice(id);
- return behaviors.getConfiguredBehavior().execute(() -> itemPrice.map(price -> ResponseEntity.ok(new PriceResponse(price))).orElseGet(() -> ResponseEntity.notFound().build()));
+ return behaviors.getConfiguredBehavior().execute(() -> itemPrice.map(price -> ResponseEntity.ok(new PriceResponse(price))).orElseThrow(() -> new PriceNotFoundException(id)));
}
}
\ No newline at end of file
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/data/CsvData.java b/product-api/src/main/java/com/target/retail/product/data/CsvData.java
similarity index 97%
rename from retail-data-services/src/main/java/com/target/retail/data/services/data/CsvData.java
rename to product-api/src/main/java/com/target/retail/product/data/CsvData.java
index 958dbc5..371fedb 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/data/CsvData.java
+++ b/product-api/src/main/java/com/target/retail/product/data/CsvData.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.data;
+package com.target.retail.product.data;
import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/data/DataException.java b/product-api/src/main/java/com/target/retail/product/data/DataException.java
similarity index 82%
rename from retail-data-services/src/main/java/com/target/retail/data/services/data/DataException.java
rename to product-api/src/main/java/com/target/retail/product/data/DataException.java
index f266619..8d1c50d 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/data/DataException.java
+++ b/product-api/src/main/java/com/target/retail/product/data/DataException.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.data;
+package com.target.retail.product.data;
public class DataException extends RuntimeException {
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/data/Identifiable.java b/product-api/src/main/java/com/target/retail/product/data/Identifiable.java
similarity index 57%
rename from retail-data-services/src/main/java/com/target/retail/data/services/data/Identifiable.java
rename to product-api/src/main/java/com/target/retail/product/data/Identifiable.java
index 0667bf7..cd2ae67 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/data/Identifiable.java
+++ b/product-api/src/main/java/com/target/retail/product/data/Identifiable.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.data;
+package com.target.retail.product.data;
public interface Identifiable {
public String getId();
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/dto/AvailabilityResponse.java b/product-api/src/main/java/com/target/retail/product/dto/AvailabilityResponse.java
similarity index 63%
rename from retail-data-services/src/main/java/com/target/retail/data/services/dto/AvailabilityResponse.java
rename to product-api/src/main/java/com/target/retail/product/dto/AvailabilityResponse.java
index 521cb15..6e18db5 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/dto/AvailabilityResponse.java
+++ b/product-api/src/main/java/com/target/retail/product/dto/AvailabilityResponse.java
@@ -1,11 +1,11 @@
-package com.target.retail.data.services.dto;
+package com.target.retail.product.dto;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
-import com.target.retail.data.services.model.ItemAvailability;
+import com.target.retail.product.model.ItemAvailability;
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
-public record AvailabilityResponse(String productId, Integer availableUnits, Integer limitedQuantityThreshold) {
+public record AvailabilityResponse(String itemId, Integer availableUnits, Integer limitedQuantityThreshold) {
public AvailabilityResponse(ItemAvailability itemAvailability) {
this(itemAvailability.itemId(), itemAvailability.availableUnits(), itemAvailability.limitedQuantityThreshold());
diff --git a/product-api/src/main/java/com/target/retail/product/dto/ErrorResponse.java b/product-api/src/main/java/com/target/retail/product/dto/ErrorResponse.java
new file mode 100644
index 0000000..ca6ed2f
--- /dev/null
+++ b/product-api/src/main/java/com/target/retail/product/dto/ErrorResponse.java
@@ -0,0 +1,4 @@
+package com.target.retail.product.dto;
+
+public record ErrorResponse(int status, String message) {
+}
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/dto/ItemResponse.java b/product-api/src/main/java/com/target/retail/product/dto/ItemResponse.java
similarity index 77%
rename from retail-data-services/src/main/java/com/target/retail/data/services/dto/ItemResponse.java
rename to product-api/src/main/java/com/target/retail/product/dto/ItemResponse.java
index f90b03d..2943446 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/dto/ItemResponse.java
+++ b/product-api/src/main/java/com/target/retail/product/dto/ItemResponse.java
@@ -1,8 +1,8 @@
-package com.target.retail.data.services.dto;
+package com.target.retail.product.dto;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
-import com.target.retail.data.services.model.Item;
+import com.target.retail.product.model.Item;
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record ItemResponse(String itemId, String smallDescription, String longDescription, String category,
@@ -16,9 +16,9 @@ public ItemResponse(Item item) {
}
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
- public record ImageData(String primaryImage, String alternateImage, String baseUrl) {
+ public record ImageData(String primary, String alternate, String baseUrl) {
public ImageData(Item item) {
- this(item.primaryImage(), item.alternateImage(), item.baseUrl());
+ this(item.primary(), item.alternate(), item.baseUrl());
}
}
}
\ No newline at end of file
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/dto/PaginatedResponse.java b/product-api/src/main/java/com/target/retail/product/dto/PaginatedResponse.java
similarity index 93%
rename from retail-data-services/src/main/java/com/target/retail/data/services/dto/PaginatedResponse.java
rename to product-api/src/main/java/com/target/retail/product/dto/PaginatedResponse.java
index 3e8e09d..46d6962 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/dto/PaginatedResponse.java
+++ b/product-api/src/main/java/com/target/retail/product/dto/PaginatedResponse.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.dto;
+package com.target.retail.product.dto;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
@@ -14,6 +14,6 @@ public PaginatedResponse(int currentPage, List items, int totalItems, int pag
private static Integer calculateNextPage(int currentPage, int totalItems, int pageSize) {
int totalPages = (totalItems + pageSize - 1) / pageSize;
- return (currentPage + 1 < totalPages) ? currentPage + 1 : 0;
+ return (currentPage + 1 < totalPages) ? currentPage + 1 : null;
}
}
\ No newline at end of file
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/dto/PriceResponse.java b/product-api/src/main/java/com/target/retail/product/dto/PriceResponse.java
similarity index 63%
rename from retail-data-services/src/main/java/com/target/retail/data/services/dto/PriceResponse.java
rename to product-api/src/main/java/com/target/retail/product/dto/PriceResponse.java
index 987a7e3..d5b7052 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/dto/PriceResponse.java
+++ b/product-api/src/main/java/com/target/retail/product/dto/PriceResponse.java
@@ -1,14 +1,14 @@
-package com.target.retail.data.services.dto;
+package com.target.retail.product.dto;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
-import com.target.retail.data.services.model.ItemPrice;
+import com.target.retail.product.model.ItemPrice;
import java.math.BigDecimal;
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
-public record PriceResponse(String productId, BigDecimal regularPrice, BigDecimal salePrice, String priceType) {
+public record PriceResponse(String itemId, BigDecimal regular, BigDecimal sale, String priceType) {
public PriceResponse(ItemPrice itemPrice) {
this(itemPrice.itemId(), itemPrice.regularPrice(), itemPrice.salePrice(), itemPrice.type());
}
diff --git a/product-api/src/main/java/com/target/retail/product/exception/AvailabilityNotFoundException.java b/product-api/src/main/java/com/target/retail/product/exception/AvailabilityNotFoundException.java
new file mode 100644
index 0000000..e299df6
--- /dev/null
+++ b/product-api/src/main/java/com/target/retail/product/exception/AvailabilityNotFoundException.java
@@ -0,0 +1,7 @@
+package com.target.retail.product.exception;
+
+public class AvailabilityNotFoundException extends RuntimeException {
+ public AvailabilityNotFoundException(String id) {
+ super("Availability not found for item id " + id);
+ }
+}
diff --git a/product-api/src/main/java/com/target/retail/product/exception/InducedFailureException.java b/product-api/src/main/java/com/target/retail/product/exception/InducedFailureException.java
new file mode 100644
index 0000000..bef1d83
--- /dev/null
+++ b/product-api/src/main/java/com/target/retail/product/exception/InducedFailureException.java
@@ -0,0 +1,7 @@
+package com.target.retail.product.exception;
+
+public class InducedFailureException extends RuntimeException {
+ public InducedFailureException(String message) {
+ super(message);
+ }
+}
diff --git a/product-api/src/main/java/com/target/retail/product/exception/ItemNotFoundException.java b/product-api/src/main/java/com/target/retail/product/exception/ItemNotFoundException.java
new file mode 100644
index 0000000..80706d3
--- /dev/null
+++ b/product-api/src/main/java/com/target/retail/product/exception/ItemNotFoundException.java
@@ -0,0 +1,7 @@
+package com.target.retail.product.exception;
+
+public class ItemNotFoundException extends RuntimeException {
+ public ItemNotFoundException(String id) {
+ super("Item not found with id " + id);
+ }
+}
diff --git a/product-api/src/main/java/com/target/retail/product/exception/PriceNotFoundException.java b/product-api/src/main/java/com/target/retail/product/exception/PriceNotFoundException.java
new file mode 100644
index 0000000..ce822d0
--- /dev/null
+++ b/product-api/src/main/java/com/target/retail/product/exception/PriceNotFoundException.java
@@ -0,0 +1,7 @@
+package com.target.retail.product.exception;
+
+public class PriceNotFoundException extends RuntimeException {
+ public PriceNotFoundException(String id) {
+ super("Price not found for item id " + id);
+ }
+}
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/model/Item.java b/product-api/src/main/java/com/target/retail/product/model/Item.java
similarity index 73%
rename from retail-data-services/src/main/java/com/target/retail/data/services/model/Item.java
rename to product-api/src/main/java/com/target/retail/product/model/Item.java
index 8eec24e..96bcdab 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/model/Item.java
+++ b/product-api/src/main/java/com/target/retail/product/model/Item.java
@@ -1,17 +1,17 @@
-package com.target.retail.data.services.model;
+package com.target.retail.product.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
-import com.target.retail.data.services.data.Identifiable;
+import com.target.retail.product.data.Identifiable;
-@JsonPropertyOrder({"itemId", "smallDescription", "longDescription", "category", "merchClass", "channelRestriction", "barcode", "brandName", "ageRestriction", "primaryImage", "alternateImage", "baseUrl"})
+@JsonPropertyOrder({"itemId", "smallDescription", "longDescription", "category", "merchClass", "channelRestriction", "barcode", "brandName", "ageRestriction", "primary", "alternate", "baseUrl"})
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record Item(String itemId, String smallDescription, String longDescription, String category, Integer merchClass,
String channelRestriction, String barcode, String brandName, Integer ageRestriction,
- String primaryImage, String alternateImage, String baseUrl) implements Identifiable {
+ String primary, String alternate, String baseUrl) implements Identifiable {
@Override
public String getId() {
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/model/ItemAvailability.java b/product-api/src/main/java/com/target/retail/product/model/ItemAvailability.java
similarity index 88%
rename from retail-data-services/src/main/java/com/target/retail/data/services/model/ItemAvailability.java
rename to product-api/src/main/java/com/target/retail/product/model/ItemAvailability.java
index 76773a6..7792bb2 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/model/ItemAvailability.java
+++ b/product-api/src/main/java/com/target/retail/product/model/ItemAvailability.java
@@ -1,10 +1,10 @@
-package com.target.retail.data.services.model;
+package com.target.retail.product.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
-import com.target.retail.data.services.data.Identifiable;
+import com.target.retail.product.data.Identifiable;
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonPropertyOrder({ "item_id", "availableUnits", "limitedQuantityThreshold" })
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/model/ItemPrice.java b/product-api/src/main/java/com/target/retail/product/model/ItemPrice.java
similarity index 76%
rename from retail-data-services/src/main/java/com/target/retail/data/services/model/ItemPrice.java
rename to product-api/src/main/java/com/target/retail/product/model/ItemPrice.java
index 38fb1d4..c5ca07c 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/model/ItemPrice.java
+++ b/product-api/src/main/java/com/target/retail/product/model/ItemPrice.java
@@ -1,14 +1,14 @@
-package com.target.retail.data.services.model;
+package com.target.retail.product.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
-import com.target.retail.data.services.data.Identifiable;
+import com.target.retail.product.data.Identifiable;
import java.math.BigDecimal;
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
-@JsonPropertyOrder({ "itemId", "regular_price", "sale_price", "type" })
+@JsonPropertyOrder({ "itemId", "regularPrice", "salePrice", "type" })
@JsonIgnoreProperties(ignoreUnknown = true)
public record ItemPrice(String itemId, BigDecimal regularPrice, BigDecimal salePrice, String type) implements Identifiable {
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/service/AvailabilityService.java b/product-api/src/main/java/com/target/retail/product/service/AvailabilityService.java
similarity index 91%
rename from retail-data-services/src/main/java/com/target/retail/data/services/service/AvailabilityService.java
rename to product-api/src/main/java/com/target/retail/product/service/AvailabilityService.java
index e940a2d..8fb070f 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/service/AvailabilityService.java
+++ b/product-api/src/main/java/com/target/retail/product/service/AvailabilityService.java
@@ -1,7 +1,7 @@
-package com.target.retail.data.services.service;
+package com.target.retail.product.service;
-import com.target.retail.data.services.data.CsvData;
-import com.target.retail.data.services.model.ItemAvailability;
+import com.target.retail.product.data.CsvData;
+import com.target.retail.product.model.ItemAvailability;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/service/ItemService.java b/product-api/src/main/java/com/target/retail/product/service/ItemService.java
similarity index 94%
rename from retail-data-services/src/main/java/com/target/retail/data/services/service/ItemService.java
rename to product-api/src/main/java/com/target/retail/product/service/ItemService.java
index d3b3fa7..242bc0b 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/service/ItemService.java
+++ b/product-api/src/main/java/com/target/retail/product/service/ItemService.java
@@ -1,7 +1,7 @@
-package com.target.retail.data.services.service;
+package com.target.retail.product.service;
-import com.target.retail.data.services.data.CsvData;
-import com.target.retail.data.services.model.Item;
+import com.target.retail.product.data.CsvData;
+import com.target.retail.product.model.Item;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/service/PriceService.java b/product-api/src/main/java/com/target/retail/product/service/PriceService.java
similarity index 91%
rename from retail-data-services/src/main/java/com/target/retail/data/services/service/PriceService.java
rename to product-api/src/main/java/com/target/retail/product/service/PriceService.java
index 0af7b7c..1671b55 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/service/PriceService.java
+++ b/product-api/src/main/java/com/target/retail/product/service/PriceService.java
@@ -1,7 +1,7 @@
-package com.target.retail.data.services.service;
+package com.target.retail.product.service;
-import com.target.retail.data.services.data.CsvData;
-import com.target.retail.data.services.model.ItemPrice;
+import com.target.retail.product.data.CsvData;
+import com.target.retail.product.model.ItemPrice;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/BehaviorType.java b/product-api/src/main/java/com/target/retail/product/service/behavior/BehaviorType.java
similarity index 57%
rename from retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/BehaviorType.java
rename to product-api/src/main/java/com/target/retail/product/service/behavior/BehaviorType.java
index ebc393b..8732bfa 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/BehaviorType.java
+++ b/product-api/src/main/java/com/target/retail/product/service/behavior/BehaviorType.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.service.behavior;
+package com.target.retail.product.service.behavior;
public enum BehaviorType {
NORMAL,
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/Behaviors.java b/product-api/src/main/java/com/target/retail/product/service/behavior/Behaviors.java
similarity index 81%
rename from retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/Behaviors.java
rename to product-api/src/main/java/com/target/retail/product/service/behavior/Behaviors.java
index c95112e..278086f 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/Behaviors.java
+++ b/product-api/src/main/java/com/target/retail/product/service/behavior/Behaviors.java
@@ -1,5 +1,6 @@
-package com.target.retail.data.services.service.behavior;
+package com.target.retail.product.service.behavior;
+import com.target.retail.product.exception.InducedFailureException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -24,7 +25,7 @@ public class Behaviors {
@Value("${DEFAULT_BEHAVIOR:NORMAL}")
private BehaviorType configuredDefaultBehavior;
- private final Map behaviorMap;
+ private final Map behaviorMap;
private Random random;
@@ -32,9 +33,9 @@ public Behaviors() {
random = new Random();
behaviorMap = new HashMap<>();
- behaviorMap.put(BehaviorType.NORMAL.name(), this::callWithNoInducedBehavior);
- behaviorMap.put(BehaviorType.SLOW_RESPONSE.name(), this::callWithSlowResponse);
- behaviorMap.put(BehaviorType.RANDOM_FAILURES.name(), this::callWithRandomFailures);
+ behaviorMap.put(BehaviorType.NORMAL, this::callWithNoInducedBehavior);
+ behaviorMap.put(BehaviorType.SLOW_RESPONSE, this::callWithSlowResponse);
+ behaviorMap.put(BehaviorType.RANDOM_FAILURES, this::callWithRandomFailures);
}
public Behaviors(BehaviorType defaultBehavior, double failingBehaviorFailureRate, int slowResponseBehaviorMinimumDelay, int slowResponseBehaviorMaximumDelay) {
@@ -56,7 +57,7 @@ public InducedBehavior getConfiguredBehavior() {
}
public Optional getBehavior(BehaviorType type) {
- return Optional.ofNullable(behaviorMap.get(type.name()));
+ return Optional.ofNullable(behaviorMap.get(type));
}
private T callWithSlowResponse(Supplier supplier) {
@@ -76,7 +77,7 @@ private T callWithNoInducedBehavior(Supplier supplier) {
private T callWithRandomFailures(Supplier supplier) {
if(random.nextDouble() < failingBehaviorFailureRate) {
- throw new RuntimeException("Failure to call the service. Please try later");
+ throw new InducedFailureException("Failure to call the service. Please try later");
}
return supplier.get();
}
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/InducedBehavior.java b/product-api/src/main/java/com/target/retail/product/service/behavior/InducedBehavior.java
similarity index 70%
rename from retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/InducedBehavior.java
rename to product-api/src/main/java/com/target/retail/product/service/behavior/InducedBehavior.java
index 1a1acbf..09c063a 100644
--- a/retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/InducedBehavior.java
+++ b/product-api/src/main/java/com/target/retail/product/service/behavior/InducedBehavior.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.service.behavior;
+package com.target.retail.product.service.behavior;
import java.util.function.Supplier;
diff --git a/retail-data-services/src/main/resources/application.yml b/product-api/src/main/resources/application.yml
similarity index 76%
rename from retail-data-services/src/main/resources/application.yml
rename to product-api/src/main/resources/application.yml
index 8c0bcf1..d2131fd 100644
--- a/retail-data-services/src/main/resources/application.yml
+++ b/product-api/src/main/resources/application.yml
@@ -6,8 +6,6 @@ spring:
server:
port: 8080
- servlet:
- context-path: /retail_data_services/v1/
management:
endpoints:
@@ -26,8 +24,6 @@ springdoc:
path: api-docs
swagger-ui:
enabled: true
- config-url: /retail_data_services/v1/api-docs/swagger-config
- url: /retail_data_services/v1/api-docs
price:
data-file: /data/prices.csv
@@ -45,4 +41,4 @@ behaviors:
failure-rate: 0.5
slow-response:
min-delay-ms: 1000
- max-delay-ms: 5000
\ No newline at end of file
+ max-delay-ms: 5000
diff --git a/retail-data-services/src/main/resources/availability.csv b/product-api/src/main/resources/availability.csv
similarity index 100%
rename from retail-data-services/src/main/resources/availability.csv
rename to product-api/src/main/resources/availability.csv
diff --git a/retail-data-services/src/main/resources/items.csv b/product-api/src/main/resources/items.csv
similarity index 100%
rename from retail-data-services/src/main/resources/items.csv
rename to product-api/src/main/resources/items.csv
diff --git a/retail-data-services/src/main/resources/prices.csv b/product-api/src/main/resources/prices.csv
similarity index 100%
rename from retail-data-services/src/main/resources/prices.csv
rename to product-api/src/main/resources/prices.csv
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/config/CorsIntegrationTest.java b/product-api/src/test/java/com/target/retail/product/config/CorsIntegrationTest.java
similarity index 96%
rename from retail-data-services/src/test/java/com/target/retail/data/services/config/CorsIntegrationTest.java
rename to product-api/src/test/java/com/target/retail/product/config/CorsIntegrationTest.java
index f844420..32d2a5d 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/config/CorsIntegrationTest.java
+++ b/product-api/src/test/java/com/target/retail/product/config/CorsIntegrationTest.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.config;
+package com.target.retail.product.config;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/config/CorsPropertiesTest.java b/product-api/src/test/java/com/target/retail/product/config/CorsPropertiesTest.java
similarity index 94%
rename from retail-data-services/src/test/java/com/target/retail/data/services/config/CorsPropertiesTest.java
rename to product-api/src/test/java/com/target/retail/product/config/CorsPropertiesTest.java
index 33fc9aa..beb6af8 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/config/CorsPropertiesTest.java
+++ b/product-api/src/test/java/com/target/retail/product/config/CorsPropertiesTest.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.config;
+package com.target.retail.product.config;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/AvailabilityControllerTest.java b/product-api/src/test/java/com/target/retail/product/controllers/AvailabilityControllerTest.java
similarity index 73%
rename from retail-data-services/src/test/java/com/target/retail/data/services/controllers/AvailabilityControllerTest.java
rename to product-api/src/test/java/com/target/retail/product/controllers/AvailabilityControllerTest.java
index 0b0c5c4..e004876 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/AvailabilityControllerTest.java
+++ b/product-api/src/test/java/com/target/retail/product/controllers/AvailabilityControllerTest.java
@@ -1,11 +1,12 @@
-package com.target.retail.data.services.controllers;
+package com.target.retail.product.controllers;
-import com.target.retail.data.services.controller.AvailabilityController;
-import com.target.retail.data.services.dto.AvailabilityResponse;
-import com.target.retail.data.services.model.ItemAvailability;
-import com.target.retail.data.services.service.AvailabilityService;
-import com.target.retail.data.services.service.behavior.Behaviors;
-import com.target.retail.data.services.service.behavior.InducedBehavior;
+import com.target.retail.product.controller.AvailabilityController;
+import com.target.retail.product.dto.AvailabilityResponse;
+import com.target.retail.product.exception.AvailabilityNotFoundException;
+import com.target.retail.product.model.ItemAvailability;
+import com.target.retail.product.service.AvailabilityService;
+import com.target.retail.product.service.behavior.Behaviors;
+import com.target.retail.product.service.behavior.InducedBehavior;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.ResponseEntity;
@@ -16,6 +17,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -47,7 +49,7 @@ void shouldReturnAvailabilityResponse() {
ResponseEntity response = availabilityController.getAvailability(productId);
- assertEquals(productId, Objects.requireNonNull(response.getBody()).productId(), "Product ID does not match.");
+ assertEquals(productId, Objects.requireNonNull(response.getBody()).itemId(), "Product ID does not match.");
AvailabilityResponse responseBody = response.getBody();
assertNotNull(responseBody);
@@ -60,8 +62,7 @@ void shouldReturnAvailabilityResponse() {
void shouldReturn404WhenAvailabilityNotFound() {
String productId = "11111";
when(availabilityService.getItemAvailability(productId)).thenReturn(Optional.empty());
- ResponseEntity response = availabilityController.getAvailability(productId);
- assertEquals(404, response.getStatusCode().value(), "Unexpected HTTP Status " + response.getStatusCode());
+ assertThrows(AvailabilityNotFoundException.class, () -> availabilityController.getAvailability(productId));
}
}
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/HealthControllerTest.java b/product-api/src/test/java/com/target/retail/product/controllers/HealthControllerTest.java
similarity index 79%
rename from retail-data-services/src/test/java/com/target/retail/data/services/controllers/HealthControllerTest.java
rename to product-api/src/test/java/com/target/retail/product/controllers/HealthControllerTest.java
index a9510b0..b09a3dc 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/HealthControllerTest.java
+++ b/product-api/src/test/java/com/target/retail/product/controllers/HealthControllerTest.java
@@ -1,7 +1,7 @@
-package com.target.retail.data.services.controllers;
+package com.target.retail.product.controllers;
-import com.target.retail.data.services.controller.HealthController;
+import com.target.retail.product.controller.HealthController;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java b/product-api/src/test/java/com/target/retail/product/controllers/ItemControllerTest.java
similarity index 88%
rename from retail-data-services/src/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java
rename to product-api/src/test/java/com/target/retail/product/controllers/ItemControllerTest.java
index 6e96b4a..794f992 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java
+++ b/product-api/src/test/java/com/target/retail/product/controllers/ItemControllerTest.java
@@ -1,13 +1,14 @@
-package com.target.retail.data.services.controllers;
-
-import com.target.retail.data.services.controller.ItemController;
-import com.target.retail.data.services.dto.ItemResponse;
-import com.target.retail.data.services.dto.ItemResponse.ImageData;
-import com.target.retail.data.services.dto.PaginatedResponse;
-import com.target.retail.data.services.model.Item;
-import com.target.retail.data.services.service.ItemService;
-import com.target.retail.data.services.service.behavior.Behaviors;
-import com.target.retail.data.services.service.behavior.InducedBehavior;
+package com.target.retail.product.controllers;
+
+import com.target.retail.product.controller.ItemController;
+import com.target.retail.product.dto.ItemResponse;
+import com.target.retail.product.dto.ItemResponse.ImageData;
+import com.target.retail.product.dto.PaginatedResponse;
+import com.target.retail.product.exception.ItemNotFoundException;
+import com.target.retail.product.model.Item;
+import com.target.retail.product.service.ItemService;
+import com.target.retail.product.service.behavior.Behaviors;
+import com.target.retail.product.service.behavior.InducedBehavior;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.ResponseEntity;
@@ -41,7 +42,7 @@ public T execute(Supplier supplier) {
@Test
public void testGetItem_Found() {
Item item = new Item("901234", "Small Desc", "Long Desc", "Category", 12, "IN-STORE", "Barcode", "Brand", 18,
- "primary_image", "alternate_image", "http://target.com");
+ "primary", "alternate", "http://target.com");
when(itemService.getItem("901234")).thenReturn(Optional.of(item));
ResponseEntity response = itemController.getItem("901234");
@@ -58,16 +59,13 @@ public void testGetItem_Found() {
@Test
public void testGetItem_NotFound() {
when(itemService.getItem("999999")).thenReturn(Optional.empty());
-
- ResponseEntity response = itemController.getItem("999999");
-
- assertEquals(404, response.getStatusCode().value());
+ assertThrows(ItemNotFoundException.class, () -> itemController.getItem("999999"));
}
@Test
public void testGetItem_ImageBlock() {
Item item = new Item("901234", "Small Desc", "Long Desc", "Category", 12, "IN-STORE", "Barcode", "Brand", 18,
- "primary_image", "alternate_image", "http://target.com");
+ "primary", "alternate", "http://target.com");
when(itemService.getItem("901234")).thenReturn(Optional.of(item));
ResponseEntity response = itemController.getItem("901234");
@@ -78,8 +76,8 @@ public void testGetItem_ImageBlock() {
assertNotNull(responseBody);
ImageData imageData = responseBody.imageData();
- assertEquals("primary_image", imageData.primaryImage());
- assertEquals("alternate_image", imageData.alternateImage());
+ assertEquals("primary", imageData.primary());
+ assertEquals("alternate", imageData.alternate());
assertEquals("http://target.com", imageData.baseUrl());
}
@@ -87,9 +85,9 @@ public void testGetItem_ImageBlock() {
public void getAllItems_ReturnsPaginatedResponse_WhenValidParameters() {
List
- items = List.of(
new Item("901234", "Small Desc", "Long Desc", "Category", 12, "IN-STORE", "Barcode", "Brand", 18,
- "primary_image", "alternate_image", "http://target.com"),
+ "primary", "alternate", "http://target.com"),
new Item("901235", "Small Desc 2", "Long Desc 2", "Category 2", 13, "ONLINE", "Barcode 2", "Brand 2",
- 21, "primary_image_2", "alternate_image_2", "http://target.com/2"));
+ 21, "primary_2", "alternate_2", "http://target.com/2"));
when(itemService.getAllItems(0, 2, null)).thenReturn(items);
when(itemService.getItemCount(null)).thenReturn(10);
@@ -128,7 +126,7 @@ public void getAllItems_ReturnsEmptyList_WhenNoItemsAvailable() {
assertEquals(0, responseBody.currentPage());
assertTrue(responseBody.items().isEmpty());
- assertEquals(0, responseBody.nextPage());
+ assertNull(responseBody.nextPage());
}
@Test
@@ -176,9 +174,9 @@ public void getAllItems_WithSmallDescriptionFilter_CaseInsensitive() {
public void getAllItems_WithEmptySmallDescriptionFilter_ReturnsAllItems() {
List
- allItems = List.of(
new Item("901234", "Small Desc", "Long Desc", "Category", 12, "IN-STORE", "Barcode", "Brand", 18,
- "primary_image", "alternate_image", "http://target.com"),
+ "primary", "alternate", "http://target.com"),
new Item("901235", "Small Desc 2", "Long Desc 2", "Category 2", 13, "ONLINE", "Barcode 2", "Brand 2",
- 21, "primary_image_2", "alternate_image_2", "http://target.com/2"));
+ 21, "primary_2", "alternate_2", "http://target.com/2"));
when(itemService.getAllItems(0, 2, "")).thenReturn(allItems);
when(itemService.getItemCount("")).thenReturn(10);
@@ -196,7 +194,7 @@ public void getAllItems_WithEmptySmallDescriptionFilter_ReturnsAllItems() {
public void getAllItems_WithWhitespaceSmallDescriptionFilter_ReturnsAllItems() {
List
- allItems = List.of(
new Item("901234", "Test Item", "Long Desc", "Category", 12, "IN-STORE", "Barcode", "Brand", 18,
- "primary_image", "alternate_image", "http://target.com"));
+ "primary", "alternate", "http://target.com"));
when(itemService.getAllItems(0, 10, " ")).thenReturn(allItems);
when(itemService.getItemCount(" ")).thenReturn(1);
@@ -224,7 +222,7 @@ public void getAllItems_WithSmallDescriptionFilter_NoMatches_ReturnsEmptyList()
assertTrue(responseBody.items().isEmpty());
assertEquals(0, responseBody.currentPage());
- assertEquals(0, responseBody.nextPage());
+ assertNull(responseBody.nextPage());
}
@Test
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java b/product-api/src/test/java/com/target/retail/product/controllers/PriceControllerTest.java
similarity index 67%
rename from retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java
rename to product-api/src/test/java/com/target/retail/product/controllers/PriceControllerTest.java
index 587c7ea..635a4c1 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java
+++ b/product-api/src/test/java/com/target/retail/product/controllers/PriceControllerTest.java
@@ -1,11 +1,12 @@
-package com.target.retail.data.services.controllers;
+package com.target.retail.product.controllers;
-import com.target.retail.data.services.dto.PriceResponse;
-import com.target.retail.data.services.model.ItemPrice;
-import com.target.retail.data.services.service.PriceService;
-import com.target.retail.data.services.controller.PriceController;
-import com.target.retail.data.services.service.behavior.Behaviors;
-import com.target.retail.data.services.service.behavior.InducedBehavior;
+import com.target.retail.product.dto.PriceResponse;
+import com.target.retail.product.exception.PriceNotFoundException;
+import com.target.retail.product.model.ItemPrice;
+import com.target.retail.product.service.PriceService;
+import com.target.retail.product.controller.PriceController;
+import com.target.retail.product.service.behavior.Behaviors;
+import com.target.retail.product.service.behavior.InducedBehavior;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.ResponseEntity;
@@ -17,6 +18,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -52,13 +54,20 @@ void shouldReturnPriceResponse() {
ResponseEntity response = priceController.getPrice(productId);
- assertEquals(productId, Objects.requireNonNull(response.getBody()).productId(), "Product ID does not match.");
+ assertEquals(productId, Objects.requireNonNull(response.getBody()).itemId(), "Product ID does not match.");
PriceResponse responseBody = response.getBody();
assertNotNull(responseBody);
- assertEquals(BigDecimal.valueOf(19.99), responseBody.regularPrice(), "Price does not match.");
+ assertEquals(BigDecimal.valueOf(19.99), responseBody.regular(), "Price does not match.");
assertEquals("REGULAR", responseBody.priceType(), "Price type does not match.");
}
+ @Test
+ void shouldThrowPriceNotFoundException_WhenPriceNotFound() {
+ String productId = "99999";
+ when(priceService.getPrice(productId)).thenReturn(Optional.empty());
+ assertThrows(PriceNotFoundException.class, () -> priceController.getPrice(productId));
+ }
+
}
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/data/CsvDataTest.java b/product-api/src/test/java/com/target/retail/product/data/CsvDataTest.java
similarity index 99%
rename from retail-data-services/src/test/java/com/target/retail/data/services/data/CsvDataTest.java
rename to product-api/src/test/java/com/target/retail/product/data/CsvDataTest.java
index eb186da..77e05f9 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/data/CsvDataTest.java
+++ b/product-api/src/test/java/com/target/retail/product/data/CsvDataTest.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.data;
+package com.target.retail.product.data;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/integration/AvailabilityIntegrationTest.java b/product-api/src/test/java/com/target/retail/product/integration/AvailabilityIntegrationTest.java
similarity index 81%
rename from retail-data-services/src/test/java/com/target/retail/data/services/integration/AvailabilityIntegrationTest.java
rename to product-api/src/test/java/com/target/retail/product/integration/AvailabilityIntegrationTest.java
index 9af6000..e373427 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/integration/AvailabilityIntegrationTest.java
+++ b/product-api/src/test/java/com/target/retail/product/integration/AvailabilityIntegrationTest.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.integration;
+package com.target.retail.product.integration;
import org.junit.jupiter.api.Test;
@@ -11,7 +11,7 @@ public class AvailabilityIntegrationTest extends BaseIntegrationTest {
public void testGetAvailability() throws Exception {
getResponse("/availability/" + testProductId)
.andExpect(status().isOk())
- .andExpect(jsonPath("$.product_id").value(equalTo(testProductId)));
+ .andExpect(jsonPath("$.item_id").value(equalTo(testProductId)));
}
@Test
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/integration/BaseIntegrationTest.java b/product-api/src/test/java/com/target/retail/product/integration/BaseIntegrationTest.java
similarity index 89%
rename from retail-data-services/src/test/java/com/target/retail/data/services/integration/BaseIntegrationTest.java
rename to product-api/src/test/java/com/target/retail/product/integration/BaseIntegrationTest.java
index 35a4ef1..5b2df35 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/integration/BaseIntegrationTest.java
+++ b/product-api/src/test/java/com/target/retail/product/integration/BaseIntegrationTest.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.integration;
+package com.target.retail.product.integration;
import org.junit.jupiter.api.TestInstance;
import org.springframework.beans.factory.annotation.Autowired;
@@ -21,6 +21,6 @@ public class BaseIntegrationTest {
protected String invalidProductId = "999999999";
public ResultActions getResponse(String url) throws Exception {
- return mockMvc.perform(get(url));
+ return mockMvc.perform(get("/v1" + url));
}
}
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/integration/HealthIntegrationTest.java b/product-api/src/test/java/com/target/retail/product/integration/HealthIntegrationTest.java
similarity index 80%
rename from retail-data-services/src/test/java/com/target/retail/data/services/integration/HealthIntegrationTest.java
rename to product-api/src/test/java/com/target/retail/product/integration/HealthIntegrationTest.java
index 02459f8..63dfe97 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/integration/HealthIntegrationTest.java
+++ b/product-api/src/test/java/com/target/retail/product/integration/HealthIntegrationTest.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.integration;
+package com.target.retail.product.integration;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.v3.core.util.Json;
@@ -7,6 +7,7 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@@ -21,7 +22,7 @@ public void testHealthCheck() throws Exception {
@Test
public void testActuatorHealth() throws Exception {
- getResponse("/actuator/health")
+ mockMvc.perform(get("/actuator/health"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("UP"));
}
@@ -29,7 +30,7 @@ public void testActuatorHealth() throws Exception {
@Test
@Disabled("Unable to get this test to work even though the endpoint is working")
public void testOpenApiSpec() throws Exception {
- String specBody = getResponse("/api-docs")
+ String specBody = mockMvc.perform(get("/api-docs"))
.andExpect(status().isOk())
.andReturn()
.getResponse()
@@ -37,6 +38,6 @@ public void testOpenApiSpec() throws Exception {
OpenAPI spec = Json.mapper().readValue(specBody, OpenAPI.class);
assertEquals(SpecVersion.V30, spec.getSpecVersion());
- assertEquals("Retail Data Services API", spec.getInfo().getTitle());
+ assertEquals("Product API", spec.getInfo().getTitle());
}
}
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/integration/ItemIntegrationTest.java b/product-api/src/test/java/com/target/retail/product/integration/ItemIntegrationTest.java
similarity index 96%
rename from retail-data-services/src/test/java/com/target/retail/data/services/integration/ItemIntegrationTest.java
rename to product-api/src/test/java/com/target/retail/product/integration/ItemIntegrationTest.java
index 9c14810..a2de86f 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/integration/ItemIntegrationTest.java
+++ b/product-api/src/test/java/com/target/retail/product/integration/ItemIntegrationTest.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.integration;
+package com.target.retail.product.integration;
import org.junit.jupiter.api.Test;
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/integration/PriceIntegrationTest.java b/product-api/src/test/java/com/target/retail/product/integration/PriceIntegrationTest.java
similarity index 80%
rename from retail-data-services/src/test/java/com/target/retail/data/services/integration/PriceIntegrationTest.java
rename to product-api/src/test/java/com/target/retail/product/integration/PriceIntegrationTest.java
index b2b26d1..3f9f15d 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/integration/PriceIntegrationTest.java
+++ b/product-api/src/test/java/com/target/retail/product/integration/PriceIntegrationTest.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.integration;
+package com.target.retail.product.integration;
import org.junit.jupiter.api.Test;
@@ -11,7 +11,7 @@ public class PriceIntegrationTest extends BaseIntegrationTest {
public void testGetPrice() throws Exception {
getResponse("/prices/" + testProductId)
.andExpect(status().isOk())
- .andExpect(jsonPath("$.product_id").value(equalTo(testProductId)));
+ .andExpect(jsonPath("$.item_id").value(equalTo(testProductId)));
}
@Test
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/service/AvailabilityServiceTest.java b/product-api/src/test/java/com/target/retail/product/service/AvailabilityServiceTest.java
similarity index 92%
rename from retail-data-services/src/test/java/com/target/retail/data/services/service/AvailabilityServiceTest.java
rename to product-api/src/test/java/com/target/retail/product/service/AvailabilityServiceTest.java
index 463584e..cd004c8 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/service/AvailabilityServiceTest.java
+++ b/product-api/src/test/java/com/target/retail/product/service/AvailabilityServiceTest.java
@@ -1,7 +1,7 @@
-package com.target.retail.data.services.service;
+package com.target.retail.product.service;
-import com.target.retail.data.services.data.CsvData;
-import com.target.retail.data.services.model.ItemAvailability;
+import com.target.retail.product.data.CsvData;
+import com.target.retail.product.model.ItemAvailability;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/service/ItemServiceTest.java b/product-api/src/test/java/com/target/retail/product/service/ItemServiceTest.java
similarity index 78%
rename from retail-data-services/src/test/java/com/target/retail/data/services/service/ItemServiceTest.java
rename to product-api/src/test/java/com/target/retail/product/service/ItemServiceTest.java
index c5dcdd7..c8eccba 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/service/ItemServiceTest.java
+++ b/product-api/src/test/java/com/target/retail/product/service/ItemServiceTest.java
@@ -1,7 +1,7 @@
-package com.target.retail.data.services.service;
+package com.target.retail.product.service;
-import com.target.retail.data.services.data.CsvData;
-import com.target.retail.data.services.model.Item;
+import com.target.retail.product.data.CsvData;
+import com.target.retail.product.model.Item;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
@@ -32,7 +32,7 @@ public void setUp() {
@Test
public void testGetItem_Found() {
- Item item = new Item("901234", "Small Desc", "Long Desc", "Category", 12 ,"IN-STORE", "Barcode", "Brand", 18, "primary_image", "alternate_image", "http://target.com");
+ Item item = new Item("901234", "Small Desc", "Long Desc", "Category", 12 ,"IN-STORE", "Barcode", "Brand", 18, "primary", "alternate", "http://target.com");
when(itemData.getById("901234")).thenReturn(Optional.of(item));
Optional
- response = itemService.getItem("901234");
@@ -59,21 +59,21 @@ public void testGetItem_NotFound() {
@Test
public void testGetItem_ImageDetails() {
- Item item = new Item("901234", "Small Desc", "Long Desc", "Category", 12 ,"IN-STORE", "Barcode", "Brand", 18, "primary_image", "alternate_image", "http://target.com");
+ Item item = new Item("901234", "Small Desc", "Long Desc", "Category", 12 ,"IN-STORE", "Barcode", "Brand", 18, "primary", "alternate", "http://target.com");
when(itemData.getById("901234")).thenReturn(Optional.of(item));
Optional
- response = itemService.getItem("901234");
assertTrue(response.isPresent());
- assertEquals("alternate_image", response.get().alternateImage());
- assertEquals("primary_image", response.get().primaryImage());
+ assertEquals("alternate", response.get().alternate());
+ assertEquals("primary", response.get().primary());
assertEquals("http://target.com", response.get().baseUrl());
}
@Test
public void getItemCount_ReturnsPaginatedList_WhenValidPageAndSize() {
List
- items = List.of(
- new Item("901234", "Small Desc", "Long Desc", "Category", 12, "IN-STORE", "Barcode", "Brand", 18, "primary_image", "alternate_image", "http://target.com"),
- new Item("901235", "Small Desc 2", "Long Desc 2", "Category 2", 13, "ONLINE", "Barcode 2", "Brand 2", 21, "primary_image_2", "alternate_image_2", "http://target.com/2")
+ new Item("901234", "Small Desc", "Long Desc", "Category", 12, "IN-STORE", "Barcode", "Brand", 18, "primary", "alternate", "http://target.com"),
+ new Item("901235", "Small Desc 2", "Long Desc 2", "Category 2", 13, "ONLINE", "Barcode 2", "Brand 2", 21, "primary_2", "alternate_2", "http://target.com/2")
);
when(itemData.getAll()).thenReturn(items);
@@ -86,7 +86,7 @@ public void getItemCount_ReturnsPaginatedList_WhenValidPageAndSize() {
@Test
public void getItemCount_ReturnsEmptyList_WhenPageOutOfBounds() {
List
- items = List.of(
- new Item("901234", "Small Desc", "Long Desc", "Category", 12, "IN-STORE", "Barcode", "Brand", 18, "primary_image", "alternate_image", "http://target.com")
+ new Item("901234", "Small Desc", "Long Desc", "Category", 12, "IN-STORE", "Barcode", "Brand", 18, "primary", "alternate", "http://target.com")
);
when(itemData.getAll()).thenReturn(items);
@@ -98,7 +98,7 @@ public void getItemCount_ReturnsEmptyList_WhenPageOutOfBounds() {
@Test
public void getItemCount_ReturnsEmptyList_WhenSizeIsZero() {
List
- items = List.of(
- new Item("901234", "Small Desc", "Long Desc", "Category", 12, "IN-STORE", "Barcode", "Brand", 18, "primary_image", "alternate_image", "http://target.com")
+ new Item("901234", "Small Desc", "Long Desc", "Category", 12, "IN-STORE", "Barcode", "Brand", 18, "primary", "alternate", "http://target.com")
);
when(itemData.getAll()).thenReturn(items);
@@ -110,8 +110,8 @@ public void getItemCount_ReturnsEmptyList_WhenSizeIsZero() {
@Test
public void getItemCount_ReturnsTotalItemCount() {
List
- items = List.of(
- new Item("901234", "Small Desc", "Long Desc", "Category", 12, "IN-STORE", "Barcode", "Brand", 18, "primary_image", "alternate_image", "http://target.com"),
- new Item("901235", "Small Desc 2", "Long Desc 2", "Category 2", 13, "ONLINE", "Barcode 2", "Brand 2", 21, "primary_image_2", "alternate_image_2", "http://target.com/2")
+ new Item("901234", "Small Desc", "Long Desc", "Category", 12, "IN-STORE", "Barcode", "Brand", 18, "primary", "alternate", "http://target.com"),
+ new Item("901235", "Small Desc 2", "Long Desc 2", "Category 2", 13, "ONLINE", "Barcode 2", "Brand 2", 21, "primary_2", "alternate_2", "http://target.com/2")
);
when(itemData.getCount()).thenReturn(items.size());
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/service/PriceServiceTest.java b/product-api/src/test/java/com/target/retail/product/service/PriceServiceTest.java
similarity index 80%
rename from retail-data-services/src/test/java/com/target/retail/data/services/service/PriceServiceTest.java
rename to product-api/src/test/java/com/target/retail/product/service/PriceServiceTest.java
index b96c74f..6df8ba2 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/service/PriceServiceTest.java
+++ b/product-api/src/test/java/com/target/retail/product/service/PriceServiceTest.java
@@ -1,8 +1,8 @@
-package com.target.retail.data.services.service;
+package com.target.retail.product.service;
-import com.target.retail.data.services.data.CsvData;
-import com.target.retail.data.services.dto.PriceResponse;
-import com.target.retail.data.services.model.ItemPrice;
+import com.target.retail.product.data.CsvData;
+import com.target.retail.product.dto.PriceResponse;
+import com.target.retail.product.model.ItemPrice;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -46,8 +46,8 @@ void shouldReturnPriceResponse() {
Optional itemPrice = priceService.getPrice(productId);
- assertEquals(expectedResponse.productId(), itemPrice.get().itemId(), "Product ID does not match.");
- assertEquals(expectedResponse.regularPrice(), itemPrice.get().regularPrice(), "Price does not match.");
+ assertEquals(expectedResponse.itemId(), itemPrice.get().itemId(), "Product ID does not match.");
+ assertEquals(expectedResponse.regular(), itemPrice.get().regularPrice(), "Price does not match.");
assertEquals(expectedResponse.priceType(), itemPrice.get().type(), "Price type does not match.");
}
diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/service/behavior/BehaviorsTest.java b/product-api/src/test/java/com/target/retail/product/service/behavior/BehaviorsTest.java
similarity index 98%
rename from retail-data-services/src/test/java/com/target/retail/data/services/service/behavior/BehaviorsTest.java
rename to product-api/src/test/java/com/target/retail/product/service/behavior/BehaviorsTest.java
index cb9e52f..1b6672c 100644
--- a/retail-data-services/src/test/java/com/target/retail/data/services/service/behavior/BehaviorsTest.java
+++ b/product-api/src/test/java/com/target/retail/product/service/behavior/BehaviorsTest.java
@@ -1,4 +1,4 @@
-package com.target.retail.data.services.service.behavior;
+package com.target.retail.product.service.behavior;
import org.junit.jupiter.api.Test;
diff --git a/retail-data-services/src/test/resources/application.yml b/product-api/src/test/resources/application.yml
similarity index 100%
rename from retail-data-services/src/test/resources/application.yml
rename to product-api/src/test/resources/application.yml
diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/controller/CustomErrorController.java b/retail-data-services/src/main/java/com/target/retail/data/services/controller/CustomErrorController.java
deleted file mode 100644
index c4d14b2..0000000
--- a/retail-data-services/src/main/java/com/target/retail/data/services/controller/CustomErrorController.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.target.retail.data.services.controller;
-
-import org.springframework.boot.web.error.ErrorAttributeOptions;
-import org.springframework.boot.web.servlet.error.ErrorController;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.context.request.WebRequest;
-import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
-
-import java.util.Map;
-
-@RestController
-public class CustomErrorController extends ResponseEntityExceptionHandler implements ErrorController {
-
- @RequestMapping("/error")
- public ResponseEntity