diff --git a/pom.xml b/pom.xml
index 77c6e23..c15391d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,10 +9,10 @@
com.learning
- SpringSecurity
+ YasMiniShop
1.0.0
- SpringSecurity
- SpringSecurity
+ YasMini
+ YasMini CarShop
21
1.18.30
@@ -25,6 +25,7 @@
0.8.12
5.1.2
9.2.0
+ 26.32.0
@@ -35,6 +36,15 @@
pom
import
+
+
+ com.google.cloud
+ libraries-bom
+ ${google-cloud-bom.version}
+ pom
+ import
+
+
@@ -106,6 +116,11 @@
spring-boot-starter-mail
+
+ com.google.cloud
+ google-cloud-vertexai
+
+
org.springframework.boot
spring-boot-starter-test
@@ -183,16 +198,15 @@
+ **/common/**
**/dto/**
- **/entity/**
**/mapper/**
- **/configs/**
-
+
src/main/resources
true
diff --git a/src/main/java/com/learning/yasminishop/cart/CartItemRepository.java b/src/main/java/com/learning/yasminishop/cart/CartItemRepository.java
index fc0e2c2..cff7711 100644
--- a/src/main/java/com/learning/yasminishop/cart/CartItemRepository.java
+++ b/src/main/java/com/learning/yasminishop/cart/CartItemRepository.java
@@ -13,4 +13,5 @@
public interface CartItemRepository extends JpaRepository{
Optional findByProductAndUser(Product product, User user);
List findAllByUserOrderByLastModifiedDateDesc(User user);
+ List findAllByUserAndProductIsAvailable(User user, Boolean isAvailable);
}
diff --git a/src/main/java/com/learning/yasminishop/common/configs/GeminiConfig.java b/src/main/java/com/learning/yasminishop/common/configs/GeminiConfig.java
new file mode 100644
index 0000000..d96d405
--- /dev/null
+++ b/src/main/java/com/learning/yasminishop/common/configs/GeminiConfig.java
@@ -0,0 +1,33 @@
+package com.learning.yasminishop.common.configs;
+
+import com.google.cloud.vertexai.VertexAI;
+import com.google.cloud.vertexai.generativeai.ChatSession;
+import com.google.cloud.vertexai.generativeai.GenerativeModel;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration(proxyBeanMethods = false)
+public class GeminiConfig {
+
+ private static final String MODEL_NAME = "gemini-1.5-flash";
+ private static final String LOCATION = "asia-southeast1";
+ private static final String PROJECT_ID = "yasmini";
+
+ @Bean
+ public VertexAI vertexAI() {
+ return new VertexAI(PROJECT_ID, LOCATION);
+ }
+
+ @Bean
+ public GenerativeModel getModel(VertexAI vertexAI) {
+ return new GenerativeModel(MODEL_NAME, vertexAI);
+ }
+
+ @Bean
+ public ChatSession chatSession(@Qualifier("getModel") GenerativeModel generativeModel) {
+ return new ChatSession(generativeModel);
+ }
+
+
+}
diff --git a/src/main/java/com/learning/yasminishop/common/configs/security/SecurityConfiguration.java b/src/main/java/com/learning/yasminishop/common/configs/security/SecurityConfiguration.java
index ad49afe..d2668fe 100644
--- a/src/main/java/com/learning/yasminishop/common/configs/security/SecurityConfiguration.java
+++ b/src/main/java/com/learning/yasminishop/common/configs/security/SecurityConfiguration.java
@@ -32,7 +32,8 @@ public class SecurityConfiguration {
"/categories/{slug}",
"/products",
"/products/{slug}",
- "/rating"
+ "/rating",
+ "notifications/**",
};
private static final String[] ALLOWED_METHODS = {
diff --git a/src/main/java/com/learning/yasminishop/common/entity/Notification.java b/src/main/java/com/learning/yasminishop/common/entity/Notification.java
new file mode 100644
index 0000000..6b6df63
--- /dev/null
+++ b/src/main/java/com/learning/yasminishop/common/entity/Notification.java
@@ -0,0 +1,35 @@
+package com.learning.yasminishop.common.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+import lombok.experimental.FieldDefaults;
+
+@Entity
+@Table(name = "t_notification")
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@FieldDefaults(level = AccessLevel.PRIVATE)
+public class Notification extends AuditEntity{
+ @Id
+ @GeneratedValue(strategy = GenerationType.UUID)
+ String id;
+
+ @Column(columnDefinition = "TEXT")
+ String title;
+
+ @Column(columnDefinition = "TEXT")
+ String content;
+
+ String thumbnail;
+
+ String link;
+
+ Boolean isRead = false;
+
+ @ManyToOne
+ User user;
+
+}
diff --git a/src/main/java/com/learning/yasminishop/common/entity/Product.java b/src/main/java/com/learning/yasminishop/common/entity/Product.java
index 2d8eabf..58ec33a 100644
--- a/src/main/java/com/learning/yasminishop/common/entity/Product.java
+++ b/src/main/java/com/learning/yasminishop/common/entity/Product.java
@@ -55,7 +55,13 @@ public class Product extends AuditEntity{
@ManyToMany
private Set categories;
- @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
+ @OneToMany(cascade = CascadeType.ALL)
private Set images;
+ @OneToMany(cascade = CascadeType.ALL)
+ private Set orderItems;
+
+ @OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
+ private Set cartItems;
+
}
diff --git a/src/main/java/com/learning/yasminishop/common/entity/User.java b/src/main/java/com/learning/yasminishop/common/entity/User.java
index 32cbc97..1bd4821 100644
--- a/src/main/java/com/learning/yasminishop/common/entity/User.java
+++ b/src/main/java/com/learning/yasminishop/common/entity/User.java
@@ -47,4 +47,7 @@ public class User extends AuditEntity {
@ManyToMany(fetch = FetchType.LAZY)
private Set roles;
+ @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
+ private Set notifications;
+
}
diff --git a/src/main/java/com/learning/yasminishop/common/exception/ErrorCode.java b/src/main/java/com/learning/yasminishop/common/exception/ErrorCode.java
index 959fa6a..4d919f3 100644
--- a/src/main/java/com/learning/yasminishop/common/exception/ErrorCode.java
+++ b/src/main/java/com/learning/yasminishop/common/exception/ErrorCode.java
@@ -34,6 +34,9 @@ public enum ErrorCode {
PRODUCT_STOCK_NOT_ENOUGH(1021, "Product stock is not enough", HttpStatus.BAD_REQUEST),
CART_ITEM_NOT_FOUND(1022, "Cart item not found", HttpStatus.NOT_FOUND),
ORDER_NOT_FOUND(1023, "Order not found", HttpStatus.NOT_FOUND),
+ GENERATIVE_AI_ERROR(1024, "Error in generative AI", HttpStatus.INTERNAL_SERVER_ERROR),
+ PRODUCT_IN_ORDER(1025, "Product is in order", HttpStatus.BAD_REQUEST),
+ PRODUCT_IN_CART(1026, "Product is in cart", HttpStatus.BAD_REQUEST),
// Constraint violation
diff --git a/src/main/java/com/learning/yasminishop/notification/NotificationController.java b/src/main/java/com/learning/yasminishop/notification/NotificationController.java
new file mode 100644
index 0000000..2743332
--- /dev/null
+++ b/src/main/java/com/learning/yasminishop/notification/NotificationController.java
@@ -0,0 +1,36 @@
+package com.learning.yasminishop.notification;
+
+import com.learning.yasminishop.common.dto.APIResponse;
+import com.learning.yasminishop.notification.dto.response.NotificationResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/notifications")
+@RequiredArgsConstructor
+@Slf4j
+public class NotificationController {
+
+ private final NotificationService notificationService;
+
+ @GetMapping(path = "/subscribe/{token}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+ public SseEmitter subscribe( @PathVariable String token) {
+ SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
+ notificationService.addEmitter(emitter, token);
+ return emitter;
+ }
+
+ @GetMapping
+ public APIResponse> getNotifications() {
+ List notifications = notificationService.getNotifications();
+ return APIResponse.>builder()
+ .result(notifications)
+ .build();
+ }
+
+}
diff --git a/src/main/java/com/learning/yasminishop/notification/NotificationRepository.java b/src/main/java/com/learning/yasminishop/notification/NotificationRepository.java
new file mode 100644
index 0000000..ee078b1
--- /dev/null
+++ b/src/main/java/com/learning/yasminishop/notification/NotificationRepository.java
@@ -0,0 +1,12 @@
+package com.learning.yasminishop.notification;
+
+import com.learning.yasminishop.common.entity.Notification;
+import com.learning.yasminishop.common.entity.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface NotificationRepository extends JpaRepository{
+
+ List findAllByUserOrderByCreatedDateDesc(User user);
+}
diff --git a/src/main/java/com/learning/yasminishop/notification/NotificationService.java b/src/main/java/com/learning/yasminishop/notification/NotificationService.java
new file mode 100644
index 0000000..72aeb6c
--- /dev/null
+++ b/src/main/java/com/learning/yasminishop/notification/NotificationService.java
@@ -0,0 +1,80 @@
+package com.learning.yasminishop.notification;
+
+import com.learning.yasminishop.common.configs.security.JwtService;
+import com.learning.yasminishop.common.entity.Notification;
+import com.learning.yasminishop.common.entity.User;
+import com.learning.yasminishop.common.exception.AppException;
+import com.learning.yasminishop.common.exception.ErrorCode;
+import com.learning.yasminishop.notification.dto.response.NotificationResponse;
+import com.learning.yasminishop.notification.mapper.NotificationMapper;
+import com.learning.yasminishop.user.UserRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+@Transactional(readOnly = true)
+public class NotificationService {
+
+ private final NotificationRepository notificationRepository;
+ private final UserRepository userRepository;
+ private final NotificationMapper notificationMapper;
+
+ private final JwtService jwtService;
+ private final Map emitters = new ConcurrentHashMap<>();
+
+
+ public void addEmitter(SseEmitter emitter, String token) {
+
+ if(!jwtService.isTokenValid(token)) {
+ log.error("Token is not valid");
+ emitter.completeWithError(new AppException(ErrorCode.INVALID_TOKEN));
+ return;
+ }
+
+ String email = jwtService.extractUserEmail(token);
+ log.info("User with email {} subscribed to notifications", email);
+ emitters.put(email, emitter);
+ }
+
+ public void sendNotification(String clientId, NotificationResponse notificationResponse) {
+ SseEmitter emitter = emitters.get(clientId);
+ if (emitter != null) {
+ try {
+ emitter.send(notificationResponse);
+ } catch (IOException e) {
+ emitter.completeWithError(e);
+ emitters.remove(clientId);
+ }
+ }
+ }
+
+ public List getNotifications() {
+ String email = SecurityContextHolder.getContext().getAuthentication().getName();
+
+ User user = userRepository.findByEmail(email)
+ .orElseThrow(() -> new AppException(ErrorCode.USER_NOT_FOUND));
+
+ List notifications = notificationRepository.findAllByUserOrderByCreatedDateDesc(user);
+ return notifications
+ .stream()
+ .map(notificationMapper::toNotificationResponse)
+ .toList();
+ }
+
+ public void createNotification(Notification notification) {
+ notificationRepository.save(notification);
+ sendNotification(notification.getUser().getEmail(), notificationMapper.toNotificationResponse(notification));
+ }
+
+}
diff --git a/src/main/java/com/learning/yasminishop/notification/dto/response/NotificationResponse.java b/src/main/java/com/learning/yasminishop/notification/dto/response/NotificationResponse.java
new file mode 100644
index 0000000..d118d9c
--- /dev/null
+++ b/src/main/java/com/learning/yasminishop/notification/dto/response/NotificationResponse.java
@@ -0,0 +1,26 @@
+package com.learning.yasminishop.notification.dto.response;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.FieldDefaults;
+
+import java.time.LocalDateTime;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@FieldDefaults(level = lombok.AccessLevel.PRIVATE)
+public class NotificationResponse {
+
+ String id;
+ String title;
+ String content;
+ String thumbnail;
+ String link;
+ Boolean isRead;
+
+ LocalDateTime createdDate;
+}
diff --git a/src/main/java/com/learning/yasminishop/notification/mapper/NotificationMapper.java b/src/main/java/com/learning/yasminishop/notification/mapper/NotificationMapper.java
new file mode 100644
index 0000000..f040bab
--- /dev/null
+++ b/src/main/java/com/learning/yasminishop/notification/mapper/NotificationMapper.java
@@ -0,0 +1,10 @@
+package com.learning.yasminishop.notification.mapper;
+
+import com.learning.yasminishop.common.entity.Notification;
+import com.learning.yasminishop.notification.dto.response.NotificationResponse;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface NotificationMapper {
+ NotificationResponse toNotificationResponse(Notification notification);
+}
diff --git a/src/main/java/com/learning/yasminishop/order/OrderService.java b/src/main/java/com/learning/yasminishop/order/OrderService.java
index 00aca38..d49af31 100644
--- a/src/main/java/com/learning/yasminishop/order/OrderService.java
+++ b/src/main/java/com/learning/yasminishop/order/OrderService.java
@@ -2,13 +2,11 @@
import com.learning.yasminishop.cart.CartItemRepository;
import com.learning.yasminishop.common.dto.PaginationResponse;
-import com.learning.yasminishop.common.entity.CartItem;
-import com.learning.yasminishop.common.entity.Order;
-import com.learning.yasminishop.common.entity.OrderItem;
-import com.learning.yasminishop.common.entity.User;
+import com.learning.yasminishop.common.entity.*;
import com.learning.yasminishop.common.enumeration.EOrderStatus;
import com.learning.yasminishop.common.exception.AppException;
import com.learning.yasminishop.common.exception.ErrorCode;
+import com.learning.yasminishop.notification.NotificationService;
import com.learning.yasminishop.order.dto.filter.OrderFilter;
import com.learning.yasminishop.order.dto.request.OrderRequest;
import com.learning.yasminishop.order.dto.response.OrderAdminResponse;
@@ -39,6 +37,8 @@ public class OrderService {
private final UserRepository userRepository;
private final CartItemRepository cartItemRepository;
+ private final NotificationService notificationService;
+
private final OrderMapper orderMapper;
@@ -51,7 +51,7 @@ public OrderResponse create(OrderRequest orderRequest) {
User user = userRepository.findByEmail(email).orElseThrow(() -> new AppException(ErrorCode.USER_NOT_FOUND));
// get the cart items of the user
- List cartItems = cartItemRepository.findAllByUserOrderByLastModifiedDateDesc(user);
+ List cartItems = cartItemRepository.findAllByUserAndProductIsAvailable(user, true);
Set cartItemRequestIds = orderRequest.getCartItemIds();
// Extract IDs from cartItems
@@ -75,7 +75,6 @@ public OrderResponse create(OrderRequest orderRequest) {
}
-
@PreAuthorize("hasRole('USER')")
public List getAllOrderByUser() {
String email = SecurityContextHolder.getContext().getAuthentication().getName();
@@ -131,6 +130,8 @@ public void updateOrderStatus(String id, String status) {
order.setStatus(EOrderStatus.valueOf(status));
orderRepository.save(order);
+
+ sendNotification(order);
}
private Order createAOrder(OrderRequest orderRequest, User user, List cartItemsToOrder) {
@@ -167,5 +168,18 @@ private Order createAOrder(OrderRequest orderRequest, User user, List
return order;
}
+ private void sendNotification(Order order) {
+ OrderItem orderItem = order.getOrderItems().stream().findFirst().orElseThrow(() -> new AppException(ErrorCode.ORDER_NOT_FOUND));
+ Notification notification = Notification.builder()
+ .user(order.getUser())
+ .title("Your order status has been updated")
+ .content("Your order status has been updated to " + order.getStatus())
+ .isRead(false)
+ .thumbnail(orderItem.getProduct().getThumbnail())
+ .link("/order/" + order.getId())
+ .build();
+
+ notificationService.createNotification(notification);
+ }
}
diff --git a/src/main/java/com/learning/yasminishop/product/ProductRepository.java b/src/main/java/com/learning/yasminishop/product/ProductRepository.java
index 2c95008..37d8e39 100644
--- a/src/main/java/com/learning/yasminishop/product/ProductRepository.java
+++ b/src/main/java/com/learning/yasminishop/product/ProductRepository.java
@@ -3,6 +3,8 @@
import com.learning.yasminishop.common.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@@ -10,6 +12,7 @@
@Repository
public interface ProductRepository extends JpaRepository , JpaSpecificationExecutor {
+
boolean existsBySlug(String slug);
boolean existsBySku(String sku);
@@ -17,5 +20,8 @@ public interface ProductRepository extends JpaRepository , JpaS
Optional findBySlug(String slug);
List findTop10ByOrderByQuantityDesc();
+
+ @Query("SELECT p FROM Product p WHERE LOWER(p.name) LIKE LOWER(CONCAT('%', :keyword, '%'))")
+ List searchByKeyword(@Param("keyword") String keyword);
}
diff --git a/src/main/java/com/learning/yasminishop/product/ProductService.java b/src/main/java/com/learning/yasminishop/product/ProductService.java
index 29e916d..5886af7 100644
--- a/src/main/java/com/learning/yasminishop/product/ProductService.java
+++ b/src/main/java/com/learning/yasminishop/product/ProductService.java
@@ -42,8 +42,7 @@ public class ProductService {
public PaginationResponse getAllProducts(
ProductFilter productFilter,
- Pageable pageable)
- {
+ Pageable pageable) {
// check if the categoryIds are valid
List categories = categoryRepository.findAllById(List.of(productFilter.getCategoryIds()));
@@ -123,8 +122,7 @@ public ProductAdminResponse getById(String id) {
@PreAuthorize("hasRole('ADMIN')")
public PaginationResponse getAllProductsForAdmin(
ProductFilter productFilter,
- Pageable pageable)
- {
+ Pageable pageable) {
// check if the categoryIds are valid
List categories = categoryRepository.findAllById(List.of(productFilter.getCategoryIds()));
@@ -195,8 +193,7 @@ public ProductAdminResponse update(String id, ProductRequest productUpdate) {
productMapper.updateProduct(product, productUpdate);
product.setCategories(new HashSet<>(categories));
- product.getImages().clear();
- product.getImages().addAll(images); // update images
+ product.setImages(new HashSet<>(images)); // update images
product.setThumbnail(images.getFirst().getUrl());
return productMapper.toProductAdminResponse(productRepository.save(product));
@@ -211,6 +208,18 @@ public void delete(List ids) {
if (products.size() != ids.size()) {
throw new AppException(ErrorCode.PRODUCT_NOT_FOUND);
}
+
+ // check if the product is in any order, cart
+ for (Product product : products) {
+
+ if (!product.getOrderItems().isEmpty()) {
+ throw new AppException(ErrorCode.PRODUCT_IN_ORDER);
+ }
+ if (!product.getCartItems().isEmpty()) {
+ throw new AppException(ErrorCode.PRODUCT_IN_CART);
+ }
+ }
+
productRepository.deleteAll(products);
}
diff --git a/src/main/java/com/learning/yasminishop/yasminiai/YasMiniAIController.java b/src/main/java/com/learning/yasminishop/yasminiai/YasMiniAIController.java
new file mode 100644
index 0000000..15e2295
--- /dev/null
+++ b/src/main/java/com/learning/yasminishop/yasminiai/YasMiniAIController.java
@@ -0,0 +1,47 @@
+package com.learning.yasminishop.yasminiai;
+
+import com.learning.yasminishop.common.dto.APIResponse;
+import com.learning.yasminishop.product.dto.response.ProductResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/ai")
+@RequiredArgsConstructor
+@Slf4j
+public class YasMiniAIController {
+
+ private final YasMiniAIService yasMiniAIService;
+
+
+ @PostMapping
+ public APIResponse> findCar(@RequestParam("file") MultipartFile file) {
+
+ var response = yasMiniAIService.findCarByImage(file);
+ return APIResponse.>builder()
+ .result(response)
+ .build();
+ }
+
+
+ @GetMapping("/{text}")
+ public APIResponse chat(@PathVariable String text) {
+ String chatResponse = yasMiniAIService.generateText(text);
+ return APIResponse.builder()
+ .result(chatResponse)
+ .build();
+ }
+
+ @GetMapping("history/{text}")
+ public APIResponse> getChatHistory(@PathVariable String text) {
+
+ var response = yasMiniAIService.generateTextWithHistory(text);
+ return APIResponse.>builder()
+ .result(response)
+ .build();
+ }
+}
diff --git a/src/main/java/com/learning/yasminishop/yasminiai/YasMiniAIService.java b/src/main/java/com/learning/yasminishop/yasminiai/YasMiniAIService.java
new file mode 100644
index 0000000..71689d8
--- /dev/null
+++ b/src/main/java/com/learning/yasminishop/yasminiai/YasMiniAIService.java
@@ -0,0 +1,110 @@
+package com.learning.yasminishop.yasminiai;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.cloud.vertexai.api.Content;
+import com.google.cloud.vertexai.api.GenerateContentResponse;
+import com.google.cloud.vertexai.api.Part;
+import com.google.cloud.vertexai.generativeai.*;
+import com.learning.yasminishop.common.entity.Product;
+import com.learning.yasminishop.common.exception.AppException;
+import com.learning.yasminishop.common.exception.ErrorCode;
+import com.learning.yasminishop.product.ProductRepository;
+import com.learning.yasminishop.product.dto.response.ProductResponse;
+import com.learning.yasminishop.product.mapper.ProductMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+@Service
+@RequiredArgsConstructor
+@Slf4j
+@Transactional(readOnly = true)
+public class YasMiniAIService {
+ private final ChatSession chatSession;
+
+ private final GenerativeModel generativeModel;
+ private final ProductRepository productRepository;
+
+ private final ProductMapper productMapper;
+
+
+ @PreAuthorize("hasRole('USER')")
+ public String generateText(String prompt){
+ try {
+ GenerateContentResponse generateContentResponse = chatSession.sendMessage(prompt);
+ return ResponseHandler.getText(generateContentResponse);
+ } catch (Exception e) {
+ log.error("Error generating text for prompt: {}", prompt, e);
+ throw new AppException(ErrorCode.GENERATIVE_AI_ERROR);
+ }
+ }
+
+ @PreAuthorize("hasRole('USER')")
+ public List generateTextWithHistory(String prompt){
+ try {
+ this.chatSession.sendMessage(prompt);
+
+ List history = this.chatSession.getHistory();
+
+ return history.stream().flatMap(h -> h.getPartsList().stream()).map(Part::getText).toList();
+ } catch (Exception e) {
+ log.error("Error generating text for prompt: {}", prompt, e);
+ throw new AppException(ErrorCode.GENERATIVE_AI_ERROR);
+ }
+ }
+
+ @PreAuthorize("hasRole('USER')")
+ public List findCarByImage(MultipartFile file){
+ try {
+ var prompt = "Extract the name car to a list keyword and output them in JSON. If you don't find any information about the car, please output the list empty.\nExample response: [\"rolls\", \"royce\", \"wraith\"]";
+ var content = this.generativeModel.generateContent(
+ ContentMaker.fromMultiModalData(
+ PartMaker.fromMimeTypeAndData(Objects.requireNonNull(file.getContentType()), file.getBytes()),
+ prompt
+ )
+ );
+
+ String jsonContent = ResponseHandler.getText(content);
+ log.info("Extracted keywords from image: {}", jsonContent);
+ List keywords = convertJsonToList(jsonContent).stream()
+ .map(String::toLowerCase)
+ .toList();
+
+ Set results = new HashSet<>();
+ for (String keyword : keywords) {
+ List products = productRepository.searchByKeyword(keyword);
+ results.addAll(products);
+ }
+
+ return results.stream()
+ .map(productMapper::toProductResponse)
+ .toList();
+
+ } catch (Exception e) {
+ log.error("Error finding car by image", e);
+ throw new AppException(ErrorCode.GENERATIVE_AI_ERROR);
+ }
+ }
+
+ private List convertJsonToList(String markdown) throws JsonProcessingException {
+ ObjectMapper objectMapper = new ObjectMapper();
+ String parseJson = markdown;
+ if(markdown.contains("```json")){
+ parseJson = extractJsonFromMarkdown(markdown);
+ }
+ return objectMapper.readValue(parseJson, List.class);
+ }
+
+ private String extractJsonFromMarkdown(String markdown) {
+ return markdown.replace("```json\n", "").replace("\n```", "");
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index f829ca2..e5bf125 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -7,6 +7,8 @@ spring:
multipart:
max-file-size: 5MB
max-request-size: 5MB
+ jpa:
+ open-in-view: false
server:
port: 8080
@@ -89,7 +91,7 @@ application:
username: ${ADMIN_USERNAME}
password: ${ADMIN_PASSWORD}
firebase:
- json-file: ${FIREBASE_JSON_FILE}
+ json-file: ${FIREBASE_JSON_FILE_PATH}
storage-bucket: ${FIREBASE_STORAGE_BUCKET}
springdoc:
@@ -133,7 +135,7 @@ application:
username: ${ADMIN_USERNAME}
password: ${ADMIN_PASSWORD}
firebase:
- json-file: ${FIREBASE_JSON_FILE}
+ json-file: ${FIREBASE_JSON_FILE_PATH}
storage-bucket: ${FIREBASE_STORAGE_BUCKET}
springdoc:
diff --git a/src/test/java/com/learning/yasminishop/controller/AuthenticationControllerTest.java b/src/test/java/com/learning/yasminishop/controller/AuthenticationControllerTest.java
index 578fac3..f65f4a4 100644
--- a/src/test/java/com/learning/yasminishop/controller/AuthenticationControllerTest.java
+++ b/src/test/java/com/learning/yasminishop/controller/AuthenticationControllerTest.java
@@ -10,6 +10,7 @@
import com.learning.yasminishop.auth.dto.response.TokenResponse;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@@ -77,192 +78,200 @@ void setUp() {
}
- @Test
- void register_validRequest_success() throws Exception {
- // GIVEN
- ObjectMapper objectMapper = new ObjectMapper();
- String registerRequestJson = objectMapper.writeValueAsString(registerRequest);
+ @Nested
+ class HappyCase {
+ @Test
+ void register_validRequest_success() throws Exception {
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ String registerRequestJson = objectMapper.writeValueAsString(registerRequest);
+
+ when(authenticationService.register(any(RegisterRequest.class)))
+ .thenReturn(authenticationResponse);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.post("/auth/register")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(registerRequestJson))
+ .andExpect(status().isCreated())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result.tokens.access_token").value("accessToken"))
+ .andExpect(jsonPath("result.tokens.refresh_token").value("refreshToken"));
+
+ }
+
+ @Test
+ void authenticate_validRequest_success() throws Exception {
+
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ String authenticationRequestJson = objectMapper.writeValueAsString(authenticationRequest);
+
+ when(authenticationService.authenticate(any(AuthenticationRequest.class)))
+ .thenReturn(authenticationResponse);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.post("/auth/authenticate")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(authenticationRequestJson))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result.tokens.access_token").value("accessToken"))
+ .andExpect(jsonPath("result.tokens.refresh_token").value("refreshToken"));
+
+ }
+
+ @Test
+ void refresh_validRequest_success() throws Exception {
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ String refreshRequestJson = objectMapper.writeValueAsString(refreshRequest);
+
+ when(authenticationService.refresh(any(RefreshRequest.class)))
+ .thenReturn(authenticationResponse);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.post("/auth/refresh")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(refreshRequestJson))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result.tokens.access_token").value("accessToken"))
+ .andExpect(jsonPath("result.tokens.refresh_token").value("refreshToken"));
+
+ }
+
+ @Test
+ void logout_validRequest_success() throws Exception {
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ String logoutRequestJson = objectMapper.writeValueAsString(logoutRequest);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.post("/auth/logout")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(logoutRequestJson))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result").value("Logout successful"));
+ }
- when(authenticationService.register(any(RegisterRequest.class)))
- .thenReturn(authenticationResponse);
-
- // WHEN THEN
- mockMvc.perform(MockMvcRequestBuilders.post("/auth/register")
- .contentType(MediaType.APPLICATION_JSON)
- .content(registerRequestJson))
- .andExpect(status().isCreated())
- .andExpect(jsonPath("internalCode").value(1000))
- .andExpect(jsonPath("result.tokens.access_token").value("accessToken"))
- .andExpect(jsonPath("result.tokens.refresh_token").value("refreshToken"));
-
- }
-
- @Test
- void register_invalidEmail_fail() throws Exception {
- // GIVEN
- ObjectMapper objectMapper = new ObjectMapper();
- registerRequest.setEmail("invalidEmail");
- String registerRequestJson = objectMapper.writeValueAsString(registerRequest);
-
- // WHEN THEN
- mockMvc.perform(MockMvcRequestBuilders.post("/auth/register")
- .contentType(MediaType.APPLICATION_JSON)
- .content(registerRequestJson))
- .andExpect(status().isBadRequest())
- .andExpect(jsonPath("internalCode").value(2002))
- .andExpect(jsonPath("message").value("Invalid email"));
- }
-
- @Test
- void authenticate_validRequest_success() throws Exception {
-
- // GIVEN
- ObjectMapper objectMapper = new ObjectMapper();
- String authenticationRequestJson = objectMapper.writeValueAsString(authenticationRequest);
-
- when(authenticationService.authenticate(any(AuthenticationRequest.class)))
- .thenReturn(authenticationResponse);
-
- // WHEN THEN
- mockMvc.perform(MockMvcRequestBuilders.post("/auth/authenticate")
- .contentType(MediaType.APPLICATION_JSON)
- .content(authenticationRequestJson))
- .andExpect(status().isOk())
- .andExpect(jsonPath("internalCode").value(1000))
- .andExpect(jsonPath("result.tokens.access_token").value("accessToken"))
- .andExpect(jsonPath("result.tokens.refresh_token").value("refreshToken"));
-
- }
-
- @Test
- void authenticate_invalidEmail_fail() throws Exception {
- // GIVEN
- ObjectMapper objectMapper = new ObjectMapper();
- authenticationRequest.setEmail("invalidEmail");
- String authenticationRequestJson = objectMapper.writeValueAsString(authenticationRequest);
-
- // WHEN THEN
- mockMvc.perform(MockMvcRequestBuilders.post("/auth/authenticate")
- .contentType(MediaType.APPLICATION_JSON)
- .content(authenticationRequestJson))
- .andExpect(status().isBadRequest())
- .andExpect(jsonPath("internalCode").value(2002))
- .andExpect(jsonPath("message").value("Invalid email"));
- }
-
- @Test
- void authenticate_invalidPassword_fail() throws Exception {
- // GIVEN
- ObjectMapper objectMapper = new ObjectMapper();
- authenticationRequest.setPassword("123");
- String authenticationRequestJson = objectMapper.writeValueAsString(authenticationRequest);
-
- // WHEN THEN
- mockMvc.perform(MockMvcRequestBuilders.post("/auth/authenticate")
- .contentType(MediaType.APPLICATION_JSON)
- .content(authenticationRequestJson))
- .andExpect(status().isBadRequest())
- .andExpect(jsonPath("internalCode").value(2001))
- .andExpect(jsonPath("message").value("Password must be at least 6 characters"));
- }
-
- @Test
- void authenticate_nullPassword_fail() throws Exception {
- // GIVEN
- ObjectMapper objectMapper = new ObjectMapper();
- authenticationRequest.setPassword(null);
- String authenticationRequestJson = objectMapper.writeValueAsString(authenticationRequest);
-
- // WHEN THEN
- mockMvc.perform(MockMvcRequestBuilders.post("/auth/authenticate")
- .contentType(MediaType.APPLICATION_JSON)
- .content(authenticationRequestJson))
- .andExpect(status().isBadRequest())
- .andExpect(jsonPath("internalCode").value(2004))
- .andExpect(jsonPath("message").value("\"password\" must not be null"));
}
- @Test
- void authenticate_nullEmail_fail() throws Exception {
- // GIVEN
- ObjectMapper objectMapper = new ObjectMapper();
- authenticationRequest.setEmail(null);
- String authenticationRequestJson = objectMapper.writeValueAsString(authenticationRequest);
-
- // WHEN THEN
- mockMvc.perform(MockMvcRequestBuilders.post("/auth/authenticate")
- .contentType(MediaType.APPLICATION_JSON)
- .content(authenticationRequestJson))
- .andExpect(status().isBadRequest())
- .andExpect(jsonPath("internalCode").value(2004))
- .andExpect(jsonPath("message").value("\"email\" must not be null"));
+ @Nested
+ class UnHappyCase {
+ @Test
+ void register_invalidEmail_fail() throws Exception {
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ registerRequest.setEmail("invalidEmail");
+ String registerRequestJson = objectMapper.writeValueAsString(registerRequest);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.post("/auth/register")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(registerRequestJson))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("internalCode").value(2002))
+ .andExpect(jsonPath("message").value("Invalid email"));
+ }
+
+ @Test
+ void authenticate_invalidEmail_fail() throws Exception {
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ authenticationRequest.setEmail("invalidEmail");
+ String authenticationRequestJson = objectMapper.writeValueAsString(authenticationRequest);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.post("/auth/authenticate")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(authenticationRequestJson))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("internalCode").value(2002))
+ .andExpect(jsonPath("message").value("Invalid email"));
+ }
+
+ @Test
+ void authenticate_invalidPassword_fail() throws Exception {
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ authenticationRequest.setPassword("123");
+ String authenticationRequestJson = objectMapper.writeValueAsString(authenticationRequest);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.post("/auth/authenticate")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(authenticationRequestJson))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("internalCode").value(2001))
+ .andExpect(jsonPath("message").value("Password must be at least 6 characters"));
+ }
+
+ @Test
+ void authenticate_nullPassword_fail() throws Exception {
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ authenticationRequest.setPassword(null);
+ String authenticationRequestJson = objectMapper.writeValueAsString(authenticationRequest);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.post("/auth/authenticate")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(authenticationRequestJson))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("internalCode").value(2004))
+ .andExpect(jsonPath("message").value("\"password\" must not be null"));
+ }
+
+ @Test
+ void authenticate_nullEmail_fail() throws Exception {
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ authenticationRequest.setEmail(null);
+ String authenticationRequestJson = objectMapper.writeValueAsString(authenticationRequest);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.post("/auth/authenticate")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(authenticationRequestJson))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("internalCode").value(2004))
+ .andExpect(jsonPath("message").value("\"email\" must not be null"));
+ }
+
+ @Test
+ void refresh_nullToken_fail() throws Exception {
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ refreshRequest.setRefreshToken(null);
+ String refreshRequestJson = objectMapper.writeValueAsString(refreshRequest);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.post("/auth/refresh")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(refreshRequestJson))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("internalCode").value(2004))
+ .andExpect(jsonPath("message").value("\"refreshToken\" must not be null"));
+ }
+
+ @Test
+ void logout_nullToken_fail() throws Exception {
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ logoutRequest.setToken(null);
+ String logoutRequestJson = objectMapper.writeValueAsString(logoutRequest);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.post("/auth/logout")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(logoutRequestJson))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("internalCode").value(2004))
+ .andExpect(jsonPath("message").value("\"token\" must not be null"));
+ }
}
- @Test
- void refresh_validRequest_success() throws Exception {
- // GIVEN
- ObjectMapper objectMapper = new ObjectMapper();
- String refreshRequestJson = objectMapper.writeValueAsString(refreshRequest);
-
- when(authenticationService.refresh(any(RefreshRequest.class)))
- .thenReturn(authenticationResponse);
-
- // WHEN THEN
- mockMvc.perform(MockMvcRequestBuilders.post("/auth/refresh")
- .contentType(MediaType.APPLICATION_JSON)
- .content(refreshRequestJson))
- .andExpect(status().isOk())
- .andExpect(jsonPath("internalCode").value(1000))
- .andExpect(jsonPath("result.tokens.access_token").value("accessToken"))
- .andExpect(jsonPath("result.tokens.refresh_token").value("refreshToken"));
-
- }
-
- @Test
- void refresh_nullToken_fail() throws Exception {
- // GIVEN
- ObjectMapper objectMapper = new ObjectMapper();
- refreshRequest.setRefreshToken(null);
- String refreshRequestJson = objectMapper.writeValueAsString(refreshRequest);
-
- // WHEN THEN
- mockMvc.perform(MockMvcRequestBuilders.post("/auth/refresh")
- .contentType(MediaType.APPLICATION_JSON)
- .content(refreshRequestJson))
- .andExpect(status().isBadRequest())
- .andExpect(jsonPath("internalCode").value(2004))
- .andExpect(jsonPath("message").value("\"refreshToken\" must not be null"));
- }
-
- @Test
- void logout_validRequest_success() throws Exception {
- // GIVEN
- ObjectMapper objectMapper = new ObjectMapper();
- String logoutRequestJson = objectMapper.writeValueAsString(logoutRequest);
-
- // WHEN THEN
- mockMvc.perform(MockMvcRequestBuilders.post("/auth/logout")
- .contentType(MediaType.APPLICATION_JSON)
- .content(logoutRequestJson))
- .andExpect(status().isOk())
- .andExpect(jsonPath("internalCode").value(1000))
- .andExpect(jsonPath("result").value("Logout successful"));
- }
-
- @Test
- void logout_nullToken_fail() throws Exception {
- // GIVEN
- ObjectMapper objectMapper = new ObjectMapper();
- logoutRequest.setToken(null);
- String logoutRequestJson = objectMapper.writeValueAsString(logoutRequest);
-
- // WHEN THEN
- mockMvc.perform(MockMvcRequestBuilders.post("/auth/logout")
- .contentType(MediaType.APPLICATION_JSON)
- .content(logoutRequestJson))
- .andExpect(status().isBadRequest())
- .andExpect(jsonPath("internalCode").value(2004))
- .andExpect(jsonPath("message").value("\"token\" must not be null"));
- }
}
diff --git a/src/test/java/com/learning/yasminishop/controller/CartControllerTest.java b/src/test/java/com/learning/yasminishop/controller/CartControllerTest.java
new file mode 100644
index 0000000..c8db9a7
--- /dev/null
+++ b/src/test/java/com/learning/yasminishop/controller/CartControllerTest.java
@@ -0,0 +1,179 @@
+package com.learning.yasminishop.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.learning.yasminishop.cart.CartItemRepository;
+import com.learning.yasminishop.cart.dto.request.CartItemRequest;
+import com.learning.yasminishop.common.entity.CartItem;
+import com.learning.yasminishop.common.entity.Product;
+import com.learning.yasminishop.common.entity.User;
+import com.learning.yasminishop.product.ProductRepository;
+import com.learning.yasminishop.user.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+
+import java.math.BigDecimal;
+
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@Slf4j
+@AutoConfigureMockMvc
+@TestPropertySource("/test.properties")
+class CartControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private ProductRepository productRepository;
+
+ @Autowired
+ private CartItemRepository cartItemRepository;
+
+ @Autowired
+ private UserRepository userRepository;
+
+ private CartItemRequest cartItemRequest;
+ private Product product;
+ private User user;
+ private CartItem cartItem;
+
+ @BeforeEach
+ void setUp() {
+ cartItemRequest = CartItemRequest.builder()
+ .productId("product-1")
+ .quantity(2)
+ .build();
+ product = Product.builder()
+ .name("Product 1")
+ .description("Product 1 description")
+ .price(BigDecimal.valueOf(100))
+ .quantity(10L)
+ .slug("product-1")
+ .sku("sku-1")
+ .isFeatured(true)
+ .isAvailable(true)
+ .build();
+ user = User.builder()
+ .email("user@gmail.com")
+ .password("password")
+ .build();
+ cartItem = CartItem.builder()
+ .product(product)
+ .quantity(2)
+ .price(BigDecimal.valueOf(200))
+ .build();
+ }
+
+ @Test
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ @WithMockUser(username = "user@gmail.com")
+ void createCart_validRequest_success() throws Exception {
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ Product savedProduct = productRepository.save(product);
+ userRepository.save(user);
+ cartItemRequest.setProductId(savedProduct.getId());
+ String requestJson = objectMapper.writeValueAsString(cartItemRequest);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.post("/carts")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestJson))
+ .andExpect(status().isCreated())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result.product.name").value("Product 1"));
+ }
+
+ @Test
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ @WithMockUser(username = "user@gmail.com")
+ void getAllCarts_validRequest_success() throws Exception {
+ // GIVEN
+ Product savedProduct = productRepository.save(product);
+ User savedUser = userRepository.save(user);
+ cartItem.setProduct(savedProduct);
+ cartItem.setUser(savedUser);
+ cartItemRepository.save(cartItem);
+
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.get("/carts"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result[0].product.name").value("Product 1"));
+ }
+
+ @Test
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ @WithMockUser(username = "user@gmail.com")
+ void updateCart_validRequest_success() throws Exception {
+ // GIVEN
+ Product savedProduct = productRepository.save(product);
+ User savedUser = userRepository.save(user);
+ cartItem.setProduct(savedProduct);
+ cartItem.setUser(savedUser);
+ CartItem savedCartItem = cartItemRepository.save(cartItem);
+ String requestJson = "{\"quantity\": 5}";
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.put("/carts/" + savedCartItem.getId())
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestJson))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result.quantity").value(5));
+ }
+
+ @Test
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ @WithMockUser(username = "user@gmail.com")
+ void deleteCart_validRequest_success() throws Exception {
+ // GIVEN
+ Product savedProduct = productRepository.save(product);
+ User savedUser = userRepository.save(user);
+ cartItem.setProduct(savedProduct);
+ cartItem.setUser(savedUser);
+ CartItem savedCartItem = cartItemRepository.save(cartItem);
+ String requestJson = "{\"ids\": [\"" + savedCartItem.getId() + "\"]}";
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.delete("/carts")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestJson))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("message").value("Cart items deleted successfully"));
+ }
+
+ @Test
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ @WithMockUser(username = "user@gmail.com")
+ void getCartItemsByIds_validRequest_success() throws Exception {
+ // GIVEN
+ Product savedProduct = productRepository.save(product);
+ User savedUser = userRepository.save(user);
+ cartItem.setProduct(savedProduct);
+ cartItem.setUser(savedUser);
+ CartItem savedCartItem = cartItemRepository.save(cartItem);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.get("/carts/get-by-ids")
+ .param("ids", savedCartItem.getId()))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result[0].product.name").value("Product 1"));
+ }
+
+}
diff --git a/src/test/java/com/learning/yasminishop/controller/CategoryControllerTest.java b/src/test/java/com/learning/yasminishop/controller/CategoryControllerTest.java
new file mode 100644
index 0000000..cbb1104
--- /dev/null
+++ b/src/test/java/com/learning/yasminishop/controller/CategoryControllerTest.java
@@ -0,0 +1,193 @@
+package com.learning.yasminishop.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.learning.yasminishop.category.CategoryRepository;
+import com.learning.yasminishop.category.dto.request.CategoryCreation;
+import com.learning.yasminishop.category.dto.request.CategoryIds;
+import com.learning.yasminishop.common.entity.Category;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+
+import java.util.List;
+
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@Slf4j
+@AutoConfigureMockMvc
+@TestPropertySource("/test.properties")
+class CategoryControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private CategoryRepository categoryRepository;
+
+ private CategoryCreation categoryCreation;
+ private Category category;
+ private CategoryIds categoryIds;
+
+ @BeforeEach
+ void setUp() {
+
+ categoryCreation = CategoryCreation.builder()
+ .name("Category Create")
+ .description("Category create description")
+ .slug("category-create")
+ .build();
+ category = Category.builder()
+ .name("Category 1")
+ .description("Category 1 description")
+ .slug("category-1")
+ .isAvailable(true)
+ .build();
+
+ categoryIds = CategoryIds.builder()
+ .ids(List.of("category-1"))
+ .build();
+
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void createCategory_validRequest_success() throws Exception {
+
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ String categoryCreationJson = objectMapper.writeValueAsString(categoryCreation);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.post("/categories")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(categoryCreationJson))
+ .andExpect(status().isCreated())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result.name").value("Category Create"))
+ .andExpect(jsonPath("result.description").value("Category create description"))
+ .andExpect(jsonPath("result.slug").value("category-create"));
+ }
+
+ @Test
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ void getAllCategories_validRequest_success() throws Exception {
+ // GIVEN
+ categoryRepository.save(category);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.get("/categories"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result").isArray());
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ void getCategoryBySlug_validRequest_success() throws Exception {
+ // GIVEN
+ categoryRepository.save(category);
+
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.get("/categories/slug/category-1"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result.name").value("Category 1"))
+ .andExpect(jsonPath("result.description").value("Category 1 description"))
+ .andExpect(jsonPath("result.slug").value("category-1"));
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ void getCategoryById_validRequest_success() throws Exception {
+ // GIVEN
+ Category saved = categoryRepository.save(category);
+ String id = saved.getId();
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.get("/categories/" + id))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result.id").value(id))
+ .andExpect(jsonPath("result.name").value("Category 1"))
+ .andExpect(jsonPath("result.description").value("Category 1 description"))
+ .andExpect(jsonPath("result.slug").value("category-1"));
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ void toggleAvailability_validRequest_success() throws Exception {
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ Category saved = categoryRepository.save(category);
+
+ categoryIds.setIds(List.of(saved.getId()));
+ String idsJson = objectMapper.writeValueAsString(categoryIds);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.patch("/categories/toggle-availability")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(idsJson))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("message").value("Categories availability toggled successfully"));
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ void deleteCategories_validRequest_success() throws Exception {
+ // GIVEN
+ ObjectMapper objectMapper = new ObjectMapper();
+ Category saved = categoryRepository.save(category);
+
+ categoryIds.setIds(List.of(saved.getId()));
+ String idsJson = objectMapper.writeValueAsString(categoryIds);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.delete("/categories")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(idsJson))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("message").value("Category deleted successfully"));
+ }
+
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ void getAllCategoriesForAdmin_validRequest_success() throws Exception {
+ // GIVEN
+ String name = "Category";
+ boolean isAvailable = true;
+ int page = 1;
+ int itemsPerPage = 10;
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.get("/categories/admin")
+ .param("name", name)
+ .param("isAvailable", Boolean.toString(isAvailable))
+ .param("page", Integer.toString(page))
+ .param("itemsPerPage", Integer.toString(itemsPerPage)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result").exists());
+ }
+
+
+}
diff --git a/src/test/java/com/learning/yasminishop/controller/OrderControllerTest.java b/src/test/java/com/learning/yasminishop/controller/OrderControllerTest.java
new file mode 100644
index 0000000..f49d2fd
--- /dev/null
+++ b/src/test/java/com/learning/yasminishop/controller/OrderControllerTest.java
@@ -0,0 +1,158 @@
+package com.learning.yasminishop.controller;
+
+import com.learning.yasminishop.common.entity.Order;
+import com.learning.yasminishop.common.entity.OrderItem;
+import com.learning.yasminishop.common.entity.Product;
+import com.learning.yasminishop.common.entity.User;
+import com.learning.yasminishop.order.OrderRepository;
+import com.learning.yasminishop.product.ProductRepository;
+import com.learning.yasminishop.user.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+
+import java.math.BigDecimal;
+import java.util.Set;
+
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@Slf4j
+@AutoConfigureMockMvc
+@TestPropertySource("/test.properties")
+class OrderControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private OrderRepository orderRepository;
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private ProductRepository productRepository;
+
+ private Order order;
+ private User user;
+ private Product product;
+
+ @BeforeEach
+ void setUp() {
+
+ product = Product.builder()
+ .name("Product 1")
+ .description("Product 1 description")
+ .price(BigDecimal.valueOf(100))
+ .quantity(10L)
+ .slug("product-1")
+ .sku("sku-1")
+ .isFeatured(true)
+ .isAvailable(true)
+ .build();
+
+ OrderItem orderItem = OrderItem.builder()
+ .product(product)
+ .quantity(2)
+ .price(BigDecimal.valueOf(200))
+ .build();
+
+ user = User.builder()
+ .email("user@test.com")
+ .password("password")
+ .build();
+ order = Order.builder()
+ .id("order-1")
+ .totalQuantity(4)
+ .totalPrice(BigDecimal.valueOf(400))
+ .orderItems(Set.of(orderItem))
+ .user(user)
+ .build();
+
+ }
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ void getAllOrders_validRequest_success() throws Exception {
+ // GIVEN
+ User savedUser = userRepository.save(user);
+ Product savedProduct = productRepository.save(product);
+ order.setUser(savedUser);
+ order.getOrderItems().forEach(orderItem -> orderItem.setProduct(savedProduct));
+ orderRepository.save(order);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.get("/orders"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result").isArray());
+ }
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ void getOrderById_validRequest_success() throws Exception {
+ // GIVEN
+ User savedUser = userRepository.save(user);
+ Product savedProduct = productRepository.save(product);
+ order.setUser(savedUser);
+ order.getOrderItems().forEach(orderItem -> orderItem.setProduct(savedProduct));
+ Order savedOrder = orderRepository.save(order);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.get("/orders/" + savedOrder.getId()))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result.id").value(savedOrder.getId()));
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ void getAllOrdersForAdmin_validRequest_success() throws Exception {
+ // GIVEN
+ User savedUser = userRepository.save(user);
+ Product savedProduct = productRepository.save(product);
+ order.setUser(savedUser);
+ order.getOrderItems().forEach(orderItem -> orderItem.setProduct(savedProduct));
+ orderRepository.save(order);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.get("/orders/admin"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result.total").value(1))
+ .andExpect(jsonPath("result.page").value(1))
+ .andExpect(jsonPath("result.data").isArray());
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ void getOrderByIdForAdmin_validRequest_success() throws Exception {
+ // GIVEN
+ User savedUser = userRepository.save(user);
+ Product savedProduct = productRepository.save(product);
+ order.setUser(savedUser);
+ order.getOrderItems().forEach(orderItem -> orderItem.setProduct(savedProduct));
+ Order savedOrder = orderRepository.save(order);
+
+ // WHEN THEN
+ mockMvc.perform(MockMvcRequestBuilders.get("/orders/" + savedOrder.getId() + "/admin"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("internalCode").value(1000))
+ .andExpect(jsonPath("result.id").value(savedOrder.getId()));
+ }
+
+}
diff --git a/src/test/java/com/learning/yasminishop/service/AuthenticationServiceTest.java b/src/test/java/com/learning/yasminishop/service/AuthenticationServiceTest.java
index 6ab24b8..6c8477a 100644
--- a/src/test/java/com/learning/yasminishop/service/AuthenticationServiceTest.java
+++ b/src/test/java/com/learning/yasminishop/service/AuthenticationServiceTest.java
@@ -14,6 +14,7 @@
import com.learning.yasminishop.user.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@@ -83,204 +84,216 @@ void setUp() {
.build();
}
- @Test
- void register_validRequest_success() {
+ @Nested
+ class HappyCase {
+ @Test
+ void register_validRequest_success() {
+
+ // GIVEN
+ when(userRepository.existsByEmail(anyString())).thenReturn(false);
+ when(roleRepository.findById(anyString()))
+ .thenReturn(Optional.of(Role.builder()
+ .name("USER")
+ .description("User role")
+ .build()));
+
+ when(userRepository.save(any()))
+ .thenReturn(new User());
+
+ when(jwtService.generateAccessToken(any()))
+ .thenReturn("accessToken");
+
+ when(jwtService.generateRefreshToken(any()))
+ .thenReturn("refreshToken");
+
+ // WHEN
+ var response = authenticationService.register(registerRequest);
+
+ // THEN
+ verify(userRepository, times(1)).save(any());
+ assertThat(response.getTokens().getAccessToken()).isEqualTo("accessToken");
+ assertThat(response.getTokens().getRefreshToken()).isEqualTo("refreshToken");
+ }
+
+ @Test
+ void authenticate_validRequest_success() {
+ // GIVEN
+
+ when(userRepository.findByEmail(anyString()))
+ .thenReturn(Optional.of(User.builder()
+ .id("abc-123")
+ .email("duongminhhieu@gmail.com")
+ .password("123456")
+ .isActive(true)
+ .build()));
+ when(jwtService.generateAccessToken(any()))
+ .thenReturn("accessToken");
+ when(jwtService.generateRefreshToken(any()))
+ .thenReturn("refreshToken");
+
+ when(passwordEncoder.matches(anyString(), anyString()))
+ .thenReturn(true);
+
+ // WHEN
+ var response = authenticationService.authenticate(authenticationRequest);
+
+ // THEN
+ assertThat(response.getTokens().getAccessToken()).isEqualTo("accessToken");
+ assertThat(response.getTokens().getRefreshToken()).isEqualTo("refreshToken");
+ }
+
+ @Test
+ void refresh_validRequest_success() {
+ // GIVEN
+ when(jwtService.isTokenValid(anyString())).thenReturn(true);
+ when(jwtService.extractUserEmail(anyString())).thenReturn("duongminhhieu@gmail.com");
+ when(userRepository.findByEmail(anyString()))
+ .thenReturn(Optional.of(User.builder()
+ .id("abc-123")
+ .email("duongminhhieu@gmail.com")
+ .password("123456")
+ .build()));
+ when(jwtService.extractIdToken(anyString())).thenReturn("idToken");
+ when(jwtService.extractExpiration(anyString()))
+ .thenReturn(Date.from(new Date().toInstant().plusSeconds(3600)));
+ when(jwtService.generateAccessToken(any()))
+ .thenReturn("accessToken");
+ when(jwtService.generateRefreshToken(any()))
+ .thenReturn("refreshToken");
+
+ // WHEN
+ var response = authenticationService.refresh(refreshRequest);
+
+ // THEN
+ assertThat(response.getTokens().getAccessToken()).isEqualTo("accessToken");
+ assertThat(response.getTokens().getRefreshToken()).isEqualTo("refreshToken");
+ verify(invalidTokenRepository, times(1)).save(any());
+ }
+
+ @Test
+ void logout_validRequest_success() {
+ // GIVEN
+ when(jwtService.isTokenValid(anyString())).thenReturn(true);
+ when(jwtService.extractIdToken(anyString())).thenReturn("idToken");
+
+ // WHEN
+ authenticationService.logout(logoutRequest);
+ // THEN
+ verify(invalidTokenRepository, times(1)).save(any());
+ }
- // GIVEN
- when(userRepository.existsByEmail(anyString())).thenReturn(false);
- when(roleRepository.findById(anyString()))
- .thenReturn(Optional.of(Role.builder()
- .name("USER")
- .description("User role")
- .build()));
-
- when(userRepository.save(any()))
- .thenReturn(new User());
-
- when(jwtService.generateAccessToken(any()))
- .thenReturn("accessToken");
-
- when(jwtService.generateRefreshToken(any()))
- .thenReturn("refreshToken");
-
- // WHEN
- var response = authenticationService.register(registerRequest);
-
- // THEN
- verify(userRepository, times(1)).save(any());
- assertThat(response.getTokens().getAccessToken()).isEqualTo("accessToken");
- assertThat(response.getTokens().getRefreshToken()).isEqualTo("refreshToken");
- }
-
- @Test
- void register_emailAlreadyExists_throwException() {
-
- // GIVEN
- when(userRepository.existsByEmail(anyString())).thenReturn(true);
-
- // WHEN
- var exception = assertThrows(AppException.class, () -> authenticationService.register(registerRequest)
- );
-
- // THEN
- assertThat(exception.getErrorCode().getInternalCode()).isEqualTo(1002);
- assertThat(exception.getErrorCode().getMessage()).isEqualTo("Email already exists");
}
- @Test
- void authenticate_validRequest_success() {
- // GIVEN
-
- when(userRepository.findByEmail(anyString()))
- .thenReturn(Optional.of(User.builder()
- .id("abc-123")
- .email("duongminhhieu@gmail.com")
- .password("123456")
- .isActive(true)
- .build()));
- when(jwtService.generateAccessToken(any()))
- .thenReturn("accessToken");
- when(jwtService.generateRefreshToken(any()))
- .thenReturn("refreshToken");
-
- when(passwordEncoder.matches(anyString(), anyString()))
- .thenReturn(true);
-
- // WHEN
- var response = authenticationService.authenticate(authenticationRequest);
-
- // THEN
- assertThat(response.getTokens().getAccessToken()).isEqualTo("accessToken");
- assertThat(response.getTokens().getRefreshToken()).isEqualTo("refreshToken");
+ @Nested
+ class UnHappyCase {
+ @Test
+ void register_emailAlreadyExists_throwException() {
+
+ // GIVEN
+ when(userRepository.existsByEmail(anyString())).thenReturn(true);
+
+ // WHEN
+ var exception = assertThrows(AppException.class, () -> authenticationService.register(registerRequest)
+ );
+
+ // THEN
+ assertThat(exception.getErrorCode().getInternalCode()).isEqualTo(1002);
+ assertThat(exception.getErrorCode().getMessage()).isEqualTo("Email already exists");
+ }
+
+
+ @Test
+ void authenticate_invalidPassword_throwException() {
+ // GIVEN
+ when(userRepository.findByEmail(anyString()))
+ .thenReturn(Optional.of(User.builder()
+ .id("abc-123")
+ .email("duongminhhieu@gmail.com")
+ .password("123456")
+ .build()));
+ when(passwordEncoder.matches(anyString(), anyString()))
+ .thenReturn(false);
+
+ // WHEN
+ var exception = assertThrows(AppException.class, () -> authenticationService.authenticate(authenticationRequest)
+ );
+
+ // THEN
+ assertThat(exception.getErrorCode().getInternalCode()).isEqualTo(1013);
+ assertThat(exception.getErrorCode().getMessage()).isEqualTo("Email or password is incorrect");
+ verify(passwordEncoder, times(1)).matches(anyString(), anyString());
+ verify(jwtService, never()).generateAccessToken(any());
+ verify(jwtService, never()).generateRefreshToken(any());
+ }
+
+ @Test
+ void authenticate_userNotFound_throwException() {
+ // GIVEN
+ when(userRepository.findByEmail(anyString()))
+ .thenReturn(Optional.empty());
+
+ // WHEN
+ var exception = assertThrows(AppException.class, () -> authenticationService.authenticate(authenticationRequest)
+ );
+
+ // THEN
+ assertThat(exception.getErrorCode().getInternalCode()).isEqualTo(1013);
+ assertThat(exception.getErrorCode().getMessage()).isEqualTo("Email or password is incorrect");
+ verify(passwordEncoder, never()).matches(anyString(), anyString());
+ verify(jwtService, never()).generateAccessToken(any());
+ verify(jwtService, never()).generateRefreshToken(any());
+ }
+
+
+ @Test
+ void refresh_invalidToken_throwException() {
+ // GIVEN
+ when(jwtService.isTokenValid(anyString())).thenReturn(false);
+
+ // WHEN
+ var exception = assertThrows(AppException.class, () -> authenticationService.refresh(refreshRequest)
+ );
+
+ // THEN
+ assertThat(exception.getErrorCode().getInternalCode()).isEqualTo(1001);
+ assertThat(exception.getErrorCode().getMessage()).isEqualTo("Invalid token");
+ verify(userRepository, never()).findByEmail(anyString());
+ verify(jwtService, never()).generateAccessToken(any());
+ verify(jwtService, never()).generateRefreshToken(any());
+ verify(invalidTokenRepository, never()).save(any());
+ }
+
+ @Test
+ void refresh_userNotFound_throwException() {
+ // GIVEN
+ when(jwtService.isTokenValid(anyString())).thenReturn(true);
+ when(jwtService.extractUserEmail(anyString())).thenReturn("hehe@gmail.com");
+
+ // WHEN
+ var exception = assertThrows(AppException.class, () -> authenticationService.refresh(refreshRequest)
+ );
+
+ // THEN
+ assertThat(exception.getErrorCode().getInternalCode()).isEqualTo(1001);
+ assertThat(exception.getErrorCode().getMessage()).isEqualTo("Invalid token");
+ }
+
+
+ @Test
+ void logout_invalidToken_throwException() {
+ // GIVEN
+ when(jwtService.isTokenValid(anyString())).thenReturn(false);
+
+ // WHEN
+ var exception = assertThrows(AppException.class, () -> authenticationService.logout(logoutRequest)
+ );
+
+ // THEN
+ assertThat(exception.getErrorCode().getInternalCode()).isEqualTo(1001);
+ assertThat(exception.getErrorCode().getMessage()).isEqualTo("Invalid token");
+ verify(invalidTokenRepository, never()).save(any());
+ }
}
- @Test
- void authenticate_invalidPassword_throwException() {
- // GIVEN
- when(userRepository.findByEmail(anyString()))
- .thenReturn(Optional.of(User.builder()
- .id("abc-123")
- .email("duongminhhieu@gmail.com")
- .password("123456")
- .build()));
- when(passwordEncoder.matches(anyString(), anyString()))
- .thenReturn(false);
-
- // WHEN
- var exception = assertThrows(AppException.class, () -> authenticationService.authenticate(authenticationRequest)
- );
-
- // THEN
- assertThat(exception.getErrorCode().getInternalCode()).isEqualTo(1013);
- assertThat(exception.getErrorCode().getMessage()).isEqualTo("Email or password is incorrect");
- verify(passwordEncoder, times(1)).matches(anyString(), anyString());
- verify(jwtService, never()).generateAccessToken(any());
- verify(jwtService, never()).generateRefreshToken(any());
- }
- @Test
- void authenticate_userNotFound_throwException() {
- // GIVEN
- when(userRepository.findByEmail(anyString()))
- .thenReturn(Optional.empty());
-
- // WHEN
- var exception = assertThrows(AppException.class, () -> authenticationService.authenticate(authenticationRequest)
- );
-
- // THEN
- assertThat(exception.getErrorCode().getInternalCode()).isEqualTo(1013);
- assertThat(exception.getErrorCode().getMessage()).isEqualTo("Email or password is incorrect");
- verify(passwordEncoder, never()).matches(anyString(), anyString());
- verify(jwtService, never()).generateAccessToken(any());
- verify(jwtService, never()).generateRefreshToken(any());
- }
-
- @Test
- void refresh_validRequest_success() {
- // GIVEN
- when(jwtService.isTokenValid(anyString())).thenReturn(true);
- when(jwtService.extractUserEmail(anyString())).thenReturn("duongminhhieu@gmail.com");
- when(userRepository.findByEmail(anyString()))
- .thenReturn(Optional.of(User.builder()
- .id("abc-123")
- .email("duongminhhieu@gmail.com")
- .password("123456")
- .build()));
- when(jwtService.extractIdToken(anyString())).thenReturn("idToken");
- when(jwtService.extractExpiration(anyString()))
- .thenReturn(Date.from(new Date().toInstant().plusSeconds(3600)));
- when(jwtService.generateAccessToken(any()))
- .thenReturn("accessToken");
- when(jwtService.generateRefreshToken(any()))
- .thenReturn("refreshToken");
-
- // WHEN
- var response = authenticationService.refresh(refreshRequest);
-
- // THEN
- assertThat(response.getTokens().getAccessToken()).isEqualTo("accessToken");
- assertThat(response.getTokens().getRefreshToken()).isEqualTo("refreshToken");
- verify(invalidTokenRepository, times(1)).save(any());
- }
-
- @Test
- void refresh_invalidToken_throwException() {
- // GIVEN
- when(jwtService.isTokenValid(anyString())).thenReturn(false);
-
- // WHEN
- var exception = assertThrows(AppException.class, () -> authenticationService.refresh(refreshRequest)
- );
-
- // THEN
- assertThat(exception.getErrorCode().getInternalCode()).isEqualTo(1001);
- assertThat(exception.getErrorCode().getMessage()).isEqualTo("Invalid token");
- verify(userRepository, never()).findByEmail(anyString());
- verify(jwtService, never()).generateAccessToken(any());
- verify(jwtService, never()).generateRefreshToken(any());
- verify(invalidTokenRepository, never()).save(any());
- }
-
- @Test
- void refresh_userNotFound_throwException() {
- // GIVEN
- when(jwtService.isTokenValid(anyString())).thenReturn(true);
- when(jwtService.extractUserEmail(anyString())).thenReturn("hehe@gmail.com");
-
- // WHEN
- var exception = assertThrows(AppException.class, () -> authenticationService.refresh(refreshRequest)
- );
-
- // THEN
- assertThat(exception.getErrorCode().getInternalCode()).isEqualTo(1001);
- assertThat(exception.getErrorCode().getMessage()).isEqualTo("Invalid token");
- }
-
- @Test
- void logout_validRequest_success() {
- // GIVEN
- when(jwtService.isTokenValid(anyString())).thenReturn(true);
- when(jwtService.extractIdToken(anyString())).thenReturn("idToken");
-
- // WHEN
- authenticationService.logout(logoutRequest);
- // THEN
- verify(invalidTokenRepository, times(1)).save(any());
- }
-
- @Test
- void logout_invalidToken_throwException() {
- // GIVEN
- when(jwtService.isTokenValid(anyString())).thenReturn(false);
-
- // WHEN
- var exception = assertThrows(AppException.class, () -> authenticationService.logout(logoutRequest)
- );
-
- // THEN
- assertThat(exception.getErrorCode().getInternalCode()).isEqualTo(1001);
- assertThat(exception.getErrorCode().getMessage()).isEqualTo("Invalid token");
- verify(invalidTokenRepository, never()).save(any());
- }
}
diff --git a/src/test/java/com/learning/yasminishop/service/CartServiceTest.java b/src/test/java/com/learning/yasminishop/service/CartServiceTest.java
new file mode 100644
index 0000000..8a2231e
--- /dev/null
+++ b/src/test/java/com/learning/yasminishop/service/CartServiceTest.java
@@ -0,0 +1,221 @@
+package com.learning.yasminishop.service;
+
+import com.learning.yasminishop.cart.CartItemRepository;
+import com.learning.yasminishop.cart.CartItemService;
+import com.learning.yasminishop.cart.dto.request.CartItemRequest;
+import com.learning.yasminishop.cart.dto.request.CartItemUpdate;
+import com.learning.yasminishop.cart.dto.response.CartItemResponse;
+import com.learning.yasminishop.common.entity.CartItem;
+import com.learning.yasminishop.common.entity.Product;
+import com.learning.yasminishop.common.entity.User;
+import com.learning.yasminishop.common.exception.AppException;
+import com.learning.yasminishop.product.ProductRepository;
+import com.learning.yasminishop.user.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.TestPropertySource;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+@SpringBootTest
+@Slf4j
+@TestPropertySource("/test.properties")
+class CartServiceTest {
+
+ @MockBean
+ private CartItemRepository cartItemRepository;
+
+ @MockBean
+ private ProductRepository productRepository;
+
+ @MockBean
+ private UserRepository userRepository;
+
+ @Autowired
+ private CartItemService cartItemService;
+
+
+ private CartItemRequest cartItemRequest;
+ private Product product;
+ private User user;
+ private CartItem cartItem;
+ private CartItemUpdate cartItemUpdate;
+ private List cartIds;
+ private List cartItems;
+
+ @BeforeEach
+ void setUp() {
+
+ cartItemRequest = CartItemRequest.builder()
+ .productId("product-1")
+ .quantity(2)
+ .build();
+
+ product = Product.builder()
+ .name("Product 1")
+ .description("Product 1 description")
+ .price(BigDecimal.valueOf(100))
+ .quantity(10L)
+ .slug("product-1")
+ .sku("sku-1")
+ .isFeatured(true)
+ .isAvailable(true)
+ .build();
+ user = User.builder()
+ .email("user@test.com")
+ .password("password")
+ .build();
+ cartItem = CartItem.builder()
+ .product(product)
+ .quantity(2)
+ .user(user)
+ .price(BigDecimal.valueOf(200))
+ .build();
+
+ cartItemUpdate = CartItemUpdate.builder()
+ .quantity(3)
+ .build();
+ cartIds = List.of("cart-1", "cart-2");
+ cartItems = List.of(CartItem.builder()
+ .id("cart-1")
+ .product(product)
+ .quantity(2)
+ .user(user)
+ .price(BigDecimal.valueOf(200))
+ .build(),
+ CartItem.builder()
+ .id("cart-2")
+ .product(product)
+ .quantity(2)
+ .user(user)
+ .price(BigDecimal.valueOf(200))
+ .build());
+ }
+
+ @Nested
+ class HappyCase {
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void create_validRequest_success() {
+ // GIVEN
+ when(productRepository.findById(cartItemRequest.getProductId())).thenReturn(Optional.of(product));
+ when(userRepository.findByEmail(any())).thenReturn(Optional.of(user));
+ when(cartItemRepository.findByProductAndUser(product, user)).thenReturn(Optional.empty());
+ when(cartItemRepository.save(any(CartItem.class))).thenReturn(cartItem);
+
+ // WHEN
+ CartItemResponse result = cartItemService.create(cartItemRequest);
+
+ // THEN
+ assertEquals(cartItem.getQuantity(), result.getQuantity());
+ assertEquals(cartItem.getPrice(), result.getPrice());
+ assertEquals(cartItem.getProduct().getId(), result.getProduct().getId());
+ }
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void getAll_validRequest_success() {
+ // GIVEN
+ when(userRepository.findByEmail(any())).thenReturn(Optional.of(user));
+ when(cartItemRepository.findAllByUserOrderByLastModifiedDateDesc(user)).thenReturn(java.util.List.of(cartItem));
+
+ // WHEN
+ var result = cartItemService.getAll();
+
+ // THEN
+ assertThat(result).isNotNull()
+ .hasSize(1)
+ .first()
+ .hasFieldOrPropertyWithValue("quantity", 2)
+ .hasFieldOrPropertyWithValue("price", BigDecimal.valueOf(200));
+ }
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void update_validRequest_success() {
+ // GIVEN
+ when(userRepository.findByEmail(any())).thenReturn(Optional.of(user));
+ when(cartItemRepository.findById(cartItem.getId())).thenReturn(Optional.of(cartItem));
+ when(cartItemRepository.save(any(CartItem.class))).thenReturn(cartItem);
+
+ // WHEN
+ var result = cartItemService.update(cartItem.getId(), cartItemUpdate);
+
+ // THEN
+ assertEquals(cartItemUpdate.getQuantity(), result.getQuantity());
+ }
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void delete_validRequest_success() {
+ // GIVEN
+ when(cartItemRepository.findAllById(cartIds)).thenReturn(cartItems);
+ when(userRepository.findByEmail(any())).thenReturn(Optional.of(user));
+
+ // WHEN
+ cartItemService.delete(cartIds);
+
+ // THEN
+ verify(cartItemRepository).deleteAll(cartItems);
+ }
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void getCartByIds_validRequest_success() {
+ // GIVEN
+ when(cartItemRepository.findAllById(cartIds)).thenReturn(cartItems);
+
+ // WHEN
+ var result = cartItemService.getCartByIds(cartIds);
+
+ // THEN
+ assertThat(result).isNotNull()
+ .hasSize(2)
+ .first()
+ .hasFieldOrPropertyWithValue("id", "cart-1")
+ .hasFieldOrPropertyWithValue("quantity", 2);
+ }
+
+ }
+
+ @Nested
+ class UnhappyCase {
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void create_productNotFound_throwException() {
+ // GIVEN
+ when(productRepository.findById(cartItemRequest.getProductId())).thenReturn(Optional.empty());
+
+ // WHEN
+ var exception = assertThrows(AppException.class, () -> cartItemService.create(cartItemRequest));
+
+ // THEN
+ assertThat(exception.getErrorCode().getInternalCode())
+ .isEqualTo(1009);
+ assertThat(exception.getErrorCode().getMessage())
+ .isEqualTo("Product not found");
+ }
+
+ }
+
+
+}
diff --git a/src/test/java/com/learning/yasminishop/service/CategoryServiceTest.java b/src/test/java/com/learning/yasminishop/service/CategoryServiceTest.java
new file mode 100644
index 0000000..fecff2e
--- /dev/null
+++ b/src/test/java/com/learning/yasminishop/service/CategoryServiceTest.java
@@ -0,0 +1,182 @@
+package com.learning.yasminishop.service;
+
+import com.learning.yasminishop.category.CategoryRepository;
+import com.learning.yasminishop.category.CategoryService;
+import com.learning.yasminishop.category.dto.request.CategoryCreation;
+import com.learning.yasminishop.category.dto.request.CategoryUpdate;
+import com.learning.yasminishop.category.dto.response.CategoryResponse;
+import com.learning.yasminishop.common.entity.Category;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.TestPropertySource;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@SpringBootTest
+@Slf4j
+@TestPropertySource("/test.properties")
+class CategoryServiceTest {
+
+ @Autowired
+ private CategoryService categoryService;
+
+ @MockBean
+ private CategoryRepository categoryRepository;
+
+ private CategoryCreation categoryCreation;
+
+ private Category category;
+ private CategoryUpdate categoryUpdate;
+
+ @BeforeEach
+ void setUp() {
+
+ categoryCreation = CategoryCreation.builder()
+ .name("Category Create")
+ .description("Category create description")
+ .slug("category-create")
+ .build();
+
+ category = Category.builder()
+ .id("cate1")
+ .name("Category 1")
+ .description("Category 1 description")
+ .slug("category-1")
+ .isAvailable(true)
+ .products(Set.of())
+ .build();
+
+ categoryUpdate = CategoryUpdate.builder()
+ .name("Category update")
+ .description("Category 1 update description")
+ .slug("category-1")
+ .isAvailable(true)
+ .build();
+
+ }
+
+ @Nested
+ class HappyCase {
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void createCategory_validRequest_success() {
+ // GIVEN
+ when(categoryRepository.existsBySlug(categoryCreation.getSlug())).thenReturn(false);
+ when(categoryRepository.save(any(Category.class))).thenReturn(category);
+
+ // WHEN
+ CategoryResponse response = categoryService.create(categoryCreation);
+
+ // THEN
+ assertThat(response).isNotNull()
+ .hasFieldOrPropertyWithValue("name", "Category 1")
+ .hasFieldOrPropertyWithValue("description", "Category 1 description")
+ .hasFieldOrPropertyWithValue("slug", "category-1");
+ }
+
+ @Test
+ void getAllCategories_validRequest_success() {
+ // GIVEN
+ when(categoryRepository.findAllByIsAvailable(true)).thenReturn(List.of(category));
+
+ // WHEN
+ List response = categoryService.getAllCategories();
+
+ // THEN
+ assertThat(response).isNotNull()
+ .hasSize(1)
+ .first()
+ .hasFieldOrPropertyWithValue("name", "Category 1")
+ .hasFieldOrPropertyWithValue("description", "Category 1 description")
+ .hasFieldOrPropertyWithValue("slug", "category-1");
+ }
+
+ @Test
+ void getBySlug_validRequest_success() {
+ // GIVEN
+ when(categoryRepository.findBySlug("category-1")).thenReturn(Optional.of(category));
+
+ // WHEN
+ CategoryResponse response = categoryService.getBySlug("category-1");
+
+ // THEN
+ assertThat(response).isNotNull()
+ .hasFieldOrPropertyWithValue("name", "Category 1")
+ .hasFieldOrPropertyWithValue("description", "Category 1 description")
+ .hasFieldOrPropertyWithValue("slug", "category-1");
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void getCategory_validRequest_success() {
+ // GIVEN
+ when(categoryRepository.findById("cate1")).thenReturn(Optional.of(category));
+
+ // WHEN
+ var response = categoryService.getCategory("cate1");
+
+ // THEN
+ assertThat(response).isNotNull()
+ .hasFieldOrPropertyWithValue("name", "Category 1")
+ .hasFieldOrPropertyWithValue("description", "Category 1 description")
+ .hasFieldOrPropertyWithValue("slug", "category-1");
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void delete_validRequest_success() {
+ // GIVEN
+ when(categoryRepository.findAllById(List.of("cate1"))).thenReturn(List.of(category));
+
+ // WHEN
+ categoryService.delete(List.of("cate1"));
+
+ // THEN
+ verify(categoryRepository).deleteAll(List.of(category));
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void toggleAvailability_validRequest_success() {
+ // GIVEN
+ when(categoryRepository.findAllById(List.of("cate1"))).thenReturn(List.of(category));
+
+ // WHEN
+ categoryService.toggleAvailability(List.of("cate1"));
+
+ // THEN
+ verify(categoryRepository).saveAll(List.of(category));
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void update_validRequest_success() {
+ // GIVEN
+ when(categoryRepository.findById("cate1")).thenReturn(Optional.of(category));
+ when(categoryRepository.save(any(Category.class))).thenReturn(category);
+
+ // WHEN
+ var response = categoryService.update("cate1", categoryUpdate);
+
+ // THEN
+ assertThat(response).isNotNull()
+ .hasFieldOrPropertyWithValue("name", "Category update");
+ }
+ }
+}
+
+
diff --git a/src/test/java/com/learning/yasminishop/service/OrderServiceTest.java b/src/test/java/com/learning/yasminishop/service/OrderServiceTest.java
new file mode 100644
index 0000000..7f068c7
--- /dev/null
+++ b/src/test/java/com/learning/yasminishop/service/OrderServiceTest.java
@@ -0,0 +1,263 @@
+package com.learning.yasminishop.service;
+
+
+import com.learning.yasminishop.cart.CartItemRepository;
+import com.learning.yasminishop.common.dto.PaginationResponse;
+import com.learning.yasminishop.common.entity.*;
+import com.learning.yasminishop.common.enumeration.EOrderStatus;
+import com.learning.yasminishop.common.exception.AppException;
+import com.learning.yasminishop.notification.NotificationRepository;
+import com.learning.yasminishop.order.OrderRepository;
+import com.learning.yasminishop.order.OrderService;
+import com.learning.yasminishop.order.dto.filter.OrderFilter;
+import com.learning.yasminishop.order.dto.request.OrderAddressRequest;
+import com.learning.yasminishop.order.dto.request.OrderRequest;
+import com.learning.yasminishop.order.dto.response.OrderAdminResponse;
+import com.learning.yasminishop.order.dto.response.OrderResponse;
+import com.learning.yasminishop.user.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.TestPropertySource;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+@SpringBootTest
+@Slf4j
+@TestPropertySource("/test.properties")
+class OrderServiceTest {
+
+ @Autowired
+ private OrderService orderService;
+
+ @MockBean
+ private UserRepository userRepository;
+
+ @MockBean
+ private CartItemRepository cartItemRepository;
+
+ @MockBean
+ private OrderRepository orderRepository;
+
+ @MockBean
+ private NotificationRepository notificationRepository;
+
+ private OrderRequest orderRequest;
+ private User user;
+ private Order order;
+
+ @BeforeEach
+ void setUp() {
+
+ Set cartItemIds = Set.of("cart-1");
+
+ OrderAddressRequest orderAddressRequest = OrderAddressRequest.builder()
+ .contactName("Hieu Duong")
+ .phone("0933444555")
+ .addressLine1("addressLine1")
+ .addressLine2("addressLine2")
+ .build();
+
+ orderRequest = OrderRequest.builder()
+ .cartItemIds(cartItemIds)
+ .orderAddress(orderAddressRequest)
+ .build();
+ user = User.builder()
+ .email("user@test.com")
+ .password("password")
+ .build();
+ Product product = Product.builder()
+ .name("Product 1")
+ .description("Product 1 description")
+ .price(BigDecimal.valueOf(100))
+ .quantity(10L)
+ .slug("product-1")
+ .sku("sku-1")
+ .isFeatured(true)
+ .isAvailable(true)
+ .build();
+
+ OrderItem orderItem = OrderItem.builder()
+ .product(product)
+ .quantity(2)
+ .price(BigDecimal.valueOf(200))
+ .build();
+ order = Order.builder()
+ .id("order-1")
+ .totalQuantity(4)
+ .totalPrice(BigDecimal.valueOf(400))
+ .orderItems(Set.of(orderItem))
+ .user(user)
+ .build();
+ }
+
+
+ @Nested
+ class HappyCase {
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void getAllOrderByUser_validRequest_success() {
+ // GIVEN
+ when(userRepository.findByEmail(any())).thenReturn(Optional.of(user));
+ when(orderRepository.findAllByUserOrderByLastModifiedDateDesc(any())).thenReturn(List.of(Order.builder().id("order-1").build()));
+
+ // WHEN
+ List orderResponses = orderService.getAllOrderByUser();
+
+ // THEN
+ assertThat(orderResponses.getFirst()).isNotNull()
+ .hasFieldOrPropertyWithValue("id", "order-1");
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void getAllOrders_validRequest_success() {
+ // GIVEN
+ OrderFilter orderFilter = new OrderFilter();
+ orderFilter.setStatus(EOrderStatus.PENDING);
+
+ Pageable pageable = PageRequest.of(0, 5);
+
+ Order order = new Order();
+ order.setId("order-1");
+
+ Page orders = new PageImpl<>(List.of(order));
+ when(orderRepository.findAll(any(Specification.class), eq(pageable))).thenReturn(orders);
+
+ // WHEN
+ PaginationResponse result = orderService.getAllOrders(orderFilter, pageable);
+
+ // THEN
+ assertThat(result).isNotNull()
+ .hasFieldOrPropertyWithValue("page", 1)
+ .hasFieldOrPropertyWithValue("total", 1L)
+ .hasFieldOrPropertyWithValue("itemsPerPage", 5)
+ .hasFieldOrPropertyWithValue("data", List.of(OrderAdminResponse.builder().id("order-1").build()));
+ }
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void getOrderById_validRequest_success() {
+ // GIVEN
+ when(userRepository.findByEmail(any())).thenReturn(Optional.of(user));
+ when(orderRepository.findByIdAndUser(any(), any())).thenReturn(Optional.of(Order.builder().id("order-1").build()));
+
+ // WHEN
+ OrderResponse result = orderService.getOrderById("order-1");
+
+ // THEN
+ assertThat(result).isNotNull()
+ .hasFieldOrPropertyWithValue("id", "order-1");
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void getOrderByIdForAdmin_validRequest_success() {
+ // GIVEN
+ when(orderRepository.findById(any())).thenReturn(Optional.of(Order.builder().id("order-1").build()));
+
+ // WHEN
+ OrderAdminResponse result = orderService.getOrderByIdForAdmin("order-1");
+
+ // THEN
+ assertThat(result).isNotNull()
+ .hasFieldOrPropertyWithValue("id", "order-1");
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void updateOrderStatus_validRequest_success() {
+ // GIVEN
+ when(orderRepository.findById(any())).thenReturn(Optional.of(order));
+ when(notificationRepository.save(any())).thenReturn(Notification.builder().id("notification-1").build());
+
+ // WHEN
+ orderService.updateOrderStatus("order-1", EOrderStatus.COMPLETED.name());
+
+ // THEN
+ verify(orderRepository, times(1)).save(any(Order.class));
+ }
+
+ }
+
+ @Nested
+ class UnHappyCase {
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void create_userNotFound_throwException() {
+ // GIVEN
+ when(userRepository.findByEmail(any())).thenReturn(Optional.empty());
+
+ // WHEN
+ var exception = assertThrows(AppException.class, () -> orderService.create(orderRequest));
+
+ // THEN
+ assertThat(exception).isInstanceOf(AppException.class);
+ Assertions.assertThat(exception.getErrorCode().getInternalCode())
+ .isEqualTo(1006);
+
+ Assertions.assertThat(exception.getErrorCode().getMessage())
+ .isEqualTo("User not found");
+ }
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void create_cartItemNotFound_throwException() {
+ // GIVEN
+ when(userRepository.findByEmail(any())).thenReturn(Optional.of(user));
+ when(cartItemRepository.findAllByUserOrderByLastModifiedDateDesc(any())).thenReturn(List.of());
+
+ // WHEN
+ var exception = assertThrows(AppException.class, () -> orderService.create(orderRequest));
+
+ // THEN
+ assertThat(exception).isInstanceOf(AppException.class);
+ Assertions.assertThat(exception.getErrorCode().getInternalCode())
+ .isEqualTo(1022);
+
+ Assertions.assertThat(exception.getErrorCode().getMessage())
+ .isEqualTo("Cart item not found");
+ }
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void getOrderById_orderNotFound_throwException() {
+ // GIVEN
+ when(userRepository.findByEmail(any())).thenReturn(Optional.of(user));
+ when(orderRepository.findByIdAndUser(any(), any())).thenReturn(Optional.empty());
+
+ // WHEN
+ var exception = assertThrows(AppException.class, () -> orderService.getOrderById("order-1"));
+
+ // THEN
+ assertThat(exception).isInstanceOf(AppException.class);
+ Assertions.assertThat(exception.getErrorCode().getInternalCode())
+ .isEqualTo(1023);
+
+ Assertions.assertThat(exception.getErrorCode().getMessage())
+ .isEqualTo("Order not found");
+ }
+ }
+
+}
diff --git a/src/test/java/com/learning/yasminishop/service/PermissionServiceTest.java b/src/test/java/com/learning/yasminishop/service/PermissionServiceTest.java
index 70eea8f..fe60af6 100644
--- a/src/test/java/com/learning/yasminishop/service/PermissionServiceTest.java
+++ b/src/test/java/com/learning/yasminishop/service/PermissionServiceTest.java
@@ -8,6 +8,7 @@
import com.learning.yasminishop.permission.dto.response.PermissionResponse;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@@ -50,50 +51,51 @@ void setUp() {
}
- @Test
- @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
- void createPermission_validRequest_success() {
- // given
- when(permissionRepository.save(any(Permission.class))).thenReturn(permission);
-
- // when
- PermissionResponse response = permissionService.createPermission(permissionRequest);
-
- // then
- assertThat(response).isNotNull();
- assertThat(response.getName()).isEqualTo("READ");
- assertThat(response.getDescription()).isEqualTo("Read permission");
- }
-
- @Test
- @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
- void getAllPermissions_validRequest_success() {
- // given
- when(permissionRepository.findAll()).thenReturn(List.of(permission));
-
- // when
- List response = permissionService.getALlPermissions();
-
- // then
- assertThat(response).isNotNull()
- .isNotEmpty()
- .hasSize(1);
-
+ @Nested
+ class HappyCase {
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void createPermission_validRequest_success() {
+ // given
+ when(permissionRepository.save(any(Permission.class))).thenReturn(permission);
+
+ // when
+ PermissionResponse response = permissionService.createPermission(permissionRequest);
+
+ // then
+ assertThat(response).isNotNull();
+ assertThat(response.getName()).isEqualTo("READ");
+ assertThat(response.getDescription()).isEqualTo("Read permission");
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void getAllPermissions_validRequest_success() {
+ // given
+ when(permissionRepository.findAll()).thenReturn(List.of(permission));
+
+ // when
+ List response = permissionService.getALlPermissions();
+
+ // then
+ assertThat(response).isNotNull()
+ .isNotEmpty()
+ .hasSize(1);
+
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void deletePermission_validRequest_success() {
+ // given
+ String permissionName = "READ";
+ doNothing().when(permissionRepository).deleteById(permissionName);
+
+ // when
+ permissionService.deletePermission(permissionName);
+
+ // then
+ verify(permissionRepository, times(1)).deleteById(permissionName);
+ }
}
-
- @Test
-@WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
-void deletePermission_validRequest_success() {
- // given
- String permissionName = "READ";
- doNothing().when(permissionRepository).deleteById(permissionName);
-
- // when
- permissionService.deletePermission(permissionName);
-
- // then
- verify(permissionRepository, times(1)).deleteById(permissionName);
-}
-
-
}
diff --git a/src/test/java/com/learning/yasminishop/service/ProductServiceTest.java b/src/test/java/com/learning/yasminishop/service/ProductServiceTest.java
index b6fc6e5..f8f08bc 100644
--- a/src/test/java/com/learning/yasminishop/service/ProductServiceTest.java
+++ b/src/test/java/com/learning/yasminishop/service/ProductServiceTest.java
@@ -1,15 +1,45 @@
package com.learning.yasminishop.service;
+import com.learning.yasminishop.category.CategoryRepository;
+import com.learning.yasminishop.category.dto.response.CategoryResponse;
+import com.learning.yasminishop.common.dto.PaginationResponse;
+import com.learning.yasminishop.common.entity.*;
+import com.learning.yasminishop.common.exception.AppException;
import com.learning.yasminishop.product.ProductRepository;
import com.learning.yasminishop.product.ProductService;
+import com.learning.yasminishop.product.dto.filter.ProductFilter;
+import com.learning.yasminishop.product.dto.request.ProductRequest;
+import com.learning.yasminishop.product.dto.response.ProductAdminResponse;
+import com.learning.yasminishop.product.dto.response.ProductResponse;
+import com.learning.yasminishop.product.mapper.ProductMapper;
+import com.learning.yasminishop.storage.StorageRepository;
+import com.learning.yasminishop.storage.dto.response.StorageResponse;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+
@SpringBootTest
@Slf4j
@TestPropertySource("/test.properties")
@@ -18,26 +48,362 @@ class ProductServiceTest {
@Autowired
private ProductService productService;
- @Autowired
+ @MockBean
private ProductRepository productRepository;
+ @MockBean
+ private ProductMapper productMapper;
+
+ @MockBean
+ private CategoryRepository categoryRepository;
+
+ @MockBean
+ private StorageRepository storageRepository;
+
+
+ private ProductRequest productCreation;
+ private Product product;
+ private ProductAdminResponse productAdminResponse;
+ private Category category1;
+ private Category category2;
+ private Storage image1;
+ private Storage image2;
+ private List categories;
+ private List images;
+
+ private ProductRequest productUpdate;
+ private ProductResponse productResponse;
+ private ProductFilter productFilter;
@BeforeEach
void setUp() {
+ productCreation = ProductRequest.builder()
+ .name("Product 1")
+ .description("Product 1 description")
+ .price(BigDecimal.valueOf(1_000_000))
+ .isAvailable(true)
+ .isFeatured(true)
+ .categoryIds(Set.of("category1", "category2"))
+ .slug("product-1")
+ .sku("sku-1")
+ .imageIds(Set.of("image1", "image2"))
+ .build();
+
+ category1 = Category.builder()
+ .id("category1")
+ .name("Category 1")
+ .build();
+
+ category2 = Category.builder()
+ .id("category2")
+ .name("Category 2")
+ .build();
+
+ categories = List.of(category1, category2);
+
+ image1 = Storage.builder()
+ .id("image1")
+ .url("image1-url")
+ .build();
+
+ image2 = Storage.builder()
+ .id("image2")
+ .url("image2-url")
+ .build();
+
+ images = List.of(image1, image2);
+
+ Set attributes = Set.of(
+ ProductAttribute.builder()
+ .id(UUID.randomUUID().toString())
+ .name("Color")
+ .values(Set.of(
+ ProductAttributeValue.builder()
+ .id(UUID.randomUUID().toString())
+ .value("Red")
+ .build()
+ ))
+ .build()
+ );
+
+ product = Product.builder()
+ .id("product-1")
+ .name("Product 1")
+ .description("Product 1 description")
+ .price(BigDecimal.valueOf(1_000_000))
+ .isAvailable(true)
+ .isFeatured(true)
+ .slug("product-1")
+ .sku("sku-1")
+ .averageRating(4.0F)
+ .quantity(10L)
+ .attributes(attributes)
+ .images(Set.of(image1, image2))
+ .categories(Set.of(category1, category2))
+ .build();
+
+ CategoryResponse categoryResponse1 = CategoryResponse.builder()
+ .id("category1")
+ .name("Category 1")
+ .build();
+ CategoryResponse categoryResponse2 = CategoryResponse.builder()
+ .id("category2")
+ .name("Category 2")
+ .build();
+
+ StorageResponse storageResponse1 = StorageResponse.builder()
+ .id("image1")
+ .url("image1-url")
+ .build();
+ StorageResponse storageResponse2 = StorageResponse.builder()
+ .id("image2")
+ .url("image2-url")
+ .build();
+
+ productAdminResponse = ProductAdminResponse.builder()
+ .id("product-1")
+ .name("Product 1")
+ .description("Product 1 description")
+ .price(BigDecimal.valueOf(1_000_000))
+ .isAvailable(true)
+ .isFeatured(true)
+ .slug("product-1")
+ .sku("sku-1")
+ .images(Set.of(storageResponse1, storageResponse2))
+ .categories(Set.of(categoryResponse1, categoryResponse2))
+ .build();
+
+ productResponse = ProductResponse.builder()
+ .id("product-1")
+ .name("Product 1")
+ .description("Product 1 description")
+ .price(BigDecimal.valueOf(1_000_000))
+ .isAvailable(true)
+ .isFeatured(true)
+ .slug("product-1")
+ .sku("sku-1")
+ .images(Set.of(storageResponse1, storageResponse2))
+ .categories(Set.of(categoryResponse1, categoryResponse2))
+ .build();
+
+ productUpdate = ProductRequest.builder()
+ .name("Product 1")
+ .description("Product 1 description")
+ .price(BigDecimal.valueOf(1_000_000))
+ .isAvailable(true)
+ .isFeatured(true)
+ .categoryIds(Set.of("category1", "category2"))
+ .slug("product-1")
+ .sku("sku-1")
+ .imageIds(Set.of("image1", "image2"))
+ .build();
+
+ productFilter = new ProductFilter();
+ productFilter.setCategoryIds(new String[]{"category1", "category2"});
+
+ Category category1 = new Category();
+ category1.setId("category1");
+ Category category2 = new Category();
+ category2.setId("category2");
+
+ Product product = new Product();
+ product.setId("product-1");
+
+ ProductAdminResponse productAdminResponse = new ProductAdminResponse();
+ productAdminResponse.setId("product-1");
}
+ @Nested
+ class HappyCase {
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void createProduct_validRequest_success() {
+ // GIVEN
+ when(productRepository.existsBySlug(any())).thenReturn(false);
+ when(productRepository.existsBySku(any())).thenReturn(false);
+ when(categoryRepository.findAllById(Set.of("category1", "category2"))).thenReturn(List.of(category1, category2));
+ when(storageRepository.findAllById(Set.of("image1", "image2"))).thenReturn(List.of(image1, image2));
+ when(productMapper.toProduct(any())).thenReturn(product);
+ when(productRepository.save(product)).thenReturn(product);
+ when(productMapper.toProductAdminResponse(product)).thenReturn(productAdminResponse);
+
+ // WHEN
+ ProductAdminResponse response = productService.create(productCreation);
+
+ // THEN
+ assertThat(response).isNotNull()
+ .hasFieldOrPropertyWithValue("id", "product-1");
+
+ verify(categoryRepository).findAllById(Set.of("category1", "category2"));
+ verify(storageRepository).findAllById(Set.of("image1", "image2"));
+ verify(productRepository).save(product);
+ }
+
+ @Test
+ void getBySlug_validSlug_success() {
+ // GIVEN
+ when(productRepository.findBySlug("product-1")).thenReturn(Optional.of(product));
+ when(productMapper.toProductResponse(any())).thenReturn(productResponse);
+
+ // WHEN
+ ProductResponse response = productService.getBySlug("product-1");
+
+ // THEN
+ assertThat(response).isNotNull()
+ .hasFieldOrPropertyWithValue("id", "product-1");
+ }
- @Test
- @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
- void createProduct_validRequest_success() {
- // GIVEN
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void getById_validId_success() {
+ // GIVEN
+ when(productRepository.findById(any())).thenReturn(Optional.of(product));
+ when(productMapper.toProductAdminResponse(any())).thenReturn(productAdminResponse);
- // WHEN
+ // WHEN
+ ProductAdminResponse productAdminResponse1 = productService.getById("product-1");
- // THEN
+ // THEN
+ assertThat(productAdminResponse1).isNotNull()
+ .hasFieldOrPropertyWithValue("id", "product-1");
+
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void getAllProductsForAdmin_validRequest_success() {
+ // GIVEN
+
+ Pageable pageable = PageRequest.of(0, 5);
+ Page productPage = new PageImpl<>(List.of(product));
+
+
+ when(categoryRepository.findAllById(anyList())).thenReturn(List.of(category1, category2));
+ when(productRepository.findAll(any(Specification.class), eq(pageable))).thenReturn(productPage);
+ when(productMapper.toProductAdminResponse(any(Product.class))).thenReturn(productAdminResponse);
+
+ // WHEN
+ PaginationResponse response = productService.getAllProductsForAdmin(productFilter, pageable);
+
+ // THEN
+ assertThat(response).isNotNull();
+ assertThat(response.getData().getFirst().getId()).isEqualTo("product-1");
+
+ verify(categoryRepository).findAllById(anyList());
+ verify(productRepository).findAll(any(Specification.class), eq(pageable));
+ verify(productMapper, times(1)).toProductAdminResponse(any(Product.class));
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void toggleAvailability_validRequest_success() {
+ // GIVEN
+ List ids = List.of("product-1", "product-2");
+
+ Product product1 = new Product();
+ product1.setId("product-1");
+ product1.setIsAvailable(true);
+
+ Product product2 = new Product();
+ product2.setId("product-2");
+ product2.setIsAvailable(false);
+
+ List products = List.of(product1, product2);
+
+ when(productRepository.findAllById(ids)).thenReturn(products);
+
+ // WHEN
+ productService.toggleAvailability(ids);
+
+ // THEN
+ verify(productRepository).findAllById(ids);
+ verify(productRepository).saveAll(products);
+
+ // Check that the availability of the products has been toggled
+ assertThat(product1.getIsAvailable()).isFalse();
+ assertThat(product2.getIsAvailable()).isTrue();
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void update_validRequest_success() {
+ // GIVEN
+ String id = "product-1";
+
+ when(productRepository.findById(id)).thenReturn(Optional.of(product));
+ when(categoryRepository.existsBySlug(productUpdate.getSlug())).thenReturn(false);
+ when(categoryRepository.existsBySlug(productUpdate.getSku())).thenReturn(false);
+ when(categoryRepository.findAllById(anySet())).thenReturn(categories);
+ when(storageRepository.findAllById(anySet())).thenReturn(images);
+ when(productRepository.save(any(Product.class))).thenReturn(product);
+ when(productMapper.toProductAdminResponse(any(Product.class))).thenReturn(productAdminResponse);
+
+ // WHEN
+ ProductAdminResponse response = productService.update(id, productUpdate);
+
+ // THEN
+ assertThat(response).isNotNull();
+ assertThat(response.getId()).isEqualTo("product-1");
+
+ verify(productRepository).findById(id);
+ verify(categoryRepository).findAllById(anySet());
+ verify(storageRepository).findAllById(anySet());
+ verify(productRepository).save(any(Product.class));
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void delete_allProductIdsExistAndNoneInOrderOrCart_success() {
+ // GIVEN
+ List ids = List.of("product-1");
+ product.setOrderItems(Set.of());
+ product.setCartItems(Set.of());
+ List products = List.of(product);
+
+ when(productRepository.findAllById(ids)).thenReturn(products);
+
+ // WHEN
+ productService.delete(ids);
+
+ // THEN
+ verify(productRepository).findAllById(ids);
+ verify(productRepository).deleteAll(products);
+ }
}
+ @Nested
+ class UnhappyCase {
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void delete_someProductIdsDoNotExist_throwsException() {
+ // GIVEN
+ List ids = List.of("product-1", "product-2");
+ Product product1 = new Product();
+ product1.setId("product-1");
+ List products = List.of(product1);
+
+ when(productRepository.findAllById(ids)).thenReturn(products);
+
+ // THEN
+ assertThrows(AppException.class, () -> productService.delete(ids));
+ }
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void delete_productInOrderOrCart_throwsException() {
+ // GIVEN
+ List ids = List.of("product-1");
+ Product product1 = new Product();
+ product1.setId("product-1");
+ product1.setOrderItems(Set.of(new OrderItem())); // product is in an order
+ List products = List.of(product1);
+
+ when(productRepository.findAllById(ids)).thenReturn(products);
+
+ // THEN
+ assertThrows(AppException.class, () -> productService.delete(ids));
+ }
+ }
}
diff --git a/src/test/java/com/learning/yasminishop/service/RatingServiceTest.java b/src/test/java/com/learning/yasminishop/service/RatingServiceTest.java
new file mode 100644
index 0000000..6830b6e
--- /dev/null
+++ b/src/test/java/com/learning/yasminishop/service/RatingServiceTest.java
@@ -0,0 +1,179 @@
+package com.learning.yasminishop.service;
+
+import com.learning.yasminishop.common.dto.PaginationResponse;
+import com.learning.yasminishop.common.entity.Product;
+import com.learning.yasminishop.common.entity.Rating;
+import com.learning.yasminishop.common.entity.User;
+import com.learning.yasminishop.common.exception.AppException;
+import com.learning.yasminishop.product.ProductRepository;
+import com.learning.yasminishop.rating.RatingRepository;
+import com.learning.yasminishop.rating.RatingService;
+import com.learning.yasminishop.rating.dto.request.RatingRequest;
+import com.learning.yasminishop.rating.dto.response.RatingResponse;
+import com.learning.yasminishop.user.UserRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.TestPropertySource;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+
+@SpringBootTest
+@Slf4j
+@TestPropertySource("/test.properties")
+class RatingServiceTest {
+
+ @Autowired
+ private RatingService ratingService;
+
+ @MockBean
+ private ProductRepository productRepository;
+
+ @MockBean
+ private RatingRepository ratingRepository;
+
+ @MockBean
+ private UserRepository userRepository;
+
+ private RatingRequest ratingRequest;
+ private Product product;
+ private User user;
+ private Rating rating;
+ private Pageable pageable;
+
+ @BeforeEach
+ void setUp() {
+ pageable = PageRequest.of(0, 5);
+
+ ratingRequest = RatingRequest.builder()
+ .productId("product-1")
+ .star(5)
+ .comment("Review")
+ .build();
+
+ product = Product.builder()
+ .id("product-1")
+ .name("Product 1")
+ .description("Product 1 description")
+ .price(BigDecimal.valueOf(100))
+ .quantity(10L)
+ .slug("product-1")
+ .sku("sku-1")
+ .isFeatured(true)
+ .isAvailable(true)
+ .build();
+ user = User.builder()
+ .email("user@test.com")
+ .password("password")
+ .build();
+
+ rating = Rating.builder()
+ .id("rating-1")
+ .star(5)
+ .comment("Review")
+ .product(product)
+ .user(user)
+ .build();
+
+ }
+
+ @Nested
+ class HappyCase {
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void create_validRequest_success() {
+ // GIVEN
+ when(productRepository.findById(anyString())).thenReturn(java.util.Optional.of(product));
+ when(userRepository.findByEmail(anyString())).thenReturn(java.util.Optional.of(user));
+ when(ratingRepository.existsByProductAndUser(any(), any())).thenReturn(false);
+ when(ratingRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0));
+
+ // WHEN
+ var result = ratingService.create(ratingRequest);
+
+ // THEN
+ assertThat(result).isNotNull();
+ assertThat(result.getStar()).isEqualTo(ratingRequest.getStar());
+ assertThat(result.getComment()).isEqualTo(ratingRequest.getComment());
+ }
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void getRatings_validRequest_success() {
+ // GIVEN
+ List ratings = List.of(rating);
+ Page ratingPage = new PageImpl<>(ratings, pageable, ratings.size());
+ when(ratingRepository.findByProductOrderByCreatedDateDesc(any(), any())).thenReturn(ratingPage);
+ when(productRepository.findById(anyString())).thenReturn(java.util.Optional.of(product));
+
+ // WHEN
+ PaginationResponse result = ratingService.getRatings("product-1", pageable);
+
+ // THEN
+ Assertions.assertThat(result).isNotNull();
+ Assertions.assertThat(result.getData()).isNotEmpty();
+ Assertions.assertThat(result.getData().getFirst().getStar()).isEqualTo(rating.getStar());
+ Assertions.assertThat(result.getData().getFirst().getComment()).isEqualTo(rating.getComment());
+ }
+
+ }
+
+ @Nested
+ class UnHappyCase {
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void create_ratingAlreadyExists_exception() {
+ // GIVEN
+ when(productRepository.findById(anyString())).thenReturn(java.util.Optional.of(product));
+ when(userRepository.findByEmail(anyString())).thenReturn(java.util.Optional.of(user));
+ when(ratingRepository.existsByProductAndUser(any(), any())).thenReturn(true);
+
+ // WHEN
+ var exception = assertThrows(AppException.class, () -> ratingService.create(ratingRequest));
+
+
+ // THEN
+ assertThat(exception).isInstanceOf(AppException.class);
+ Assertions.assertThat(exception.getErrorCode().getInternalCode())
+ .isEqualTo(1017);
+
+ }
+
+ @Test
+ @WithMockUser(username = "user@test.com")
+ void create_productNotFound_throwException() {
+ // GIVEN
+ when(productRepository.findById(anyString())).thenReturn(java.util.Optional.empty());
+
+ // WHEN
+ var exception = assertThrows(AppException.class, () -> ratingService.create(ratingRequest));
+
+ // THEN
+ assertThat(exception).isInstanceOf(AppException.class);
+ Assertions.assertThat(exception.getErrorCode().getInternalCode())
+ .isEqualTo(1009);
+ }
+
+
+ }
+
+
+}
diff --git a/src/test/java/com/learning/yasminishop/service/RoleServiceTest.java b/src/test/java/com/learning/yasminishop/service/RoleServiceTest.java
index cc3b7b6..8c17d81 100644
--- a/src/test/java/com/learning/yasminishop/service/RoleServiceTest.java
+++ b/src/test/java/com/learning/yasminishop/service/RoleServiceTest.java
@@ -10,6 +10,7 @@
import com.learning.yasminishop.role.dto.response.RoleResponse;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@@ -20,6 +21,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
@@ -69,59 +71,61 @@ void setUp() {
.build());
}
- @Test
- @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
- void create_validRequest_success() {
- // GIVEN
- when(permissionRepository.findAllById(any())).thenReturn(permissionList);
- when(roleRepository.save(any())).thenReturn(Role.builder()
- .name("USER")
- .description("User role")
- .permissions(new HashSet<>(permissionList))
- .build());
-
- // WHEN
- RoleResponse response = roleService.create(roleRequest);
-
- // THEN
- assertThat(response).isNotNull();
- assertThat(response.getName()).isEqualTo("USER");
- assertThat(response.getDescription()).isEqualTo("User role");
- assertThat(response.getPermissions()).isNotEmpty();
- assertThat(response.getPermissions()).hasSize(1);
+ @Nested
+ class HappyCase {
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void create_validRequest_success() {
+ // GIVEN
+ when(permissionRepository.findAllById(any())).thenReturn(permissionList);
+ when(roleRepository.save(any())).thenReturn(Role.builder()
+ .name("USER")
+ .description("User role")
+ .permissions(new HashSet<>(permissionList))
+ .build());
+
+ // WHEN
+ RoleResponse response = roleService.create(roleRequest);
+
+ // THEN
+ assertThat(response).isNotNull();
+ assertThat(response.getName()).isEqualTo("USER");
+ assertThat(response.getDescription()).isEqualTo("User role");
+ assertThat(response.getPermissions()).isNotEmpty();
+ assertThat(response.getPermissions()).hasSize(1);
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void getAll_validRequest_success() {
+ // GIVEN
+ when(roleRepository.findAll()).thenReturn(List.of(Role.builder()
+ .name("USER")
+ .description("User role")
+ .permissions(new HashSet<>(permissionList))
+ .build()));
+
+ // WHEN
+ List response = roleService.getAll();
+
+ // THEN
+ assertThat(response).isNotNull()
+ .isNotEmpty()
+ .hasSize(1);
+ }
+
+ @Test
+ @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
+ void delete_validRequest_success() {
+ // GIVEN
+ doNothing().when(roleRepository).deleteById(anyString());
+
+ // WHEN
+ roleService.delete("USER");
+
+ // THEN
+ verify(roleRepository, times(1)).deleteById("USER");
+ }
}
- @Test
- @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
- void getAll_validRequest_success() {
- // GIVEN
- when(roleRepository.findAll()).thenReturn(List.of(Role.builder()
- .name("USER")
- .description("User role")
- .permissions(new HashSet<>(permissionList))
- .build()));
-
- // WHEN
- List response = roleService.getAll();
-
- // THEN
- assertThat(response).isNotNull()
- .isNotEmpty()
- .hasSize(1);
- }
-
- @Test
- @WithMockUser(username = "admin@test.com", roles = {"ADMIN"})
- void delete_validRequest_success() {
- // GIVEN
- doNothing().when(roleRepository).deleteById(anyString());
-
- // WHEN
- roleService.delete("USER");
-
- // THEN
- verify(roleRepository, times(1)).deleteById("USER");
- }
-
-
}
diff --git a/src/test/java/com/learning/yasminishop/service/UserServiceTest.java b/src/test/java/com/learning/yasminishop/service/UserServiceTest.java
index 00a3e36..5a095c3 100644
--- a/src/test/java/com/learning/yasminishop/service/UserServiceTest.java
+++ b/src/test/java/com/learning/yasminishop/service/UserServiceTest.java
@@ -15,6 +15,7 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@@ -48,8 +49,6 @@ class UserServiceTest {
private User user;
private UserUpdateRequest userUpdateRequest;
-
-
@BeforeEach
void setUp() {
@@ -78,133 +77,141 @@ void setUp() {
.build();
-
- }
-
- @Test
- @WithMockUser(username = "duongminhhieu@gmail.com")
- void getMyInfo_validRequest_success() {
- // Given
-
- when(userRepository.findByEmail(anyString()))
- .thenReturn(Optional.of(user));
-
- // When
- var userResponse = userService.getMyInfo();
-
- // Then
- assertThat(userResponse)
- .isNotNull()
- .hasFieldOrPropertyWithValue("id", "abc-123")
- .hasFieldOrPropertyWithValue("email", "duongminhhieu@gmail.com")
- .hasFieldOrPropertyWithValue("firstName", "Hieu")
- .hasFieldOrPropertyWithValue("lastName", "Duong")
- .hasFieldOrPropertyWithValue("dob", LocalDate.of(1999, 1, 1));
- }
-
- @Test
- @WithMockUser(username = "abc@gmail.com")
- void getMyInfo_userNotFound_error() {
- // Given
- when(userRepository.findByEmail(anyString()))
- .thenReturn(Optional.empty());
-
- // When
- var exception = assertThrows(AppException.class, () -> userService.getMyInfo());
-
- // Then
- assertThat(exception.getErrorCode().getInternalCode())
- .isEqualTo(1006);
- assertThat(exception.getErrorCode().getMessage())
- .isEqualTo("User not found");
}
-
- @Test
- @WithMockUser(username = "admin@spring.com", roles = {"ADMIN"})
- void updateUser_validRequest_success() {
- // Given
- when(userRepository.findById(anyString()))
- .thenReturn(Optional.of(user));
-
- when(userRepository.save(user))
- .thenReturn(user);
-
- // When
- var userResponse = userService.updateUser("abc-123", userUpdateRequest);
-
- // Then
- assertThat(userResponse)
- .isNotNull()
- .hasFieldOrPropertyWithValue("id", "abc-123")
- .hasFieldOrPropertyWithValue("firstName", "Hieu")
- .hasFieldOrPropertyWithValue("lastName", "Duong")
-
- ;
- assertThat(userResponse.getRoles())
- .containsExactlyInAnyOrder(
- RoleResponse.builder()
- .name("USER")
- .description("User role")
- .permissions(Set.of())
- .build(),
- RoleResponse.builder()
- .name("ADMIN")
- .description("Admin role")
- .permissions(Set.of())
- .build()
- );
+ @Nested
+ class HappyCase {
+ @Test
+ @WithMockUser(username = "duongminhhieu@gmail.com")
+ void getMyInfo_validRequest_success() {
+ // Given
+
+ when(userRepository.findByEmail(anyString()))
+ .thenReturn(Optional.of(user));
+
+ // When
+ var userResponse = userService.getMyInfo();
+
+ // Then
+ assertThat(userResponse)
+ .isNotNull()
+ .hasFieldOrPropertyWithValue("id", "abc-123")
+ .hasFieldOrPropertyWithValue("email", "duongminhhieu@gmail.com")
+ .hasFieldOrPropertyWithValue("firstName", "Hieu")
+ .hasFieldOrPropertyWithValue("lastName", "Duong")
+ .hasFieldOrPropertyWithValue("dob", LocalDate.of(1999, 1, 1));
+ }
+
+ @Test
+ @WithMockUser(username = "admin@spring.com", roles = {"ADMIN"})
+ void updateUser_validRequest_success() {
+ // Given
+ when(userRepository.findById(anyString()))
+ .thenReturn(Optional.of(user));
+
+ when(userRepository.save(user))
+ .thenReturn(user);
+
+ // When
+ var userResponse = userService.updateUser("abc-123", userUpdateRequest);
+
+ // Then
+ assertThat(userResponse)
+ .isNotNull()
+ .hasFieldOrPropertyWithValue("id", "abc-123")
+ .hasFieldOrPropertyWithValue("firstName", "Hieu")
+ .hasFieldOrPropertyWithValue("lastName", "Duong")
+
+ ;
+ assertThat(userResponse.getRoles())
+ .containsExactlyInAnyOrder(
+ RoleResponse.builder()
+ .name("USER")
+ .description("User role")
+ .permissions(Set.of())
+ .build(),
+ RoleResponse.builder()
+ .name("ADMIN")
+ .description("Admin role")
+ .permissions(Set.of())
+ .build()
+ );
+
+ }
+
+ @Test
+ @WithMockUser(username = "admin@spring.com", roles = {"ADMIN"})
+ void deleteUser_valid_success() {
+ // Given
+ when(userRepository.existsById(anyString()))
+ .thenReturn(true);
+ // When
+ userService.deleteUser("abc-123");
+ // Then
+ verify(userRepository, times(1)).deleteById("abc-123");
+ }
+
+
+ @Test
+ @WithMockUser(username = "admin@spring.com", roles = {"ADMIN"})
+ void getUserById_valid_success() {
+ // Given
+ when(userRepository.findById(anyString()))
+ .thenReturn(Optional.of(user));
+ // When
+ var response = userService.getUserById("abc-123");
+ // Then
+ assertThat(response)
+ .isNotNull()
+ .hasFieldOrPropertyWithValue("id", "abc-123")
+ .hasFieldOrPropertyWithValue("email", "duongminhhieu@gmail.com")
+ .hasFieldOrPropertyWithValue("firstName", "Hieu")
+ .hasFieldOrPropertyWithValue("lastName", "Duong")
+ .hasFieldOrPropertyWithValue("dob", LocalDate.of(1999, 1, 1));
+
+ }
}
- @Test
- @WithMockUser(username = "admin@spring.com", roles = {"ADMIN"})
- void updateUser_notFoundUser_error() {
- // Given
- when(userRepository.findById(anyString()))
- .thenReturn(Optional.empty());
-
- // When
- var exception = assertThrows(AppException.class, () -> userService.updateUser("abc-123", userUpdateRequest));
-
- // Then
- assertThat(exception.getErrorCode().getInternalCode())
- .isEqualTo(1006);
-
- assertThat(exception.getErrorCode().getMessage())
- .isEqualTo("User not found");
- }
+ @Nested
+ class UnHappyCase {
+ @Test
+ @WithMockUser(username = "abc@gmail.com")
+ void getMyInfo_userNotFound_error() {
+ // Given
+ when(userRepository.findByEmail(anyString()))
+ .thenReturn(Optional.empty());
+
+ // When
+ var exception = assertThrows(AppException.class, () -> userService.getMyInfo());
+
+ // Then
+ assertThat(exception.getErrorCode().getInternalCode())
+ .isEqualTo(1006);
+ assertThat(exception.getErrorCode().getMessage())
+ .isEqualTo("User not found");
+ }
+
+ @Test
+ @WithMockUser(username = "admin@spring.com", roles = {"ADMIN"})
+ void updateUser_notFoundUser_error() {
+ // Given
+ when(userRepository.findById(anyString()))
+ .thenReturn(Optional.empty());
+
+ // When
+ var exception = assertThrows(AppException.class, () -> userService.updateUser("abc-123", userUpdateRequest));
+
+ // Then
+ assertThat(exception.getErrorCode().getInternalCode())
+ .isEqualTo(1006);
+
+ assertThat(exception.getErrorCode().getMessage())
+ .isEqualTo("User not found");
+ }
- @Test
- @WithMockUser(username = "admin@spring.com", roles = {"ADMIN"})
- void deleteUser_valid_success() {
- // Given
- when(userRepository.existsById(anyString()))
- .thenReturn(true);
- // When
- userService.deleteUser("abc-123");
- // Then
- verify(userRepository, times(1)).deleteById("abc-123");
}
- @Test
- @WithMockUser(username = "admin@spring.com", roles = {"ADMIN"})
- void getUserById_valid_success() {
- // Given
- when(userRepository.findById(anyString()))
- .thenReturn(Optional.of(user));
- // When
- var response = userService.getUserById("abc-123");
- // Then
- assertThat(response)
- .isNotNull()
- .hasFieldOrPropertyWithValue("id", "abc-123")
- .hasFieldOrPropertyWithValue("email", "duongminhhieu@gmail.com")
- .hasFieldOrPropertyWithValue("firstName", "Hieu")
- .hasFieldOrPropertyWithValue("lastName", "Duong")
- .hasFieldOrPropertyWithValue("dob", LocalDate.of(1999, 1, 1));
-
- }
}