From 6bbc664d0a1d694398a242075c887dc0afaaa1af Mon Sep 17 00:00:00 2001 From: jimmy-guzman Date: Tue, 9 Jun 2026 15:24:25 -0500 Subject: [PATCH 01/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20align=20image=20fie?= =?UTF-8?q?ld=20names=20to=20primary/alternate=20across=20both=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: jimmy-guzman --- .../cart/controller/dto/CartResponse.java | 2 +- .../com/target/retail/cart/model/Item.java | 4 +- .../retail/cart/service/CartService.java | 4 +- .../service/client/dto/ItemApiResponse.java | 2 +- .../cart/controller/CartControllerTest.java | 2 +- .../cart/controller/dto/CartResponseTest.java | 8 +- plan.md | 317 ++++++++++++++++++ .../data/services/dto/ItemResponse.java | 4 +- .../retail/data/services/model/Item.java | 4 +- .../controllers/ItemControllerTest.java | 18 +- .../services/service/ItemServiceTest.java | 20 +- 11 files changed, 351 insertions(+), 34 deletions(-) create mode 100644 plan.md 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..b3927d9 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 @@ -52,7 +52,7 @@ public static CartResponse from(Cart cart) { List items = cart.cartLineItems().stream().map(it -> new ItemResponse(it.item().tcin(), 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().regularPrice(), it.price().salePrice().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/model/Item.java b/cart-service/src/main/java/com/target/retail/cart/model/Item.java index 4ab7fab..2face25 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 @@ -7,8 +7,8 @@ public record Item( String brand, String category, String merchClass, - String primaryImage, - String alternateImage, + String primary, + String alternate, String baseUrl ) { } 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..f8ce940 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 @@ -176,8 +176,8 @@ private Item getItem(String tcin) { itemApiResponse.brandName(), itemApiResponse.category(), itemApiResponse.merchClass().toString(), - itemApiResponse.imageData().primaryImage(), - itemApiResponse.imageData().alternateImage(), + itemApiResponse.imageData().primary(), + itemApiResponse.imageData().alternate(), itemApiResponse.imageData().baseUrl()); } 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/test/java/com/target/retail/cart/controller/CartControllerTest.java b/cart-service/src/test/java/com/target/retail/cart/controller/CartControllerTest.java index a387807..d758f8a 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 @@ -266,7 +266,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", "Class", "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/CartResponseTest.java b/cart-service/src/test/java/com/target/retail/cart/controller/dto/CartResponseTest.java index 6d1bc4a..0acb91d 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", "MerchClass", "primary", "alternate", "baseUrl"), 2, new Price("12345", new BigDecimal("8.00"), Optional.of(new BigDecimal("6.00"))), ZonedDateTime.now(), ZonedDateTime.now()))); @@ -51,8 +51,8 @@ public void testFrom() { 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.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", "MerchClass", "primary", "alternate", "baseUrl"), 2, new Price("12345", new BigDecimal("8.00"), Optional.of(new BigDecimal("6.50"))), ZonedDateTime.now(), diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..4b6cdb1 --- /dev/null +++ b/plan.md @@ -0,0 +1,317 @@ +# API Consistency & Rename Plan + +## Context + +Two services in this repo: + +- `retail-data-services` (port 8080) — read-only product catalog serving items, prices, and availability from CSV-backed data. Consumed by `cart-service` at runtime. +- `cart-service` (port 8081) — shopping cart service. Calls `retail-data-services` for item/price enrichment on every cart read. + +Both are mock APIs for retail case studies, run locally via Docker. No gateway, no production infrastructure. + +--- + +## Decisions + +### 1. Rename `retail-data-services` → `product-api` + +The service is a product catalog — items, prices, availability. "Retail data services" communicates nothing about the domain. Everything is in scope: directory, packages, Docker service name, paths, docs. + +| Thing | Before | After | +| ----------------------------- | ------------------------------------------------ | ----------------------------------------------------------------- | +| Submodule directory | `retail-data-services/` | `product-api/` | +| Gradle subproject | `retail-data-services` | `product-api` | +| Java package root | `com.target.retail.data.services` | `com.target.retail.product` | +| Java source tree | `src/main/java/com/target/retail/data/services/` | `src/main/java/com/target/retail/product/` | +| JAR artifact | `retail-data-services.jar` | `product-api.jar` | +| Docker service name | `data` | `product-api` | +| Docker hostname (cart client) | `http://data:8080` | `http://product-api:8080` | +| OpenAPI title | `"Retail Data Services API"` | `"Product API"` | +| OpenAPI description | `"API for managing retail data services."` | `"Read-only API for product catalog, pricing, and availability."` | +| API spec filename | `retail_data_services-v1.yaml` | `product-api-v1.yaml` | +| HTTP requests file | `retail-data-services.http` | `product-api.http` | + +### 2. API path strategy + +REST best practice: paths identify resources, not services. Service names belong in DNS/hostnames, not URL paths. Version belongs in the path, declared in `@RequestMapping` on the controller (visible in code and Swagger — not hidden in config). + +| Before | After | +| -------------------------------------------- | ----------------------------- | +| `/retail_data_services/v1/items/{id}` | `/v1/items/{id}` | +| `/retail_data_services/v1/items` | `/v1/items` | +| `/retail_data_services/v1/prices/{id}` | `/v1/prices/{id}` | +| `/retail_data_services/v1/availability/{id}` | `/v1/availability/{id}` | +| `/cart/v1/carts/{id}` | `/v1/carts/{id}` | +| `/cart/v1/carts` | `/v1/carts` | +| `/cart/v1/carts/{id}/items` | `/v1/carts/{id}/items` | +| `/cart/v1/carts/{id}/items/{tcin}` | `/v1/carts/{id}/items/{tcin}` | + +`context-path` removed from `product-api` `application.yml`. `@RequestMapping("/v1")` added to all controllers in both services. + +### 3. Response field name alignment + +Cart is the consumer-facing API. Upstream (`product-api`) adopts cart's shorter names where they differ. Cart's internal rename (when re-serializing consumed data) is removed. + +#### Image fields + +| Layer | Before | After | +| ------------------------------------------------------- | ---------------------------------- | ---------------------- | +| `product-api` `ItemResponse.ImageData` | `primary_image`, `alternate_image` | `primary`, `alternate` | +| `product-api` `Item` model (flat fields) | `primaryImage`, `alternateImage` | `primary`, `alternate` | +| `cart-service` `ItemApiResponse.ImageData` (client DTO) | `primaryImage`, `alternateImage` | `primary`, `alternate` | +| `cart-service` `CartResponse.ImageResponse` | `primary`, `alternate` | no change | + +#### Price fields + +| Layer | Before | After | +| ---------------------------------------------- | ----------------------------- | ----------------- | +| `product-api` `PriceResponse` | `regular_price`, `sale_price` | `regular`, `sale` | +| `cart-service` `PriceApiResponse` (client DTO) | `regularPrice`, `salePrice` | `regular`, `sale` | +| `cart-service` `Price` model | `regularPrice`, `salePrice` | `regular`, `sale` | +| `cart-service` `CartResponse.PriceResponse` | `regular`, `sale` | no change | + +### 4. ID field standardization — `product_id`, `tcin` → `item_id` everywhere + +`tcin` (Target Corporation Item Number) and `item_id` are the same concept — the unique product identifier — but named differently across both services with no clear boundary rule. Standardize to `item_id` everywhere. + +Three distinct problems rolled into one decision: + +- `PriceResponse` and `AvailabilityResponse` in `product-api` expose `product_id` while `ItemResponse` exposes `item_id` — inconsistent within the same service +- `cart-service` uses `tcin` throughout — models, DTOs, path variables, CSV headers, service methods — while consuming `item_id` from `product-api` +- `cart-service` HTTP clients pass a `tcin` parameter into a `{item_id}` URI slot — the naming mismatch is explicit at the cross-service boundary + +#### `product-api` changes + +| File | Before | After | +| ---- | ------ | ----- | +| `PriceResponse` | `productId` / `product_id` | `itemId` / `item_id` | +| `AvailabilityResponse` | `productId` / `product_id` | `itemId` / `item_id` | +| `PriceIntegrationTest` (line 14) | `jsonPath("$.product_id")` | `jsonPath("$.item_id")` | +| `AvailabilityIntegrationTest` (line 14) | `jsonPath("$.product_id")` | `jsonPath("$.item_id")` | +| API spec `retail_data_services-v1.yaml` | `product_id` in PriceResponse + AvailabilityResponse schemas | `item_id` | + +#### `cart-service` changes + +**Models:** + +| File | Before | After | +| ---- | ------ | ----- | +| `model/Item.java` | `String tcin` | `String itemId` | +| `model/Price.java` | `String tcin` | `String itemId` | +| `model/StoredCartLine.java` | `String tcin`, `@JsonPropertyOrder` literal `"tcin"` | `String itemId`, `"itemId"` | +| `model/Cart.java` | `findByTcin(String tcin)` | `findByItemId(String itemId)` | + +**Controller + DTOs:** + +| File | Before | After | +| ---- | ------ | ----- | +| `CartController.java` | `@PathVariable String tcin`, `{tcin}` in `@DeleteMapping`/`@PatchMapping` | `@PathVariable String itemId`, `{itemId}` | +| `CartController.java` line 42 | `"duplicate TCINs"` in `@ApiResponse` description | `"duplicate item IDs"` | +| `AddItemRequest.java` | `String tcin` | `String itemId` | +| `CartResponse.ItemResponse` | `String tcin` | `String itemId` | + +**Service + clients:** + +| File | Before | After | +| ---- | ------ | ----- | +| `CartService.java` — `removeItem`, `addItem`, `updateCartItem`, `getPriceForItem`, `getItem` params | `String tcin` | `String itemId` | +| `CartService.java` — exception message (line 141) | `"No cart line found for tcin " + tcin` | `"No cart line found for item id " + itemId` | +| `CartService.java` — local vars `storedCartLineForTcin` | `storedCartLineForTcin` | `storedCartLineForItemId` | +| `ItemApiClient.java` | `getItem(String tcin)`, `.uri("/items/{item_id}", tcin)` | `getItem(String itemId)`, `.uri("/items/{itemId}", itemId)` | +| `PriceApiClient.java` | `getPricing(String tcin)`, `.uri("/prices/{item_id}", tcin)` | `getPricing(String itemId)`, `.uri("/prices/{itemId}", itemId)` | +| `PriceApiResponse.java` | `productId` | `itemId` | + +**CSV data files:** + +| File | Before | After | +| ---- | ------ | ----- | +| `src/main/resources/data/100.csv` header | `lineId,cartId,tcin,...` | `lineId,cartId,itemId,...` | +| `src/main/resources/data/101.csv` header | `lineId,cartId,tcin,...` | `lineId,cartId,itemId,...` | + +**Tests:** + +| File | Change | +| ---- | ------ | +| `CartControllerTest.java` | All `tcin` local vars → `itemId`; test method `testUpdateCartItemWhenTcinNotFoundInCart` → `testUpdateCartItemWhenItemIdNotFoundInCart`; comment `// Duplicate tcin` → `// Duplicate item ID` | +| `CartResponseTest.java` | `cartLineItem.item().tcin()` → `cartLineItem.item().itemId()` | +| `CartServiceTest.java` | All `tcin`/`tcin1`/`tcin2`/`tcinToRemove`/`tcinToKeep` vars → `itemId`/`itemId1`/`itemId2`/`itemIdToRemove`/`itemIdToKeep`; test method names updated; exception message assertion updated | +| `CartDatabaseTest.java` | Inline CSV string literal header `tcin` → `itemId` | + +**Docs / HTTP files:** + +| File | Change | +| ---- | ------ | +| `README.md` lines 123, 129, 130, 141, 146, 156 | `tcin` → `item_id` in prose, request body examples, curl commands, path examples | +| `cart-service/cart-service.http` lines 14, 15, 25 | `"tcin"` key in JSON bodies → `"item_id"` | + +### 5. `merch_class` type + +`product-api` correctly exposes `merch_class` as `Integer`. `cart-service` casts it to `String` unnecessarily. Fix cart-service to use `Integer` end-to-end. + +| File | Before | After | +| ------------------------------------------ | ----------------------------------------- | ------------------------------ | +| `cart-service` `Item` model | `String merchClass` | `Integer merchClass` | +| `cart-service` `CartResponse.ItemResponse` | `String merchClass` | `Integer merchClass` | +| `cart-service` `CartService.getItem()` | `itemApiResponse.merchClass().toString()` | `itemApiResponse.merchClass()` | + +### 6. `next_page` sentinel + +`PaginatedResponse.calculateNextPage` returns `0` when there is no next page. `0` is also a valid page number, making "no more pages" ambiguous. Return `null` instead. + +| File | Before | After | +| --------------------------------------------------- | ------------------------------------------ | ------------------------------------- | +| `product-api` `PaginatedResponse` | `return ... : 0` | `return ... : null` | +| `product-api` `ItemControllerTest` (lines 131, 227) | `assertEquals(0, responseBody.nextPage())` | `assertNull(responseBody.nextPage())` | + +### 7. Typed exceptions and consistent error handling + +Replace bare `RuntimeException` throws with typed domain exceptions. Each service gets its own `GlobalExceptionHandler` (`@ControllerAdvice`) and `ErrorResponse` record — not shared, but identical shape. + +#### Error response shape (both services) + +```json +{ "status": 404, "message": "No cart found with id 123" } +``` + +Record: `public record ErrorResponse(int status, String message) {}` + +#### New exception types + +**`product-api`:** + +- `ItemNotFoundException extends RuntimeException` — thrown by `ItemService` when item not found +- `PriceNotFoundException extends RuntimeException` — thrown by `PriceService` when price not found +- `AvailabilityNotFoundException extends RuntimeException` — thrown by `AvailabilityService` when availability not found +- `InducedFailureException extends RuntimeException` — thrown by `Behaviors.callWithRandomFailures` + +**`cart-service`:** + +- `CartNotFoundException extends RuntimeException` — thrown by `CartService` when cart not found +- `CartLineItemNotFoundException extends RuntimeException` — thrown by `CartService` when line item not found +- `InducedFailureException extends RuntimeException` — thrown by `Behaviors.callWithRandomFailures` + +#### Controller refactor + +Resource controllers in `product-api` (`ItemController`, `PriceController`, `AvailabilityController`) currently handle 404 via `Optional.empty()` → `ResponseEntity.notFound().build()`. Refactor to throw typed exceptions from the service layer; let the advice handle the 404 response uniformly. + +`CartController` pre-check guard pattern (`cartService.getCart(id).isEmpty()` before service calls) removed. Typed exceptions from `CartService` propagate to the advice. Exception: `removeItemFromCart` retains its post-removal `getCart` call for the 204 vs 200 logic. + +#### `GlobalExceptionHandler` mappings + +| Exception | HTTP Status | `message` | +| -------------------------------------------------------------------------------------------------- | ----------- | ----------------------------------- | +| `ItemNotFoundException` / `PriceNotFoundException` / `AvailabilityNotFoundException` (product-api) | 404 | exception message | +| `CartNotFoundException` / `CartLineItemNotFoundException` (cart-service) | 404 | exception message | +| `InducedFailureException` (both) | 503 | exception message | +| `DataException` (both) | 500 | `"An internal data error occurred"` | +| `RuntimeException` catch-all (both) | 500 | `"An unexpected error occurred"` | + +#### Delete + +`CustomErrorController.java` in `product-api` — replaced entirely by `GlobalExceptionHandler`. Its `/error` path entries removed from the API spec. + +### 8. `AddItemRequest` missing `@JsonNaming` + +`UpdateItemRequest` has `@JsonNaming(SnakeCaseStrategy)`. `AddItemRequest` does not. Add it for consistency. + +### 9. `Behaviors` map key type + +`product-api` `Behaviors` uses `Map` with `.name()` lookups. `cart-service` uses the type-safe `Map`. Align `product-api` to use enum keys directly. + +--- + +## Commit sequence + +Commits 1–8 execute while the directory is still named `retail-data-services` so each change is focused and reviewable. Commit 9 is the atomic rename that touches all structural identifiers at once. + +| # | Type | Subject | +| --- | ---------- | --------------------------------------------------------------------------------- | +| 1 | `fix` | align image field names to primary/alternate across both services | +| 2 | `fix` | align price field names to regular/sale across both services | +| 3 | `fix` | standardize item identifier to item_id across both services | +| 4 | `fix` | correct merch_class type from String to Integer in cart-service | +| 5 | `fix` | return null for next_page when no further pages exist | +| 6 | `refactor` | introduce typed exceptions and consistent GlobalExceptionHandler in both services | +| 7 | `style` | add @JsonNaming to AddItemRequest | +| 8 | `refactor` | use type-safe Map in product-api Behaviors | +| 9 | `refactor` | rename retail-data-services to product-api with REST-idiomatic paths | + +All commits use gitzy with `--signoff`, no scopes. + +--- + +## Commit 9 full touch-point inventory + +Every file that changes in the rename commit: + +### Java source files (38 files — package + import statements) + +All files under `retail-data-services/src/main/java/com/target/retail/data/services/` and `retail-data-services/src/test/java/com/target/retail/data/services/`. + +### Build files + +| File | Change | +| ------------------------------ | ------------------------------------------------------------ | +| `settings.gradle.kts` | `include("retail-data-services")` → `include("product-api")` | +| `product-api/build.gradle.kts` | `mainClass` package + `archiveFileName` | +| `product-api/Dockerfile` | JAR filename ×2 | + +### Docker / CI + +| File | Change | +| --------------------------- | ---------------------------------------------------------------------- | +| `docker-compose.yml` | Service name `data:` → `product-api:`, build context, `depends_on` key | +| `.github/workflows/cd.yaml` | Matrix entries ×2 | + +### Spring config + +| File | Change | +| ------------------------------------------------- | ---------------------------------------------------------------------------- | +| `product-api/src/main/resources/application.yml` | Remove `context-path`, remove Swagger URL overrides | +| `cart-service/src/main/resources/application.yml` | `http://data:8080/retail_data_services/v1` → `http://product-api:8080/v1` ×2 | +| `cart-service/src/test/resources/application.yml` | Same ×2 | + +### Controllers + +| File | Change | +| --------------------------------------------------------------------------------- | -------------------------------------------------------- | +| `ItemController`, `PriceController`, `AvailabilityController`, `HealthController` | Add `@RequestMapping("/v1")` | +| `CartController` | `@RequestMapping("/cart/v1")` → `@RequestMapping("/v1")` | + +### Docs / specs / scripts + +| File | Change | +| ------------------------------------------ | ---------------------------------------------------------------------- | +| `retail_data_services-v1.yaml` | Rename to `product-api-v1.yaml`; update server URL, title, description | +| `retail-data-services.http` | Rename to `product-api.http`; update all URLs (already updated by commit 3) | +| `README.md` | ~25 occurrences | +| `product-api/scripts/benchmark-startup.sh` | IMAGE_NAME, HEALTH_ENDPOINT ×2, JAR path ×2 | +| `product-api/scripts/README.md` | 1 mention | +| `product-api/induced_behaviors.md` | ~18 occurrences | + +### CI/CD + +| File | Line(s) | Change | +| --------------------------- | ------- | ---------------------------------------------------------------------------- | +| `.github/workflows/cd.yaml` | 24 | Matrix entry `retail-data-services` → `product-api` (`docker-dry-run` job) | +| `.github/workflows/cd.yaml` | 59 | Matrix entry `retail-data-services` → `product-api` (`docker-build-push` job) | + +The matrix value `${{ matrix.service }}` is used as-is for the Gradle task (`:retail-data-services:build` → `:product-api:build`), Docker build context, GHCR image name, and GHA cache scope — so changing the matrix entry fixes all four in one place. + +#### GHCR package rename + +The published image path changes from: + +``` +ghcr.io//tech-case-studies-retail-data-services/retail-data-services +``` + +to: + +``` +ghcr.io//tech-case-studies-retail-data-services/product-api +``` + +Existing images published under the old name are not automatically redirected. Anyone pulling the old image name will need to update their reference. All tags (`latest`, branch, SHA, semver) will be published fresh under the new name on the next push to `main` after the rename commit lands. + +`ci.yaml`, `zizmor.yml`, and `dependabot.yml` have no service-name references and require no changes. diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/dto/ItemResponse.java b/retail-data-services/src/main/java/com/target/retail/data/services/dto/ItemResponse.java index f90b03d..571db94 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/dto/ItemResponse.java +++ b/retail-data-services/src/main/java/com/target/retail/data/services/dto/ItemResponse.java @@ -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/model/Item.java b/retail-data-services/src/main/java/com/target/retail/data/services/model/Item.java index 8eec24e..7606196 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/model/Item.java +++ b/retail-data-services/src/main/java/com/target/retail/data/services/model/Item.java @@ -6,12 +6,12 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; import com.target.retail.data.services.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/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java index 6e96b4a..9ee21dd 100644 --- a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java +++ b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java @@ -41,7 +41,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"); @@ -67,7 +67,7 @@ public void testGetItem_NotFound() { @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 +78,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 +87,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); @@ -176,9 +176,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 +196,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); diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/service/ItemServiceTest.java b/retail-data-services/src/test/java/com/target/retail/data/services/service/ItemServiceTest.java index c5dcdd7..91fe3d5 100644 --- a/retail-data-services/src/test/java/com/target/retail/data/services/service/ItemServiceTest.java +++ b/retail-data-services/src/test/java/com/target/retail/data/services/service/ItemServiceTest.java @@ -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()); From 46b448758f7436db28d3ef8cd3c7317842dde8f4 Mon Sep 17 00:00:00 2001 From: jimmy-guzman Date: Tue, 9 Jun 2026 15:26:34 -0500 Subject: [PATCH 02/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20align=20price=20fie?= =?UTF-8?q?ld=20names=20to=20regular/sale=20across=20both=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: jimmy-guzman --- .../com/target/retail/cart/controller/dto/CartResponse.java | 2 +- .../src/main/java/com/target/retail/cart/model/Price.java | 4 ++-- .../main/java/com/target/retail/cart/service/CartService.java | 4 ++-- .../retail/cart/service/client/dto/PriceApiResponse.java | 2 +- .../target/retail/cart/controller/dto/CartResponseTest.java | 4 ++-- .../com/target/retail/data/services/dto/PriceResponse.java | 2 +- .../java/com/target/retail/data/services/model/ItemPrice.java | 2 +- .../retail/data/services/controllers/PriceControllerTest.java | 2 +- .../target/retail/data/services/service/PriceServiceTest.java | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) 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 b3927d9..7db6574 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 @@ -52,7 +52,7 @@ public static CartResponse from(Cart cart) { List items = cart.cartLineItems().stream().map(it -> new ItemResponse(it.item().tcin(), 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().primary(), it.item().alternate(), 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/model/Price.java b/cart-service/src/main/java/com/target/retail/cart/model/Price.java index c7ede55..d0f0683 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 tcin, 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/service/CartService.java b/cart-service/src/main/java/com/target/retail/cart/service/CartService.java index f8ce940..5a78ad8 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 @@ -160,9 +160,9 @@ private Price getPriceForItem(String tcin) { PriceApiResponse priceResponse = priceApiClient.getPricing(tcin); if(priceResponse.priceType().equals("SALE")) { - return new Price(tcin, priceResponse.regularPrice(), Optional.of(priceResponse.salePrice())); + return new Price(tcin, priceResponse.regular(), Optional.of(priceResponse.sale())); } else { - return new Price(tcin, priceResponse.regularPrice(), Optional.empty()); + return new Price(tcin, priceResponse.regular(), Optional.empty()); } } 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..56a12a2 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 productId, BigDecimal regular, BigDecimal sale, String priceType) {} 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 0acb91d..0634814 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 @@ -49,8 +49,8 @@ public void testFrom() { 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.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()); diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/dto/PriceResponse.java b/retail-data-services/src/main/java/com/target/retail/data/services/dto/PriceResponse.java index 987a7e3..c6621c2 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/dto/PriceResponse.java +++ b/retail-data-services/src/main/java/com/target/retail/data/services/dto/PriceResponse.java @@ -8,7 +8,7 @@ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -public record PriceResponse(String productId, BigDecimal regularPrice, BigDecimal salePrice, String priceType) { +public record PriceResponse(String productId, BigDecimal regular, BigDecimal sale, String priceType) { public PriceResponse(ItemPrice itemPrice) { this(itemPrice.itemId(), itemPrice.regularPrice(), itemPrice.salePrice(), itemPrice.type()); } diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/model/ItemPrice.java b/retail-data-services/src/main/java/com/target/retail/data/services/model/ItemPrice.java index 38fb1d4..743ed67 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/model/ItemPrice.java +++ b/retail-data-services/src/main/java/com/target/retail/data/services/model/ItemPrice.java @@ -8,7 +8,7 @@ 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/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java index 587c7ea..a6aac3c 100644 --- a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java +++ b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java @@ -57,7 +57,7 @@ void shouldReturnPriceResponse() { 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."); } diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/service/PriceServiceTest.java b/retail-data-services/src/test/java/com/target/retail/data/services/service/PriceServiceTest.java index b96c74f..07776bd 100644 --- a/retail-data-services/src/test/java/com/target/retail/data/services/service/PriceServiceTest.java +++ b/retail-data-services/src/test/java/com/target/retail/data/services/service/PriceServiceTest.java @@ -47,7 +47,7 @@ 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.regular(), itemPrice.get().regularPrice(), "Price does not match."); assertEquals(expectedResponse.priceType(), itemPrice.get().type(), "Price type does not match."); } From 31fea9cfd8658863fd6c38fbc30a7518106dd456 Mon Sep 17 00:00:00 2001 From: jimmy-guzman Date: Tue, 9 Jun 2026 15:31:53 -0500 Subject: [PATCH 03/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20standardize=20item?= =?UTF-8?q?=20identifier=20to=20item=5Fid=20across=20both=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: jimmy-guzman --- .../cart/controller/CartController.java | 22 ++--- .../cart/controller/dto/AddItemRequest.java | 2 +- .../cart/controller/dto/CartResponse.java | 4 +- .../com/target/retail/cart/model/Cart.java | 4 +- .../com/target/retail/cart/model/Item.java | 2 +- .../com/target/retail/cart/model/Price.java | 2 +- .../retail/cart/model/StoredCartLine.java | 4 +- .../retail/cart/service/CartService.java | 50 +++++----- .../cart/service/client/ItemApiClient.java | 4 +- .../cart/service/client/PriceApiClient.java | 4 +- .../service/client/dto/PriceApiResponse.java | 2 +- cart-service/src/main/resources/data/100.csv | 2 +- cart-service/src/main/resources/data/101.csv | 2 +- .../cart/controller/CartControllerTest.java | 16 +-- .../cart/controller/dto/CartResponseTest.java | 2 +- .../retail/cart/data/CartDatabaseTest.java | 4 +- .../retail/cart/service/CartServiceTest.java | 98 +++++++++---------- .../services/dto/AvailabilityResponse.java | 2 +- .../data/services/dto/PriceResponse.java | 2 +- .../AvailabilityControllerTest.java | 2 +- .../controllers/PriceControllerTest.java | 2 +- .../AvailabilityIntegrationTest.java | 2 +- .../integration/PriceIntegrationTest.java | 2 +- .../services/service/PriceServiceTest.java | 2 +- 24 files changed, 119 insertions(+), 119 deletions(-) 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..d341c05 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 @@ -39,18 +39,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 +83,12 @@ 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) { + @DeleteMapping("/carts/{id}/items/{itemId}") + public ResponseEntity removeItemFromCart(@PathVariable String id, @PathVariable String itemId) { if(cartService.getCart(id).isEmpty()) { return ResponseEntity.notFound().build(); } - cartService.removeItem(id, tcin); + cartService.removeItem(id, itemId); if (cartService.getCart(id).isEmpty()) { return ResponseEntity.noContent().build(); } else { @@ -109,7 +109,7 @@ public ResponseEntity addItem(@PathVariable String id, @RequestBod 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,16 +121,16 @@ 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) { + @PatchMapping("/carts/{id}/items/{itemId}") + public ResponseEntity updateItem(@PathVariable String id, @PathVariable String itemId, @RequestBody UpdateItemRequest updateItemRequest) { Optional cartLineItem = cartService.getCart(id) - .flatMap( it -> it.findByTcin(tcin)); + .flatMap(it -> it.findByItemId(itemId)); if(cartLineItem.isEmpty()) { return ResponseEntity.notFound().build(); } - cartService.updateCartItem(id, tcin, updateItemRequest.quantity()); + cartService.updateCartItem(id, itemId, updateItemRequest.quantity()); return getCart(id); } 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..f609809 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,4 @@ package com.target.retail.cart.controller.dto; -public record AddItemRequest(String tcin, Integer quantity) { +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 7db6574..2a35ef6 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, String merchClass, Integer quantity, PriceResponse price, ImageResponse imageData) { public record PriceResponse(BigDecimal regular, BigDecimal sale) {} @@ -50,7 +50,7 @@ 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().regular(), it.price().sale().orElse(null)), new ItemResponse.ImageResponse(it.item().primary(), it.item().alternate(), it.item().baseUrl()))).toList(); 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 2face25..11695e4 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,7 +1,7 @@ package com.target.retail.cart.model; public record Item( - String tcin, + String itemId, String title, String description, String brand, 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 d0f0683..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,7 +3,7 @@ import java.math.BigDecimal; import java.util.Optional; -public record Price(String tcin, BigDecimal regular, Optional sale) { +public record Price(String itemId, BigDecimal regular, Optional sale) { public BigDecimal getCurrentPrice(){ 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 5a78ad8..9ac168e 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 @@ -82,21 +82,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); } 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 +104,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); } - 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 RuntimeException("No cart line found for item id " + itemId); } } @@ -148,27 +148,27 @@ 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.regular(), Optional.of(priceResponse.sale())); + return new Price(itemId, priceResponse.regular(), Optional.of(priceResponse.sale())); } else { - return new Price(tcin, priceResponse.regular(), 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(), 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/PriceApiResponse.java b/cart-service/src/main/java/com/target/retail/cart/service/client/dto/PriceApiResponse.java index 56a12a2..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 regular, BigDecimal sale, String priceType) {} +public record PriceApiResponse(String itemId, BigDecimal regular, BigDecimal sale, String priceType) {} 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 d758f8a..dec05aa 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 @@ -146,7 +146,7 @@ public void testRemoveItemFromCartWhenCartDoesNotExist() { public void testUpdateCartItem() { // Mock data String cartId = "cart123"; - String tcin = "item1"; + String itemId = "item1"; int newQuantity = 5; Cart mockCart = new Cart( cartId, @@ -160,7 +160,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); @@ -170,7 +170,7 @@ public void testUpdateCartItem() { public void testUpdateCartItemWhenCartDoesNotExist() { // Mock data String cartId = "cart123"; - String tcin = "item1"; + String itemId = "item1"; int newQuantity = 5; // Mock behavior @@ -178,17 +178,17 @@ public void testUpdateCartItemWhenCartDoesNotExist() { 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.notFound().build(), response); } @Test - public void testUpdateCartItemWhenTcinNotFoundInCart() { + public void testUpdateCartItemWhenItemIdNotFoundInCart() { // Mock data String cartId = "cart123"; - String tcin = "missingItemId"; + String itemId = "missingItemId"; int newQuantity = 5; Cart mockCart = new Cart( cartId, @@ -202,7 +202,7 @@ public void testUpdateCartItemWhenTcinNotFoundInCart() { 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.notFound().build(), response); @@ -240,7 +240,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 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 0634814..3d6d416 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 @@ -43,7 +43,7 @@ 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()); 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..2a56498 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 @@ -50,18 +50,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", "MerchClass", "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 +74,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", "MerchClass", "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 +107,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 +178,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,7 +203,7 @@ 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 @@ -212,38 +212,38 @@ public void testUpdateCartItem_ItemNotFoundInCart() { // Assert that the method throws a RuntimeException RuntimeException exception = org.junit.jupiter.api.Assertions.assertThrows( RuntimeException.class, - () -> cartService.updateCartItem(cartId, tcin, newQuantity) + () -> 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 +252,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 +261,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/retail-data-services/src/main/java/com/target/retail/data/services/dto/AvailabilityResponse.java b/retail-data-services/src/main/java/com/target/retail/data/services/dto/AvailabilityResponse.java index 521cb15..04681b3 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/dto/AvailabilityResponse.java +++ b/retail-data-services/src/main/java/com/target/retail/data/services/dto/AvailabilityResponse.java @@ -5,7 +5,7 @@ import com.target.retail.data.services.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/retail-data-services/src/main/java/com/target/retail/data/services/dto/PriceResponse.java b/retail-data-services/src/main/java/com/target/retail/data/services/dto/PriceResponse.java index c6621c2..aec5cc9 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/dto/PriceResponse.java +++ b/retail-data-services/src/main/java/com/target/retail/data/services/dto/PriceResponse.java @@ -8,7 +8,7 @@ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) -public record PriceResponse(String productId, BigDecimal regular, BigDecimal sale, 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/retail-data-services/src/test/java/com/target/retail/data/services/controllers/AvailabilityControllerTest.java b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/AvailabilityControllerTest.java index 0b0c5c4..ebc3164 100644 --- a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/AvailabilityControllerTest.java +++ b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/AvailabilityControllerTest.java @@ -47,7 +47,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); diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java index a6aac3c..19c6b9e 100644 --- a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java +++ b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java @@ -52,7 +52,7 @@ 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); diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/integration/AvailabilityIntegrationTest.java b/retail-data-services/src/test/java/com/target/retail/data/services/integration/AvailabilityIntegrationTest.java index 9af6000..f2b2e37 100644 --- a/retail-data-services/src/test/java/com/target/retail/data/services/integration/AvailabilityIntegrationTest.java +++ b/retail-data-services/src/test/java/com/target/retail/data/services/integration/AvailabilityIntegrationTest.java @@ -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/PriceIntegrationTest.java b/retail-data-services/src/test/java/com/target/retail/data/services/integration/PriceIntegrationTest.java index b2b26d1..260e0a4 100644 --- a/retail-data-services/src/test/java/com/target/retail/data/services/integration/PriceIntegrationTest.java +++ b/retail-data-services/src/test/java/com/target/retail/data/services/integration/PriceIntegrationTest.java @@ -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/PriceServiceTest.java b/retail-data-services/src/test/java/com/target/retail/data/services/service/PriceServiceTest.java index 07776bd..1b9f9a7 100644 --- a/retail-data-services/src/test/java/com/target/retail/data/services/service/PriceServiceTest.java +++ b/retail-data-services/src/test/java/com/target/retail/data/services/service/PriceServiceTest.java @@ -46,7 +46,7 @@ void shouldReturnPriceResponse() { Optional itemPrice = priceService.getPrice(productId); - assertEquals(expectedResponse.productId(), itemPrice.get().itemId(), "Product ID 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."); } From ba412d95400a7e29299fcde997b53f24443e9e7f Mon Sep 17 00:00:00 2001 From: jimmy-guzman Date: Tue, 9 Jun 2026 15:33:20 -0500 Subject: [PATCH 04/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20correct=20merch=5Fc?= =?UTF-8?q?lass=20type=20from=20String=20to=20Integer=20in=20cart-service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: jimmy-guzman --- .../com/target/retail/cart/controller/dto/CartResponse.java | 2 +- .../src/main/java/com/target/retail/cart/model/Item.java | 2 +- .../main/java/com/target/retail/cart/service/CartService.java | 2 +- .../com/target/retail/cart/controller/CartControllerTest.java | 2 +- .../cart/controller/dto/CartResponseSerializationTest.java | 2 +- .../target/retail/cart/controller/dto/CartResponseTest.java | 4 ++-- .../java/com/target/retail/cart/service/CartServiceTest.java | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) 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 2a35ef6..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 itemId, 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) {} 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 11695e4..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 @@ -6,7 +6,7 @@ public record Item( String description, String brand, String category, - String merchClass, + Integer merchClass, String primary, String alternate, String baseUrl 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 9ac168e..ec78080 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 @@ -175,7 +175,7 @@ private Item getItem(String itemId) { itemApiResponse.longDescription(), itemApiResponse.brandName(), itemApiResponse.category(), - itemApiResponse.merchClass().toString(), + itemApiResponse.merchClass(), itemApiResponse.imageData().primary(), itemApiResponse.imageData().alternate(), itemApiResponse.imageData().baseUrl()); 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 dec05aa..f9e85a9 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 @@ -266,7 +266,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", "primary", "alternate", "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 3d6d416..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", "primary", "alternate", "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()))); @@ -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", "primary", "alternate", "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/service/CartServiceTest.java b/cart-service/src/test/java/com/target/retail/cart/service/CartServiceTest.java index 2a56498..f1afe48 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 @@ -55,7 +55,7 @@ public void testGetCart() { StoredCartLine storedCartLine = new StoredCartLine("1", cartId, itemId, 2, ZonedDateTime.now(), ZonedDateTime.now()); List storedCartLines = List.of(storedCartLine); - Item item = new Item(itemId, "Short description", "Long description", "Brand", "Category", "MerchClass", "PrimaryImage", "AlternateImage", "BaseUrl"); + 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()); @@ -90,7 +90,7 @@ public void testRemoveItem() { List storedCartLines = List.of(storedCartLine1, storedCartLine2); - Item item = new Item(itemId1, "Short description", "Long description", "Brand", "Category", "MerchClass", "PrimaryImage", "AlternateImage", "BaseUrl"); + 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()); From 504bbdd10c8be1caf9bf658ac86ab5101f967728 Mon Sep 17 00:00:00 2001 From: jimmy-guzman Date: Tue, 9 Jun 2026 15:34:29 -0500 Subject: [PATCH 05/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20return=20null=20for?= =?UTF-8?q?=20next=5Fpage=20when=20no=20further=20pages=20exist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: jimmy-guzman --- .../target/retail/data/services/dto/PaginatedResponse.java | 2 +- .../retail/data/services/controllers/ItemControllerTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/dto/PaginatedResponse.java b/retail-data-services/src/main/java/com/target/retail/data/services/dto/PaginatedResponse.java index 3e8e09d..9b83a51 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/dto/PaginatedResponse.java +++ b/retail-data-services/src/main/java/com/target/retail/data/services/dto/PaginatedResponse.java @@ -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/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java index 9ee21dd..39e681b 100644 --- a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java +++ b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java @@ -128,7 +128,7 @@ public void getAllItems_ReturnsEmptyList_WhenNoItemsAvailable() { assertEquals(0, responseBody.currentPage()); assertTrue(responseBody.items().isEmpty()); - assertEquals(0, responseBody.nextPage()); + assertNull(responseBody.nextPage()); } @Test @@ -224,7 +224,7 @@ public void getAllItems_WithSmallDescriptionFilter_NoMatches_ReturnsEmptyList() assertTrue(responseBody.items().isEmpty()); assertEquals(0, responseBody.currentPage()); - assertEquals(0, responseBody.nextPage()); + assertNull(responseBody.nextPage()); } @Test From 824b36d0838c402336233823bf37d5d9252c8809 Mon Sep 17 00:00:00 2001 From: jimmy-guzman Date: Tue, 9 Jun 2026 15:43:30 -0500 Subject: [PATCH 06/10] =?UTF-8?q?refactor:=20=F0=9F=94=84=20introduce=20ty?= =?UTF-8?q?ped=20exceptions=20and=20consistent=20GlobalExceptionHandler=20?= =?UTF-8?q?in=20both=20services?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: jimmy-guzman --- .../cart/controller/CartController.java | 15 ------- .../controller/GlobalExceptionHandler.java | 39 ++++++++++++++++++ .../cart/controller/dto/ErrorResponse.java | 4 ++ .../CartLineItemNotFoundException.java | 7 ++++ .../cart/exception/CartNotFoundException.java | 7 ++++ .../exception/InducedFailureException.java | 7 ++++ .../retail/cart/service/CartService.java | 8 ++-- .../cart/service/behavior/Behaviors.java | 3 +- .../cart/controller/CartControllerTest.java | 41 +++++-------------- .../retail/cart/service/CartServiceTest.java | 7 ++-- .../controller/AvailabilityController.java | 3 +- .../controller/CustomErrorController.java | 34 --------------- .../controller/GlobalExceptionHandler.java | 40 ++++++++++++++++++ .../services/controller/ItemController.java | 5 ++- .../services/controller/PriceController.java | 3 +- .../data/services/dto/ErrorResponse.java | 4 ++ .../AvailabilityNotFoundException.java | 7 ++++ .../exception/InducedFailureException.java | 7 ++++ .../exception/ItemNotFoundException.java | 7 ++++ .../exception/PriceNotFoundException.java | 7 ++++ .../services/service/behavior/Behaviors.java | 3 +- .../AvailabilityControllerTest.java | 5 ++- .../controllers/ItemControllerTest.java | 6 +-- .../controllers/PriceControllerTest.java | 9 ++++ 24 files changed, 181 insertions(+), 97 deletions(-) create mode 100644 cart-service/src/main/java/com/target/retail/cart/controller/GlobalExceptionHandler.java create mode 100644 cart-service/src/main/java/com/target/retail/cart/controller/dto/ErrorResponse.java create mode 100644 cart-service/src/main/java/com/target/retail/cart/exception/CartLineItemNotFoundException.java create mode 100644 cart-service/src/main/java/com/target/retail/cart/exception/CartNotFoundException.java create mode 100644 cart-service/src/main/java/com/target/retail/cart/exception/InducedFailureException.java delete mode 100644 retail-data-services/src/main/java/com/target/retail/data/services/controller/CustomErrorController.java create mode 100644 retail-data-services/src/main/java/com/target/retail/data/services/controller/GlobalExceptionHandler.java create mode 100644 retail-data-services/src/main/java/com/target/retail/data/services/dto/ErrorResponse.java create mode 100644 retail-data-services/src/main/java/com/target/retail/data/services/exception/AvailabilityNotFoundException.java create mode 100644 retail-data-services/src/main/java/com/target/retail/data/services/exception/InducedFailureException.java create mode 100644 retail-data-services/src/main/java/com/target/retail/data/services/exception/ItemNotFoundException.java create mode 100644 retail-data-services/src/main/java/com/target/retail/data/services/exception/PriceNotFoundException.java 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 d341c05..e66751d 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; @@ -85,9 +84,6 @@ public ResponseEntity getCart(@PathVariable String id) { }) @DeleteMapping("/carts/{id}/items/{itemId}") public ResponseEntity removeItemFromCart(@PathVariable String id, @PathVariable String itemId) { - if(cartService.getCart(id).isEmpty()) { - return ResponseEntity.notFound().build(); - } cartService.removeItem(id, itemId); if (cartService.getCart(id).isEmpty()) { return ResponseEntity.noContent().build(); @@ -106,9 +102,6 @@ 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.itemId(), addItemRequest.quantity()); return getCart(id); } @@ -123,16 +116,8 @@ public ResponseEntity addItem(@PathVariable String id, @RequestBod }) @PatchMapping("/carts/{id}/items/{itemId}") public ResponseEntity updateItem(@PathVariable String id, @PathVariable String itemId, @RequestBody UpdateItemRequest updateItemRequest) { - - Optional cartLineItem = cartService.getCart(id) - .flatMap(it -> it.findByItemId(itemId)); - if(cartLineItem.isEmpty()) { - return ResponseEntity.notFound().build(); - } - 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/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/service/CartService.java b/cart-service/src/main/java/com/target/retail/cart/service/CartService.java index ec78080..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; @@ -85,7 +87,7 @@ public Optional getCart(String cartId) { 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() @@ -107,7 +109,7 @@ public void removeItem(String cartId, String itemId) { 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 storedCartLineForItemId = storedCartLines.stream().filter(it -> it.itemId().equals(itemId)).findFirst(); @@ -138,7 +140,7 @@ public void updateCartItem(String cartId, String itemId, Integer quantity) { } cartDatabase.updateCart(cartId, storedCartLines); } else { - throw new RuntimeException("No cart line found for item id " + itemId); + throw new CartLineItemNotFoundException(itemId); } } 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/test/java/com/target/retail/cart/controller/CartControllerTest.java b/cart-service/src/test/java/com/target/retail/cart/controller/CartControllerTest.java index f9e85a9..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,15 +133,10 @@ 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 @@ -168,44 +165,26 @@ public void testUpdateCartItem() { @Test public void testUpdateCartItemWhenCartDoesNotExist() { - // Mock data String cartId = "cart123"; 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, itemId, new UpdateItemRequest(newQuantity)); - - // Verify the response - assertEquals(ResponseEntity.notFound().build(), response); + assertThrows(CartNotFoundException.class, () -> cartController.updateItem(cartId, itemId, new UpdateItemRequest(newQuantity))); } @Test public void testUpdateCartItemWhenItemIdNotFoundInCart() { - // Mock data String cartId = "cart123"; 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, itemId, new UpdateItemRequest(newQuantity)); - - // Verify the response - assertEquals(ResponseEntity.notFound().build(), response); + assertThrows(CartLineItemNotFoundException.class, () -> cartController.updateItem(cartId, itemId, new UpdateItemRequest(newQuantity))); } @Test 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 f1afe48..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; @@ -209,9 +210,9 @@ public void testUpdateCartItem_ItemNotFoundInCart() { // 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, + // Assert that the method throws a CartLineItemNotFoundException + CartLineItemNotFoundException exception = org.junit.jupiter.api.Assertions.assertThrows( + CartLineItemNotFoundException.class, () -> cartService.updateCartItem(cartId, itemId, newQuantity) ); diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/controller/AvailabilityController.java b/retail-data-services/src/main/java/com/target/retail/data/services/controller/AvailabilityController.java index d1622ab..4bcc089 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/controller/AvailabilityController.java +++ b/retail-data-services/src/main/java/com/target/retail/data/services/controller/AvailabilityController.java @@ -1,6 +1,7 @@ package com.target.retail.data.services.controller; import com.target.retail.data.services.dto.AvailabilityResponse; +import com.target.retail.data.services.exception.AvailabilityNotFoundException; 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; @@ -43,7 +44,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/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> handleError(WebRequest webRequest) { - Map body = getErrorAttributes(webRequest, ErrorAttributeOptions.defaults()); - HttpStatus status = HttpStatus.valueOf((int) body.get("status")); - return new ResponseEntity<>(body, status); - } - - private Map getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { - // Implement the logic to get error attributes - // This is a placeholder implementation - return Map.of( - "status", 500, - "error", "Internal Server Error", - "message", "An unexpected error occurred", - "error_message", options.toString() - ); - } -} \ No newline at end of file diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/controller/GlobalExceptionHandler.java b/retail-data-services/src/main/java/com/target/retail/data/services/controller/GlobalExceptionHandler.java new file mode 100644 index 0000000..230746d --- /dev/null +++ b/retail-data-services/src/main/java/com/target/retail/data/services/controller/GlobalExceptionHandler.java @@ -0,0 +1,40 @@ +package com.target.retail.data.services.controller; + +import com.target.retail.data.services.data.DataException; +import com.target.retail.data.services.dto.ErrorResponse; +import com.target.retail.data.services.exception.AvailabilityNotFoundException; +import com.target.retail.data.services.exception.InducedFailureException; +import com.target.retail.data.services.exception.ItemNotFoundException; +import com.target.retail.data.services.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/ItemController.java b/retail-data-services/src/main/java/com/target/retail/data/services/controller/ItemController.java index 1137ef7..3283747 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/controller/ItemController.java +++ b/retail-data-services/src/main/java/com/target/retail/data/services/controller/ItemController.java @@ -2,6 +2,7 @@ import com.target.retail.data.services.dto.ItemResponse; import com.target.retail.data.services.dto.PaginatedResponse; +import com.target.retail.data.services.exception.ItemNotFoundException; 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; @@ -35,7 +36,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/retail-data-services/src/main/java/com/target/retail/data/services/controller/PriceController.java index e88d79b..9b8a5d8 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/controller/PriceController.java +++ b/retail-data-services/src/main/java/com/target/retail/data/services/controller/PriceController.java @@ -1,6 +1,7 @@ package com.target.retail.data.services.controller; import com.target.retail.data.services.dto.PriceResponse; +import com.target.retail.data.services.exception.PriceNotFoundException; 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; @@ -32,7 +33,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/dto/ErrorResponse.java b/retail-data-services/src/main/java/com/target/retail/data/services/dto/ErrorResponse.java new file mode 100644 index 0000000..fcc2058 --- /dev/null +++ b/retail-data-services/src/main/java/com/target/retail/data/services/dto/ErrorResponse.java @@ -0,0 +1,4 @@ +package com.target.retail.data.services.dto; + +public record ErrorResponse(int status, String message) { +} diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/exception/AvailabilityNotFoundException.java b/retail-data-services/src/main/java/com/target/retail/data/services/exception/AvailabilityNotFoundException.java new file mode 100644 index 0000000..54f6032 --- /dev/null +++ b/retail-data-services/src/main/java/com/target/retail/data/services/exception/AvailabilityNotFoundException.java @@ -0,0 +1,7 @@ +package com.target.retail.data.services.exception; + +public class AvailabilityNotFoundException extends RuntimeException { + public AvailabilityNotFoundException(String id) { + super("Availability not found for item id " + id); + } +} diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/exception/InducedFailureException.java b/retail-data-services/src/main/java/com/target/retail/data/services/exception/InducedFailureException.java new file mode 100644 index 0000000..750d47c --- /dev/null +++ b/retail-data-services/src/main/java/com/target/retail/data/services/exception/InducedFailureException.java @@ -0,0 +1,7 @@ +package com.target.retail.data.services.exception; + +public class InducedFailureException extends RuntimeException { + public InducedFailureException(String message) { + super(message); + } +} diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/exception/ItemNotFoundException.java b/retail-data-services/src/main/java/com/target/retail/data/services/exception/ItemNotFoundException.java new file mode 100644 index 0000000..c5a178b --- /dev/null +++ b/retail-data-services/src/main/java/com/target/retail/data/services/exception/ItemNotFoundException.java @@ -0,0 +1,7 @@ +package com.target.retail.data.services.exception; + +public class ItemNotFoundException extends RuntimeException { + public ItemNotFoundException(String id) { + super("Item not found with id " + id); + } +} diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/exception/PriceNotFoundException.java b/retail-data-services/src/main/java/com/target/retail/data/services/exception/PriceNotFoundException.java new file mode 100644 index 0000000..b10668a --- /dev/null +++ b/retail-data-services/src/main/java/com/target/retail/data/services/exception/PriceNotFoundException.java @@ -0,0 +1,7 @@ +package com.target.retail.data.services.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/service/behavior/Behaviors.java b/retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/Behaviors.java index c95112e..49d14bc 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/Behaviors.java +++ b/retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/Behaviors.java @@ -1,5 +1,6 @@ package com.target.retail.data.services.service.behavior; +import com.target.retail.data.services.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/retail-data-services/src/test/java/com/target/retail/data/services/controllers/AvailabilityControllerTest.java b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/AvailabilityControllerTest.java index ebc3164..c0d644a 100644 --- a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/AvailabilityControllerTest.java +++ b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/AvailabilityControllerTest.java @@ -2,6 +2,7 @@ import com.target.retail.data.services.controller.AvailabilityController; import com.target.retail.data.services.dto.AvailabilityResponse; +import com.target.retail.data.services.exception.AvailabilityNotFoundException; 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; @@ -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; @@ -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/ItemControllerTest.java b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java index 39e681b..c955d32 100644 --- a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java +++ b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/ItemControllerTest.java @@ -4,6 +4,7 @@ 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.exception.ItemNotFoundException; 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; @@ -58,10 +59,7 @@ 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 diff --git a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java index 19c6b9e..cc3d6cb 100644 --- a/retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java +++ b/retail-data-services/src/test/java/com/target/retail/data/services/controllers/PriceControllerTest.java @@ -1,6 +1,7 @@ package com.target.retail.data.services.controllers; import com.target.retail.data.services.dto.PriceResponse; +import com.target.retail.data.services.exception.PriceNotFoundException; import com.target.retail.data.services.model.ItemPrice; import com.target.retail.data.services.service.PriceService; import com.target.retail.data.services.controller.PriceController; @@ -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; @@ -61,4 +63,11 @@ void shouldReturnPriceResponse() { 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)); + } + } From bdc02ada4bf75dcd9a855069b1c00ff00af6ec5b Mon Sep 17 00:00:00 2001 From: jimmy-guzman Date: Tue, 9 Jun 2026 15:44:40 -0500 Subject: [PATCH 07/10] =?UTF-8?q?style:=20=F0=9F=8E=A8=20add=20@JsonNaming?= =?UTF-8?q?=20to=20AddItemRequest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: jimmy-guzman --- .../com/target/retail/cart/controller/dto/AddItemRequest.java | 4 ++++ 1 file changed, 4 insertions(+) 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 f609809..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; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public record AddItemRequest(String itemId, Integer quantity) { } From 10381864113087db47bc9b7049acaad28a613b55 Mon Sep 17 00:00:00 2001 From: jimmy-guzman Date: Tue, 9 Jun 2026 15:44:46 -0500 Subject: [PATCH 08/10] =?UTF-8?q?refactor:=20=F0=9F=94=84=20use=20type-saf?= =?UTF-8?q?e=20Map=20in=20product-api=20B?= =?UTF-8?q?ehaviors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: jimmy-guzman --- .../data/services/service/behavior/Behaviors.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/Behaviors.java b/retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/Behaviors.java index 49d14bc..d461995 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/Behaviors.java +++ b/retail-data-services/src/main/java/com/target/retail/data/services/service/behavior/Behaviors.java @@ -25,7 +25,7 @@ public class Behaviors { @Value("${DEFAULT_BEHAVIOR:NORMAL}") private BehaviorType configuredDefaultBehavior; - private final Map behaviorMap; + private final Map behaviorMap; private Random random; @@ -33,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) { @@ -57,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) { From 13704345a16e0830498533c89cbeaaea61286bfd Mon Sep 17 00:00:00 2001 From: jimmy-guzman Date: Tue, 9 Jun 2026 15:50:55 -0500 Subject: [PATCH 09/10] =?UTF-8?q?refactor:=20=F0=9F=94=84=20rename=20retai?= =?UTF-8?q?l-data-services=20to=20product-api=20with=20REST-idiomatic=20pa?= =?UTF-8?q?ths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: jimmy-guzman --- .github/workflows/cd.yaml | 4 ++-- .../cart/controller/CartController.java | 2 +- .../src/main/resources/application.yml | 4 ++-- .../src/test/resources/application.yml | 4 ++-- docker-compose.yml | 8 +++---- .../.dockerignore | 0 .../Dockerfile | 4 ++-- .../api-spec/product-api-v1.yaml | 6 ++--- .../build.gradle.kts | 4 ++-- .../data-formats.md | 0 .../induced_behaviors.md | 0 .../product-api.http | 0 .../scripts/README.md | 0 .../scripts/benchmark-startup.sh | 0 .../java/com/target/retail/product}/Main.java | 2 +- .../product}/config/CorsGlobalConfig.java | 2 +- .../product}/config/CorsProperties.java | 2 +- .../retail/product}/config/OpenApiConfig.java | 6 ++--- .../controller/AvailabilityController.java | 13 ++++++----- .../controller/GlobalExceptionHandler.java | 14 ++++++------ .../product}/controller/HealthController.java | 3 ++- .../product}/controller/ItemController.java | 15 +++++++------ .../product}/controller/PriceController.java | 13 ++++++----- .../target/retail/product}/data/CsvData.java | 2 +- .../retail/product}/data/DataException.java | 2 +- .../retail/product}/data/Identifiable.java | 2 +- .../product}/dto/AvailabilityResponse.java | 4 ++-- .../retail/product}/dto/ErrorResponse.java | 2 +- .../retail/product}/dto/ItemResponse.java | 4 ++-- .../product}/dto/PaginatedResponse.java | 2 +- .../retail/product}/dto/PriceResponse.java | 4 ++-- .../AvailabilityNotFoundException.java | 2 +- .../exception/InducedFailureException.java | 2 +- .../exception/ItemNotFoundException.java | 2 +- .../exception/PriceNotFoundException.java | 2 +- .../target/retail/product}/model/Item.java | 4 ++-- .../product}/model/ItemAvailability.java | 4 ++-- .../retail/product}/model/ItemPrice.java | 4 ++-- .../product}/service/AvailabilityService.java | 6 ++--- .../retail/product}/service/ItemService.java | 6 ++--- .../retail/product}/service/PriceService.java | 6 ++--- .../service/behavior/BehaviorType.java | 2 +- .../product}/service/behavior/Behaviors.java | 4 ++-- .../service/behavior/InducedBehavior.java | 2 +- .../src/main/resources/application.yml | 6 +---- .../src/main/resources/availability.csv | 0 .../src/main/resources/items.csv | 0 .../src/main/resources/prices.csv | 0 .../product}/config/CorsIntegrationTest.java | 2 +- .../product}/config/CorsPropertiesTest.java | 2 +- .../AvailabilityControllerTest.java | 16 +++++++------- .../controllers/HealthControllerTest.java | 4 ++-- .../controllers/ItemControllerTest.java | 22 +++++++++---------- .../controllers/PriceControllerTest.java | 16 +++++++------- .../retail/product}/data/CsvDataTest.java | 2 +- .../AvailabilityIntegrationTest.java | 2 +- .../integration/BaseIntegrationTest.java | 4 ++-- .../integration/HealthIntegrationTest.java | 9 ++++---- .../integration/ItemIntegrationTest.java | 2 +- .../integration/PriceIntegrationTest.java | 2 +- .../service/AvailabilityServiceTest.java | 6 ++--- .../product}/service/ItemServiceTest.java | 6 ++--- .../product}/service/PriceServiceTest.java | 8 +++---- .../service/behavior/BehaviorsTest.java | 2 +- .../src/test/resources/application.yml | 0 settings.gradle.kts | 2 +- 66 files changed, 144 insertions(+), 143 deletions(-) rename {retail-data-services => product-api}/.dockerignore (100%) rename {retail-data-services => product-api}/Dockerfile (86%) rename retail-data-services/api-spec/retail_data_services-v1.yaml => product-api/api-spec/product-api-v1.yaml (97%) rename {retail-data-services => product-api}/build.gradle.kts (82%) rename {retail-data-services => product-api}/data-formats.md (100%) rename {retail-data-services => product-api}/induced_behaviors.md (100%) rename retail-data-services/retail-data-services.http => product-api/product-api.http (100%) rename {retail-data-services => product-api}/scripts/README.md (100%) rename {retail-data-services => product-api}/scripts/benchmark-startup.sh (100%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/Main.java (86%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/config/CorsGlobalConfig.java (95%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/config/CorsProperties.java (96%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/config/OpenApiConfig.java (67%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/controller/AvailabilityController.java (84%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/controller/GlobalExceptionHandler.java (77%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/controller/HealthController.java (91%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/controller/ItemController.java (89%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/controller/PriceController.java (80%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/data/CsvData.java (97%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/data/DataException.java (82%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/data/Identifiable.java (57%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/dto/AvailabilityResponse.java (82%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/dto/ErrorResponse.java (57%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/dto/ItemResponse.java (91%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/dto/PaginatedResponse.java (94%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/dto/PriceResponse.java (82%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/exception/AvailabilityNotFoundException.java (79%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/exception/InducedFailureException.java (74%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/exception/ItemNotFoundException.java (76%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/exception/PriceNotFoundException.java (77%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/model/Item.java (89%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/model/ItemAvailability.java (88%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/model/ItemPrice.java (86%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/service/AvailabilityService.java (91%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/service/ItemService.java (94%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/service/PriceService.java (91%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/service/behavior/BehaviorType.java (57%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/service/behavior/Behaviors.java (95%) rename {retail-data-services/src/main/java/com/target/retail/data/services => product-api/src/main/java/com/target/retail/product}/service/behavior/InducedBehavior.java (70%) rename {retail-data-services => product-api}/src/main/resources/application.yml (76%) rename {retail-data-services => product-api}/src/main/resources/availability.csv (100%) rename {retail-data-services => product-api}/src/main/resources/items.csv (100%) rename {retail-data-services => product-api}/src/main/resources/prices.csv (100%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/config/CorsIntegrationTest.java (96%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/config/CorsPropertiesTest.java (94%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/controllers/AvailabilityControllerTest.java (80%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/controllers/HealthControllerTest.java (79%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/controllers/ItemControllerTest.java (95%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/controllers/PriceControllerTest.java (82%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/data/CsvDataTest.java (99%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/integration/AvailabilityIntegrationTest.java (92%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/integration/BaseIntegrationTest.java (89%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/integration/HealthIntegrationTest.java (80%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/integration/ItemIntegrationTest.java (96%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/integration/PriceIntegrationTest.java (92%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/service/AvailabilityServiceTest.java (92%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/service/ItemServiceTest.java (96%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/service/PriceServiceTest.java (90%) rename {retail-data-services/src/test/java/com/target/retail/data/services => product-api/src/test/java/com/target/retail/product}/service/behavior/BehaviorsTest.java (98%) rename {retail-data-services => product-api}/src/test/resources/application.yml (100%) 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/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 e66751d..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 @@ -20,7 +20,7 @@ import java.util.stream.Collectors; @RestController -@RequestMapping("/cart/v1") +@RequestMapping("/v1") public class CartController { private CartService cartService; 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/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 100% rename from retail-data-services/induced_behaviors.md rename to product-api/induced_behaviors.md 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 100% rename from retail-data-services/scripts/README.md rename to product-api/scripts/README.md diff --git a/retail-data-services/scripts/benchmark-startup.sh b/product-api/scripts/benchmark-startup.sh similarity index 100% rename from retail-data-services/scripts/benchmark-startup.sh rename to product-api/scripts/benchmark-startup.sh 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 84% 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 4bcc089..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,10 +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.exception.AvailabilityNotFoundException; -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; @@ -19,6 +19,7 @@ import java.util.Optional; @RestController +@RequestMapping("/v1") public class AvailabilityController { private final AvailabilityService availabilityService; diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/controller/GlobalExceptionHandler.java b/product-api/src/main/java/com/target/retail/product/controller/GlobalExceptionHandler.java similarity index 77% rename from retail-data-services/src/main/java/com/target/retail/data/services/controller/GlobalExceptionHandler.java rename to product-api/src/main/java/com/target/retail/product/controller/GlobalExceptionHandler.java index 230746d..122e322 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/controller/GlobalExceptionHandler.java +++ b/product-api/src/main/java/com/target/retail/product/controller/GlobalExceptionHandler.java @@ -1,11 +1,11 @@ -package com.target.retail.data.services.controller; +package com.target.retail.product.controller; -import com.target.retail.data.services.data.DataException; -import com.target.retail.data.services.dto.ErrorResponse; -import com.target.retail.data.services.exception.AvailabilityNotFoundException; -import com.target.retail.data.services.exception.InducedFailureException; -import com.target.retail.data.services.exception.ItemNotFoundException; -import com.target.retail.data.services.exception.PriceNotFoundException; +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; 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 89% 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 3283747..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,11 +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.exception.ItemNotFoundException; -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; @@ -19,6 +19,7 @@ import java.util.Optional; @RestController +@RequestMapping("/v1") public class ItemController { private final ItemService itemService; 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 80% 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 9b8a5d8..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,10 +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.exception.PriceNotFoundException; -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; @@ -17,6 +17,7 @@ import java.util.Optional; @RestController +@RequestMapping("/v1") public class PriceController { private final PriceService priceService; 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 82% 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 04681b3..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,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.ItemAvailability; +import com.target.retail.product.model.ItemAvailability; @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public record AvailabilityResponse(String itemId, Integer availableUnits, Integer limitedQuantityThreshold) { diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/dto/ErrorResponse.java b/product-api/src/main/java/com/target/retail/product/dto/ErrorResponse.java similarity index 57% rename from retail-data-services/src/main/java/com/target/retail/data/services/dto/ErrorResponse.java rename to product-api/src/main/java/com/target/retail/product/dto/ErrorResponse.java index fcc2058..ca6ed2f 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/dto/ErrorResponse.java +++ b/product-api/src/main/java/com/target/retail/product/dto/ErrorResponse.java @@ -1,4 +1,4 @@ -package com.target.retail.data.services.dto; +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 91% 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 571db94..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, 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 94% 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 9b83a51..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; 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 82% 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 aec5cc9..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,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.ItemPrice; +import com.target.retail.product.model.ItemPrice; import java.math.BigDecimal; diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/exception/AvailabilityNotFoundException.java b/product-api/src/main/java/com/target/retail/product/exception/AvailabilityNotFoundException.java similarity index 79% rename from retail-data-services/src/main/java/com/target/retail/data/services/exception/AvailabilityNotFoundException.java rename to product-api/src/main/java/com/target/retail/product/exception/AvailabilityNotFoundException.java index 54f6032..e299df6 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/exception/AvailabilityNotFoundException.java +++ b/product-api/src/main/java/com/target/retail/product/exception/AvailabilityNotFoundException.java @@ -1,4 +1,4 @@ -package com.target.retail.data.services.exception; +package com.target.retail.product.exception; public class AvailabilityNotFoundException extends RuntimeException { public AvailabilityNotFoundException(String id) { diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/exception/InducedFailureException.java b/product-api/src/main/java/com/target/retail/product/exception/InducedFailureException.java similarity index 74% rename from retail-data-services/src/main/java/com/target/retail/data/services/exception/InducedFailureException.java rename to product-api/src/main/java/com/target/retail/product/exception/InducedFailureException.java index 750d47c..bef1d83 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/exception/InducedFailureException.java +++ b/product-api/src/main/java/com/target/retail/product/exception/InducedFailureException.java @@ -1,4 +1,4 @@ -package com.target.retail.data.services.exception; +package com.target.retail.product.exception; public class InducedFailureException extends RuntimeException { public InducedFailureException(String message) { diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/exception/ItemNotFoundException.java b/product-api/src/main/java/com/target/retail/product/exception/ItemNotFoundException.java similarity index 76% rename from retail-data-services/src/main/java/com/target/retail/data/services/exception/ItemNotFoundException.java rename to product-api/src/main/java/com/target/retail/product/exception/ItemNotFoundException.java index c5a178b..80706d3 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/exception/ItemNotFoundException.java +++ b/product-api/src/main/java/com/target/retail/product/exception/ItemNotFoundException.java @@ -1,4 +1,4 @@ -package com.target.retail.data.services.exception; +package com.target.retail.product.exception; public class ItemNotFoundException extends RuntimeException { public ItemNotFoundException(String id) { diff --git a/retail-data-services/src/main/java/com/target/retail/data/services/exception/PriceNotFoundException.java b/product-api/src/main/java/com/target/retail/product/exception/PriceNotFoundException.java similarity index 77% rename from retail-data-services/src/main/java/com/target/retail/data/services/exception/PriceNotFoundException.java rename to product-api/src/main/java/com/target/retail/product/exception/PriceNotFoundException.java index b10668a..ce822d0 100644 --- a/retail-data-services/src/main/java/com/target/retail/data/services/exception/PriceNotFoundException.java +++ b/product-api/src/main/java/com/target/retail/product/exception/PriceNotFoundException.java @@ -1,4 +1,4 @@ -package com.target.retail.data.services.exception; +package com.target.retail.product.exception; public class PriceNotFoundException extends RuntimeException { public PriceNotFoundException(String 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 89% 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 7606196..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,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; @JsonPropertyOrder({"itemId", "smallDescription", "longDescription", "category", "merchClass", "channelRestriction", "barcode", "brandName", "ageRestriction", "primary", "alternate", "baseUrl"}) @JsonIgnoreProperties(ignoreUnknown = true) 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 86% 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 743ed67..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,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; import java.math.BigDecimal; @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) 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 95% 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 d461995..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,6 +1,6 @@ -package com.target.retail.data.services.service.behavior; +package com.target.retail.product.service.behavior; -import com.target.retail.data.services.exception.InducedFailureException; +import com.target.retail.product.exception.InducedFailureException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; 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 80% 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 c0d644a..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,12 +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.exception.AvailabilityNotFoundException; -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; 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 95% 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 c955d32..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,14 +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.exception.ItemNotFoundException; -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; 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 82% 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 cc3d6cb..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,12 +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.exception.PriceNotFoundException; -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; 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 92% 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 f2b2e37..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; 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 92% 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 260e0a4..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; 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 96% 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 91fe3d5..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; 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 90% 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 1b9f9a7..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; 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/settings.gradle.kts b/settings.gradle.kts index 3a121b3..77fe352 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ rootProject.name = "tech-case-studies" -include("retail-data-services") +include("product-api") include("cart-service") pluginManagement { From 26dfa536717e984a4f43c4cf3d3150e5171f4ef9 Mon Sep 17 00:00:00 2001 From: jimmy-guzman Date: Wed, 10 Jun 2026 08:48:48 -0500 Subject: [PATCH 10/10] =?UTF-8?q?chore:=20=F0=9F=A4=96=20renaming=20docume?= =?UTF-8?q?ntation=20as=20well?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: jimmy-guzman --- README.md | 91 +++---- plan.md | 317 ----------------------- product-api/induced_behaviors.md | 72 ++--- product-api/scripts/README.md | 2 +- product-api/scripts/benchmark-startup.sh | 26 +- 5 files changed, 96 insertions(+), 412 deletions(-) delete mode 100644 plan.md 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/plan.md b/plan.md deleted file mode 100644 index 4b6cdb1..0000000 --- a/plan.md +++ /dev/null @@ -1,317 +0,0 @@ -# API Consistency & Rename Plan - -## Context - -Two services in this repo: - -- `retail-data-services` (port 8080) — read-only product catalog serving items, prices, and availability from CSV-backed data. Consumed by `cart-service` at runtime. -- `cart-service` (port 8081) — shopping cart service. Calls `retail-data-services` for item/price enrichment on every cart read. - -Both are mock APIs for retail case studies, run locally via Docker. No gateway, no production infrastructure. - ---- - -## Decisions - -### 1. Rename `retail-data-services` → `product-api` - -The service is a product catalog — items, prices, availability. "Retail data services" communicates nothing about the domain. Everything is in scope: directory, packages, Docker service name, paths, docs. - -| Thing | Before | After | -| ----------------------------- | ------------------------------------------------ | ----------------------------------------------------------------- | -| Submodule directory | `retail-data-services/` | `product-api/` | -| Gradle subproject | `retail-data-services` | `product-api` | -| Java package root | `com.target.retail.data.services` | `com.target.retail.product` | -| Java source tree | `src/main/java/com/target/retail/data/services/` | `src/main/java/com/target/retail/product/` | -| JAR artifact | `retail-data-services.jar` | `product-api.jar` | -| Docker service name | `data` | `product-api` | -| Docker hostname (cart client) | `http://data:8080` | `http://product-api:8080` | -| OpenAPI title | `"Retail Data Services API"` | `"Product API"` | -| OpenAPI description | `"API for managing retail data services."` | `"Read-only API for product catalog, pricing, and availability."` | -| API spec filename | `retail_data_services-v1.yaml` | `product-api-v1.yaml` | -| HTTP requests file | `retail-data-services.http` | `product-api.http` | - -### 2. API path strategy - -REST best practice: paths identify resources, not services. Service names belong in DNS/hostnames, not URL paths. Version belongs in the path, declared in `@RequestMapping` on the controller (visible in code and Swagger — not hidden in config). - -| Before | After | -| -------------------------------------------- | ----------------------------- | -| `/retail_data_services/v1/items/{id}` | `/v1/items/{id}` | -| `/retail_data_services/v1/items` | `/v1/items` | -| `/retail_data_services/v1/prices/{id}` | `/v1/prices/{id}` | -| `/retail_data_services/v1/availability/{id}` | `/v1/availability/{id}` | -| `/cart/v1/carts/{id}` | `/v1/carts/{id}` | -| `/cart/v1/carts` | `/v1/carts` | -| `/cart/v1/carts/{id}/items` | `/v1/carts/{id}/items` | -| `/cart/v1/carts/{id}/items/{tcin}` | `/v1/carts/{id}/items/{tcin}` | - -`context-path` removed from `product-api` `application.yml`. `@RequestMapping("/v1")` added to all controllers in both services. - -### 3. Response field name alignment - -Cart is the consumer-facing API. Upstream (`product-api`) adopts cart's shorter names where they differ. Cart's internal rename (when re-serializing consumed data) is removed. - -#### Image fields - -| Layer | Before | After | -| ------------------------------------------------------- | ---------------------------------- | ---------------------- | -| `product-api` `ItemResponse.ImageData` | `primary_image`, `alternate_image` | `primary`, `alternate` | -| `product-api` `Item` model (flat fields) | `primaryImage`, `alternateImage` | `primary`, `alternate` | -| `cart-service` `ItemApiResponse.ImageData` (client DTO) | `primaryImage`, `alternateImage` | `primary`, `alternate` | -| `cart-service` `CartResponse.ImageResponse` | `primary`, `alternate` | no change | - -#### Price fields - -| Layer | Before | After | -| ---------------------------------------------- | ----------------------------- | ----------------- | -| `product-api` `PriceResponse` | `regular_price`, `sale_price` | `regular`, `sale` | -| `cart-service` `PriceApiResponse` (client DTO) | `regularPrice`, `salePrice` | `regular`, `sale` | -| `cart-service` `Price` model | `regularPrice`, `salePrice` | `regular`, `sale` | -| `cart-service` `CartResponse.PriceResponse` | `regular`, `sale` | no change | - -### 4. ID field standardization — `product_id`, `tcin` → `item_id` everywhere - -`tcin` (Target Corporation Item Number) and `item_id` are the same concept — the unique product identifier — but named differently across both services with no clear boundary rule. Standardize to `item_id` everywhere. - -Three distinct problems rolled into one decision: - -- `PriceResponse` and `AvailabilityResponse` in `product-api` expose `product_id` while `ItemResponse` exposes `item_id` — inconsistent within the same service -- `cart-service` uses `tcin` throughout — models, DTOs, path variables, CSV headers, service methods — while consuming `item_id` from `product-api` -- `cart-service` HTTP clients pass a `tcin` parameter into a `{item_id}` URI slot — the naming mismatch is explicit at the cross-service boundary - -#### `product-api` changes - -| File | Before | After | -| ---- | ------ | ----- | -| `PriceResponse` | `productId` / `product_id` | `itemId` / `item_id` | -| `AvailabilityResponse` | `productId` / `product_id` | `itemId` / `item_id` | -| `PriceIntegrationTest` (line 14) | `jsonPath("$.product_id")` | `jsonPath("$.item_id")` | -| `AvailabilityIntegrationTest` (line 14) | `jsonPath("$.product_id")` | `jsonPath("$.item_id")` | -| API spec `retail_data_services-v1.yaml` | `product_id` in PriceResponse + AvailabilityResponse schemas | `item_id` | - -#### `cart-service` changes - -**Models:** - -| File | Before | After | -| ---- | ------ | ----- | -| `model/Item.java` | `String tcin` | `String itemId` | -| `model/Price.java` | `String tcin` | `String itemId` | -| `model/StoredCartLine.java` | `String tcin`, `@JsonPropertyOrder` literal `"tcin"` | `String itemId`, `"itemId"` | -| `model/Cart.java` | `findByTcin(String tcin)` | `findByItemId(String itemId)` | - -**Controller + DTOs:** - -| File | Before | After | -| ---- | ------ | ----- | -| `CartController.java` | `@PathVariable String tcin`, `{tcin}` in `@DeleteMapping`/`@PatchMapping` | `@PathVariable String itemId`, `{itemId}` | -| `CartController.java` line 42 | `"duplicate TCINs"` in `@ApiResponse` description | `"duplicate item IDs"` | -| `AddItemRequest.java` | `String tcin` | `String itemId` | -| `CartResponse.ItemResponse` | `String tcin` | `String itemId` | - -**Service + clients:** - -| File | Before | After | -| ---- | ------ | ----- | -| `CartService.java` — `removeItem`, `addItem`, `updateCartItem`, `getPriceForItem`, `getItem` params | `String tcin` | `String itemId` | -| `CartService.java` — exception message (line 141) | `"No cart line found for tcin " + tcin` | `"No cart line found for item id " + itemId` | -| `CartService.java` — local vars `storedCartLineForTcin` | `storedCartLineForTcin` | `storedCartLineForItemId` | -| `ItemApiClient.java` | `getItem(String tcin)`, `.uri("/items/{item_id}", tcin)` | `getItem(String itemId)`, `.uri("/items/{itemId}", itemId)` | -| `PriceApiClient.java` | `getPricing(String tcin)`, `.uri("/prices/{item_id}", tcin)` | `getPricing(String itemId)`, `.uri("/prices/{itemId}", itemId)` | -| `PriceApiResponse.java` | `productId` | `itemId` | - -**CSV data files:** - -| File | Before | After | -| ---- | ------ | ----- | -| `src/main/resources/data/100.csv` header | `lineId,cartId,tcin,...` | `lineId,cartId,itemId,...` | -| `src/main/resources/data/101.csv` header | `lineId,cartId,tcin,...` | `lineId,cartId,itemId,...` | - -**Tests:** - -| File | Change | -| ---- | ------ | -| `CartControllerTest.java` | All `tcin` local vars → `itemId`; test method `testUpdateCartItemWhenTcinNotFoundInCart` → `testUpdateCartItemWhenItemIdNotFoundInCart`; comment `// Duplicate tcin` → `// Duplicate item ID` | -| `CartResponseTest.java` | `cartLineItem.item().tcin()` → `cartLineItem.item().itemId()` | -| `CartServiceTest.java` | All `tcin`/`tcin1`/`tcin2`/`tcinToRemove`/`tcinToKeep` vars → `itemId`/`itemId1`/`itemId2`/`itemIdToRemove`/`itemIdToKeep`; test method names updated; exception message assertion updated | -| `CartDatabaseTest.java` | Inline CSV string literal header `tcin` → `itemId` | - -**Docs / HTTP files:** - -| File | Change | -| ---- | ------ | -| `README.md` lines 123, 129, 130, 141, 146, 156 | `tcin` → `item_id` in prose, request body examples, curl commands, path examples | -| `cart-service/cart-service.http` lines 14, 15, 25 | `"tcin"` key in JSON bodies → `"item_id"` | - -### 5. `merch_class` type - -`product-api` correctly exposes `merch_class` as `Integer`. `cart-service` casts it to `String` unnecessarily. Fix cart-service to use `Integer` end-to-end. - -| File | Before | After | -| ------------------------------------------ | ----------------------------------------- | ------------------------------ | -| `cart-service` `Item` model | `String merchClass` | `Integer merchClass` | -| `cart-service` `CartResponse.ItemResponse` | `String merchClass` | `Integer merchClass` | -| `cart-service` `CartService.getItem()` | `itemApiResponse.merchClass().toString()` | `itemApiResponse.merchClass()` | - -### 6. `next_page` sentinel - -`PaginatedResponse.calculateNextPage` returns `0` when there is no next page. `0` is also a valid page number, making "no more pages" ambiguous. Return `null` instead. - -| File | Before | After | -| --------------------------------------------------- | ------------------------------------------ | ------------------------------------- | -| `product-api` `PaginatedResponse` | `return ... : 0` | `return ... : null` | -| `product-api` `ItemControllerTest` (lines 131, 227) | `assertEquals(0, responseBody.nextPage())` | `assertNull(responseBody.nextPage())` | - -### 7. Typed exceptions and consistent error handling - -Replace bare `RuntimeException` throws with typed domain exceptions. Each service gets its own `GlobalExceptionHandler` (`@ControllerAdvice`) and `ErrorResponse` record — not shared, but identical shape. - -#### Error response shape (both services) - -```json -{ "status": 404, "message": "No cart found with id 123" } -``` - -Record: `public record ErrorResponse(int status, String message) {}` - -#### New exception types - -**`product-api`:** - -- `ItemNotFoundException extends RuntimeException` — thrown by `ItemService` when item not found -- `PriceNotFoundException extends RuntimeException` — thrown by `PriceService` when price not found -- `AvailabilityNotFoundException extends RuntimeException` — thrown by `AvailabilityService` when availability not found -- `InducedFailureException extends RuntimeException` — thrown by `Behaviors.callWithRandomFailures` - -**`cart-service`:** - -- `CartNotFoundException extends RuntimeException` — thrown by `CartService` when cart not found -- `CartLineItemNotFoundException extends RuntimeException` — thrown by `CartService` when line item not found -- `InducedFailureException extends RuntimeException` — thrown by `Behaviors.callWithRandomFailures` - -#### Controller refactor - -Resource controllers in `product-api` (`ItemController`, `PriceController`, `AvailabilityController`) currently handle 404 via `Optional.empty()` → `ResponseEntity.notFound().build()`. Refactor to throw typed exceptions from the service layer; let the advice handle the 404 response uniformly. - -`CartController` pre-check guard pattern (`cartService.getCart(id).isEmpty()` before service calls) removed. Typed exceptions from `CartService` propagate to the advice. Exception: `removeItemFromCart` retains its post-removal `getCart` call for the 204 vs 200 logic. - -#### `GlobalExceptionHandler` mappings - -| Exception | HTTP Status | `message` | -| -------------------------------------------------------------------------------------------------- | ----------- | ----------------------------------- | -| `ItemNotFoundException` / `PriceNotFoundException` / `AvailabilityNotFoundException` (product-api) | 404 | exception message | -| `CartNotFoundException` / `CartLineItemNotFoundException` (cart-service) | 404 | exception message | -| `InducedFailureException` (both) | 503 | exception message | -| `DataException` (both) | 500 | `"An internal data error occurred"` | -| `RuntimeException` catch-all (both) | 500 | `"An unexpected error occurred"` | - -#### Delete - -`CustomErrorController.java` in `product-api` — replaced entirely by `GlobalExceptionHandler`. Its `/error` path entries removed from the API spec. - -### 8. `AddItemRequest` missing `@JsonNaming` - -`UpdateItemRequest` has `@JsonNaming(SnakeCaseStrategy)`. `AddItemRequest` does not. Add it for consistency. - -### 9. `Behaviors` map key type - -`product-api` `Behaviors` uses `Map` with `.name()` lookups. `cart-service` uses the type-safe `Map`. Align `product-api` to use enum keys directly. - ---- - -## Commit sequence - -Commits 1–8 execute while the directory is still named `retail-data-services` so each change is focused and reviewable. Commit 9 is the atomic rename that touches all structural identifiers at once. - -| # | Type | Subject | -| --- | ---------- | --------------------------------------------------------------------------------- | -| 1 | `fix` | align image field names to primary/alternate across both services | -| 2 | `fix` | align price field names to regular/sale across both services | -| 3 | `fix` | standardize item identifier to item_id across both services | -| 4 | `fix` | correct merch_class type from String to Integer in cart-service | -| 5 | `fix` | return null for next_page when no further pages exist | -| 6 | `refactor` | introduce typed exceptions and consistent GlobalExceptionHandler in both services | -| 7 | `style` | add @JsonNaming to AddItemRequest | -| 8 | `refactor` | use type-safe Map in product-api Behaviors | -| 9 | `refactor` | rename retail-data-services to product-api with REST-idiomatic paths | - -All commits use gitzy with `--signoff`, no scopes. - ---- - -## Commit 9 full touch-point inventory - -Every file that changes in the rename commit: - -### Java source files (38 files — package + import statements) - -All files under `retail-data-services/src/main/java/com/target/retail/data/services/` and `retail-data-services/src/test/java/com/target/retail/data/services/`. - -### Build files - -| File | Change | -| ------------------------------ | ------------------------------------------------------------ | -| `settings.gradle.kts` | `include("retail-data-services")` → `include("product-api")` | -| `product-api/build.gradle.kts` | `mainClass` package + `archiveFileName` | -| `product-api/Dockerfile` | JAR filename ×2 | - -### Docker / CI - -| File | Change | -| --------------------------- | ---------------------------------------------------------------------- | -| `docker-compose.yml` | Service name `data:` → `product-api:`, build context, `depends_on` key | -| `.github/workflows/cd.yaml` | Matrix entries ×2 | - -### Spring config - -| File | Change | -| ------------------------------------------------- | ---------------------------------------------------------------------------- | -| `product-api/src/main/resources/application.yml` | Remove `context-path`, remove Swagger URL overrides | -| `cart-service/src/main/resources/application.yml` | `http://data:8080/retail_data_services/v1` → `http://product-api:8080/v1` ×2 | -| `cart-service/src/test/resources/application.yml` | Same ×2 | - -### Controllers - -| File | Change | -| --------------------------------------------------------------------------------- | -------------------------------------------------------- | -| `ItemController`, `PriceController`, `AvailabilityController`, `HealthController` | Add `@RequestMapping("/v1")` | -| `CartController` | `@RequestMapping("/cart/v1")` → `@RequestMapping("/v1")` | - -### Docs / specs / scripts - -| File | Change | -| ------------------------------------------ | ---------------------------------------------------------------------- | -| `retail_data_services-v1.yaml` | Rename to `product-api-v1.yaml`; update server URL, title, description | -| `retail-data-services.http` | Rename to `product-api.http`; update all URLs (already updated by commit 3) | -| `README.md` | ~25 occurrences | -| `product-api/scripts/benchmark-startup.sh` | IMAGE_NAME, HEALTH_ENDPOINT ×2, JAR path ×2 | -| `product-api/scripts/README.md` | 1 mention | -| `product-api/induced_behaviors.md` | ~18 occurrences | - -### CI/CD - -| File | Line(s) | Change | -| --------------------------- | ------- | ---------------------------------------------------------------------------- | -| `.github/workflows/cd.yaml` | 24 | Matrix entry `retail-data-services` → `product-api` (`docker-dry-run` job) | -| `.github/workflows/cd.yaml` | 59 | Matrix entry `retail-data-services` → `product-api` (`docker-build-push` job) | - -The matrix value `${{ matrix.service }}` is used as-is for the Gradle task (`:retail-data-services:build` → `:product-api:build`), Docker build context, GHCR image name, and GHA cache scope — so changing the matrix entry fixes all four in one place. - -#### GHCR package rename - -The published image path changes from: - -``` -ghcr.io//tech-case-studies-retail-data-services/retail-data-services -``` - -to: - -``` -ghcr.io//tech-case-studies-retail-data-services/product-api -``` - -Existing images published under the old name are not automatically redirected. Anyone pulling the old image name will need to update their reference. All tags (`latest`, branch, SHA, semver) will be published fresh under the new name on the next push to `main` after the rename commit lands. - -`ci.yaml`, `zizmor.yml`, and `dependabot.yml` have no service-name references and require no changes. diff --git a/product-api/induced_behaviors.md b/product-api/induced_behaviors.md index 88a2dbe..69f0922 100644 --- a/product-api/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/product-api/scripts/README.md b/product-api/scripts/README.md index b0d4acf..e9fb7b2 100644 --- a/product-api/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/product-api/scripts/benchmark-startup.sh b/product-api/scripts/benchmark-startup.sh index ea7e2de..a005418 100755 --- a/product-api/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 ""