From 1abdd28e0e7eaf2c3de90fa57f23e5cbc420bd61 Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Sat, 3 May 2025 00:36:06 +0300 Subject: [PATCH 01/20] feat : adding the crud operations in all layers --- .idea/.gitignore | 8 ++ .idea/compiler.xml | 26 ++++ .idea/encodings.xml | 7 ++ .idea/jarRepositories.xml | 20 +++ .idea/misc.xml | 14 +++ .idea/vcs.xml | 6 + pom.xml | 115 ++++++++++++++++++ .../java/cart/controller/CartController.java | 111 +++++++++++++++++ src/main/java/cart/model/Cart.java | 59 +++++++++ src/main/java/cart/model/CartItem.java | 39 ++++++ .../java/cart/repository/CartRepository.java | 12 ++ src/main/java/cart/service/CartService.java | 93 ++++++++++++++ 12 files changed, 510 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 pom.xml create mode 100644 src/main/java/cart/controller/CartController.java create mode 100644 src/main/java/cart/model/Cart.java create mode 100644 src/main/java/cart/model/CartItem.java create mode 100644 src/main/java/cart/repository/CartRepository.java create mode 100644 src/main/java/cart/service/CartService.java diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..3f44e05 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0e6e319 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..67ec612 --- /dev/null +++ b/pom.xml @@ -0,0 +1,115 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.4.5 + + + + com.podzilla + cart + 0.0.1-SNAPSHOT + cart + This is the cart service for Podzilla + + + 23 + + + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + + org.springframework.boot + spring-boot-starter-web + + + + + jakarta.validation + jakarta.validation-api + 3.0.2 + + + + + org.projectlombok + lombok + true + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.8.5 + + + + + net.logstash.logback + logstash-logback-encoder + 7.4 + + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.security + spring-security-test + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/src/main/java/cart/controller/CartController.java b/src/main/java/cart/controller/CartController.java new file mode 100644 index 0000000..9b13d51 --- /dev/null +++ b/src/main/java/cart/controller/CartController.java @@ -0,0 +1,111 @@ +package cart.controller; + + + +import cart.model.Cart; +import cart.service.CartService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import cart.model.CartItem; +import io.swagger.v3.oas.annotations.media.Content; + +@RestController +@RequestMapping("/api/cart") +@RequiredArgsConstructor +@Tag(name = "Cart Controller", description = "Handles cart operations like add, update, remove items and manage cart") +public class CartController { + + private final CartService cartService; + + @Operation(summary = "Create a new cart for a customer or return existing one") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Cart created or retrieved successfully"), + @ApiResponse(responseCode = "400", description = "Invalid customer ID provided", content = @Content), + @ApiResponse(responseCode = "500", description = "Internal server error", content = @Content) + }) + @PostMapping("/create/{customerId}") + public ResponseEntity createCart(@PathVariable("customerId") String customerId) { + Cart cart = cartService.createCart(customerId); + return ResponseEntity.ok(cart); + } + + @Operation(summary = "Get cart by customer ID") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Cart retrieved successfully"), + @ApiResponse(responseCode = "404", description = "Cart not found for this customer") + }) + @GetMapping("/customer/{customerId}") + public ResponseEntity getCartByCustomerId(@PathVariable("customerId") String customerId) { + Cart cart = cartService.getCartByCustomerId(customerId); + return ResponseEntity.ok(cart); + } + + @Operation(summary = "Delete cart by customer ID") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Cart deleted successfully"), + @ApiResponse(responseCode = "404", description = "Cart not found") + }) + @DeleteMapping("/customer/{customerId}") + public ResponseEntity deleteCart(@PathVariable("customerId") String customerId) { + cartService.deleteCartByCustomerId(customerId); + return ResponseEntity.noContent().build(); + } + + @Operation(summary = "Add an item to the cart or update its quantity if already exists") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Item added or updated successfully"), + @ApiResponse(responseCode = "400", description = "Invalid item data provided"), + @ApiResponse(responseCode = "404", description = "Cart not found for this customer") + }) + @PostMapping("/{customerId}/items") + public ResponseEntity addItemToCart( + @PathVariable("customerId") String customerId, + @RequestBody CartItem cartItem) { + Cart updatedCart = cartService.addItemToCart(customerId, cartItem); + return ResponseEntity.ok(updatedCart); + } + + @Operation(summary = "Update quantity of an existing item in the cart") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Quantity updated successfully"), + @ApiResponse(responseCode = "400", description = "Invalid quantity value"), + @ApiResponse(responseCode = "404", description = "Cart or item not found") + }) + @PatchMapping("/{customerId}/items/{productId}") + public ResponseEntity updateItemQuantity( + @PathVariable("customerId") String customerId, + @PathVariable("productId") String productId, + @RequestParam int quantity) { + Cart updatedCart = cartService.updateItemQuantity(customerId, productId, quantity); + return ResponseEntity.ok(updatedCart); + } + + @Operation(summary = "Remove an item from the cart") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Item removed successfully"), + @ApiResponse(responseCode = "404", description = "Cart or item not found") + }) + @DeleteMapping("/{customerId}/items/{productId}") + public ResponseEntity removeItemFromCart( + @PathVariable("customerId") String customerId, + @PathVariable("productId") String productId) { + Cart updatedCart = cartService.removeItemFromCart(customerId, productId); + return ResponseEntity.ok(updatedCart); + } + + @Operation(summary = "Clear all items from the cart") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Cart cleared successfully"), + @ApiResponse(responseCode = "404", description = "Cart not found") + }) + @DeleteMapping("/{customerId}/clear") + public ResponseEntity clearCart(@PathVariable("customerId") String customerId) { + cartService.clearCart(customerId); + return ResponseEntity.noContent().build(); + } +} \ No newline at end of file diff --git a/src/main/java/cart/model/Cart.java b/src/main/java/cart/model/Cart.java new file mode 100644 index 0000000..a34754d --- /dev/null +++ b/src/main/java/cart/model/Cart.java @@ -0,0 +1,59 @@ +package cart.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.ArrayList; +import java.util.List; + +@Document(collection = "carts") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Cart { + + + @Id + private String id; + + private String customerId; + + private List items = new ArrayList<>(); + + @Override + public String toString() { + return "Cart{" + + "id='" + id + '\'' + + ", customerId='" + customerId + '\'' + + ", items=" + items + + '}'; + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getCustomerId() { + return customerId; + } + + public void setCustomerId(final String customerId) { + this.customerId = customerId; + } + + public List getItems() { + return items; + } + + public void setItems(final List items) { + this.items = items; + } +} + diff --git a/src/main/java/cart/model/CartItem.java b/src/main/java/cart/model/CartItem.java new file mode 100644 index 0000000..92e77dc --- /dev/null +++ b/src/main/java/cart/model/CartItem.java @@ -0,0 +1,39 @@ +package cart.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CartItem { + + private String productId; + + private int quantity; + + @Override + public String toString() { + return "CartItem{" + + "productId='" + productId + '\'' + + ", quantity=" + quantity + + '}'; + } + + public String getProductId() { + return productId; + } + + public void setProductId(final String productId) { + this.productId = productId; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } +} diff --git a/src/main/java/cart/repository/CartRepository.java b/src/main/java/cart/repository/CartRepository.java new file mode 100644 index 0000000..d637b49 --- /dev/null +++ b/src/main/java/cart/repository/CartRepository.java @@ -0,0 +1,12 @@ +package cart.repository; + +import cart.model.Cart; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface CartRepository extends MongoRepository { + Optional findByCustomerId(final String customerId); +} \ No newline at end of file diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java new file mode 100644 index 0000000..4995a1d --- /dev/null +++ b/src/main/java/cart/service/CartService.java @@ -0,0 +1,93 @@ +package cart.service; + +import cart.model.Cart; +import cart.model.CartItem; +import cart.repository.CartRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import java.util.ArrayList; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class CartService { + + private final CartRepository cartRepository; + + + public Cart createCart(final String customerId) { + return cartRepository.findByCustomerId(customerId) + .orElseGet(() -> { + Cart newCart = new Cart(UUID.randomUUID().toString(), customerId, new ArrayList<>()); + return cartRepository.save(newCart); + }); + } + + public Cart addItemToCart(final String customerId, final CartItem newItem) { + Cart cart = getCartByCustomerId(customerId); + + Optional existingItem = cart.getItems().stream() + .filter(i -> i.getProductId().equals(newItem.getProductId())) + .findFirst(); + + if (existingItem.isPresent()) { + existingItem.get().setQuantity(existingItem.get().getQuantity() + newItem.getQuantity()); + } else { + cart.getItems().add(newItem); + } + + return cartRepository.save(cart); + } + + + public Cart updateItemQuantity(final String customerId, final String productId, final int quantity) { + Cart cart = getCartByCustomerId(customerId); + + Optional existingItemOpt = cart.getItems().stream() + .filter(i -> i.getProductId().equals(productId)) + .findFirst(); + + if (existingItemOpt.isEmpty()) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found in cart"); + } + + CartItem item = existingItemOpt.get(); + + if (quantity <= 0) { + cart.getItems().remove(item); + } else { + item.setQuantity(quantity); + } + + return cartRepository.save(cart); + + } + + + public Cart removeItemFromCart(final String customerId,final String productId) { + Cart cart = getCartByCustomerId(customerId); + cart.getItems().removeIf(i -> i.getProductId().equals(productId)); + return cartRepository.save(cart); + } + + public void deleteCartByCustomerId(final String customerId) { + cartRepository.findByCustomerId(customerId).ifPresent(cartRepository::delete); + } + + public Cart getCartByCustomerId(final String customerId) { + return cartRepository.findByCustomerId(customerId) + .orElseThrow(() -> new NoSuchElementException("Cart not found")); + } + + public void clearCart(final String customerId) { + Cart cart = getCartByCustomerId(customerId); + cart.getItems().clear(); + cartRepository.save(cart); + } + +} From 16a78e3b183f3e107c0d5c88e6e0c2ee0d661546 Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Sat, 3 May 2025 00:42:50 +0300 Subject: [PATCH 02/20] adding clear cart method --- src/main/java/cart/service/CartService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java index 4995a1d..b9168cd 100644 --- a/src/main/java/cart/service/CartService.java +++ b/src/main/java/cart/service/CartService.java @@ -65,7 +65,6 @@ public Cart updateItemQuantity(final String customerId, final String productId, } return cartRepository.save(cart); - } From 39fce86d2767c46383670b99a74bd232177ce90b Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Sat, 3 May 2025 00:58:03 +0300 Subject: [PATCH 03/20] adding docker file --- docker-compose.yml | 61 +++++++++++++++++++++++++ promtail.yml | 18 ++++++++ src/main/java/cart/CartApplication.java | 11 +++++ 3 files changed, 90 insertions(+) create mode 100644 docker-compose.yml create mode 100644 promtail.yml create mode 100644 src/main/java/cart/CartApplication.java diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d1ded4d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,61 @@ +version: '3.8' + +services: + + cart-service: + image: openjdk:25-ea-4-jdk-oraclelinux9 + container_name: cart-service + ports: + - "8081:8080" + env_file: + - secret.env + environment: + SPRING_DATA_MONGODB_URI: mongodb://cart-db:27017/cartDB + SPRING_DATA_MONGODB_DATABASE: cartDB + depends_on: + - cart-db + volumes: + - ./target:/app + - ./logs/cart:/logs + command: ["java", "-jar", "/app/cart-service.jar"] + + cart-db: + image: mongo:latest + container_name: cart-db + environment: + MONGO_INITDB_DATABASE: cartDB + ports: + - "27018:27017" + volumes: + - cart-mongo-data:/data/db + + # Reuse Loki and Promtail from your existing setup + loki: + image: grafana/loki:latest + container_name: loki + ports: + - "3100:3100" + command: + - -config.file=/etc/loki/local-config.yaml + + promtail: + image: grafana/promtail:latest + container_name: promtail + volumes: + - ./promtail-config.yml:/etc/promtail/promtail-config.yaml + - ./logs:/logs + command: + - -config.file=/etc/promtail/promtail-config.yaml + depends_on: + - loki + + grafana: + image: grafana/grafana:latest + container_name: grafana + ports: + - "3000:3000" + depends_on: + - loki + +volumes: + cart-mongo-data: \ No newline at end of file diff --git a/promtail.yml b/promtail.yml new file mode 100644 index 0000000..c3a4e5b --- /dev/null +++ b/promtail.yml @@ -0,0 +1,18 @@ +server: + http_listen_port: 9080 + grpc_listen_port: 0 + +positions: + filename: /tmp/positions.yaml + +clients: + - url: http://loki:3100/loki/api/v1/push + +scrape_configs: + - job_name: cart-service + static_configs: + - targets: + - localhost + labels: + job: cart-service + __path__: /logs/*.log \ No newline at end of file diff --git a/src/main/java/cart/CartApplication.java b/src/main/java/cart/CartApplication.java new file mode 100644 index 0000000..85eeedf --- /dev/null +++ b/src/main/java/cart/CartApplication.java @@ -0,0 +1,11 @@ +package cart; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class CartApplication { + public static void main(final String [] args){ + SpringApplication.run(CartApplication.class, args); + } +} From 075828c5c0fbc7ff10922c7fbc8760fddfa13a0c Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Sat, 3 May 2025 00:59:24 +0300 Subject: [PATCH 04/20] adding git ignore --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12b534a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Ignore Maven build output +/target/ + +# Ignore IntelliJ IDEA files +.idea/ +*.iml +*.ipr +*.iws From 58841eeb0a097e4c83a6f40b644ce48c2a82593d Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Sat, 3 May 2025 15:56:10 +0300 Subject: [PATCH 05/20] adding the 3 endpoints archive and unarchive and checkout --- src/main/java/cart/config/AppConfig.java | 15 ++++++ .../java/cart/controller/CartController.java | 38 +++++++++++++++ src/main/java/cart/model/Cart.java | 11 +++++ src/main/java/cart/model/OrderRequest.java | 26 +++++++++++ .../java/cart/repository/CartRepository.java | 2 + src/main/java/cart/service/CartService.java | 46 ++++++++++++++++++- src/main/resources/application.properties | 2 + 7 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 src/main/java/cart/config/AppConfig.java create mode 100644 src/main/java/cart/model/OrderRequest.java create mode 100644 src/main/resources/application.properties diff --git a/src/main/java/cart/config/AppConfig.java b/src/main/java/cart/config/AppConfig.java new file mode 100644 index 0000000..6fa562e --- /dev/null +++ b/src/main/java/cart/config/AppConfig.java @@ -0,0 +1,15 @@ +package cart.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class AppConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} \ No newline at end of file diff --git a/src/main/java/cart/controller/CartController.java b/src/main/java/cart/controller/CartController.java index 9b13d51..02328a7 100644 --- a/src/main/java/cart/controller/CartController.java +++ b/src/main/java/cart/controller/CartController.java @@ -108,4 +108,42 @@ public ResponseEntity clearCart(@PathVariable("customerId") String custome cartService.clearCart(customerId); return ResponseEntity.noContent().build(); } + + @Operation(summary = "Archive the cart (soft-delete)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Cart successfully archived"), + @ApiResponse(responseCode = "404", description = "Cart not found") + }) + @PatchMapping("/{customerId}/archive") + public ResponseEntity archiveCart(@PathVariable("customerId") String customerId) { + Cart archivedCart = cartService.archiveCart(customerId); + return ResponseEntity.ok(archivedCart); + } + + @Operation(summary = "Unarchive a previously archived cart") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Cart successfully unarchived"), + @ApiResponse(responseCode = "404", description = "Archived cart not found") + }) + @PatchMapping("/{customerId}/unarchive") + public ResponseEntity unarchiveCart(@PathVariable("customerId") String customerId) { + Cart activeCart = cartService.unarchiveCart(customerId); + return ResponseEntity.ok(activeCart); + } + + @Operation(summary = "Checkout cart by sending it to the Order Service") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Cart checked out and sent to Order Service"), + @ApiResponse(responseCode = "404", description = "Cart not found"), + @ApiResponse(responseCode = "500", description = "Failed to communicate with Order Service") + }) + @PostMapping("/{customerId}/checkout") + public ResponseEntity checkoutCart(@PathVariable("customerId") String customerId) { + try { + Cart updatedCart = cartService.checkoutCart(customerId); + return ResponseEntity.ok(updatedCart); + } catch (Exception ex) { + throw new IllegalCallerException("Error communicating with Order Service"); + } + } } \ No newline at end of file diff --git a/src/main/java/cart/model/Cart.java b/src/main/java/cart/model/Cart.java index a34754d..30f4680 100644 --- a/src/main/java/cart/model/Cart.java +++ b/src/main/java/cart/model/Cart.java @@ -23,12 +23,15 @@ public class Cart { private List items = new ArrayList<>(); + private boolean archived = false; + @Override public String toString() { return "Cart{" + "id='" + id + '\'' + ", customerId='" + customerId + '\'' + ", items=" + items + + ", archived" + archived + '}'; } @@ -55,5 +58,13 @@ public List getItems() { public void setItems(final List items) { this.items = items; } + + public boolean isArchived() { + return archived; + } + + public void setArchived(final boolean archived) { + this.archived = archived; + } } diff --git a/src/main/java/cart/model/OrderRequest.java b/src/main/java/cart/model/OrderRequest.java new file mode 100644 index 0000000..8ebb3a8 --- /dev/null +++ b/src/main/java/cart/model/OrderRequest.java @@ -0,0 +1,26 @@ +package cart.model; + + +import java.util.List; + +public class OrderRequest { + private String customerId; + private List items; + + // Getters and setters + public String getCustomerId() { + return customerId; + } + + public void setCustomerId(String customerId) { + this.customerId = customerId; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } +} \ No newline at end of file diff --git a/src/main/java/cart/repository/CartRepository.java b/src/main/java/cart/repository/CartRepository.java index d637b49..8e33a69 100644 --- a/src/main/java/cart/repository/CartRepository.java +++ b/src/main/java/cart/repository/CartRepository.java @@ -9,4 +9,6 @@ @Repository public interface CartRepository extends MongoRepository { Optional findByCustomerId(final String customerId); + Optional findByCustomerIdAndArchived(String customerId, boolean archived); + } \ No newline at end of file diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java index b9168cd..3fbfa9f 100644 --- a/src/main/java/cart/service/CartService.java +++ b/src/main/java/cart/service/CartService.java @@ -2,10 +2,13 @@ import cart.model.Cart; import cart.model.CartItem; +import cart.model.OrderRequest; import cart.repository.CartRepository; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; import org.springframework.web.server.ResponseStatusException; import java.util.ArrayList; @@ -19,11 +22,16 @@ public class CartService { private final CartRepository cartRepository; + private final RestTemplate restTemplate; + + @Value("${order.service.url}") + private String orderServiceUrl; + public Cart createCart(final String customerId) { return cartRepository.findByCustomerId(customerId) .orElseGet(() -> { - Cart newCart = new Cart(UUID.randomUUID().toString(), customerId, new ArrayList<>()); + Cart newCart = new Cart(UUID.randomUUID().toString(), customerId, new ArrayList<>(), false); return cartRepository.save(newCart); }); } @@ -89,4 +97,40 @@ public void clearCart(final String customerId) { cartRepository.save(cart); } + public Cart archiveCart(String customerId) { + Cart cart = getActiveCart(customerId); + cart.setArchived(true); + return cartRepository.save(cart); + } + + public Cart unarchiveCart(String customerId) { + Cart cart = getArchivedCart(customerId); + cart.setArchived(false); + return cartRepository.save(cart); + } + + public Cart checkoutCart(String customerId) { + Cart cart = getActiveCart(customerId); + + OrderRequest orderRequest = new OrderRequest(); + orderRequest.setCustomerId(customerId); + orderRequest.setItems(cart.getItems()); + + restTemplate.postForObject(orderServiceUrl + "/orders", orderRequest, Void.class); + + cart.getItems().clear(); + return cartRepository.save(cart); + } + + + private Cart getActiveCart(String customerId) { + return cartRepository.findByCustomerIdAndArchived(customerId, false) + .orElseThrow(() -> new NoSuchElementException("Cart not found for customer ID: " + customerId)); + } + + private Cart getArchivedCart(String customerId) { + return cartRepository.findByCustomerIdAndArchived(customerId, true) + .orElseThrow(() -> new NoSuchElementException("No archived cart found for customer ID: " + customerId)); + } + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..2e21dc6 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,2 @@ +order.service.url=http://order-service:8082 +spring.data.mongodb.uri=mongodb://localhost:27018/cartDB \ No newline at end of file From 26c31a8113bb5d7a22e739d7a6545819f6c0ad5a Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Sat, 3 May 2025 23:47:42 +0300 Subject: [PATCH 06/20] fixing comments and logger config --- docker-compose.yml | 8 +- logs/app.log | 367 ++++++++++++++++++++++ promtail.yml | 2 +- src/main/resources/application.properties | 5 +- src/main/resources/logback-spring.xml | 15 + 5 files changed, 390 insertions(+), 7 deletions(-) create mode 100644 logs/app.log create mode 100644 src/main/resources/logback-spring.xml diff --git a/docker-compose.yml b/docker-compose.yml index d1ded4d..e35d882 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,6 @@ services: container_name: cart-service ports: - "8081:8080" - env_file: - - secret.env environment: SPRING_DATA_MONGODB_URI: mongodb://cart-db:27017/cartDB SPRING_DATA_MONGODB_DATABASE: cartDB @@ -16,8 +14,8 @@ services: - cart-db volumes: - ./target:/app - - ./logs/cart:/logs - command: ["java", "-jar", "/app/cart-service.jar"] + - ./logs:/logs + command: ["java", "-jar", "/app/cart-0.0.1-SNAPSHOT.jar"] cart-db: image: mongo:latest @@ -42,7 +40,7 @@ services: image: grafana/promtail:latest container_name: promtail volumes: - - ./promtail-config.yml:/etc/promtail/promtail-config.yaml + - ./promtail.yml:/etc/promtail/promtail-config.yaml - ./logs:/logs command: - -config.file=/etc/promtail/promtail-config.yaml diff --git a/logs/app.log b/logs/app.log new file mode 100644 index 0000000..20570ce --- /dev/null +++ b/logs/app.log @@ -0,0 +1,367 @@ +2025-05-03T20:31:22.279Z INFO 1 --- [main] cart.CartApplication : Starting CartApplication v0.0.1-SNAPSHOT using Java 25-ea with PID 1 (/app/cart-0.0.1-SNAPSHOT.jar started by root in /) +2025-05-03T20:31:22.309Z INFO 1 --- [main] cart.CartApplication : No active profile set, falling back to 1 default profile: "default" +2025-05-03T20:31:28.546Z INFO 1 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data MongoDB repositories in DEFAULT mode. +2025-05-03T20:31:28.823Z INFO 1 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 221 ms. Found 1 MongoDB repository interface. +2025-05-03T20:31:31.202Z INFO 1 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) +2025-05-03T20:31:31.305Z INFO 1 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat] +2025-05-03T20:31:31.311Z INFO 1 --- [main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.40] +2025-05-03T20:31:31.496Z INFO 1 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext +2025-05-03T20:31:31.506Z INFO 1 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 8747 ms +2025-05-03T20:31:33.078Z INFO 1 --- [main] org.mongodb.driver.client : MongoClient with metadata {"driver": {"name": "mongo-java-driver|sync|spring-boot", "version": "5.2.1"}, "os": {"type": "Linux", "name": "Linux", "architecture": "amd64", "version": "5.15.133.1-microsoft-standard-WSL2"}, "platform": "Java/Oracle Corporation/25-ea+4-344", "env": {"container": {"runtime": "docker"}}} created with settings MongoClientSettings{readPreference=primary, writeConcern=WriteConcern{w=null, wTimeout=null ms, journal=null}, retryWrites=true, retryReads=true, readConcern=ReadConcern{level=null}, credential=null, transportSettings=null, commandListeners=[], codecRegistry=ProvidersCodecRegistry{codecProviders=[ValueCodecProvider{}, BsonValueCodecProvider{}, DBRefCodecProvider{}, DBObjectCodecProvider{}, DocumentCodecProvider{}, CollectionCodecProvider{}, IterableCodecProvider{}, MapCodecProvider{}, GeoJsonCodecProvider{}, GridFSFileCodecProvider{}, Jsr310CodecProvider{}, JsonObjectCodecProvider{}, BsonCodecProvider{}, EnumCodecProvider{}, com.mongodb.client.model.mql.ExpressionCodecProvider@5b1c32e4, com.mongodb.Jep395RecordCodecProvider@2bab618, com.mongodb.KotlinCodecProvider@48bc2fce]}, loggerSettings=LoggerSettings{maxDocumentLength=1000}, clusterSettings={hosts=[cart-db:27017], srvServiceName=mongodb, mode=SINGLE, requiredClusterType=UNKNOWN, requiredReplicaSetName='null', serverSelector='null', clusterListeners='[]', serverSelectionTimeout='30000 ms', localThreshold='15 ms'}, socketSettings=SocketSettings{connectTimeoutMS=10000, readTimeoutMS=0, receiveBufferSize=0, proxySettings=ProxySettings{host=null, port=null, username=null, password=null}}, heartbeatSocketSettings=SocketSettings{connectTimeoutMS=10000, readTimeoutMS=10000, receiveBufferSize=0, proxySettings=ProxySettings{host=null, port=null, username=null, password=null}}, connectionPoolSettings=ConnectionPoolSettings{maxSize=100, minSize=0, maxWaitTimeMS=120000, maxConnectionLifeTimeMS=0, maxConnectionIdleTimeMS=0, maintenanceInitialDelayMS=0, maintenanceFrequencyMS=60000, connectionPoolListeners=[], maxConnecting=2}, serverSettings=ServerSettings{heartbeatFrequencyMS=10000, minHeartbeatFrequencyMS=500, serverMonitoringMode=AUTO, serverListeners='[]', serverMonitorListeners='[]'}, sslSettings=SslSettings{enabled=false, invalidHostNameAllowed=false, context=null}, applicationName='null', compressorList=[], uuidRepresentation=JAVA_LEGACY, serverApi=null, autoEncryptionSettings=null, dnsClient=null, inetAddressResolver=null, contextProvider=null, timeoutMS=null} +2025-05-03T20:31:33.264Z INFO 1 --- [cluster-ClusterId{value='68167d2481d2884e0500a1eb', description='null'}-cart-db:27017] org.mongodb.driver.cluster : Monitor thread successfully connected to server with description ServerDescription{address=cart-db:27017, type=STANDALONE, cryptd=false, state=CONNECTED, ok=true, minWireVersion=0, maxWireVersion=21, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=256246676, minRoundTripTimeNanos=0} +2025-05-03T20:31:38.379Z INFO 1 --- [main] o.s.v.b.OptionalValidatorFactoryBean : Failed to set up a Bean Validation provider: jakarta.validation.NoProviderFoundException: Unable to create a Configuration, because no Jakarta Bean Validation provider could be found. Add a provider like Hibernate Validator (RI) to your classpath. +2025-05-03T20:31:39.314Z DEBUG 1 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : 16 mappings in 'requestMappingHandlerMapping' +2025-05-03T20:31:40.217Z DEBUG 1 --- [main] o.s.w.s.handler.SimpleUrlHandlerMapping : Patterns [/webjars/**, /**, /swagger-ui*/*swagger-initializer.js, /swagger-ui*/**] in 'resourceHandlerMapping' +2025-05-03T20:31:40.665Z DEBUG 1 --- [main] s.w.s.m.m.a.RequestMappingHandlerAdapter : ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 RequestBodyAdvice, 1 ResponseBodyAdvice +2025-05-03T20:31:42.243Z DEBUG 1 --- [main] .m.m.a.ExceptionHandlerExceptionResolver : ControllerAdvice beans: 1 @ExceptionHandler, 1 ResponseBodyAdvice +2025-05-03T20:31:43.617Z INFO 1 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/' +2025-05-03T20:31:43.762Z INFO 1 --- [main] cart.CartApplication : Started CartApplication in 34.402 seconds (process running for 45.042) +2025-05-03T20:38:11.427Z INFO 1 --- [http-nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' +2025-05-03T20:38:11.437Z INFO 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' +2025-05-03T20:38:11.462Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Detected StandardServletMultipartResolver +2025-05-03T20:38:11.466Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Detected AcceptHeaderLocaleResolver +2025-05-03T20:38:11.479Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Detected FixedThemeResolver +2025-05-03T20:38:11.535Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Detected org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@305063b9 +2025-05-03T20:38:11.542Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Detected org.springframework.web.servlet.support.SessionFlashMapManager@38b1666c +2025-05-03T20:38:11.558Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data +2025-05-03T20:38:11.568Z INFO 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 119 ms +2025-05-03T20:38:11.680Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/123456", parameters={} +2025-05-03T20:38:11.829Z DEBUG 1 --- [http-nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) +2025-05-03T20:38:13.541Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Failed to complete request: java.util.NoSuchElementException: Cart not found +2025-05-03T20:38:13.564Z ERROR 1 --- [http-nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.util.NoSuchElementException: Cart not found] with root cause + +java.util.NoSuchElementException: Cart not found + at cart.service.CartService.lambda$getCartByCustomerId$4(CartService.java:91) + at java.base/java.util.Optional.orElseThrow(Optional.java:403) + at cart.service.CartService.getCartByCustomerId(CartService.java:91) + at cart.controller.CartController.getCartByCustomerId(CartController.java:44) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1447) + +2025-05-03T20:38:13.778Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={} +2025-05-03T20:38:13.798Z DEBUG 1 --- [http-nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest) +2025-05-03T20:38:14.135Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:38:14.179Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Sat May 03 20:38:13 UTC 2025, status=500, error=Internal Server Error, path=/api/cart/cus (truncated)...] +2025-05-03T20:38:14.631Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500 +2025-05-03T20:38:23.070Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12345", parameters={} +2025-05-03T20:38:23.082Z DEBUG 1 --- [http-nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) +2025-05-03T20:38:23.152Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Failed to complete request: java.util.NoSuchElementException: Cart not found +2025-05-03T20:38:23.161Z ERROR 1 --- [http-nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.util.NoSuchElementException: Cart not found] with root cause + +java.util.NoSuchElementException: Cart not found + at cart.service.CartService.lambda$getCartByCustomerId$4(CartService.java:91) + at java.base/java.util.Optional.orElseThrow(Optional.java:403) + at cart.service.CartService.getCartByCustomerId(CartService.java:91) + at cart.controller.CartController.getCartByCustomerId(CartController.java:44) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1447) + +2025-05-03T20:38:23.186Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={} +2025-05-03T20:38:23.205Z DEBUG 1 --- [http-nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest) +2025-05-03T20:38:23.222Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:38:23.253Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Sat May 03 20:38:23 UTC 2025, status=500, error=Internal Server Error, path=/api/cart/cus (truncated)...] +2025-05-03T20:38:23.387Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500 +2025-05-03T20:39:12.637Z DEBUG 1 --- [http-nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : POST "/api/cart/create/12356", parameters={} +2025-05-03T20:39:12.641Z DEBUG 1 --- [http-nio-8080-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#createCart(String) +2025-05-03T20:39:13.272Z DEBUG 1 --- [http-nio-8080-exec-3] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:39:13.297Z DEBUG 1 --- [http-nio-8080-exec-3] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] +2025-05-03T20:39:13.325Z DEBUG 1 --- [http-nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:39:32.998Z DEBUG 1 --- [http-nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/123456", parameters={} +2025-05-03T20:39:33.007Z DEBUG 1 --- [http-nio-8080-exec-6] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) +2025-05-03T20:39:33.036Z DEBUG 1 --- [http-nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : Failed to complete request: java.util.NoSuchElementException: Cart not found +2025-05-03T20:39:33.042Z ERROR 1 --- [http-nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.util.NoSuchElementException: Cart not found] with root cause + +java.util.NoSuchElementException: Cart not found + at cart.service.CartService.lambda$getCartByCustomerId$4(CartService.java:91) + at java.base/java.util.Optional.orElseThrow(Optional.java:403) + at cart.service.CartService.getCartByCustomerId(CartService.java:91) + at cart.controller.CartController.getCartByCustomerId(CartController.java:44) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1447) + +2025-05-03T20:39:33.051Z DEBUG 1 --- [http-nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={} +2025-05-03T20:39:33.066Z DEBUG 1 --- [http-nio-8080-exec-6] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest) +2025-05-03T20:39:33.077Z DEBUG 1 --- [http-nio-8080-exec-6] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:39:33.084Z DEBUG 1 --- [http-nio-8080-exec-6] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Sat May 03 20:39:33 UTC 2025, status=500, error=Internal Server Error, path=/api/cart/cus (truncated)...] +2025-05-03T20:39:33.088Z DEBUG 1 --- [http-nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500 +2025-05-03T20:39:47.537Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : POST "/api/cart/create/12356", parameters={} +2025-05-03T20:39:47.545Z DEBUG 1 --- [http-nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#createCart(String) +2025-05-03T20:39:47.575Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:39:47.632Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] +2025-05-03T20:39:47.664Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:39:57.758Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} +2025-05-03T20:39:57.767Z DEBUG 1 --- [http-nio-8080-exec-8] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) +2025-05-03T20:39:57.808Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:39:57.811Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] +2025-05-03T20:39:57.824Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:40:20.555Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.web.servlet.DispatcherServlet : PATCH "/api/cart/12356/archive", parameters={} +2025-05-03T20:40:20.561Z DEBUG 1 --- [http-nio-8080-exec-10] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#archiveCart(String) +2025-05-03T20:40:20.842Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:40:20.844Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedtrue}] +2025-05-03T20:40:20.856Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:40:33.609Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} +2025-05-03T20:40:33.612Z DEBUG 1 --- [http-nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) +2025-05-03T20:40:33.629Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:40:33.641Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedtrue}] +2025-05-03T20:40:33.657Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:40:44.620Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : PATCH "/api/cart/12356/unarchive", parameters={} +2025-05-03T20:40:44.623Z DEBUG 1 --- [http-nio-8080-exec-4] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#unarchiveCart(String) +2025-05-03T20:40:44.659Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:40:44.669Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] +2025-05-03T20:40:44.672Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:41:26.966Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : POST "/api/cart/12356/items", parameters={} +2025-05-03T20:41:26.982Z DEBUG 1 --- [http-nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#addItemToCart(String, CartItem) +2025-05-03T20:41:28.869Z DEBUG 1 --- [http-nio-8080-exec-7] m.m.a.RequestResponseBodyMethodProcessor : Read "application/json;charset=UTF-8" to [CartItem{productId='123', quantity=2}] +2025-05-03T20:41:29.043Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:41:29.070Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[CartItem{productId='123', (truncated)...] +2025-05-03T20:41:29.127Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:41:44.041Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} +2025-05-03T20:41:44.044Z DEBUG 1 --- [http-nio-8080-exec-8] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) +2025-05-03T20:41:44.149Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:41:44.151Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[CartItem{productId='123', (truncated)...] +2025-05-03T20:41:44.154Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:42:11.607Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.web.servlet.DispatcherServlet : PATCH "/api/cart/12356/items/123?quantity=4", parameters={masked} +2025-05-03T20:42:11.613Z DEBUG 1 --- [http-nio-8080-exec-10] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#updateItemQuantity(String, String, int) +2025-05-03T20:42:11.684Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:42:11.687Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[CartItem{productId='123', (truncated)...] +2025-05-03T20:42:11.705Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:42:31.364Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : DELETE "/api/cart/12356/clear", parameters={} +2025-05-03T20:42:31.369Z DEBUG 1 --- [http-nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#clearCart(String) +2025-05-03T20:42:31.399Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:42:31.404Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Nothing to write: null body +2025-05-03T20:42:31.407Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 204 NO_CONTENT +2025-05-03T20:42:48.558Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} +2025-05-03T20:42:48.560Z DEBUG 1 --- [http-nio-8080-exec-4] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) +2025-05-03T20:42:48.575Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:42:48.578Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] +2025-05-03T20:42:48.581Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:43:11.772Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : POST "/api/cart/12356/items", parameters={} +2025-05-03T20:43:11.775Z DEBUG 1 --- [http-nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#addItemToCart(String, CartItem) +2025-05-03T20:43:11.834Z DEBUG 1 --- [http-nio-8080-exec-7] m.m.a.RequestResponseBodyMethodProcessor : Read "application/json;charset=UTF-8" to [CartItem{productId='124', quantity=3}] +2025-05-03T20:43:11.891Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:43:11.894Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[CartItem{productId='124', (truncated)...] +2025-05-03T20:43:11.905Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:43:21.624Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} +2025-05-03T20:43:21.629Z DEBUG 1 --- [http-nio-8080-exec-8] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) +2025-05-03T20:43:21.649Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:43:21.659Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[CartItem{productId='124', (truncated)...] +2025-05-03T20:43:21.681Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:43:44.983Z DEBUG 1 --- [http-nio-8080-exec-9] o.s.web.servlet.DispatcherServlet : PATCH "/api/cart/12356/items/124?quantity=-1", parameters={masked} +2025-05-03T20:43:44.986Z DEBUG 1 --- [http-nio-8080-exec-9] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#updateItemQuantity(String, String, int) +2025-05-03T20:43:45.145Z DEBUG 1 --- [http-nio-8080-exec-9] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:43:45.151Z DEBUG 1 --- [http-nio-8080-exec-9] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] +2025-05-03T20:43:45.170Z DEBUG 1 --- [http-nio-8080-exec-9] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:43:52.213Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} +2025-05-03T20:43:52.217Z DEBUG 1 --- [http-nio-8080-exec-10] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) +2025-05-03T20:43:52.245Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:43:52.249Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] +2025-05-03T20:43:52.260Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.web.servlet.DispatcherServlet : Completed 200 OK +2025-05-03T20:44:04.613Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : DELETE "/api/cart/customer/12356", parameters={} +2025-05-03T20:44:04.616Z DEBUG 1 --- [http-nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#deleteCart(String) +2025-05-03T20:44:04.737Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:44:04.739Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Nothing to write: null body +2025-05-03T20:44:04.742Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed 204 NO_CONTENT +2025-05-03T20:44:10.048Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} +2025-05-03T20:44:10.060Z DEBUG 1 --- [http-nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) +2025-05-03T20:44:10.095Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Failed to complete request: java.util.NoSuchElementException: Cart not found +2025-05-03T20:44:10.101Z ERROR 1 --- [http-nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.util.NoSuchElementException: Cart not found] with root cause + +java.util.NoSuchElementException: Cart not found + at cart.service.CartService.lambda$getCartByCustomerId$4(CartService.java:91) + at java.base/java.util.Optional.orElseThrow(Optional.java:403) + at cart.service.CartService.getCartByCustomerId(CartService.java:91) + at cart.controller.CartController.getCartByCustomerId(CartController.java:44) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1447) + +2025-05-03T20:44:10.113Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={} +2025-05-03T20:44:10.120Z DEBUG 1 --- [http-nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest) +2025-05-03T20:44:10.138Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] +2025-05-03T20:44:10.166Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Sat May 03 20:44:10 UTC 2025, status=500, error=Internal Server Error, path=/api/cart/cus (truncated)...] +2025-05-03T20:44:10.183Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500 +2025-05-03T20:44:29.759Z INFO 1 --- [SpringApplicationShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete +2025-05-03T20:44:29.781Z INFO 1 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete diff --git a/promtail.yml b/promtail.yml index c3a4e5b..1ede004 100644 --- a/promtail.yml +++ b/promtail.yml @@ -15,4 +15,4 @@ scrape_configs: - localhost labels: job: cart-service - __path__: /logs/*.log \ No newline at end of file + __path__: ./logs/*.log \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2e21dc6..f14db7a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,5 @@ order.service.url=http://order-service:8082 -spring.data.mongodb.uri=mongodb://localhost:27018/cartDB \ No newline at end of file +spring.data.mongodb.uri=mongodb://localhost:27018/cartDB +logging.file.name=./logs/app.log +logging.level.root=info +logging.level.com.podzilla.cart=debug \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..39be967 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,15 @@ + + + + logs/app.log + + + + + + + + + + + \ No newline at end of file From 4a54a1887028469e5a77e04084d7ea26d6d2235c Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Sun, 4 May 2025 13:40:22 +0300 Subject: [PATCH 07/20] fixing lint errors --- .../java/cart/controller/CartController.java | 139 ++++++++++++------ src/main/java/cart/model/CartItem.java | 2 +- src/main/java/cart/model/OrderRequest.java | 6 +- .../java/cart/repository/CartRepository.java | 7 +- src/main/java/cart/service/CartService.java | 51 ++++--- 5 files changed, 134 insertions(+), 71 deletions(-) diff --git a/src/main/java/cart/controller/CartController.java b/src/main/java/cart/controller/CartController.java index 02328a7..6c45acf 100644 --- a/src/main/java/cart/controller/CartController.java +++ b/src/main/java/cart/controller/CartController.java @@ -10,140 +10,187 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestBody; import cart.model.CartItem; import io.swagger.v3.oas.annotations.media.Content; @RestController @RequestMapping("/api/cart") @RequiredArgsConstructor -@Tag(name = "Cart Controller", description = "Handles cart operations like add, update, remove items and manage cart") +@Tag(name = "Cart Controller", description = "Handles cart" + + " operations like add, update," + + " remove items and manage cart") public class CartController { private final CartService cartService; - @Operation(summary = "Create a new cart for a customer or return existing one") + @Operation(summary = "Create a new cart for a " + + "customer or return existing one") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Cart created or retrieved successfully"), - @ApiResponse(responseCode = "400", description = "Invalid customer ID provided", content = @Content), - @ApiResponse(responseCode = "500", description = "Internal server error", content = @Content) + @ApiResponse(responseCode = "200", + description = "Cart created or retrieved successfully"), + @ApiResponse(responseCode = "400", + description = "Invalid customer ID provided", content = @Content), + @ApiResponse(responseCode = "500", + description = "Internal server error", content = @Content) }) @PostMapping("/create/{customerId}") - public ResponseEntity createCart(@PathVariable("customerId") String customerId) { + public ResponseEntity createCart( + @PathVariable("customerId") final String customerId) { Cart cart = cartService.createCart(customerId); return ResponseEntity.ok(cart); } @Operation(summary = "Get cart by customer ID") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Cart retrieved successfully"), - @ApiResponse(responseCode = "404", description = "Cart not found for this customer") + @ApiResponse(responseCode = "200", + description = "Cart retrieved successfully"), + @ApiResponse(responseCode = "404", + description = "Cart not found for this customer") }) @GetMapping("/customer/{customerId}") - public ResponseEntity getCartByCustomerId(@PathVariable("customerId") String customerId) { + public ResponseEntity getCartByCustomerId( + @PathVariable("customerId") final String customerId) { Cart cart = cartService.getCartByCustomerId(customerId); return ResponseEntity.ok(cart); } @Operation(summary = "Delete cart by customer ID") @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "Cart deleted successfully"), - @ApiResponse(responseCode = "404", description = "Cart not found") + @ApiResponse(responseCode = "204", + description = "Cart deleted successfully"), + @ApiResponse(responseCode = "404", + description = "Cart not found") }) @DeleteMapping("/customer/{customerId}") - public ResponseEntity deleteCart(@PathVariable("customerId") String customerId) { + public ResponseEntity deleteCart( + @PathVariable("customerId") final String customerId) { cartService.deleteCartByCustomerId(customerId); return ResponseEntity.noContent().build(); } - @Operation(summary = "Add an item to the cart or update its quantity if already exists") + @Operation(summary = "Add an item to the cart" + + " or update its quantity if already exists") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Item added or updated successfully"), - @ApiResponse(responseCode = "400", description = "Invalid item data provided"), - @ApiResponse(responseCode = "404", description = "Cart not found for this customer") + @ApiResponse(responseCode = "200", + description = "Item added or updated successfully"), + @ApiResponse(responseCode = "400", + description = "Invalid item data provided"), + @ApiResponse(responseCode = "404", + description = "Cart not found for this customer") }) @PostMapping("/{customerId}/items") public ResponseEntity addItemToCart( - @PathVariable("customerId") String customerId, - @RequestBody CartItem cartItem) { + @PathVariable("customerId") final String customerId, + @RequestBody final CartItem cartItem) { Cart updatedCart = cartService.addItemToCart(customerId, cartItem); return ResponseEntity.ok(updatedCart); } - @Operation(summary = "Update quantity of an existing item in the cart") + @Operation(summary = "Update quantity " + + "of an existing item in the cart") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Quantity updated successfully"), - @ApiResponse(responseCode = "400", description = "Invalid quantity value"), - @ApiResponse(responseCode = "404", description = "Cart or item not found") + @ApiResponse(responseCode = "200", + description = "Quantity updated successfully"), + @ApiResponse(responseCode = "400", + description = "Invalid quantity value"), + @ApiResponse(responseCode = "404", + description = "Cart or item not found") }) @PatchMapping("/{customerId}/items/{productId}") public ResponseEntity updateItemQuantity( - @PathVariable("customerId") String customerId, - @PathVariable("productId") String productId, + @PathVariable("customerId") final String customerId, + @PathVariable("productId") final String productId, @RequestParam int quantity) { - Cart updatedCart = cartService.updateItemQuantity(customerId, productId, quantity); + Cart updatedCart = cartService.updateItemQuantity( + customerId, productId, quantity); return ResponseEntity.ok(updatedCart); } @Operation(summary = "Remove an item from the cart") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Item removed successfully"), - @ApiResponse(responseCode = "404", description = "Cart or item not found") + @ApiResponse(responseCode = "200", + description = "Item removed successfully"), + @ApiResponse(responseCode = "404", + description = "Cart or item not found") }) @DeleteMapping("/{customerId}/items/{productId}") public ResponseEntity removeItemFromCart( - @PathVariable("customerId") String customerId, - @PathVariable("productId") String productId) { - Cart updatedCart = cartService.removeItemFromCart(customerId, productId); + @PathVariable("customerId") final String customerId, + @PathVariable("productId") final String productId) { + Cart updatedCart = cartService + .removeItemFromCart(customerId, productId); return ResponseEntity.ok(updatedCart); } @Operation(summary = "Clear all items from the cart") @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "Cart cleared successfully"), - @ApiResponse(responseCode = "404", description = "Cart not found") + @ApiResponse(responseCode = "204", + description = "Cart cleared successfully"), + @ApiResponse(responseCode = "404", + description = "Cart not found") }) @DeleteMapping("/{customerId}/clear") - public ResponseEntity clearCart(@PathVariable("customerId") String customerId) { + public ResponseEntity clearCart( + @PathVariable("customerId") final String customerId) { cartService.clearCart(customerId); return ResponseEntity.noContent().build(); } @Operation(summary = "Archive the cart (soft-delete)") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Cart successfully archived"), - @ApiResponse(responseCode = "404", description = "Cart not found") + @ApiResponse(responseCode = "200", + description = "Cart successfully archived"), + @ApiResponse(responseCode = "404", + description = "Cart not found") }) @PatchMapping("/{customerId}/archive") - public ResponseEntity archiveCart(@PathVariable("customerId") String customerId) { + public ResponseEntity archiveCart( + @PathVariable("customerId") final String customerId) { Cart archivedCart = cartService.archiveCart(customerId); return ResponseEntity.ok(archivedCart); } @Operation(summary = "Unarchive a previously archived cart") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Cart successfully unarchived"), - @ApiResponse(responseCode = "404", description = "Archived cart not found") + @ApiResponse(responseCode = "200", + description = "Cart successfully unarchived"), + @ApiResponse(responseCode = "404", + description = "Archived cart not found") }) @PatchMapping("/{customerId}/unarchive") - public ResponseEntity unarchiveCart(@PathVariable("customerId") String customerId) { + public ResponseEntity unarchiveCart( + @PathVariable("customerId") final String customerId) { Cart activeCart = cartService.unarchiveCart(customerId); return ResponseEntity.ok(activeCart); } @Operation(summary = "Checkout cart by sending it to the Order Service") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Cart checked out and sent to Order Service"), - @ApiResponse(responseCode = "404", description = "Cart not found"), - @ApiResponse(responseCode = "500", description = "Failed to communicate with Order Service") + @ApiResponse(responseCode = "200", + description = "Cart checked out and sent to Order Service"), + @ApiResponse(responseCode = "404", + description = "Cart not found"), + @ApiResponse(responseCode = "500", + description = "Failed to communicate with Order Service") }) @PostMapping("/{customerId}/checkout") - public ResponseEntity checkoutCart(@PathVariable("customerId") String customerId) { + public ResponseEntity checkoutCart( + @PathVariable("customerId") final String customerId) { try { Cart updatedCart = cartService.checkoutCart(customerId); return ResponseEntity.ok(updatedCart); } catch (Exception ex) { - throw new IllegalCallerException("Error communicating with Order Service"); + throw new IllegalCallerException("Error " + + "communicating with Order Service"); } } -} \ No newline at end of file +} diff --git a/src/main/java/cart/model/CartItem.java b/src/main/java/cart/model/CartItem.java index 92e77dc..38dd7ce 100644 --- a/src/main/java/cart/model/CartItem.java +++ b/src/main/java/cart/model/CartItem.java @@ -33,7 +33,7 @@ public int getQuantity() { return quantity; } - public void setQuantity(int quantity) { + public void setQuantity(final int quantity) { this.quantity = quantity; } } diff --git a/src/main/java/cart/model/OrderRequest.java b/src/main/java/cart/model/OrderRequest.java index 8ebb3a8..03e899e 100644 --- a/src/main/java/cart/model/OrderRequest.java +++ b/src/main/java/cart/model/OrderRequest.java @@ -12,7 +12,7 @@ public String getCustomerId() { return customerId; } - public void setCustomerId(String customerId) { + public void setCustomerId(final String customerId) { this.customerId = customerId; } @@ -20,7 +20,7 @@ public List getItems() { return items; } - public void setItems(List items) { + public void setItems(final List items) { this.items = items; } -} \ No newline at end of file +} diff --git a/src/main/java/cart/repository/CartRepository.java b/src/main/java/cart/repository/CartRepository.java index 8e33a69..2b10e48 100644 --- a/src/main/java/cart/repository/CartRepository.java +++ b/src/main/java/cart/repository/CartRepository.java @@ -8,7 +8,8 @@ @Repository public interface CartRepository extends MongoRepository { - Optional findByCustomerId(final String customerId); - Optional findByCustomerIdAndArchived(String customerId, boolean archived); + Optional findByCustomerId(String customerId); + Optional findByCustomerIdAndArchived + (String customerId, boolean archived); -} \ No newline at end of file +} diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java index 3fbfa9f..2a4de6c 100644 --- a/src/main/java/cart/service/CartService.java +++ b/src/main/java/cart/service/CartService.java @@ -31,20 +31,27 @@ public class CartService { public Cart createCart(final String customerId) { return cartRepository.findByCustomerId(customerId) .orElseGet(() -> { - Cart newCart = new Cart(UUID.randomUUID().toString(), customerId, new ArrayList<>(), false); + Cart newCart = new Cart(UUID.randomUUID() + .toString(), customerId, new + ArrayList<>(), false); return cartRepository.save(newCart); }); } - public Cart addItemToCart(final String customerId, final CartItem newItem) { + public Cart addItemToCart(final String customerId + , final CartItem newItem) { Cart cart = getCartByCustomerId(customerId); - Optional existingItem = cart.getItems().stream() - .filter(i -> i.getProductId().equals(newItem.getProductId())) + Optional existingItem = + cart.getItems().stream() + .filter(i -> i.getProductId() + .equals(newItem.getProductId())) .findFirst(); if (existingItem.isPresent()) { - existingItem.get().setQuantity(existingItem.get().getQuantity() + newItem.getQuantity()); + existingItem.get() + .setQuantity(existingItem.get(). + getQuantity() + newItem.getQuantity()); } else { cart.getItems().add(newItem); } @@ -53,7 +60,8 @@ public Cart addItemToCart(final String customerId, final CartItem newItem) { } - public Cart updateItemQuantity(final String customerId, final String productId, final int quantity) { + public Cart updateItemQuantity(final String customerId, + final String productId, final int quantity) { Cart cart = getCartByCustomerId(customerId); Optional existingItemOpt = cart.getItems().stream() @@ -61,7 +69,8 @@ public Cart updateItemQuantity(final String customerId, final String productId, .findFirst(); if (existingItemOpt.isEmpty()) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found in cart"); + throw new ResponseStatusException + (HttpStatus.NOT_FOUND, "Product not found in cart"); } CartItem item = existingItemOpt.get(); @@ -76,19 +85,22 @@ public Cart updateItemQuantity(final String customerId, final String productId, } - public Cart removeItemFromCart(final String customerId,final String productId) { + public Cart removeItemFromCart(final String customerId, + final String productId) { Cart cart = getCartByCustomerId(customerId); cart.getItems().removeIf(i -> i.getProductId().equals(productId)); return cartRepository.save(cart); } public void deleteCartByCustomerId(final String customerId) { - cartRepository.findByCustomerId(customerId).ifPresent(cartRepository::delete); + cartRepository.findByCustomerId(customerId) + .ifPresent(cartRepository::delete); } public Cart getCartByCustomerId(final String customerId) { return cartRepository.findByCustomerId(customerId) - .orElseThrow(() -> new NoSuchElementException("Cart not found")); + .orElseThrow(() -> new + NoSuchElementException("Cart not found")); } public void clearCart(final String customerId) { @@ -97,40 +109,43 @@ public void clearCart(final String customerId) { cartRepository.save(cart); } - public Cart archiveCart(String customerId) { + public Cart archiveCart(final String customerId) { Cart cart = getActiveCart(customerId); cart.setArchived(true); return cartRepository.save(cart); } - public Cart unarchiveCart(String customerId) { + public Cart unarchiveCart(final String customerId) { Cart cart = getArchivedCart(customerId); cart.setArchived(false); return cartRepository.save(cart); } - public Cart checkoutCart(String customerId) { + public Cart checkoutCart(final String customerId) { Cart cart = getActiveCart(customerId); OrderRequest orderRequest = new OrderRequest(); orderRequest.setCustomerId(customerId); orderRequest.setItems(cart.getItems()); - restTemplate.postForObject(orderServiceUrl + "/orders", orderRequest, Void.class); + restTemplate.postForObject + (orderServiceUrl + "/orders", orderRequest, Void.class); cart.getItems().clear(); return cartRepository.save(cart); } - private Cart getActiveCart(String customerId) { + private Cart getActiveCart(final String customerId) { return cartRepository.findByCustomerIdAndArchived(customerId, false) - .orElseThrow(() -> new NoSuchElementException("Cart not found for customer ID: " + customerId)); + .orElseThrow(() -> new + NoSuchElementException("Cart not found for customer ID: " + customerId)); } - private Cart getArchivedCart(String customerId) { + private Cart getArchivedCart(final String customerId) { return cartRepository.findByCustomerIdAndArchived(customerId, true) - .orElseThrow(() -> new NoSuchElementException("No archived cart found for customer ID: " + customerId)); + .orElseThrow(() -> + new NoSuchElementException("No archived cart found for customer ID: " + customerId)); } } From 95a864048c4b96b9f836f465f2d7a107dba09c2e Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Sun, 4 May 2025 14:03:38 +0300 Subject: [PATCH 08/20] fixing lint errors --- src/main/java/cart/CartApplication.java | 2 +- src/main/java/cart/config/AppConfig.java | 2 +- .../java/cart/controller/CartController.java | 24 ++++++++++--------- .../java/cart/repository/CartRepository.java | 4 ++-- src/main/java/cart/service/CartService.java | 18 +++++++------- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/main/java/cart/CartApplication.java b/src/main/java/cart/CartApplication.java index 85eeedf..3a1e3fc 100644 --- a/src/main/java/cart/CartApplication.java +++ b/src/main/java/cart/CartApplication.java @@ -5,7 +5,7 @@ @SpringBootApplication public class CartApplication { - public static void main(final String [] args){ + public static void main(final String[] args) { SpringApplication.run(CartApplication.class, args); } } diff --git a/src/main/java/cart/config/AppConfig.java b/src/main/java/cart/config/AppConfig.java index 6fa562e..70500cc 100644 --- a/src/main/java/cart/config/AppConfig.java +++ b/src/main/java/cart/config/AppConfig.java @@ -12,4 +12,4 @@ public class AppConfig { public RestTemplate restTemplate() { return new RestTemplate(); } -} \ No newline at end of file +} diff --git a/src/main/java/cart/controller/CartController.java b/src/main/java/cart/controller/CartController.java index 6c45acf..df1c19c 100644 --- a/src/main/java/cart/controller/CartController.java +++ b/src/main/java/cart/controller/CartController.java @@ -32,15 +32,17 @@ public class CartController { private final CartService cartService; - @Operation(summary = "Create a new cart for a " + - "customer or return existing one") + @Operation(summary = "Create a new cart for a " + + "customer or return existing one") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Cart created or retrieved successfully"), @ApiResponse(responseCode = "400", - description = "Invalid customer ID provided", content = @Content), + description = "Invalid customer ID provided", + content = @Content), @ApiResponse(responseCode = "500", - description = "Internal server error", content = @Content) + description = "Internal server error", + content = @Content) }) @PostMapping("/create/{customerId}") public ResponseEntity createCart( @@ -77,8 +79,8 @@ public ResponseEntity deleteCart( return ResponseEntity.noContent().build(); } - @Operation(summary = "Add an item to the cart" + - " or update its quantity if already exists") + @Operation(summary = "Add an item to the cart" + + " or update its quantity if already exists") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Item added or updated successfully"), @@ -95,8 +97,8 @@ public ResponseEntity addItemToCart( return ResponseEntity.ok(updatedCart); } - @Operation(summary = "Update quantity " + - "of an existing item in the cart") + @Operation(summary = "Update quantity " + + "of an existing item in the cart") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Quantity updated successfully"), @@ -109,7 +111,7 @@ public ResponseEntity addItemToCart( public ResponseEntity updateItemQuantity( @PathVariable("customerId") final String customerId, @PathVariable("productId") final String productId, - @RequestParam int quantity) { + @RequestParam final int quantity) { Cart updatedCart = cartService.updateItemQuantity( customerId, productId, quantity); return ResponseEntity.ok(updatedCart); @@ -189,8 +191,8 @@ public ResponseEntity checkoutCart( Cart updatedCart = cartService.checkoutCart(customerId); return ResponseEntity.ok(updatedCart); } catch (Exception ex) { - throw new IllegalCallerException("Error " + - "communicating with Order Service"); + throw new IllegalCallerException("Error " + + "communicating with Order Service"); } } } diff --git a/src/main/java/cart/repository/CartRepository.java b/src/main/java/cart/repository/CartRepository.java index 2b10e48..65d18fd 100644 --- a/src/main/java/cart/repository/CartRepository.java +++ b/src/main/java/cart/repository/CartRepository.java @@ -9,7 +9,7 @@ @Repository public interface CartRepository extends MongoRepository { Optional findByCustomerId(String customerId); - Optional findByCustomerIdAndArchived - (String customerId, boolean archived); + Optional findByCustomerIdAndArchived( + String customerId, boolean archived); } diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java index 2a4de6c..8eda01d 100644 --- a/src/main/java/cart/service/CartService.java +++ b/src/main/java/cart/service/CartService.java @@ -38,8 +38,8 @@ public Cart createCart(final String customerId) { }); } - public Cart addItemToCart(final String customerId - , final CartItem newItem) { + public Cart addItemToCart(final String customerId, + final CartItem newItem) { Cart cart = getCartByCustomerId(customerId); Optional existingItem = @@ -69,8 +69,8 @@ public Cart updateItemQuantity(final String customerId, .findFirst(); if (existingItemOpt.isEmpty()) { - throw new ResponseStatusException - (HttpStatus.NOT_FOUND, "Product not found in cart"); + throw new ResponseStatusException( + HttpStatus.NOT_FOUND, "Product not found in cart"); } CartItem item = existingItemOpt.get(); @@ -128,8 +128,8 @@ public Cart checkoutCart(final String customerId) { orderRequest.setCustomerId(customerId); orderRequest.setItems(cart.getItems()); - restTemplate.postForObject - (orderServiceUrl + "/orders", orderRequest, Void.class); + restTemplate.postForObject( + orderServiceUrl + "/orders", orderRequest, Void.class); cart.getItems().clear(); return cartRepository.save(cart); @@ -139,13 +139,15 @@ public Cart checkoutCart(final String customerId) { private Cart getActiveCart(final String customerId) { return cartRepository.findByCustomerIdAndArchived(customerId, false) .orElseThrow(() -> new - NoSuchElementException("Cart not found for customer ID: " + customerId)); + NoSuchElementException("Cart" + + " not found for customer ID: " + customerId)); } private Cart getArchivedCart(final String customerId) { return cartRepository.findByCustomerIdAndArchived(customerId, true) .orElseThrow(() -> - new NoSuchElementException("No archived cart found for customer ID: " + customerId)); + new NoSuchElementException("No archived" + + " cart found for customer ID: " + customerId)); } } From 838c2098eedde1d2f8ec37ca7131d50f4082cf8d Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Sun, 4 May 2025 14:44:10 +0300 Subject: [PATCH 09/20] fixing lint errors --- src/main/java/cart/controller/CartController.java | 6 +++--- src/main/java/cart/service/CartService.java | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/cart/controller/CartController.java b/src/main/java/cart/controller/CartController.java index df1c19c..138f68e 100644 --- a/src/main/java/cart/controller/CartController.java +++ b/src/main/java/cart/controller/CartController.java @@ -25,9 +25,9 @@ @RestController @RequestMapping("/api/cart") @RequiredArgsConstructor -@Tag(name = "Cart Controller", description = "Handles cart" + - " operations like add, update," + - " remove items and manage cart") +@Tag(name = "Cart Controller", description = "Handles cart" + + " operations like add, update," + + " remove items and manage cart") public class CartController { private final CartService cartService; diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java index 8eda01d..1f7e48a 100644 --- a/src/main/java/cart/service/CartService.java +++ b/src/main/java/cart/service/CartService.java @@ -147,7 +147,8 @@ private Cart getArchivedCart(final String customerId) { return cartRepository.findByCustomerIdAndArchived(customerId, true) .orElseThrow(() -> new NoSuchElementException("No archived" - + " cart found for customer ID: " + customerId)); + + " cart found for customer ID: " + + customerId)); } } From e7ff6319e96dce38427d700e7acc14203c6cd013 Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Wed, 7 May 2025 13:00:09 +0300 Subject: [PATCH 10/20] refactore : adding logger to controller and resloving comments --- .../java/cart/controller/CartController.java | 45 +++++++++++++++++- src/main/java/cart/model/Cart.java | 46 ++----------------- src/main/java/cart/model/CartItem.java | 25 +--------- src/main/java/cart/model/OrderRequest.java | 27 +++++------ src/main/java/cart/service/CartService.java | 8 +++- 5 files changed, 68 insertions(+), 83 deletions(-) diff --git a/src/main/java/cart/controller/CartController.java b/src/main/java/cart/controller/CartController.java index 138f68e..6260409 100644 --- a/src/main/java/cart/controller/CartController.java +++ b/src/main/java/cart/controller/CartController.java @@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @@ -23,11 +24,12 @@ import io.swagger.v3.oas.annotations.media.Content; @RestController -@RequestMapping("/api/cart") +@RequestMapping("/api/carts") @RequiredArgsConstructor @Tag(name = "Cart Controller", description = "Handles cart" + " operations like add, update," + " remove items and manage cart") +@Slf4j public class CartController { private final CartService cartService; @@ -47,7 +49,10 @@ public class CartController { @PostMapping("/create/{customerId}") public ResponseEntity createCart( @PathVariable("customerId") final String customerId) { + log.debug("Entering createCart endpoint" + + " with customerId:", customerId); Cart cart = cartService.createCart(customerId); + log.debug("Cart created or retrieved:", cart); return ResponseEntity.ok(cart); } @@ -61,7 +66,11 @@ public ResponseEntity createCart( @GetMapping("/customer/{customerId}") public ResponseEntity getCartByCustomerId( @PathVariable("customerId") final String customerId) { + log.debug("Entering getCartByCustomerId" + + " endpoint with customerId:", + customerId); Cart cart = cartService.getCartByCustomerId(customerId); + log.debug("Cart retrieved:", cart); return ResponseEntity.ok(cart); } @@ -75,7 +84,12 @@ public ResponseEntity getCartByCustomerId( @DeleteMapping("/customer/{customerId}") public ResponseEntity deleteCart( @PathVariable("customerId") final String customerId) { + log.debug("Entering deleteCart end" + + "point with customerId:", customerId); cartService.deleteCartByCustomerId(customerId); + log.debug("Cart deleted for customerId:", + customerId); + return ResponseEntity.noContent().build(); } @@ -93,7 +107,11 @@ public ResponseEntity deleteCart( public ResponseEntity addItemToCart( @PathVariable("customerId") final String customerId, @RequestBody final CartItem cartItem) { + log.debug("Entering addItemToCart" + + " endpoint with customerId: {}," + + " cartItem: {}", customerId, cartItem); Cart updatedCart = cartService.addItemToCart(customerId, cartItem); + log.debug("Cart updated with new item: {}", updatedCart); return ResponseEntity.ok(updatedCart); } @@ -112,8 +130,14 @@ public ResponseEntity updateItemQuantity( @PathVariable("customerId") final String customerId, @PathVariable("productId") final String productId, @RequestParam final int quantity) { + log.debug("Entering updateItemQuantity" + + " endpoint with customerId:," + + " productId: {}, quantity: {}", + customerId, productId, quantity); Cart updatedCart = cartService.updateItemQuantity( customerId, productId, quantity); + log.debug("Cart updated with new quantity:", + updatedCart); return ResponseEntity.ok(updatedCart); } @@ -128,8 +152,13 @@ public ResponseEntity updateItemQuantity( public ResponseEntity removeItemFromCart( @PathVariable("customerId") final String customerId, @PathVariable("productId") final String productId) { + log.debug("Entering removeItemFromCart" + + " endpoint with customerId:," + + " productId:", customerId, productId); Cart updatedCart = cartService .removeItemFromCart(customerId, productId); + log.debug("Cart updated after item removal:", + updatedCart); return ResponseEntity.ok(updatedCart); } @@ -143,7 +172,10 @@ public ResponseEntity removeItemFromCart( @DeleteMapping("/{customerId}/clear") public ResponseEntity clearCart( @PathVariable("customerId") final String customerId) { + log.debug("Entering clearCart" + + " endpoint with customerId:", customerId); cartService.clearCart(customerId); + log.debug("Cart cleared for customerId:", customerId); return ResponseEntity.noContent().build(); } @@ -157,7 +189,10 @@ public ResponseEntity clearCart( @PatchMapping("/{customerId}/archive") public ResponseEntity archiveCart( @PathVariable("customerId") final String customerId) { + log.debug("Entering archiveCart" + + " endpoint with customerId:", customerId); Cart archivedCart = cartService.archiveCart(customerId); + log.debug("Cart archived:", archivedCart); return ResponseEntity.ok(archivedCart); } @@ -171,7 +206,10 @@ public ResponseEntity archiveCart( @PatchMapping("/{customerId}/unarchive") public ResponseEntity unarchiveCart( @PathVariable("customerId") final String customerId) { + log.debug("Entering unarchiveCart" + + " endpoint with customerId:", customerId); Cart activeCart = cartService.unarchiveCart(customerId); + log.debug("Cart unarchived:", activeCart); return ResponseEntity.ok(activeCart); } @@ -187,10 +225,15 @@ public ResponseEntity unarchiveCart( @PostMapping("/{customerId}/checkout") public ResponseEntity checkoutCart( @PathVariable("customerId") final String customerId) { + log.debug("Entering checkoutCart" + + " endpoint with customerId:", customerId); try { Cart updatedCart = cartService.checkoutCart(customerId); + log.debug("Cart checked out: {}", updatedCart); return ResponseEntity.ok(updatedCart); } catch (Exception ex) { + log.error("Error during checkout" + + " for customerId: {}", customerId, ex); throw new IllegalCallerException("Error " + "communicating with Order Service"); } diff --git a/src/main/java/cart/model/Cart.java b/src/main/java/cart/model/Cart.java index 30f4680..6d93d92 100644 --- a/src/main/java/cart/model/Cart.java +++ b/src/main/java/cart/model/Cart.java @@ -1,10 +1,12 @@ package cart.model; +import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; import java.util.ArrayList; import java.util.List; @@ -15,56 +17,16 @@ @AllArgsConstructor public class Cart { - @Id + @Field("_id") private String id; + @NotBlank private String customerId; private List items = new ArrayList<>(); private boolean archived = false; - @Override - public String toString() { - return "Cart{" - + "id='" + id + '\'' - + ", customerId='" + customerId + '\'' - + ", items=" + items - + ", archived" + archived - + '}'; - } - - public String getId() { - return id; - } - - public void setId(final String id) { - this.id = id; - } - - public String getCustomerId() { - return customerId; - } - - public void setCustomerId(final String customerId) { - this.customerId = customerId; - } - - public List getItems() { - return items; - } - - public void setItems(final List items) { - this.items = items; - } - - public boolean isArchived() { - return archived; - } - - public void setArchived(final boolean archived) { - this.archived = archived; - } } diff --git a/src/main/java/cart/model/CartItem.java b/src/main/java/cart/model/CartItem.java index 38dd7ce..1a147c3 100644 --- a/src/main/java/cart/model/CartItem.java +++ b/src/main/java/cart/model/CartItem.java @@ -1,5 +1,6 @@ package cart.model; +import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -9,31 +10,9 @@ @AllArgsConstructor public class CartItem { + @NotBlank private String productId; private int quantity; - @Override - public String toString() { - return "CartItem{" - + "productId='" + productId + '\'' - + ", quantity=" + quantity - + '}'; - } - - public String getProductId() { - return productId; - } - - public void setProductId(final String productId) { - this.productId = productId; - } - - public int getQuantity() { - return quantity; - } - - public void setQuantity(final int quantity) { - this.quantity = quantity; - } } diff --git a/src/main/java/cart/model/OrderRequest.java b/src/main/java/cart/model/OrderRequest.java index 03e899e..80b8bb2 100644 --- a/src/main/java/cart/model/OrderRequest.java +++ b/src/main/java/cart/model/OrderRequest.java @@ -1,26 +1,21 @@ package cart.model; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + import java.util.List; +@Data +@NoArgsConstructor +@AllArgsConstructor public class OrderRequest { - private String customerId; - private List items; - // Getters and setters - public String getCustomerId() { - return customerId; - } - - public void setCustomerId(final String customerId) { - this.customerId = customerId; - } + @NotBlank + private String customerId; - public List getItems() { - return items; - } + private List items; - public void setItems(final List items) { - this.items = items; - } } diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java index 1f7e48a..eadfcef 100644 --- a/src/main/java/cart/service/CartService.java +++ b/src/main/java/cart/service/CartService.java @@ -5,6 +5,7 @@ import cart.model.OrderRequest; import cart.repository.CartRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -18,6 +19,7 @@ @Service @RequiredArgsConstructor +@Slf4j public class CartService { private final CartRepository cartRepository; @@ -29,13 +31,17 @@ public class CartService { public Cart createCart(final String customerId) { - return cartRepository.findByCustomerId(customerId) + log.debug("Entering createCart" + + " with customerId:", customerId); + Cart cart = cartRepository.findByCustomerId(customerId) .orElseGet(() -> { Cart newCart = new Cart(UUID.randomUUID() .toString(), customerId, new ArrayList<>(), false); return cartRepository.save(newCart); }); + log.debug("Cart created/retrieved:", cart); + return cart; } public Cart addItemToCart(final String customerId, From bac8a8fb7ab2eef6ed03a473cffa7ea9874f943e Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Wed, 7 May 2025 18:32:40 +0200 Subject: [PATCH 11/20] feat : adding logger to the cart service --- src/main/java/cart/service/CartService.java | 116 ++++++++++++++++---- 1 file changed, 92 insertions(+), 24 deletions(-) diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java index eadfcef..ab0fb07 100644 --- a/src/main/java/cart/service/CartService.java +++ b/src/main/java/cart/service/CartService.java @@ -38,14 +38,19 @@ public Cart createCart(final String customerId) { Cart newCart = new Cart(UUID.randomUUID() .toString(), customerId, new ArrayList<>(), false); + log.debug("Cart created:", newCart); return cartRepository.save(newCart); }); - log.debug("Cart created/retrieved:", cart); + log.debug("Cart retrieved:", cart); return cart; } public Cart addItemToCart(final String customerId, final CartItem newItem) { + log.debug("Entering addItemToCart " + + "with customerId:, newItem:", + customerId, newItem); + Cart cart = getCartByCustomerId(customerId); Optional existingItem = @@ -55,19 +60,29 @@ public Cart addItemToCart(final String customerId, .findFirst(); if (existingItem.isPresent()) { + log.debug("Item already exists, updating" + + " quantity for productId:", newItem.getProductId()); + existingItem.get() .setQuantity(existingItem.get(). getQuantity() + newItem.getQuantity()); } else { + log.debug("Adding new item to cart for productId: ", + newItem.getProductId()); cart.getItems().add(newItem); } - return cartRepository.save(cart); + Cart updatedCart = cartRepository.save(cart); + log.debug("Cart updated:", updatedCart); + return updatedCart; } public Cart updateItemQuantity(final String customerId, final String productId, final int quantity) { + log.debug("Entering updateItemQuantity with" + + " customerId:, productId:, quantity: ", + customerId, productId, quantity); Cart cart = getCartByCustomerId(customerId); Optional existingItemOpt = cart.getItems().stream() @@ -75,6 +90,8 @@ public Cart updateItemQuantity(final String customerId, .findFirst(); if (existingItemOpt.isEmpty()) { + log.error("Product not found in" + + " cart for productId:", productId); throw new ResponseStatusException( HttpStatus.NOT_FOUND, "Product not found in cart"); } @@ -82,79 +99,130 @@ public Cart updateItemQuantity(final String customerId, CartItem item = existingItemOpt.get(); if (quantity <= 0) { + log.debug("Removing item from cart" + + " as quantity <= 0 for productId:", productId); cart.getItems().remove(item); } else { + log.debug("Updating quantity to:" + + " for productId:", quantity, productId); item.setQuantity(quantity); } - return cartRepository.save(cart); + Cart updatedCart = cartRepository.save(cart); + log.debug("Cart updated: {}", updatedCart); + return updatedCart; } public Cart removeItemFromCart(final String customerId, final String productId) { + log.debug("Entering removeItemFromCart" + + " with customerId:, productId:", customerId, productId); Cart cart = getCartByCustomerId(customerId); cart.getItems().removeIf(i -> i.getProductId().equals(productId)); - return cartRepository.save(cart); + Cart updatedCart = cartRepository.save(cart); + log.debug("Item removed, updated cart: {}", updatedCart); + return updatedCart; } public void deleteCartByCustomerId(final String customerId) { + log.debug("Entering deleteCartByCustomerId" + + " with customerId:", customerId); cartRepository.findByCustomerId(customerId) - .ifPresent(cartRepository::delete); + .ifPresent(cart -> { + log.debug("Deleting cart for customerId:", customerId); + cartRepository.delete(cart); + }); + log.debug("Cart deletion completed for" + + " customerId:", customerId); } public Cart getCartByCustomerId(final String customerId) { - return cartRepository.findByCustomerId(customerId) - .orElseThrow(() -> new - NoSuchElementException("Cart not found")); + log.debug("Entering getCartByCustomerId" + + " with customerId:", customerId); + Cart cart = cartRepository.findByCustomerId(customerId) + .orElseThrow(() -> { + log.error("Cart not found for customerId:", customerId); + return new NoSuchElementException("Cart not found"); + }); + log.debug("Cart retrieved:", cart); + return cart; + } public void clearCart(final String customerId) { + log.debug("Entering clearCart with customerId:", customerId); Cart cart = getCartByCustomerId(customerId); cart.getItems().clear(); cartRepository.save(cart); + log.debug("Cart cleared for customerId:", customerId); } public Cart archiveCart(final String customerId) { + log.debug("Entering archiveCart with customerId:", customerId); Cart cart = getActiveCart(customerId); cart.setArchived(true); - return cartRepository.save(cart); + Cart archivedCart = cartRepository.save(cart); + log.debug("Cart archived: {}", archivedCart); + return archivedCart; } public Cart unarchiveCart(final String customerId) { + log.debug("Entering unarchiveCart with customerId:", customerId); Cart cart = getArchivedCart(customerId); cart.setArchived(false); - return cartRepository.save(cart); + Cart activeCart = cartRepository.save(cart); + log.debug("Cart unarchived:", activeCart); + return activeCart; } public Cart checkoutCart(final String customerId) { + log.debug("Entering checkoutCart with customerId:", customerId); Cart cart = getActiveCart(customerId); OrderRequest orderRequest = new OrderRequest(); orderRequest.setCustomerId(customerId); orderRequest.setItems(cart.getItems()); - restTemplate.postForObject( - orderServiceUrl + "/orders", orderRequest, Void.class); - - cart.getItems().clear(); - return cartRepository.save(cart); + try { + log.debug("Sending order request to" + + " Order Service for customerId:", customerId); + restTemplate.postForObject(orderServiceUrl + "/orders", orderRequest, Void.class); + cart.getItems().clear(); + Cart updatedCart = cartRepository.save(cart); + log.debug("Cart checked out and cleared:", updatedCart); + return updatedCart; + } catch (Exception e) { + log.error("Failed to checkout cart for customerId:", customerId, e); + throw new RuntimeException("Error" + + " communicating with Order Service", e); + } } private Cart getActiveCart(final String customerId) { - return cartRepository.findByCustomerIdAndArchived(customerId, false) - .orElseThrow(() -> new - NoSuchElementException("Cart" - + " not found for customer ID: " + customerId)); + log.debug("Entering getActiveCart with customerId:", customerId); + Cart cart = cartRepository.findByCustomerIdAndArchived(customerId, false) + .orElseThrow(() -> { + log.error("Active cart not found" + + " for customerId:", customerId); + return new NoSuchElementException("Cart not" + + " found for customer ID: " + customerId); + }); + log.debug("Active cart retrieved:", cart); } private Cart getArchivedCart(final String customerId) { - return cartRepository.findByCustomerIdAndArchived(customerId, true) - .orElseThrow(() -> - new NoSuchElementException("No archived" - + " cart found for customer ID: " - + customerId)); + log.debug("Entering getArchivedCart with customerId:", customerId); + Cart cart = cartRepository.findByCustomerIdAndArchived(customerId, true) + .orElseThrow(() -> { + log.error("Archived cart not found" + + " for customerId:", customerId); + return new NoSuchElementException("No archived " + + "cart found for customer ID: " + customerId); + }); + log.debug("Archived cart retrieved:", cart); + return cart; } } From 2f3e9a3f074087be5499bef3b5b4bb6720da3d19 Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Wed, 7 May 2025 18:33:02 +0200 Subject: [PATCH 12/20] fix : fixing sytnax error --- src/main/java/cart/service/CartService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java index ab0fb07..ecd6640 100644 --- a/src/main/java/cart/service/CartService.java +++ b/src/main/java/cart/service/CartService.java @@ -210,6 +210,7 @@ private Cart getActiveCart(final String customerId) { + " found for customer ID: " + customerId); }); log.debug("Active cart retrieved:", cart); + return cart; } private Cart getArchivedCart(final String customerId) { From 2bd01abe932a13932ec751474e7a4b3c77b107ef Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Wed, 7 May 2025 18:45:48 +0200 Subject: [PATCH 13/20] adding logs to the gitignire and fixing lint errors --- .gitignore | 2 +- src/main/java/cart/service/CartService.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index a5d2318..773a43e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Ignore Maven build output /target/ - +/logs/ HELP.md target/ diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java index ecd6640..5f52353 100644 --- a/src/main/java/cart/service/CartService.java +++ b/src/main/java/cart/service/CartService.java @@ -187,7 +187,8 @@ public Cart checkoutCart(final String customerId) { try { log.debug("Sending order request to" + " Order Service for customerId:", customerId); - restTemplate.postForObject(orderServiceUrl + "/orders", orderRequest, Void.class); + restTemplate.postForObject(orderServiceUrl + + "/orders", orderRequest, Void.class); cart.getItems().clear(); Cart updatedCart = cartRepository.save(cart); log.debug("Cart checked out and cleared:", updatedCart); @@ -202,7 +203,8 @@ public Cart checkoutCart(final String customerId) { private Cart getActiveCart(final String customerId) { log.debug("Entering getActiveCart with customerId:", customerId); - Cart cart = cartRepository.findByCustomerIdAndArchived(customerId, false) + Cart cart = cartRepository.findByCustomerIdAndArchived(customerId, + false) .orElseThrow(() -> { log.error("Active cart not found" + " for customerId:", customerId); From 79640f1918915358385c3e6368e75bdf7e88f008 Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Thu, 8 May 2025 15:30:58 +0300 Subject: [PATCH 14/20] tests : adding unit tests for the cart service --- pom.xml | 29 ++ src/test/java/service/CartServiceTest.java | 325 +++++++++++++++++++++ src/test/resources/application.properties | 4 + 3 files changed, 358 insertions(+) create mode 100644 src/test/java/service/CartServiceTest.java create mode 100644 src/test/resources/application.properties diff --git a/pom.xml b/pom.xml index 67ec612..32d7846 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,35 @@ test + + + org.junit.jupiter + junit-jupiter + 5.10.0 + test + + + + org.mockito + mockito-core + 5.5.0 + test + + + + org.mockito + mockito-junit-jupiter + 5.5.0 + test + + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + 4.9.3 + test + + org.springframework.security spring-security-test diff --git a/src/test/java/service/CartServiceTest.java b/src/test/java/service/CartServiceTest.java new file mode 100644 index 0000000..983f455 --- /dev/null +++ b/src/test/java/service/CartServiceTest.java @@ -0,0 +1,325 @@ +package service; + +import cart.model.Cart; +import cart.model.CartItem; +import cart.model.OrderRequest; +import cart.repository.CartRepository; +import cart.service.CartService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.server.ResponseStatusException; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CartServiceTest { + + @Mock + private CartRepository cartRepository; + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private CartService cartService; + + private Cart cart; + private CartItem cartItem; + private final String customerId = "cust123"; + private final String productId = "prod456"; + private final String cartId = UUID.randomUUID().toString(); + + @BeforeEach + void setUp() throws NoSuchFieldException, IllegalAccessException { + cart = new Cart(cartId, customerId, new ArrayList<>(), false); + cartItem = new CartItem(); + cartItem.setProductId(productId); + cartItem.setQuantity(1); + + // Set orderServiceUrl using reflection + Field orderServiceUrlField = CartService.class.getDeclaredField("orderServiceUrl"); + orderServiceUrlField.setAccessible(true); + orderServiceUrlField.set(cartService, "http://localhost:8080"); + } + + @Test + void createCart_existingCart_returnsCart() { + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); + + Cart result = cartService.createCart(customerId); + + assertEquals(cart, result); + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository, never()).save(any()); + } + + @Test + void createCart_noExistingCart_createsAndSavesCart() { + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.empty()); + when(cartRepository.save(any(Cart.class))).thenAnswer(invocation -> invocation.getArgument(0)); + + Cart result = cartService.createCart(customerId); + + assertEquals(customerId, result.getCustomerId()); + assertFalse(result.isArchived()); + assertTrue(result.getItems().isEmpty()); + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository).save(any(Cart.class)); + } + + @Test + void addItemToCart_newItem_addsItem() { + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); + when(cartRepository.save(any(Cart.class))).thenReturn(cart); + + Cart result = cartService.addItemToCart(customerId, cartItem); + + assertEquals(1, result.getItems().size()); + assertEquals(cartItem, result.getItems().get(0)); + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository).save(cart); + } + + @Test + void addItemToCart_existingItem_updatesQuantity() { + cart.getItems().add(cartItem); + CartItem newItem = new CartItem(); + newItem.setProductId(productId); + newItem.setQuantity(2); + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); + when(cartRepository.save(any(Cart.class))).thenReturn(cart); + + Cart result = cartService.addItemToCart(customerId, newItem); + + assertEquals(1, result.getItems().size()); + assertEquals(3, result.getItems().get(0).getQuantity()); + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository).save(cart); + } + + @Test + void addItemToCart_cartNotFound_throwsNoSuchElementException() { + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.empty()); + + assertThrows(NoSuchElementException.class, () -> cartService.addItemToCart(customerId, cartItem)); + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository, never()).save(any()); + } + + @Test + void updateItemQuantity_existingItem_updatesQuantity() { + cart.getItems().add(cartItem); + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); + when(cartRepository.save(any(Cart.class))).thenReturn(cart); + + Cart result = cartService.updateItemQuantity(customerId, productId, 5); + + assertEquals(5, result.getItems().get(0).getQuantity()); + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository).save(cart); + } + + @Test + void updateItemQuantity_quantityZero_removesItem() { + cart.getItems().add(cartItem); + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); + when(cartRepository.save(any(Cart.class))).thenReturn(cart); + + Cart result = cartService.updateItemQuantity(customerId, productId, 0); + + assertTrue(result.getItems().isEmpty()); + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository).save(cart); + } + + @Test + void updateItemQuantity_itemNotFound_throwsResponseStatusException() { + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); + + ResponseStatusException exception = assertThrows(ResponseStatusException.class, + () -> cartService.updateItemQuantity(customerId, productId, 5)); + assertEquals(HttpStatus.NOT_FOUND, exception.getStatusCode()); + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository, never()).save(any()); + } + + @Test + void removeItemFromCart_itemExists_removesItem() { + cart.getItems().add(cartItem); + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); + when(cartRepository.save(any(Cart.class))).thenReturn(cart); + + Cart result = cartService.removeItemFromCart(customerId, productId); + + assertTrue(result.getItems().isEmpty()); + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository).save(cart); + } + + @Test + void removeItemFromCart_cartNotFound_throwsNoSuchElementException() { + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.empty()); + + assertThrows(NoSuchElementException.class, () -> cartService.removeItemFromCart(customerId, productId)); + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository, never()).save(any()); + } + + @Test + void deleteCartByCustomerId_cartExists_deletesCart() { + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); + + cartService.deleteCartByCustomerId(customerId); + + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository).delete(cart); + } + + @Test + void deleteCartByCustomerId_cartNotFound_doesNothing() { + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.empty()); + + cartService.deleteCartByCustomerId(customerId); + + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository, never()).delete(any()); + } + + @Test + void getCartByCustomerId_cartExists_returnsCart() { + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); + + Cart result = cartService.getCartByCustomerId(customerId); + + assertEquals(cart, result); + verify(cartRepository).findByCustomerId(customerId); + } + + @Test + void getCartByCustomerId_cartNotFound_throwsNoSuchElementException() { + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.empty()); + + assertThrows(NoSuchElementException.class, () -> cartService.getCartByCustomerId(customerId)); + verify(cartRepository).findByCustomerId(customerId); + } + + @Test + void clearCart_cartExists_clearsItems() { + cart.getItems().add(cartItem); + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); + when(cartRepository.save(any(Cart.class))).thenReturn(cart); + + cartService.clearCart(customerId); + + assertTrue(cart.getItems().isEmpty()); + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository).save(cart); + } + + @Test + void clearCart_cartNotFound_throwsNoSuchElementException() { + when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.empty()); + + assertThrows(NoSuchElementException.class, () -> cartService.clearCart(customerId)); + verify(cartRepository).findByCustomerId(customerId); + verify(cartRepository, never()).save(any()); + } + + @Test + void archiveCart_activeCart_archivesCart() { + when(cartRepository.findByCustomerIdAndArchived(customerId, false)).thenReturn(Optional.of(cart)); + when(cartRepository.save(any(Cart.class))).thenReturn(cart); + + Cart result = cartService.archiveCart(customerId); + + assertTrue(result.isArchived()); + verify(cartRepository).findByCustomerIdAndArchived(customerId, false); + verify(cartRepository).save(cart); + } + + @Test + void archiveCart_noActiveCart_throwsNoSuchElementException() { + when(cartRepository.findByCustomerIdAndArchived(customerId, false)).thenReturn(Optional.empty()); + + assertThrows(NoSuchElementException.class, () -> cartService.archiveCart(customerId)); + verify(cartRepository).findByCustomerIdAndArchived(customerId, false); + verify(cartRepository, never()).save(any()); + } + + @Test + void unarchiveCart_archivedCart_unarchivesCart() { + cart.setArchived(true); + when(cartRepository.findByCustomerIdAndArchived(customerId, true)).thenReturn(Optional.of(cart)); + when(cartRepository.save(any(Cart.class))).thenReturn(cart); + + Cart result = cartService.unarchiveCart(customerId); + + assertFalse(result.isArchived()); + verify(cartRepository).findByCustomerIdAndArchived(customerId, true); + verify(cartRepository).save(cart); + } + + @Test + void unarchiveCart_noArchivedCart_throwsNoSuchElementException() { + when(cartRepository.findByCustomerIdAndArchived(customerId, true)).thenReturn(Optional.empty()); + + assertThrows(NoSuchElementException.class, () -> cartService.unarchiveCart(customerId)); + verify(cartRepository).findByCustomerIdAndArchived(customerId, true); + verify(cartRepository, never()).save(any()); + } + + @Test + void checkoutCart_validCart_sendsToOrderServiceAndClearsCart() { + cart.getItems().add(cartItem); + when(cartRepository.findByCustomerIdAndArchived(customerId, false)).thenReturn(Optional.of(cart)); + when(cartRepository.save(any(Cart.class))).thenReturn(cart); + when(restTemplate.postForObject(eq("http://localhost:8080/orders"), any(OrderRequest.class), eq(Void.class))) + .thenReturn(null); + + Cart result = cartService.checkoutCart(customerId); + + assertTrue(result.getItems().isEmpty()); + verify(cartRepository).findByCustomerIdAndArchived(customerId, false); + verify(restTemplate).postForObject(eq("http://localhost:8080/orders"), any(OrderRequest.class), eq(Void.class)); + verify(cartRepository).save(cart); + } + + @Test + void checkoutCart_noActiveCart_throwsNoSuchElementException() { + when(cartRepository.findByCustomerIdAndArchived(customerId, false)).thenReturn(Optional.empty()); + + assertThrows(NoSuchElementException.class, () -> cartService.checkoutCart(customerId)); + verify(cartRepository).findByCustomerIdAndArchived(customerId, false); + verify(restTemplate, never()).postForObject(any(), any(), any()); + verify(cartRepository, never()).save(any()); + } + + @Test + void checkoutCart_orderServiceFails_throwsRuntimeException() { + cart.getItems().add(cartItem); + when(cartRepository.findByCustomerIdAndArchived(customerId, false)).thenReturn(Optional.of(cart)); + when(restTemplate.postForObject(eq("http://localhost:8080/orders"), any(OrderRequest.class), eq(Void.class))) + .thenThrow(new RuntimeException("Order Service error")); + + RuntimeException exception = assertThrows(RuntimeException.class, () -> cartService.checkoutCart(customerId)); + assertEquals("Error communicating with Order Service", exception.getMessage()); + verify(cartRepository).findByCustomerIdAndArchived(customerId, false); + verify(restTemplate).postForObject(eq("http://localhost:8080/orders"), any(OrderRequest.class), eq(Void.class)); + verify(cartRepository, never()).save(any()); + } +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..a38ed3d --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,4 @@ +spring.data.mongodb.uri=mongodb://localhost:27017/testdb +spring.data.mongodb.database=testdb +spring.main.banner-mode= off +spring.main.log-startup-info=false \ No newline at end of file From f5822f6e9bd843076b9b78b9781380077a78bc3d Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Thu, 8 May 2025 15:45:31 +0300 Subject: [PATCH 15/20] adding global hanlder --- docker-compose.yml | 2 +- .../exception/GlobalExceptionHandler.java | 24 ++++++++++++++++++ .../exception/GlobalHandlerException.java | 25 +++++++++++++++++++ src/main/java/cart/service/CartService.java | 7 +++--- 4 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 src/main/java/cart/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/cart/exception/GlobalHandlerException.java diff --git a/docker-compose.yml b/docker-compose.yml index e35d882..fd7dc73 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,7 +18,7 @@ services: command: ["java", "-jar", "/app/cart-0.0.1-SNAPSHOT.jar"] cart-db: - image: mongo:latest + image: mongo:8.0.9 container_name: cart-db environment: MONGO_INITDB_DATABASE: cartDB diff --git a/src/main/java/cart/exception/GlobalExceptionHandler.java b/src/main/java/cart/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..cacdeb6 --- /dev/null +++ b/src/main/java/cart/exception/GlobalExceptionHandler.java @@ -0,0 +1,24 @@ +package cart.exception; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import java.util.HashMap; +import java.util.Map; + +@ControllerAdvice +public class GlobalExceptionHandler + extends ResponseEntityExceptionHandler { + @ExceptionHandler(GlobalHandlerException.class) + public ResponseEntity> + handleGlobalHandlerException(GlobalHandlerException + ex) { + Map errorResponse = new HashMap<>(); + errorResponse.put("status", ex.getStatus().value()); + errorResponse.put("error", ex.getStatus().getReasonPhrase()); + errorResponse.put("message", ex.getMessage()); + return new ResponseEntity<>(errorResponse, ex.getStatus()); + } +} diff --git a/src/main/java/cart/exception/GlobalHandlerException.java b/src/main/java/cart/exception/GlobalHandlerException.java new file mode 100644 index 0000000..b10ea67 --- /dev/null +++ b/src/main/java/cart/exception/GlobalHandlerException.java @@ -0,0 +1,25 @@ +package cart.exception; + +import org.springframework.http.HttpStatus; + +public class GlobalHandlerException extends + RuntimeException { + + private final HttpStatus status; + + public GlobalHandlerException(HttpStatus status, + String message) { + super(message); + this.status = status; + } + + public GlobalHandlerException(HttpStatus status, + String message, Throwable cause) { + super(message, cause); + this.status = status; + } + + public HttpStatus getStatus() { + return status; + } +} diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java index 5f52353..edfce98 100644 --- a/src/main/java/cart/service/CartService.java +++ b/src/main/java/cart/service/CartService.java @@ -1,5 +1,6 @@ package cart.service; +import cart.exception.GlobalHandlerException; import cart.model.Cart; import cart.model.CartItem; import cart.model.OrderRequest; @@ -10,7 +11,6 @@ import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; -import org.springframework.web.server.ResponseStatusException; import java.util.ArrayList; import java.util.NoSuchElementException; @@ -92,7 +92,7 @@ public Cart updateItemQuantity(final String customerId, if (existingItemOpt.isEmpty()) { log.error("Product not found in" + " cart for productId:", productId); - throw new ResponseStatusException( + throw new GlobalHandlerException( HttpStatus.NOT_FOUND, "Product not found in cart"); } @@ -143,7 +143,8 @@ public Cart getCartByCustomerId(final String customerId) { Cart cart = cartRepository.findByCustomerId(customerId) .orElseThrow(() -> { log.error("Cart not found for customerId:", customerId); - return new NoSuchElementException("Cart not found"); + throw new GlobalHandlerException( + HttpStatus.NOT_FOUND, "Cart not found"); }); log.debug("Cart retrieved:", cart); return cart; From e09f575b4b7517c38385b114886a44b6a7d2d895 Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Thu, 8 May 2025 15:50:17 +0300 Subject: [PATCH 16/20] fix : fixing lint errors --- src/main/java/cart/exception/GlobalExceptionHandler.java | 4 ++-- src/main/java/cart/exception/GlobalHandlerException.java | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/cart/exception/GlobalExceptionHandler.java b/src/main/java/cart/exception/GlobalExceptionHandler.java index cacdeb6..4b03133 100644 --- a/src/main/java/cart/exception/GlobalExceptionHandler.java +++ b/src/main/java/cart/exception/GlobalExceptionHandler.java @@ -13,8 +13,8 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(GlobalHandlerException.class) public ResponseEntity> - handleGlobalHandlerException(GlobalHandlerException - ex) { + handleGlobalHandlerException(final + GlobalHandlerException ex) { Map errorResponse = new HashMap<>(); errorResponse.put("status", ex.getStatus().value()); errorResponse.put("error", ex.getStatus().getReasonPhrase()); diff --git a/src/main/java/cart/exception/GlobalHandlerException.java b/src/main/java/cart/exception/GlobalHandlerException.java index b10ea67..d6b665b 100644 --- a/src/main/java/cart/exception/GlobalHandlerException.java +++ b/src/main/java/cart/exception/GlobalHandlerException.java @@ -7,14 +7,15 @@ public class GlobalHandlerException extends private final HttpStatus status; - public GlobalHandlerException(HttpStatus status, - String message) { + public GlobalHandlerException(final + HttpStatus status, final String message) { super(message); this.status = status; } - public GlobalHandlerException(HttpStatus status, - String message, Throwable cause) { + public GlobalHandlerException(final + HttpStatus status, final String message, + final Throwable cause) { super(message, cause); this.status = status; } From 1e78cd7430602e05f9d31eaf21598d33b9e249ca Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Thu, 8 May 2025 15:58:00 +0300 Subject: [PATCH 17/20] tests: fixing tests after adding the handler --- src/test/java/service/CartServiceTest.java | 39 +++++++++++++++------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/test/java/service/CartServiceTest.java b/src/test/java/service/CartServiceTest.java index 983f455..1e1318f 100644 --- a/src/test/java/service/CartServiceTest.java +++ b/src/test/java/service/CartServiceTest.java @@ -1,5 +1,6 @@ package service; +import cart.exception.GlobalHandlerException; import cart.model.Cart; import cart.model.CartItem; import cart.model.OrderRequest; @@ -46,6 +47,7 @@ class CartServiceTest { @BeforeEach void setUp() throws NoSuchFieldException, IllegalAccessException { + // Initialize test data cart = new Cart(cartId, customerId, new ArrayList<>(), false); cartItem = new CartItem(); cartItem.setProductId(productId); @@ -113,10 +115,13 @@ void addItemToCart_existingItem_updatesQuantity() { } @Test - void addItemToCart_cartNotFound_throwsNoSuchElementException() { + void addItemToCart_cartNotFound_throwsGlobalHandlerException() { when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.empty()); - assertThrows(NoSuchElementException.class, () -> cartService.addItemToCart(customerId, cartItem)); + GlobalHandlerException exception = assertThrows(GlobalHandlerException.class, + () -> cartService.addItemToCart(customerId, cartItem)); + assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); + assertEquals("Cart not found", exception.getMessage()); verify(cartRepository).findByCustomerId(customerId); verify(cartRepository, never()).save(any()); } @@ -148,12 +153,13 @@ void updateItemQuantity_quantityZero_removesItem() { } @Test - void updateItemQuantity_itemNotFound_throwsResponseStatusException() { + void updateItemQuantity_itemNotFound_throwsGlobalHandlerException() { when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, + GlobalHandlerException exception = assertThrows(GlobalHandlerException.class, () -> cartService.updateItemQuantity(customerId, productId, 5)); - assertEquals(HttpStatus.NOT_FOUND, exception.getStatusCode()); + assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); + assertEquals("Product not found in cart", exception.getMessage()); verify(cartRepository).findByCustomerId(customerId); verify(cartRepository, never()).save(any()); } @@ -172,10 +178,13 @@ void removeItemFromCart_itemExists_removesItem() { } @Test - void removeItemFromCart_cartNotFound_throwsNoSuchElementException() { + void removeItemFromCart_cartNotFound_throwsGlobalHandlerException() { when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.empty()); - assertThrows(NoSuchElementException.class, () -> cartService.removeItemFromCart(customerId, productId)); + GlobalHandlerException exception = assertThrows(GlobalHandlerException.class, + () -> cartService.removeItemFromCart(customerId, productId)); + assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); + assertEquals("Cart not found", exception.getMessage()); verify(cartRepository).findByCustomerId(customerId); verify(cartRepository, never()).save(any()); } @@ -211,10 +220,13 @@ void getCartByCustomerId_cartExists_returnsCart() { } @Test - void getCartByCustomerId_cartNotFound_throwsNoSuchElementException() { + void getCartByCustomerId_cartNotFound_throwsGlobalHandlerException() { when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.empty()); - assertThrows(NoSuchElementException.class, () -> cartService.getCartByCustomerId(customerId)); + GlobalHandlerException exception = assertThrows(GlobalHandlerException.class, + () -> cartService.getCartByCustomerId(customerId)); + assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); + assertEquals("Cart not found", exception.getMessage()); verify(cartRepository).findByCustomerId(customerId); } @@ -232,10 +244,13 @@ void clearCart_cartExists_clearsItems() { } @Test - void clearCart_cartNotFound_throwsNoSuchElementException() { + void clearCart_cartNotFound_throwsGlobalHandlerException() { when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.empty()); - assertThrows(NoSuchElementException.class, () -> cartService.clearCart(customerId)); + GlobalHandlerException exception = assertThrows(GlobalHandlerException.class, + () -> cartService.clearCart(customerId)); + assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); + assertEquals("Cart not found", exception.getMessage()); verify(cartRepository).findByCustomerId(customerId); verify(cartRepository, never()).save(any()); } @@ -322,4 +337,4 @@ void checkoutCart_orderServiceFails_throwsRuntimeException() { verify(restTemplate).postForObject(eq("http://localhost:8080/orders"), any(OrderRequest.class), eq(Void.class)); verify(cartRepository, never()).save(any()); } -} +} \ No newline at end of file From 84a5d71bc729460a55365649f0ddfc87c4186e1f Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Thu, 8 May 2025 16:10:28 +0300 Subject: [PATCH 18/20] Delete logs/app.log --- logs/app.log | 367 --------------------------------------------------- 1 file changed, 367 deletions(-) delete mode 100644 logs/app.log diff --git a/logs/app.log b/logs/app.log deleted file mode 100644 index 20570ce..0000000 --- a/logs/app.log +++ /dev/null @@ -1,367 +0,0 @@ -2025-05-03T20:31:22.279Z INFO 1 --- [main] cart.CartApplication : Starting CartApplication v0.0.1-SNAPSHOT using Java 25-ea with PID 1 (/app/cart-0.0.1-SNAPSHOT.jar started by root in /) -2025-05-03T20:31:22.309Z INFO 1 --- [main] cart.CartApplication : No active profile set, falling back to 1 default profile: "default" -2025-05-03T20:31:28.546Z INFO 1 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data MongoDB repositories in DEFAULT mode. -2025-05-03T20:31:28.823Z INFO 1 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 221 ms. Found 1 MongoDB repository interface. -2025-05-03T20:31:31.202Z INFO 1 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) -2025-05-03T20:31:31.305Z INFO 1 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat] -2025-05-03T20:31:31.311Z INFO 1 --- [main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.40] -2025-05-03T20:31:31.496Z INFO 1 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext -2025-05-03T20:31:31.506Z INFO 1 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 8747 ms -2025-05-03T20:31:33.078Z INFO 1 --- [main] org.mongodb.driver.client : MongoClient with metadata {"driver": {"name": "mongo-java-driver|sync|spring-boot", "version": "5.2.1"}, "os": {"type": "Linux", "name": "Linux", "architecture": "amd64", "version": "5.15.133.1-microsoft-standard-WSL2"}, "platform": "Java/Oracle Corporation/25-ea+4-344", "env": {"container": {"runtime": "docker"}}} created with settings MongoClientSettings{readPreference=primary, writeConcern=WriteConcern{w=null, wTimeout=null ms, journal=null}, retryWrites=true, retryReads=true, readConcern=ReadConcern{level=null}, credential=null, transportSettings=null, commandListeners=[], codecRegistry=ProvidersCodecRegistry{codecProviders=[ValueCodecProvider{}, BsonValueCodecProvider{}, DBRefCodecProvider{}, DBObjectCodecProvider{}, DocumentCodecProvider{}, CollectionCodecProvider{}, IterableCodecProvider{}, MapCodecProvider{}, GeoJsonCodecProvider{}, GridFSFileCodecProvider{}, Jsr310CodecProvider{}, JsonObjectCodecProvider{}, BsonCodecProvider{}, EnumCodecProvider{}, com.mongodb.client.model.mql.ExpressionCodecProvider@5b1c32e4, com.mongodb.Jep395RecordCodecProvider@2bab618, com.mongodb.KotlinCodecProvider@48bc2fce]}, loggerSettings=LoggerSettings{maxDocumentLength=1000}, clusterSettings={hosts=[cart-db:27017], srvServiceName=mongodb, mode=SINGLE, requiredClusterType=UNKNOWN, requiredReplicaSetName='null', serverSelector='null', clusterListeners='[]', serverSelectionTimeout='30000 ms', localThreshold='15 ms'}, socketSettings=SocketSettings{connectTimeoutMS=10000, readTimeoutMS=0, receiveBufferSize=0, proxySettings=ProxySettings{host=null, port=null, username=null, password=null}}, heartbeatSocketSettings=SocketSettings{connectTimeoutMS=10000, readTimeoutMS=10000, receiveBufferSize=0, proxySettings=ProxySettings{host=null, port=null, username=null, password=null}}, connectionPoolSettings=ConnectionPoolSettings{maxSize=100, minSize=0, maxWaitTimeMS=120000, maxConnectionLifeTimeMS=0, maxConnectionIdleTimeMS=0, maintenanceInitialDelayMS=0, maintenanceFrequencyMS=60000, connectionPoolListeners=[], maxConnecting=2}, serverSettings=ServerSettings{heartbeatFrequencyMS=10000, minHeartbeatFrequencyMS=500, serverMonitoringMode=AUTO, serverListeners='[]', serverMonitorListeners='[]'}, sslSettings=SslSettings{enabled=false, invalidHostNameAllowed=false, context=null}, applicationName='null', compressorList=[], uuidRepresentation=JAVA_LEGACY, serverApi=null, autoEncryptionSettings=null, dnsClient=null, inetAddressResolver=null, contextProvider=null, timeoutMS=null} -2025-05-03T20:31:33.264Z INFO 1 --- [cluster-ClusterId{value='68167d2481d2884e0500a1eb', description='null'}-cart-db:27017] org.mongodb.driver.cluster : Monitor thread successfully connected to server with description ServerDescription{address=cart-db:27017, type=STANDALONE, cryptd=false, state=CONNECTED, ok=true, minWireVersion=0, maxWireVersion=21, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=256246676, minRoundTripTimeNanos=0} -2025-05-03T20:31:38.379Z INFO 1 --- [main] o.s.v.b.OptionalValidatorFactoryBean : Failed to set up a Bean Validation provider: jakarta.validation.NoProviderFoundException: Unable to create a Configuration, because no Jakarta Bean Validation provider could be found. Add a provider like Hibernate Validator (RI) to your classpath. -2025-05-03T20:31:39.314Z DEBUG 1 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : 16 mappings in 'requestMappingHandlerMapping' -2025-05-03T20:31:40.217Z DEBUG 1 --- [main] o.s.w.s.handler.SimpleUrlHandlerMapping : Patterns [/webjars/**, /**, /swagger-ui*/*swagger-initializer.js, /swagger-ui*/**] in 'resourceHandlerMapping' -2025-05-03T20:31:40.665Z DEBUG 1 --- [main] s.w.s.m.m.a.RequestMappingHandlerAdapter : ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 RequestBodyAdvice, 1 ResponseBodyAdvice -2025-05-03T20:31:42.243Z DEBUG 1 --- [main] .m.m.a.ExceptionHandlerExceptionResolver : ControllerAdvice beans: 1 @ExceptionHandler, 1 ResponseBodyAdvice -2025-05-03T20:31:43.617Z INFO 1 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/' -2025-05-03T20:31:43.762Z INFO 1 --- [main] cart.CartApplication : Started CartApplication in 34.402 seconds (process running for 45.042) -2025-05-03T20:38:11.427Z INFO 1 --- [http-nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' -2025-05-03T20:38:11.437Z INFO 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' -2025-05-03T20:38:11.462Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Detected StandardServletMultipartResolver -2025-05-03T20:38:11.466Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Detected AcceptHeaderLocaleResolver -2025-05-03T20:38:11.479Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Detected FixedThemeResolver -2025-05-03T20:38:11.535Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Detected org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@305063b9 -2025-05-03T20:38:11.542Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Detected org.springframework.web.servlet.support.SessionFlashMapManager@38b1666c -2025-05-03T20:38:11.558Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data -2025-05-03T20:38:11.568Z INFO 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 119 ms -2025-05-03T20:38:11.680Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/123456", parameters={} -2025-05-03T20:38:11.829Z DEBUG 1 --- [http-nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) -2025-05-03T20:38:13.541Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Failed to complete request: java.util.NoSuchElementException: Cart not found -2025-05-03T20:38:13.564Z ERROR 1 --- [http-nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.util.NoSuchElementException: Cart not found] with root cause - -java.util.NoSuchElementException: Cart not found - at cart.service.CartService.lambda$getCartByCustomerId$4(CartService.java:91) - at java.base/java.util.Optional.orElseThrow(Optional.java:403) - at cart.service.CartService.getCartByCustomerId(CartService.java:91) - at cart.controller.CartController.getCartByCustomerId(CartController.java:44) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) - at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) - at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) - at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) - at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) - at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) - at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) - at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) - at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) - at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) - at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) - at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) - at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) - at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) - at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) - at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) - at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) - at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) - at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) - at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) - at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) - at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) - at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) - at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) - at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) - at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740) - at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) - at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) - at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) - at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) - at java.base/java.lang.Thread.run(Thread.java:1447) - -2025-05-03T20:38:13.778Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={} -2025-05-03T20:38:13.798Z DEBUG 1 --- [http-nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest) -2025-05-03T20:38:14.135Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:38:14.179Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Sat May 03 20:38:13 UTC 2025, status=500, error=Internal Server Error, path=/api/cart/cus (truncated)...] -2025-05-03T20:38:14.631Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500 -2025-05-03T20:38:23.070Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12345", parameters={} -2025-05-03T20:38:23.082Z DEBUG 1 --- [http-nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) -2025-05-03T20:38:23.152Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Failed to complete request: java.util.NoSuchElementException: Cart not found -2025-05-03T20:38:23.161Z ERROR 1 --- [http-nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.util.NoSuchElementException: Cart not found] with root cause - -java.util.NoSuchElementException: Cart not found - at cart.service.CartService.lambda$getCartByCustomerId$4(CartService.java:91) - at java.base/java.util.Optional.orElseThrow(Optional.java:403) - at cart.service.CartService.getCartByCustomerId(CartService.java:91) - at cart.controller.CartController.getCartByCustomerId(CartController.java:44) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) - at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) - at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) - at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) - at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) - at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) - at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) - at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) - at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) - at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) - at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) - at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) - at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) - at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) - at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) - at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) - at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) - at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) - at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) - at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) - at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) - at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) - at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) - at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) - at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) - at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740) - at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) - at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) - at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) - at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) - at java.base/java.lang.Thread.run(Thread.java:1447) - -2025-05-03T20:38:23.186Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={} -2025-05-03T20:38:23.205Z DEBUG 1 --- [http-nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest) -2025-05-03T20:38:23.222Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:38:23.253Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Sat May 03 20:38:23 UTC 2025, status=500, error=Internal Server Error, path=/api/cart/cus (truncated)...] -2025-05-03T20:38:23.387Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500 -2025-05-03T20:39:12.637Z DEBUG 1 --- [http-nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : POST "/api/cart/create/12356", parameters={} -2025-05-03T20:39:12.641Z DEBUG 1 --- [http-nio-8080-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#createCart(String) -2025-05-03T20:39:13.272Z DEBUG 1 --- [http-nio-8080-exec-3] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:39:13.297Z DEBUG 1 --- [http-nio-8080-exec-3] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] -2025-05-03T20:39:13.325Z DEBUG 1 --- [http-nio-8080-exec-3] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:39:32.998Z DEBUG 1 --- [http-nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/123456", parameters={} -2025-05-03T20:39:33.007Z DEBUG 1 --- [http-nio-8080-exec-6] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) -2025-05-03T20:39:33.036Z DEBUG 1 --- [http-nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : Failed to complete request: java.util.NoSuchElementException: Cart not found -2025-05-03T20:39:33.042Z ERROR 1 --- [http-nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.util.NoSuchElementException: Cart not found] with root cause - -java.util.NoSuchElementException: Cart not found - at cart.service.CartService.lambda$getCartByCustomerId$4(CartService.java:91) - at java.base/java.util.Optional.orElseThrow(Optional.java:403) - at cart.service.CartService.getCartByCustomerId(CartService.java:91) - at cart.controller.CartController.getCartByCustomerId(CartController.java:44) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) - at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) - at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) - at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) - at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) - at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) - at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) - at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) - at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) - at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) - at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) - at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) - at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) - at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) - at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) - at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) - at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) - at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) - at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) - at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) - at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) - at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) - at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) - at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) - at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) - at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740) - at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) - at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) - at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) - at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) - at java.base/java.lang.Thread.run(Thread.java:1447) - -2025-05-03T20:39:33.051Z DEBUG 1 --- [http-nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={} -2025-05-03T20:39:33.066Z DEBUG 1 --- [http-nio-8080-exec-6] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest) -2025-05-03T20:39:33.077Z DEBUG 1 --- [http-nio-8080-exec-6] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:39:33.084Z DEBUG 1 --- [http-nio-8080-exec-6] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Sat May 03 20:39:33 UTC 2025, status=500, error=Internal Server Error, path=/api/cart/cus (truncated)...] -2025-05-03T20:39:33.088Z DEBUG 1 --- [http-nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500 -2025-05-03T20:39:47.537Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : POST "/api/cart/create/12356", parameters={} -2025-05-03T20:39:47.545Z DEBUG 1 --- [http-nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#createCart(String) -2025-05-03T20:39:47.575Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:39:47.632Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] -2025-05-03T20:39:47.664Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:39:57.758Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} -2025-05-03T20:39:57.767Z DEBUG 1 --- [http-nio-8080-exec-8] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) -2025-05-03T20:39:57.808Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:39:57.811Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] -2025-05-03T20:39:57.824Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:40:20.555Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.web.servlet.DispatcherServlet : PATCH "/api/cart/12356/archive", parameters={} -2025-05-03T20:40:20.561Z DEBUG 1 --- [http-nio-8080-exec-10] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#archiveCart(String) -2025-05-03T20:40:20.842Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:40:20.844Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedtrue}] -2025-05-03T20:40:20.856Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:40:33.609Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} -2025-05-03T20:40:33.612Z DEBUG 1 --- [http-nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) -2025-05-03T20:40:33.629Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:40:33.641Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedtrue}] -2025-05-03T20:40:33.657Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:40:44.620Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : PATCH "/api/cart/12356/unarchive", parameters={} -2025-05-03T20:40:44.623Z DEBUG 1 --- [http-nio-8080-exec-4] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#unarchiveCart(String) -2025-05-03T20:40:44.659Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:40:44.669Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] -2025-05-03T20:40:44.672Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:41:26.966Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : POST "/api/cart/12356/items", parameters={} -2025-05-03T20:41:26.982Z DEBUG 1 --- [http-nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#addItemToCart(String, CartItem) -2025-05-03T20:41:28.869Z DEBUG 1 --- [http-nio-8080-exec-7] m.m.a.RequestResponseBodyMethodProcessor : Read "application/json;charset=UTF-8" to [CartItem{productId='123', quantity=2}] -2025-05-03T20:41:29.043Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:41:29.070Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[CartItem{productId='123', (truncated)...] -2025-05-03T20:41:29.127Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:41:44.041Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} -2025-05-03T20:41:44.044Z DEBUG 1 --- [http-nio-8080-exec-8] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) -2025-05-03T20:41:44.149Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:41:44.151Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[CartItem{productId='123', (truncated)...] -2025-05-03T20:41:44.154Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:42:11.607Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.web.servlet.DispatcherServlet : PATCH "/api/cart/12356/items/123?quantity=4", parameters={masked} -2025-05-03T20:42:11.613Z DEBUG 1 --- [http-nio-8080-exec-10] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#updateItemQuantity(String, String, int) -2025-05-03T20:42:11.684Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:42:11.687Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[CartItem{productId='123', (truncated)...] -2025-05-03T20:42:11.705Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:42:31.364Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : DELETE "/api/cart/12356/clear", parameters={} -2025-05-03T20:42:31.369Z DEBUG 1 --- [http-nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#clearCart(String) -2025-05-03T20:42:31.399Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:42:31.404Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Nothing to write: null body -2025-05-03T20:42:31.407Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 204 NO_CONTENT -2025-05-03T20:42:48.558Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} -2025-05-03T20:42:48.560Z DEBUG 1 --- [http-nio-8080-exec-4] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) -2025-05-03T20:42:48.575Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:42:48.578Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] -2025-05-03T20:42:48.581Z DEBUG 1 --- [http-nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:43:11.772Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : POST "/api/cart/12356/items", parameters={} -2025-05-03T20:43:11.775Z DEBUG 1 --- [http-nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#addItemToCart(String, CartItem) -2025-05-03T20:43:11.834Z DEBUG 1 --- [http-nio-8080-exec-7] m.m.a.RequestResponseBodyMethodProcessor : Read "application/json;charset=UTF-8" to [CartItem{productId='124', quantity=3}] -2025-05-03T20:43:11.891Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:43:11.894Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[CartItem{productId='124', (truncated)...] -2025-05-03T20:43:11.905Z DEBUG 1 --- [http-nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:43:21.624Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} -2025-05-03T20:43:21.629Z DEBUG 1 --- [http-nio-8080-exec-8] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) -2025-05-03T20:43:21.649Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:43:21.659Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[CartItem{productId='124', (truncated)...] -2025-05-03T20:43:21.681Z DEBUG 1 --- [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:43:44.983Z DEBUG 1 --- [http-nio-8080-exec-9] o.s.web.servlet.DispatcherServlet : PATCH "/api/cart/12356/items/124?quantity=-1", parameters={masked} -2025-05-03T20:43:44.986Z DEBUG 1 --- [http-nio-8080-exec-9] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#updateItemQuantity(String, String, int) -2025-05-03T20:43:45.145Z DEBUG 1 --- [http-nio-8080-exec-9] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:43:45.151Z DEBUG 1 --- [http-nio-8080-exec-9] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] -2025-05-03T20:43:45.170Z DEBUG 1 --- [http-nio-8080-exec-9] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:43:52.213Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} -2025-05-03T20:43:52.217Z DEBUG 1 --- [http-nio-8080-exec-10] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) -2025-05-03T20:43:52.245Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:43:52.249Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Cart{id='42ce5685-e107-4872-a6b0-78daca819b9b', customerId='12356', items=[], archivedfalse}] -2025-05-03T20:43:52.260Z DEBUG 1 --- [http-nio-8080-exec-10] o.s.web.servlet.DispatcherServlet : Completed 200 OK -2025-05-03T20:44:04.613Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : DELETE "/api/cart/customer/12356", parameters={} -2025-05-03T20:44:04.616Z DEBUG 1 --- [http-nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#deleteCart(String) -2025-05-03T20:44:04.737Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:44:04.739Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Nothing to write: null body -2025-05-03T20:44:04.742Z DEBUG 1 --- [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed 204 NO_CONTENT -2025-05-03T20:44:10.048Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/api/cart/customer/12356", parameters={} -2025-05-03T20:44:10.060Z DEBUG 1 --- [http-nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to cart.controller.CartController#getCartByCustomerId(String) -2025-05-03T20:44:10.095Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Failed to complete request: java.util.NoSuchElementException: Cart not found -2025-05-03T20:44:10.101Z ERROR 1 --- [http-nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.util.NoSuchElementException: Cart not found] with root cause - -java.util.NoSuchElementException: Cart not found - at cart.service.CartService.lambda$getCartByCustomerId$4(CartService.java:91) - at java.base/java.util.Optional.orElseThrow(Optional.java:403) - at cart.service.CartService.getCartByCustomerId(CartService.java:91) - at cart.controller.CartController.getCartByCustomerId(CartController.java:44) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) - at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) - at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) - at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) - at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) - at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) - at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) - at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) - at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) - at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) - at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) - at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) - at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) - at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) - at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) - at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) - at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) - at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) - at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) - at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) - at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) - at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) - at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) - at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) - at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) - at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) - at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) - at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) - at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740) - at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) - at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) - at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) - at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) - at java.base/java.lang.Thread.run(Thread.java:1447) - -2025-05-03T20:44:10.113Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={} -2025-05-03T20:44:10.120Z DEBUG 1 --- [http-nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest) -2025-05-03T20:44:10.138Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/yaml] -2025-05-03T20:44:10.166Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Sat May 03 20:44:10 UTC 2025, status=500, error=Internal Server Error, path=/api/cart/cus (truncated)...] -2025-05-03T20:44:10.183Z DEBUG 1 --- [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500 -2025-05-03T20:44:29.759Z INFO 1 --- [SpringApplicationShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete -2025-05-03T20:44:29.781Z INFO 1 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete From 53468506402fb8a842aca3090de4ef14d340deaf Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Sun, 11 May 2025 22:17:33 +0300 Subject: [PATCH 19/20] feat : adding command design pattern and singlton and updating tests --- pom.xml | 35 ++ src/main/java/cart/CartApplication.java | 2 + .../java/cart/service/AddItemCommand.java | 66 ++++ src/main/java/cart/service/CartCommand.java | 8 + src/main/java/cart/service/CartService.java | 69 +--- .../java/cart/service/RemoveItemCommand.java | 57 +++ .../cart/service/UpdateQuantityCommand.java | 81 +++++ src/test/java/api/CartControllerTests.java | 325 ++++++++++++++++++ src/test/java/service/CartServiceTest.java | 48 ++- 9 files changed, 606 insertions(+), 85 deletions(-) create mode 100644 src/main/java/cart/service/AddItemCommand.java create mode 100644 src/main/java/cart/service/CartCommand.java create mode 100644 src/main/java/cart/service/RemoveItemCommand.java create mode 100644 src/main/java/cart/service/UpdateQuantityCommand.java create mode 100644 src/test/java/api/CartControllerTests.java diff --git a/pom.xml b/pom.xml index 32d7846..337a47c 100644 --- a/pom.xml +++ b/pom.xml @@ -110,8 +110,43 @@ spring-security-test test + + + + com.github.tomakehurst + wiremock-jre8 + 2.35.0 + test + + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + 4.9.3 + test + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.github.tomakehurst + wiremock-jre8 + 2.35.0 + test + + diff --git a/src/main/java/cart/CartApplication.java b/src/main/java/cart/CartApplication.java index 3a1e3fc..2ef8d6d 100644 --- a/src/main/java/cart/CartApplication.java +++ b/src/main/java/cart/CartApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; @SpringBootApplication +@EnableMongoRepositories(basePackages = "cart.repository") public class CartApplication { public static void main(final String[] args) { SpringApplication.run(CartApplication.class, args); diff --git a/src/main/java/cart/service/AddItemCommand.java b/src/main/java/cart/service/AddItemCommand.java new file mode 100644 index 0000000..a0755c2 --- /dev/null +++ b/src/main/java/cart/service/AddItemCommand.java @@ -0,0 +1,66 @@ +package cart.service; + +import cart.model.Cart; +import cart.model.CartItem; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.Optional; + +@RequiredArgsConstructor +@Slf4j +public class AddItemCommand implements CartCommand { + + private final CartService cartService; + private final String customerId; + private final CartItem newItem; + + @Override + public Cart execute() { + log.debug("Executing AddItemCommand for customerId: {}, item: {}", customerId, newItem); + Cart cart = cartService.getCartByCustomerId(customerId); + + Optional existingItem = cart.getItems().stream() + .filter(i -> i.getProductId().equals(newItem.getProductId())) + .findFirst(); + + if (existingItem.isPresent()) { + log.debug("Item exists, updating quantity for productId: {}", newItem.getProductId()); + existingItem.get().setQuantity(existingItem.get().getQuantity() + newItem.getQuantity()); + } else { + log.debug("Adding new item to cart for productId: {}", newItem.getProductId()); + cart.getItems().add(newItem); + } + + Cart updatedCart = cartService.saveCart(cart); + log.debug("AddItemCommand executed, updated cart: {}", updatedCart); + return updatedCart; + } + + @Override + public Cart undo() { + log.debug("Undoing AddItemCommand for customerId: {}, item: {}", customerId, newItem); + Cart cart = cartService.getCartByCustomerId(customerId); + + Optional existingItem = cart.getItems().stream() + .filter(i -> i.getProductId().equals(newItem.getProductId())) + .findFirst(); + + if (existingItem.isPresent()) { + int newQuantity = existingItem.get().getQuantity() - newItem.getQuantity(); + if (newQuantity <= 0) { + log.debug("Removing item during undo for productId: {}", newItem.getProductId()); + cart.getItems().removeIf(i -> i.getProductId().equals(newItem.getProductId())); + } else { + log.debug("Reducing quantity during undo for productId: {}", newItem.getProductId()); + existingItem.get().setQuantity(newQuantity); + } + } else { + log.warn("Item not found during undo for productId: {}", newItem.getProductId()); + } + + Cart updatedCart = cartService.saveCart(cart); + log.debug("AddItemCommand undone, updated cart: {}", updatedCart); + return updatedCart; + } +} diff --git a/src/main/java/cart/service/CartCommand.java b/src/main/java/cart/service/CartCommand.java new file mode 100644 index 0000000..052da6f --- /dev/null +++ b/src/main/java/cart/service/CartCommand.java @@ -0,0 +1,8 @@ +package cart.service; + +import cart.model.Cart; + +public interface CartCommand { + Cart execute(); + Cart undo(); +} \ No newline at end of file diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java index edfce98..2745f4e 100644 --- a/src/main/java/cart/service/CartService.java +++ b/src/main/java/cart/service/CartService.java @@ -50,31 +50,8 @@ public Cart addItemToCart(final String customerId, log.debug("Entering addItemToCart " + "with customerId:, newItem:", customerId, newItem); - - Cart cart = getCartByCustomerId(customerId); - - Optional existingItem = - cart.getItems().stream() - .filter(i -> i.getProductId() - .equals(newItem.getProductId())) - .findFirst(); - - if (existingItem.isPresent()) { - log.debug("Item already exists, updating" - + " quantity for productId:", newItem.getProductId()); - - existingItem.get() - .setQuantity(existingItem.get(). - getQuantity() + newItem.getQuantity()); - } else { - log.debug("Adding new item to cart for productId: ", - newItem.getProductId()); - cart.getItems().add(newItem); - } - - Cart updatedCart = cartRepository.save(cart); - log.debug("Cart updated:", updatedCart); - return updatedCart; + CartCommand command = new AddItemCommand(this, customerId, newItem); + return command.execute(); } @@ -83,34 +60,8 @@ public Cart updateItemQuantity(final String customerId, log.debug("Entering updateItemQuantity with" + " customerId:, productId:, quantity: ", customerId, productId, quantity); - Cart cart = getCartByCustomerId(customerId); - - Optional existingItemOpt = cart.getItems().stream() - .filter(i -> i.getProductId().equals(productId)) - .findFirst(); - - if (existingItemOpt.isEmpty()) { - log.error("Product not found in" - + " cart for productId:", productId); - throw new GlobalHandlerException( - HttpStatus.NOT_FOUND, "Product not found in cart"); - } - - CartItem item = existingItemOpt.get(); - - if (quantity <= 0) { - log.debug("Removing item from cart" - + " as quantity <= 0 for productId:", productId); - cart.getItems().remove(item); - } else { - log.debug("Updating quantity to:" - + " for productId:", quantity, productId); - item.setQuantity(quantity); - } - - Cart updatedCart = cartRepository.save(cart); - log.debug("Cart updated: {}", updatedCart); - return updatedCart; + CartCommand command = new UpdateQuantityCommand(this, customerId, productId, quantity); + return command.execute(); } @@ -118,11 +69,9 @@ public Cart removeItemFromCart(final String customerId, final String productId) { log.debug("Entering removeItemFromCart" + " with customerId:, productId:", customerId, productId); - Cart cart = getCartByCustomerId(customerId); - cart.getItems().removeIf(i -> i.getProductId().equals(productId)); - Cart updatedCart = cartRepository.save(cart); - log.debug("Item removed, updated cart: {}", updatedCart); - return updatedCart; + + CartCommand command = new RemoveItemCommand(this, customerId, productId); + return command.execute(); } public void deleteCartByCustomerId(final String customerId) { @@ -229,4 +178,8 @@ private Cart getArchivedCart(final String customerId) { return cart; } + public Cart saveCart(Cart cart) { + return cartRepository.save(cart); + } + } diff --git a/src/main/java/cart/service/RemoveItemCommand.java b/src/main/java/cart/service/RemoveItemCommand.java new file mode 100644 index 0000000..2745cdc --- /dev/null +++ b/src/main/java/cart/service/RemoveItemCommand.java @@ -0,0 +1,57 @@ +package cart.service; + +import cart.model.Cart; +import cart.model.CartItem; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.Optional; + +@RequiredArgsConstructor +@Slf4j +public class RemoveItemCommand implements CartCommand { + + private final CartService cartService; + private final String customerId; + private final String productId; + private CartItem removedItem; + + @Override + public Cart execute() { + log.debug("Executing RemoveItemCommand for customerId: {}, productId: {}", customerId, productId); + Cart cart = cartService.getCartByCustomerId(customerId); + + Optional itemToRemove = cart.getItems().stream() + .filter(i -> i.getProductId().equals(productId)) + .findFirst(); + + if (itemToRemove.isPresent()) { + removedItem = new CartItem(itemToRemove.get().getProductId(), itemToRemove.get().getQuantity()); + cart.getItems().removeIf(i -> i.getProductId().equals(productId)); + log.debug("Item removed for productId: {}", productId); + } else { + log.warn("Item not found for removal, productId: {}", productId); + } + + Cart updatedCart = cartService.saveCart(cart); + log.debug("RemoveItemCommand executed, updated cart: {}", updatedCart); + return updatedCart; + } + + @Override + public Cart undo() { + log.debug("Undoing RemoveItemCommand for customerId: {}, productId: {}", customerId, productId); + Cart cart = cartService.getCartByCustomerId(customerId); + + if (removedItem != null) { + log.debug("Restoring item during undo for productId: {}", productId); + cart.getItems().add(removedItem); + } else { + log.warn("No item to restore during undo for productId: {}", productId); + } + + Cart updatedCart = cartService.saveCart(cart); + log.debug("RemoveItemCommand undone, updated cart: {}", updatedCart); + return updatedCart; + } +} \ No newline at end of file diff --git a/src/main/java/cart/service/UpdateQuantityCommand.java b/src/main/java/cart/service/UpdateQuantityCommand.java new file mode 100644 index 0000000..02dbc66 --- /dev/null +++ b/src/main/java/cart/service/UpdateQuantityCommand.java @@ -0,0 +1,81 @@ +package cart.service; + +import cart.exception.GlobalHandlerException; +import cart.model.Cart; +import cart.model.CartItem; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; + +import java.util.Optional; + +@RequiredArgsConstructor +@Slf4j +public class UpdateQuantityCommand implements CartCommand { + + private final CartService cartService; + private final String customerId; + private final String productId; + private final int newQuantity; + private Integer previousQuantity; // For undo + + @Override + public Cart execute() { + log.debug("Executing UpdateQuantityCommand for customerId: {}, productId: {}, quantity: {}", customerId, productId, newQuantity); + Cart cart = cartService.getCartByCustomerId(customerId); + + Optional existingItemOpt = cart.getItems().stream() + .filter(i -> i.getProductId().equals(productId)) + .findFirst(); + + if (existingItemOpt.isEmpty()) { + log.error("Product not found in cart for productId: {}", productId); + throw new GlobalHandlerException(HttpStatus.NOT_FOUND, "Product not found in cart"); + } + + CartItem item = existingItemOpt.get(); + previousQuantity = item.getQuantity(); + + if (newQuantity <= 0) { + log.debug("Removing item as quantity <= 0 for productId: {}", productId); + cart.getItems().remove(item); + } else { + log.debug("Updating quantity to: {} for productId: {}", newQuantity, productId); + item.setQuantity(newQuantity); + } + + Cart updatedCart = cartService.saveCart(cart); + log.debug("UpdateQuantityCommand executed, updated cart: {}", updatedCart); + return updatedCart; + } + + @Override + public Cart undo() { + log.debug("Undoing UpdateQuantityCommand for customerId: {}, productId: {}", customerId, productId); + Cart cart = cartService.getCartByCustomerId(customerId); + + if (previousQuantity == null) { + log.warn("No previous quantity to restore for productId: {}", productId); + return cart; + } + + Optional existingItemOpt = cart.getItems().stream() + .filter(i -> i.getProductId().equals(productId)) + .findFirst(); + + if (previousQuantity <= 0) { + log.debug("Restoring removed item during undo for productId: {}", productId); + cart.getItems().add(new CartItem(productId, previousQuantity)); + } else if (existingItemOpt.isPresent()) { + log.debug("Restoring previous quantity during undo for productId: {}", productId); + existingItemOpt.get().setQuantity(previousQuantity); + } else { + log.debug("Adding item back during undo for productId: {}", productId); + cart.getItems().add(new CartItem(productId, previousQuantity)); + } + + Cart updatedCart = cartService.saveCart(cart); + log.debug("UpdateQuantityCommand undone, updated cart: {}", updatedCart); + return updatedCart; + } +} \ No newline at end of file diff --git a/src/test/java/api/CartControllerTests.java b/src/test/java/api/CartControllerTests.java new file mode 100644 index 0000000..919e57d --- /dev/null +++ b/src/test/java/api/CartControllerTests.java @@ -0,0 +1,325 @@ +//package api; +// +//import cart.model.Cart; +//import cart.model.CartItem; +//import cart.repository.CartRepository; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import lombok.RequiredArgsConstructor; +//import org.junit.jupiter.api.AfterEach; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.Test; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.http.HttpMethod; +//import org.springframework.http.MediaType; +//import org.springframework.test.context.ActiveProfiles; +//import org.springframework.test.web.client.ExpectedCount; +//import org.springframework.test.web.client.MockRestServiceServer; +//import org.springframework.test.web.servlet.MockMvc; +//import org.springframework.web.client.RestTemplate; +// +//import java.util.ArrayList; +//import java.util.List; +//import java.util.UUID; +// +//import static org.hamcrest.Matchers.*; +//import static org.junit.jupiter.api.Assertions.*; +//import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; +//import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +//import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; +//import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError; +//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +// +//@SpringBootTest +//@AutoConfigureMockMvc +//@RequiredArgsConstructor +//@ActiveProfiles("test") +//public class CartControllerTests { +// +// private MockMvc mockMvc; +// +// private CartRepository cartRepository; +// +// private ObjectMapper objectMapper; +// +// private RestTemplate restTemplate; +// +// private MockRestServiceServer mockServer; +// +// @Value("${order.service.url}") +// private String orderServiceUrl; +// +// private String customerId; +// private String productId1; +// private String productId2; +// +// @BeforeEach +// void setUp() { +// // Initialize MockRestServiceServer +// mockServer = MockRestServiceServer.createServer(restTemplate); +// +// cartRepository.deleteAll(); // Clean slate before each test +// +// customerId = "cust-" + UUID.randomUUID().toString(); +// productId1 = "prod-" + UUID.randomUUID().toString(); +// productId2 = "prod-" + UUID.randomUUID().toString(); +// } +// +// @AfterEach +// void tearDown() { +// cartRepository.deleteAll(); // Clean up after each test +// mockServer.verify(); // Verify all expected RestTemplate calls were made +// } +// +// private Cart createAndSaveTestCart(String custId, boolean archived, CartItem... items) { +// Cart cart = new Cart(UUID.randomUUID().toString(), custId, new ArrayList<>(List.of(items)), archived); +// return cartRepository.save(cart); +// } +// +// @Test +// void createCart_shouldCreateNewCart_whenCartDoesNotExist() throws Exception { +// mockMvc.perform(post("/api/carts/create/{customerId}", customerId)) +// .andExpect(status().isOk()) +// .andExpect(content().contentType(MediaType.APPLICATION_JSON)) +// .andExpect(jsonPath("$.customerId", is(customerId))) +// .andExpect(jsonPath("$.items", empty())) +// .andExpect(jsonPath("$.archived", is(false))); +// +// assertTrue(cartRepository.findByCustomerId(customerId).isPresent()); +// } +// +// @Test +// void createCart_shouldReturnExistingCart_whenCartExists() throws Exception { +// Cart existingCart = createAndSaveTestCart(customerId, false, new CartItem(productId1, 1)); +// +// mockMvc.perform(post("/api/carts/create/{customerId}", customerId)) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.id", is(existingCart.getId()))) +// .andExpect(jsonPath("$.customerId", is(customerId))) +// .andExpect(jsonPath("$.items[0].productId", is(productId1))); +// +// assertEquals(1, cartRepository.count()); +// } +// +// @Test +// void getCartByCustomerId_shouldReturnCart_whenExists() throws Exception { +// Cart cart = createAndSaveTestCart(customerId, false, new CartItem(productId1, 2)); +// +// mockMvc.perform(get("/api/carts/customer/{customerId}", customerId)) +// .andExpect(status().isOk()) +// .andExpect(content().contentType(MediaType.APPLICATION_JSON)) +// .andExpect(jsonPath("$.id", is(cart.getId()))) +// .andExpect(jsonPath("$.items[0].productId", is(productId1))) +// .andExpect(jsonPath("$.items[0].quantity", is(2))); +// } +// +// @Test +// void getCartByCustomerId_shouldReturnNotFound_whenNotExists() throws Exception { +// mockMvc.perform(get("/api/carts/customer/{customerId}", "non-existent-customer")) +// .andExpect(status().isNotFound()); +// } +// +// @Test +// void deleteCart_shouldDeleteCartAndReturnNoContent() throws Exception { +// createAndSaveTestCart(customerId, false); +// +// mockMvc.perform(delete("/api/carts/customer/{customerId}", customerId)) +// .andExpect(status().isNoContent()); +// +// assertFalse(cartRepository.findByCustomerId(customerId).isPresent()); +// } +// +// @Test +// void deleteCart_shouldDoNothingAndReturnNoContent_whenCartNotFound() throws Exception { +// mockMvc.perform(delete("/api/carts/customer/{customerId}", customerId)) +// .andExpect(status().isNoContent()); // Service method handles not found gracefully for delete +// } +// +// +// @Test +// void addItemToCart_shouldAddNewItemToExistingCart() throws Exception { +// createAndSaveTestCart(customerId, false); +// CartItem newItem = new CartItem(productId1, 3); +// +// mockMvc.perform(post("/api/carts/{customerId}/items", customerId) +// .contentType(MediaType.APPLICATION_JSON) +// .content(objectMapper.writeValueAsString(newItem))) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.items", hasSize(1))) +// .andExpect(jsonPath("$.items[0].productId", is(productId1))) +// .andExpect(jsonPath("$.items[0].quantity", is(3))); +// +// Cart updatedCart = cartRepository.findByCustomerId(customerId).orElseThrow(); +// assertEquals(1, updatedCart.getItems().size()); +// assertEquals(productId1, updatedCart.getItems().get(0).getProductId()); +// } +// +// @Test +// void addItemToCart_shouldUpdateQuantityIfItemExists() throws Exception { +// createAndSaveTestCart(customerId, false, new CartItem(productId1, 2)); +// CartItem itemToAdd = new CartItem(productId1, 3); // Adding more of the same item +// +// mockMvc.perform(post("/api/carts/{customerId}/items", customerId) +// .contentType(MediaType.APPLICATION_JSON) +// .content(objectMapper.writeValueAsString(itemToAdd))) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.items", hasSize(1))) +// .andExpect(jsonPath("$.items[0].productId", is(productId1))) +// .andExpect(jsonPath("$.items[0].quantity", is(5))); // 2 + 3 +// +// Cart updatedCart = cartRepository.findByCustomerId(customerId).orElseThrow(); +// assertEquals(5, updatedCart.getItems().get(0).getQuantity()); +// } +// +// @Test +// void updateItemQuantity_shouldUpdateQuantityOfExistingItem() throws Exception { +// createAndSaveTestCart(customerId, false, new CartItem(productId1, 2)); +// int newQuantity = 5; +// +// mockMvc.perform(patch("/api/carts/{customerId}/items/{productId}", customerId, productId1) +// .param("quantity", String.valueOf(newQuantity))) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.items[0].quantity", is(newQuantity))); +// +// Cart updatedCart = cartRepository.findByCustomerId(customerId).orElseThrow(); +// assertEquals(newQuantity, updatedCart.getItems().get(0).getQuantity()); +// } +// +// @Test +// void updateItemQuantity_shouldRemoveItemIfQuantityIsZero() throws Exception { +// createAndSaveTestCart(customerId, false, new CartItem(productId1, 2)); +// +// mockMvc.perform(patch("/api/carts/{customerId}/items/{productId}", customerId, productId1) +// .param("quantity", "0")) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.items", empty())); +// +// Cart updatedCart = cartRepository.findByCustomerId(customerId).orElseThrow(); +// assertTrue(updatedCart.getItems().isEmpty()); +// } +// +// @Test +// void updateItemQuantity_shouldReturnNotFound_whenItemNotInCart() throws Exception { +// createAndSaveTestCart(customerId, false); // Cart exists but is empty +// +// mockMvc.perform(patch("/api/carts/{customerId}/items/{productId}", customerId, "non-existent-product") +// .param("quantity", "5")) +// .andExpect(status().isNotFound()); // Based on CartService logic throwing GlobalHandlerException +// } +// +// +// @Test +// void removeItemFromCart_shouldRemoveItem() throws Exception { +// createAndSaveTestCart(customerId, false, new CartItem(productId1, 1), new CartItem(productId2, 1)); +// +// mockMvc.perform(delete("/api/carts/{customerId}/items/{productId}", customerId, productId1)) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.items", hasSize(1))) +// .andExpect(jsonPath("$.items[0].productId", is(productId2))); +// +// Cart updatedCart = cartRepository.findByCustomerId(customerId).orElseThrow(); +// assertEquals(1, updatedCart.getItems().size()); +// assertEquals(productId2, updatedCart.getItems().get(0).getProductId()); +// } +// +// @Test +// void clearCart_shouldRemoveAllItemsFromCart() throws Exception { +// createAndSaveTestCart(customerId, false, new CartItem(productId1, 1), new CartItem(productId2, 1)); +// +// mockMvc.perform(delete("/api/carts/{customerId}/clear", customerId)) +// .andExpect(status().isNoContent()); +// +// Cart clearedCart = cartRepository.findByCustomerId(customerId).orElseThrow(); +// assertTrue(clearedCart.getItems().isEmpty()); +// } +// +// @Test +// void archiveCart_shouldSetArchivedToTrue() throws Exception { +// createAndSaveTestCart(customerId, false, new CartItem(productId1, 1)); +// +// mockMvc.perform(patch("/api/carts/{customerId}/archive", customerId)) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.archived", is(true))); +// +// Cart archivedCart = cartRepository.findByCustomerId(customerId).orElseThrow(); +// assertTrue(archivedCart.isArchived()); +// } +// +// @Test +// void archiveCart_shouldReturnNotFound_whenActiveCartNotExists() throws Exception { +// // Ensure no active cart exists or only an archived one +// createAndSaveTestCart(customerId, true); // Save an already archived cart +// +// mockMvc.perform(patch("/api/carts/{customerId}/archive", customerId)) +// .andExpect(status().isNotFound()); // Because getActiveCart will throw NoSuchElementException +// } +// +// @Test +// void unarchiveCart_shouldSetArchivedToFalse() throws Exception { +// createAndSaveTestCart(customerId, true, new CartItem(productId1, 1)); // Start with an archived cart +// +// mockMvc.perform(patch("/api/carts/{customerId}/unarchive", customerId)) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.archived", is(false))); +// +// Cart unarchivedCart = cartRepository.findByCustomerId(customerId).orElseThrow(); +// assertFalse(unarchivedCart.isArchived()); +// } +// +// @Test +// void unarchiveCart_shouldReturnNotFound_whenArchivedCartNotExists() throws Exception { +// // Ensure no archived cart exists or only an active one +// createAndSaveTestCart(customerId, false); // Save an active cart +// +// mockMvc.perform(patch("/api/carts/{customerId}/unarchive", customerId)) +// .andExpect(status().isNotFound()); // Because getArchivedCart will throw NoSuchElementException +// } +// +// @Test +// void checkoutCart_shouldClearCartAndCallOrderService_whenSuccessful() throws Exception { +// Cart cartToCheckout = createAndSaveTestCart(customerId, false, new CartItem(productId1, 2), new CartItem(productId2, 1)); +// +// // Expect a POST request to the order service +// mockServer.expect(ExpectedCount.once(), +// requestTo(orderServiceUrl + "/orders")) +// .andExpect(method(HttpMethod.POST)) +// // You can add more specific assertions for the request body if needed: +// // .andExpect(content().json(objectMapper.writeValueAsString(expectedOrderRequest))) +// .andRespond(withSuccess()); // Simulate a successful response from Order Service +// +// mockMvc.perform(post("/api/carts/{customerId}/checkout", customerId)) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.customerId", is(customerId))) +// .andExpect(jsonPath("$.items", empty())); // Cart items should be cleared +// +// Cart finalCartState = cartRepository.findByCustomerId(customerId).orElseThrow(); +// assertTrue(finalCartState.getItems().isEmpty()); +// assertFalse(finalCartState.isArchived()); // Should remain active but empty +// } +// +// @Test +// void checkoutCart_shouldReturnInternalServerError_whenOrderServiceFails() throws Exception { +// createAndSaveTestCart(customerId, false, new CartItem(productId1, 1)); +// +// mockServer.expect(ExpectedCount.once(), +// requestTo(orderServiceUrl + "/orders")) +// .andExpect(method(HttpMethod.POST)) +// .andRespond(withServerError()); // Simulate an error from Order Service +// +// mockMvc.perform(post("/api/carts/{customerId}/checkout", customerId)) +// .andExpect(status().isInternalServerError()) // Based on your controller's exception handling +// .andExpect(content().string(containsString("Error communicating with Order Service"))); // Check error message +// +// // Cart should NOT be cleared if order service call fails +// Cart cartStateAfterFailure = cartRepository.findByCustomerId(customerId).orElseThrow(); +// assertFalse(cartStateAfterFailure.getItems().isEmpty()); +// } +// +// @Test +// void checkoutCart_shouldReturnNotFound_whenCartNotExists() throws Exception { +// mockMvc.perform(post("/api/carts/{customerId}/checkout", "non-existent-customer")) +// .andExpect(status().isNotFound()); +// } +//} \ No newline at end of file diff --git a/src/test/java/service/CartServiceTest.java b/src/test/java/service/CartServiceTest.java index 1e1318f..d7670d3 100644 --- a/src/test/java/service/CartServiceTest.java +++ b/src/test/java/service/CartServiceTest.java @@ -1,5 +1,4 @@ package service; - import cart.exception.GlobalHandlerException; import cart.model.Cart; import cart.model.CartItem; @@ -13,10 +12,9 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.client.RestTemplate; -import org.springframework.web.server.ResponseStatusException; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.NoSuchElementException; import java.util.Optional; @@ -44,19 +42,16 @@ class CartServiceTest { private final String customerId = "cust123"; private final String productId = "prod456"; private final String cartId = UUID.randomUUID().toString(); + private final String orderServiceUrl = "http://localhost:8080"; @BeforeEach - void setUp() throws NoSuchFieldException, IllegalAccessException { + void setUp() { // Initialize test data cart = new Cart(cartId, customerId, new ArrayList<>(), false); - cartItem = new CartItem(); - cartItem.setProductId(productId); - cartItem.setQuantity(1); - - // Set orderServiceUrl using reflection - Field orderServiceUrlField = CartService.class.getDeclaredField("orderServiceUrl"); - orderServiceUrlField.setAccessible(true); - orderServiceUrlField.set(cartService, "http://localhost:8080"); + cartItem = new CartItem(productId, 1); + + // Set orderServiceUrl + ReflectionTestUtils.setField(cartService, "orderServiceUrl", orderServiceUrl); } @Test @@ -92,17 +87,16 @@ void addItemToCart_newItem_addsItem() { Cart result = cartService.addItemToCart(customerId, cartItem); assertEquals(1, result.getItems().size()); - assertEquals(cartItem, result.getItems().get(0)); + assertEquals(cartItem.getProductId(), result.getItems().get(0).getProductId()); + assertEquals(cartItem.getQuantity(), result.getItems().get(0).getQuantity()); verify(cartRepository).findByCustomerId(customerId); verify(cartRepository).save(cart); } @Test void addItemToCart_existingItem_updatesQuantity() { - cart.getItems().add(cartItem); - CartItem newItem = new CartItem(); - newItem.setProductId(productId); - newItem.setQuantity(2); + cart.getItems().add(new CartItem(productId, 1)); + CartItem newItem = new CartItem(productId, 2); when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); when(cartRepository.save(any(Cart.class))).thenReturn(cart); @@ -128,7 +122,7 @@ void addItemToCart_cartNotFound_throwsGlobalHandlerException() { @Test void updateItemQuantity_existingItem_updatesQuantity() { - cart.getItems().add(cartItem); + cart.getItems().add(new CartItem(productId, 1)); when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); when(cartRepository.save(any(Cart.class))).thenReturn(cart); @@ -141,7 +135,7 @@ void updateItemQuantity_existingItem_updatesQuantity() { @Test void updateItemQuantity_quantityZero_removesItem() { - cart.getItems().add(cartItem); + cart.getItems().add(new CartItem(productId, 1)); when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); when(cartRepository.save(any(Cart.class))).thenReturn(cart); @@ -166,7 +160,7 @@ void updateItemQuantity_itemNotFound_throwsGlobalHandlerException() { @Test void removeItemFromCart_itemExists_removesItem() { - cart.getItems().add(cartItem); + cart.getItems().add(new CartItem(productId, 1)); when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); when(cartRepository.save(any(Cart.class))).thenReturn(cart); @@ -232,7 +226,7 @@ void getCartByCustomerId_cartNotFound_throwsGlobalHandlerException() { @Test void clearCart_cartExists_clearsItems() { - cart.getItems().add(cartItem); + cart.getItems().add(new CartItem(productId, 1)); when(cartRepository.findByCustomerId(customerId)).thenReturn(Optional.of(cart)); when(cartRepository.save(any(Cart.class))).thenReturn(cart); @@ -300,17 +294,17 @@ void unarchiveCart_noArchivedCart_throwsNoSuchElementException() { @Test void checkoutCart_validCart_sendsToOrderServiceAndClearsCart() { - cart.getItems().add(cartItem); + cart.getItems().add(new CartItem(productId, 1)); when(cartRepository.findByCustomerIdAndArchived(customerId, false)).thenReturn(Optional.of(cart)); when(cartRepository.save(any(Cart.class))).thenReturn(cart); - when(restTemplate.postForObject(eq("http://localhost:8080/orders"), any(OrderRequest.class), eq(Void.class))) + when(restTemplate.postForObject(eq(orderServiceUrl + "/orders"), any(OrderRequest.class), eq(Void.class))) .thenReturn(null); Cart result = cartService.checkoutCart(customerId); assertTrue(result.getItems().isEmpty()); verify(cartRepository).findByCustomerIdAndArchived(customerId, false); - verify(restTemplate).postForObject(eq("http://localhost:8080/orders"), any(OrderRequest.class), eq(Void.class)); + verify(restTemplate).postForObject(eq(orderServiceUrl + "/orders"), any(OrderRequest.class), eq(Void.class)); verify(cartRepository).save(cart); } @@ -326,15 +320,15 @@ void checkoutCart_noActiveCart_throwsNoSuchElementException() { @Test void checkoutCart_orderServiceFails_throwsRuntimeException() { - cart.getItems().add(cartItem); + cart.getItems().add(new CartItem(productId, 1)); when(cartRepository.findByCustomerIdAndArchived(customerId, false)).thenReturn(Optional.of(cart)); - when(restTemplate.postForObject(eq("http://localhost:8080/orders"), any(OrderRequest.class), eq(Void.class))) + when(restTemplate.postForObject(eq(orderServiceUrl + "/orders"), any(OrderRequest.class), eq(Void.class))) .thenThrow(new RuntimeException("Order Service error")); RuntimeException exception = assertThrows(RuntimeException.class, () -> cartService.checkoutCart(customerId)); assertEquals("Error communicating with Order Service", exception.getMessage()); verify(cartRepository).findByCustomerIdAndArchived(customerId, false); - verify(restTemplate).postForObject(eq("http://localhost:8080/orders"), any(OrderRequest.class), eq(Void.class)); + verify(restTemplate).postForObject(eq(orderServiceUrl + "/orders"), any(OrderRequest.class), eq(Void.class)); verify(cartRepository, never()).save(any()); } } \ No newline at end of file From bebc8270e8b472fc912dad3e2dd3e1a3ce6db79c Mon Sep 17 00:00:00 2001 From: Abdelrahman Elmeky <65960126+Aelmeky@users.noreply.github.com> Date: Sun, 11 May 2025 22:24:02 +0300 Subject: [PATCH 20/20] reafctor : fixing lint error --- src/main/java/cart/service/CartCommand.java | 2 +- src/main/java/cart/service/CartService.java | 3 +- .../java/cart/service/RemoveItemCommand.java | 2 +- .../cart/service/UpdateQuantityCommand.java | 35 ++++++++++++------- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/main/java/cart/service/CartCommand.java b/src/main/java/cart/service/CartCommand.java index 052da6f..da64aad 100644 --- a/src/main/java/cart/service/CartCommand.java +++ b/src/main/java/cart/service/CartCommand.java @@ -5,4 +5,4 @@ public interface CartCommand { Cart execute(); Cart undo(); -} \ No newline at end of file +} diff --git a/src/main/java/cart/service/CartService.java b/src/main/java/cart/service/CartService.java index 2745f4e..cd53992 100644 --- a/src/main/java/cart/service/CartService.java +++ b/src/main/java/cart/service/CartService.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.NoSuchElementException; -import java.util.Optional; import java.util.UUID; @Service @@ -178,7 +177,7 @@ private Cart getArchivedCart(final String customerId) { return cart; } - public Cart saveCart(Cart cart) { + public Cart saveCart(final Cart cart) { return cartRepository.save(cart); } diff --git a/src/main/java/cart/service/RemoveItemCommand.java b/src/main/java/cart/service/RemoveItemCommand.java index 2745cdc..1486f4a 100644 --- a/src/main/java/cart/service/RemoveItemCommand.java +++ b/src/main/java/cart/service/RemoveItemCommand.java @@ -54,4 +54,4 @@ public Cart undo() { log.debug("RemoveItemCommand undone, updated cart: {}", updatedCart); return updatedCart; } -} \ No newline at end of file +} diff --git a/src/main/java/cart/service/UpdateQuantityCommand.java b/src/main/java/cart/service/UpdateQuantityCommand.java index 02dbc66..0b3f4b7 100644 --- a/src/main/java/cart/service/UpdateQuantityCommand.java +++ b/src/main/java/cart/service/UpdateQuantityCommand.java @@ -17,11 +17,13 @@ public class UpdateQuantityCommand implements CartCommand { private final String customerId; private final String productId; private final int newQuantity; - private Integer previousQuantity; // For undo + private Integer previousQuantity; @Override public Cart execute() { - log.debug("Executing UpdateQuantityCommand for customerId: {}, productId: {}, quantity: {}", customerId, productId, newQuantity); + log.debug("Executing UpdateQuantityCommand " + + "for customerId: {}, productId: {}, quantity: " + + "{}", customerId, productId, newQuantity); Cart cart = cartService.getCartByCustomerId(customerId); Optional existingItemOpt = cart.getItems().stream() @@ -29,18 +31,22 @@ public Cart execute() { .findFirst(); if (existingItemOpt.isEmpty()) { - log.error("Product not found in cart for productId: {}", productId); - throw new GlobalHandlerException(HttpStatus.NOT_FOUND, "Product not found in cart"); + log.error("Product not found in cart for " + + "productId: {}", productId); + throw new GlobalHandlerException( + HttpStatus.NOT_FOUND, "Product not found in cart"); } CartItem item = existingItemOpt.get(); previousQuantity = item.getQuantity(); if (newQuantity <= 0) { - log.debug("Removing item as quantity <= 0 for productId: {}", productId); + log.debug("Removing item as quantity <= 0 for" + + " productId: {}", productId); cart.getItems().remove(item); } else { - log.debug("Updating quantity to: {} for productId: {}", newQuantity, productId); + log.debug("Updating quantity to: {} for " + + "productId: {}", newQuantity, productId); item.setQuantity(newQuantity); } @@ -51,7 +57,8 @@ public Cart execute() { @Override public Cart undo() { - log.debug("Undoing UpdateQuantityCommand for customerId: {}, productId: {}", customerId, productId); + log.debug("Undoing UpdateQuantityCommand for" + + " customerId: {}, productId: {}", customerId, productId); Cart cart = cartService.getCartByCustomerId(customerId); if (previousQuantity == null) { @@ -64,18 +71,22 @@ public Cart undo() { .findFirst(); if (previousQuantity <= 0) { - log.debug("Restoring removed item during undo for productId: {}", productId); + log.debug("Restoring removed item during " + + "undo for productId: {}", productId); cart.getItems().add(new CartItem(productId, previousQuantity)); } else if (existingItemOpt.isPresent()) { - log.debug("Restoring previous quantity during undo for productId: {}", productId); + log.debug("Restoring previous quantity " + + "during undo for productId: {}", productId); existingItemOpt.get().setQuantity(previousQuantity); } else { - log.debug("Adding item back during undo for productId: {}", productId); + log.debug("Adding item back during" + + " undo for productId: {}", productId); cart.getItems().add(new CartItem(productId, previousQuantity)); } Cart updatedCart = cartService.saveCart(cart); - log.debug("UpdateQuantityCommand undone, updated cart: {}", updatedCart); + log.debug("UpdateQuantityCommand undone," + + " updated cart: {}", updatedCart); return updatedCart; } -} \ No newline at end of file +}