Skip to content

Commit

Permalink
Merge pull request #4 from KBTG-Kampus-ClassNest-SE-Java/story-4
Browse files Browse the repository at this point in the history
Story 4
  • Loading branch information
forceattack012 authored Mar 23, 2024
2 parents 6ab4ff9 + f1fff50 commit 34283df
Show file tree
Hide file tree
Showing 12 changed files with 291 additions and 14 deletions.
5 changes: 3 additions & 2 deletions kbazaar/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ dependencies {
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.projectlombok:lombok:1.18.22'
implementation 'org.postgresql:postgresql:42.7.3'
implementation 'org.springframework.boot:spring-boot-starter-validation:3.2.2'

implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
Expand Down
25 changes: 19 additions & 6 deletions kbazaar/src/main/java/com/kampus/kbazaar/cart/Cart.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
package com.kampus.kbazaar.cart;

import com.kampus.kbazaar.product.Product;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Positive;
import lombok.Data;

@Entity(name = "cart")
@Data
public class Cart {
private int userID;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private Product[] products;
@NotBlank @NotEmpty private String username;

public Cart(int userID, Product[] products) {
this.userID = userID;
this.products = products;
@NotBlank @NotEmpty private String sku;

@Positive private int quantity;

public Cart() {}

public Cart(String username, String sku, int quantity) {
this.username = username;
this.sku = sku;
this.quantity = quantity;
}
}
17 changes: 17 additions & 0 deletions kbazaar/src/main/java/com/kampus/kbazaar/cart/CartController.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
package com.kampus.kbazaar.cart;

import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1")
public class CartController {

private final CartService cartService;

public CartController(CartService cartService) {
this.cartService = cartService;
}

@GetMapping("/carts")
public ResponseEntity getCart() { // NOSONAR
return ResponseEntity.ok().build();
}

@PostMapping("/carts/{username}/items")
public ResponseEntity<CartResponse> addCart(
@PathVariable("username") String userName,
@RequestBody @Valid CartRequest cartRequest) {

CartResponse cartResponse = cartService.addCart(userName, cartRequest);
return ResponseEntity.status(HttpStatus.CREATED).body(cartResponse);
}
}
13 changes: 13 additions & 0 deletions kbazaar/src/main/java/com/kampus/kbazaar/cart/CartRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.kampus.kbazaar.cart;

import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CartRepository extends JpaRepository<Cart, Long> {
Optional<Cart> findByUsernameAndSku(String username, String sku);

List<Cart> findAllByUsername(String username);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.kampus.kbazaar.cart;

import jakarta.validation.constraints.*;

public record CartRequest(@NotEmpty String productSku, @Positive int quantity) {}
11 changes: 11 additions & 0 deletions kbazaar/src/main/java/com/kampus/kbazaar/cart/CartResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.kampus.kbazaar.cart;

import com.kampus.kbazaar.product.ProductResponseWithDiscount;
import java.math.BigDecimal;
import java.util.List;

public record CartResponse(
String username,
List<ProductResponseWithDiscount> products,
BigDecimal totalPrice,
int totalDiscount) {}
87 changes: 87 additions & 0 deletions kbazaar/src/main/java/com/kampus/kbazaar/cart/CartService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.kampus.kbazaar.cart;

import com.kampus.kbazaar.product.Product;
import com.kampus.kbazaar.product.ProductResponse;
import com.kampus.kbazaar.product.ProductResponseWithDiscount;
import com.kampus.kbazaar.product.ProductService;
import com.kampus.kbazaar.shopper.ShopperResponse;
import com.kampus.kbazaar.shopper.ShopperService;
import jakarta.transaction.Transactional;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CartService {

private final CartRepository cartRepository;
private final ShopperService shopperService;
private final ProductService productService;

@Autowired
public CartService(
CartRepository cartRepository,
ShopperService shopperService,
ProductService productService) {
this.cartRepository = cartRepository;
this.shopperService = shopperService;
this.productService = productService;
}

@Transactional
public CartResponse addCart(String userName, CartRequest cartRequest) {
ShopperResponse shopperResponse = shopperService.getByUsername(userName);
ProductResponse productResponse = productService.getBySku(cartRequest.productSku());

Optional<Cart> cartOptional =
cartRepository.findByUsernameAndSku(
shopperResponse.username(), productResponse.sku());

if (cartOptional.isEmpty()) {
cartRepository.save(
new Cart(
shopperResponse.username(),
productResponse.sku(),
cartRequest.quantity()));
} else {
Cart cart = cartOptional.get();
cart.setQuantity(cartRequest.quantity());
cartRepository.save(cart);
}

List<ProductResponseWithDiscount> productList = new ArrayList<>();
BigDecimal totalPrice = BigDecimal.ZERO;

List<Cart> cartList = cartRepository.findAllByUsername(userName);

for (Cart cart : cartList) {
ProductResponse productServiceBySku = productService.getBySku(cart.getSku());
Product p =
new Product(
productServiceBySku.id(),
productServiceBySku.name(),
productServiceBySku.sku(),
productServiceBySku.price(),
cart.getQuantity());
BigDecimal finalPrice =
productServiceBySku.price().multiply(new BigDecimal(cart.getQuantity()));
productList.add(
new ProductResponseWithDiscount(
p.getName(),
p.getSku(),
p.getPrice(),
cart.getQuantity(),
0,
finalPrice));

BigDecimal total =
productServiceBySku.price().multiply(new BigDecimal(cart.getQuantity()));
totalPrice = totalPrice.add(total);
}

return new CartResponse(userName, productList, totalPrice, 0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.kampus.kbazaar.product;

import java.math.BigDecimal;

public record ProductResponseWithDiscount(
String name,
String sku,
BigDecimal price,
int quantity,
int discount,
BigDecimal finalPrice) {}
4 changes: 4 additions & 0 deletions kbazaar/src/main/resources/sql/data/cart.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Smartphones
INSERT INTO cart (username, sku, quantity) VALUES ('GeekChic','MOBILE-APPLE-IPHONE-12-PRO', 1) ON CONFLICT DO NOTHING;

INSERT INTO cart (username, sku, quantity) VALUES ('GeekChic','BEV-COCA-COLA', 1) ON CONFLICT DO NOTHING;
8 changes: 8 additions & 0 deletions kbazaar/src/main/resources/sql/schema/cart.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
DROP TABLE IF EXISTS cart;

CREATE TABLE IF NOT EXISTS cart (
id SERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
sku VARCHAR(255) NOT NULL,
quantity INT NOT NULL
);
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
package com.kampus.kbazaar.cart;

import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.core.Is.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.kampus.kbazaar.security.JwtAuthFilter;
import java.math.BigDecimal;
import java.util.ArrayList;
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.MockitoAnnotations;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;

@ExtendWith(SpringExtension.class)
@ExtendWith(MockitoExtension.class)
@AutoConfigureMockMvc(addFilters = false)
@WebMvcTest(
controllers = CartController.class,
excludeFilters =
Expand All @@ -28,18 +39,39 @@
public class CartControllerTest {

@Autowired private MockMvc mockMvc;
private ObjectMapper objectMapper;

@InjectMocks private CartController cartController;
@MockBean private CartService cartService;

@BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
CartController cartController = new CartController(cartService);
this.mockMvc = MockMvcBuilders.standaloneSetup(cartController).build();
objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}

@Test
public void getCart_ReturnsOk() throws Exception {
mockMvc.perform(get("/api/v1/carts").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}

@Test
public void addCart_ReturnsOK() throws Exception {
CartRequest cartRequest = new CartRequest("sku-d1", 100);
CartResponse cartResponse = cartService.addCart("Ninja", cartRequest);
when(cartService.addCart("Ninja", cartRequest))
.thenReturn(
new CartResponse(
"Ninja", new ArrayList<>(), BigDecimal.valueOf(123.1), 100));

mockMvc.perform(
post("/api/v1/carts/Ninja/items")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(cartRequest)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.username", is("Ninja")))
.andExpect(jsonPath("$.products", hasSize(0)));
}
}
75 changes: 75 additions & 0 deletions kbazaar/src/test/java/com/kampus/kbazaar/cart/CartServiceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.kampus.kbazaar.cart;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import com.kampus.kbazaar.product.ProductResponse;
import com.kampus.kbazaar.product.ProductService;
import com.kampus.kbazaar.shopper.ShopperResponse;
import com.kampus.kbazaar.shopper.ShopperService;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Assertions;
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;

@ExtendWith(MockitoExtension.class)
public class CartServiceTest {
@InjectMocks private CartService cartService;
@Mock private CartRepository cartRepository;
@Mock private ShopperService shopperService;
@Mock private ProductService productService;

@Test
public void addNewCartSuccess() {
CartRequest cartRequest = new CartRequest("MOBILE-LG-VELVET", 6);
String userName = "GeekChic";
String sku = "MOBILE-LG-VELVET";

Cart cart = new Cart(userName, sku, 6);
ProductResponse productResponse =
new ProductResponse(1L, "apple", sku, new BigDecimal("1000"), 10);

when(cartRepository.findByUsernameAndSku(any(), any())).thenReturn(Optional.empty());
when(shopperService.getByUsername(userName))
.thenReturn(new ShopperResponse(1L, userName, "email"));
when(productService.getBySku(sku)).thenReturn(productResponse);
when(cartRepository.save(any())).thenReturn(cart);
when(cartRepository.findAllByUsername(userName)).thenReturn(List.of(cart));

CartResponse cartResponse = cartService.addCart(userName, cartRequest);

Assertions.assertEquals(cartResponse.totalPrice(), new BigDecimal("6000"));
Assertions.assertEquals(userName, cartResponse.username());
Assertions.assertEquals(sku, cartResponse.products().get(0).sku());

verify(cartRepository, times(1)).save(any());
}

@Test
public void updateOldCartSuccess() {
CartRequest cartRequest = new CartRequest("MOBILE-LG-VELVET", 6);
String userName = "GeekChic";
String sku = "MOBILE-LG-VELVET";

Cart cart = new Cart(userName, sku, 1);
ProductResponse productResponse =
new ProductResponse(1L, "apple", sku, new BigDecimal("1000"), 1);

when(cartRepository.findByUsernameAndSku(any(), any())).thenReturn(Optional.of(cart));
when(shopperService.getByUsername(userName))
.thenReturn(new ShopperResponse(1L, userName, "email"));
when(productService.getBySku(sku)).thenReturn(productResponse);
when(cartRepository.save(any())).thenReturn(cart);
when(cartRepository.findAllByUsername(userName)).thenReturn(List.of(cart));

CartResponse cartResponse = cartService.addCart(userName, cartRequest);

Assertions.assertEquals(cartResponse.totalPrice(), new BigDecimal("6000"));
verify(cartRepository, times(1)).save(any());
}
}

0 comments on commit 34283df

Please sign in to comment.