Sauvegarde de l'avancée

This commit is contained in:
2025-06-13 06:52:41 +02:00
parent d5049671e6
commit 2a5cbbd745
18 changed files with 915 additions and 173 deletions

View File

@@ -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<OrderLineDTO> orderLines;
private AddressDTO address;
private String paymentMethod;
}

View File

@@ -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();
}
}

View File

@@ -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<OrderLineDTO> 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<OrderLineDTO> 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;
}
}

View File

@@ -0,0 +1,7 @@
package fr.iut_fbleau.but3.dev62.mylibrary.order.exception;
public class NotValidOrderException extends Exception {
public NotValidOrderException(String message) {
super(message);
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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<Order> orders = new ArrayList<>();
public List<Order> findAll() {
return orders;
}
public Order save(Order newOrder) {
Optional<Order> optionalOrderWithSameId = this.findById(newOrder.getId());
optionalOrderWithSameId.ifPresent(orders::remove);
if (newOrder.getId() == null) {
newOrder.setRandomUUID();
}
this.orders.add(newOrder);
return newOrder;
}
public Optional<Order> findById(UUID uuid) {
return this.orders.stream()
.filter(order -> order.getId().equals(uuid))
.findFirst();
}
public List<Order> findByCustomerId(UUID customerId) {
List<Order> 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));
}
}

View File

@@ -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<OrderLineDTO> 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<OrderLineDTO> orderLines) throws NotValidOrderException {
this.tempOrderLines = orderLines;
}
public void setDeliveryAddress(AddressDTO address) {
this.tempAddress = address;
}
private double computeTotalPrice(List<OrderLineDTO> 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<OrderDTO> findOrderById(UUID orderId) {
Optional<Order> order = orderRepository.findById(orderId);
return order.map(OrderConverter::toDTO);
}
public List<OrderDTO> findOrdersByCustomerId(UUID customerId) throws UserNotFoundException {
List<Order> orders = orderRepository.findByCustomerId(customerId);
List<OrderDTO> 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();
}
}

View File

@@ -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<OrderLineDTO> 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);
}
}
}

View File

@@ -2,9 +2,12 @@ package fr.iut_fbleau.but3.dev62.mylibrary.features.order;
import static org.junit.jupiter.api.Assertions.*; 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.OrderLineDTO;
import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderDTO; 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.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.OrderInfo;
import fr.iut_fbleau.but3.dev62.mylibrary.order.repository.OrderRepository; import fr.iut_fbleau.but3.dev62.mylibrary.order.repository.OrderRepository;
import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException;
@@ -26,16 +29,15 @@ import java.text.SimpleDateFormat;
public class OrderSteps { public class OrderSteps {
private final OrderRepository orderRepository = new OrderRepository(); private final OrderRepository orderRepository = new OrderRepository();
private final OrderUserCase orderUserCase = new OrderUseCase(orderRepository);
private final CustomerRepository customerRepository = new CustomerRepository();
private final Map<String, UUID> customerPhoneUUID = new HashMap<>();
private final BookRepository bookRepository = new BookRepository(); private final BookRepository bookRepository = new BookRepository();
private final CustomerRepository customerRepository = new CustomerRepository();
private final OrderUseCase orderUseCase = new OrderUseCase(orderRepository, bookRepository, customerRepository);
private final Map<String, UUID> customerPhoneUUID = new HashMap<>();
private final Map<String, String> bookISBN = new HashMap<>(); private final Map<String, String> bookISBN = new HashMap<>();
private UUID orderRegistration; private UUID orderId;
private Optional<OrderDTO> orderByUUID; private Optional<OrderDTO> orderByUUID;
private Book updatedBook; private List<OrderDTO> orders;
private Customer updatedCustomer;
private Exception exception; private Exception exception;
@@ -56,7 +58,7 @@ public class OrderSteps {
List<Map<String, String>> books = dataTable.asMaps(String.class, String.class); List<Map<String, String>> books = dataTable.asMaps(String.class, String.class);
for (Map<String, String> book : books) { for (Map<String, String> book : books) {
String ISBN = book.get("ISBN"); String ISBN = book.get("isbn");
Book newBook = Book.builder() Book newBook = Book.builder()
.isbn(ISBN) .isbn(ISBN)
.title(book.get("titre")) .title(book.get("titre"))
@@ -76,7 +78,7 @@ public class OrderSteps {
assertEquals(books.size(), bookRepository.findAll().size()); 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) { public void theSystemHasTheFollowingCustomers(DataTable dataTable) {
int size = customerRepository.findAll().size(); int size = customerRepository.findAll().size();
@@ -87,12 +89,12 @@ public class OrderSteps {
List<Map<String, String>> customers = dataTable.asMaps(String.class, String.class); List<Map<String, String>> customers = dataTable.asMaps(String.class, String.class);
for (Map<String, String> customer : customers) { for (Map<String, String> customer : customers) {
String numeroTelephone = customer.get("numeroTelephone"); String numeroTelephone = customer.get("phoneNumber");
Customer newCustomer = Customer.builder() Customer newCustomer = Customer.builder()
.firstName(customer.get("prenom")) .firstName(customer.get("firstName"))
.lastName(customer.get("nom")) .lastName(customer.get("lastName"))
.phoneNumber(numeroTelephone) .phoneNumber(numeroTelephone)
.loyaltyPoints(Integer.parseInt(customer.get("pointsFidelite"))) .loyaltyPoints(Integer.parseInt(customer.get("loyaltyPoints")))
.build(); .build();
Customer save = customerRepository.save(newCustomer); Customer save = customerRepository.save(newCustomer);
customerPhoneUUID.put(numeroTelephone, save.getId()); customerPhoneUUID.put(numeroTelephone, save.getId());
@@ -104,47 +106,42 @@ public class OrderSteps {
@When("I create a new order with the following information:") @When("I create a new order with the following information:")
public void iCreateANewOrderWithTheFollowingInformation(DataTable dataTable) { public void iCreateANewOrderWithTheFollowingInformation(DataTable dataTable) {
Map<String, String> orderData = dataTable.asMaps(String.class, String.class).getFirst(); Map<String, String> orderData = dataTable.asMaps(String.class, String.class).getFirst();
OrderInfo newOrder = new OrderInfo( OrderInfo newOrder = OrderInfo.builder()
UUID.fromString(orderData.get("customerId")), .customerId(UUID.fromString(orderData.get("customerId")))
orderData.get("paymentMethod") .paymentMethod(orderData.get("paymentMethod"))
); .build();
try{ try{
orderRegistration = orderUseCase.registerOrder(newOrder); orderUseCase.registerOrderInfo(newOrder);
exception = null; exception = null;
} catch (Exception e) { } catch (Exception e) {
exception = e; exception = e;
orderRegistration = null;
} }
} }
@And("the order includes the following books:") @And("the order includes the following books:")
public void theOrderIncludesTheFollowingBooks(DataTable dataTable){ public void theOrderIncludesTheFollowingBooks(DataTable dataTable) {
if (exception != null) return;
List<Map<String, String>> books = dataTable.asMaps(String.class, String.class); List<Map<String, String>> books = dataTable.asMaps(String.class, String.class);
List<OrderLineDTO> orderLines = new ArrayList<>(); List<OrderLineDTO> orderLines = new ArrayList<>();
for (Map<String, String> book : books) { for (Map<String, String> book : books) {
String isbn = book.get("ISBN"); String bookId = book.get("bookId");
int quantity = Integer.parseInt(book.get("quantity")); int quantity = Integer.parseInt(book.get("quantity"));
orderLines.add(OrderLineDTO.builder() orderLines.add(OrderLineDTO.builder()
.bookId(isbn) .bookId(bookId)
.quantity(quantity) .quantity(quantity)
.build()); .build());
} }
try { try {
orderUseCase.addBooksToOrder(orderRegistration, orderLines); orderUseCase.addBooksToOrder(orderLines);
exception = null; exception = null;
} catch (Exception e) { } catch (Exception e) {
exception = e; exception = e;
orderRegistration = null;
} }
} }
@And("the delivery address is:") @And("the delivery address is:")
public void theDeliveryAddressIs(DataTable dataTable) { public void theDeliveryAddressIs(DataTable dataTable) {
if (exception != null) return;
Map<String, String> addressData = dataTable.asMaps(String.class, String.class).getFirst(); Map<String, String> addressData = dataTable.asMaps(String.class, String.class).getFirst();
AddressDTO address = AddressDTO.builder() AddressDTO address = AddressDTO.builder()
.street(addressData.get("street")) .street(addressData.get("street"))
@@ -152,35 +149,61 @@ public class OrderSteps {
.postalCode(addressData.get("postalCode")) .postalCode(addressData.get("postalCode"))
.country(addressData.get("country")) .country(addressData.get("country"))
.build(); .build();
try { try {
orderUseCase.setDeliveryAddress(orderRegistration, address); orderUseCase.setDeliveryAddress(address);
exception = null; exception = null;
} catch (Exception e) { } catch (Exception e) {
exception = e; exception = e;
orderRegistration = null;
} }
} }
@Then("a new order is created") @Then("a new order is created")
public void aNewOrderIsCreated() { 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"); 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}") @And("the total price is {double}")
public void theTotalPriceIs(double expectedPrice) { public void theTotalPriceIs(double expectedPrice) throws Exception {
Order order = orderRepository.findById(orderRegistration) Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new Exception("Order not found")); .orElseThrow(() -> new Exception("Order not found"));
double totalPrice = order.getTotalPrice(); double totalPrice = order.getTotalPrice();
assertEquals(expectedPrice, totalPrice, "The total price of the order should match the expected price"); 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") @And("{int} loyalty points are deducted")
public void loyaltyPointsAreDeducted(int loyaltyPoints) { public void loyaltyPointsAreDeducted(int loyaltyPoints) throws Exception {
Order order = orderRepository.findById(orderRegistration).orElseThrow(); Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new Exception("Order not found"));
Customer customer = customerRepository.findById(order.getCustomerId()) Customer customer = customerRepository.findById(order.getCustomerId())
.orElseThrow(() -> new Exception("Customer not found")); .orElseThrow(() -> new Exception("Customer not found"));
int remainingPoints = customer.getLoyaltyPoints() - loyaltyPoints; int remainingPoints = customer.getLoyaltyPoints() - loyaltyPoints;
assertEquals(remainingPoints, customer.getLoyaltyPoints(), "The customer's loyalty points should be deducted correctly"); 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) { public void iReceiveAnErrorForNotFoundExceptionMessageContaining(String errorMessage) {
assertNotNull(exception, "An exception should be thrown during order retrieval"); assertNotNull(exception, "An exception should be thrown during order retrieval");
assertInstanceOf(OrderNotFoundException.class, exception, "The exception should be of type OrderNotFoundException"); 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}") @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") @And("the order includes no books")
public void theOrderIncludesNoBooks() { public void theOrderIncludesNoBooks() {
if (exception != null) return;
List<OrderLineDTO> orderLines = new ArrayList<>(); List<OrderLineDTO> orderLines = new ArrayList<>();
try { try {
orderUseCase.addBooksToOrder(orderRegistration, orderLines); orderUseCase.addBooksToOrder(orderLines);
exception = null; exception = null;
} catch (Exception e) { } catch (Exception e) {
exception = e; exception = e;
orderRegistration = null;
} }
} }
@@ -245,19 +267,21 @@ public class OrderSteps {
.postalCode("12345") .postalCode("12345")
.country("USA") .country("USA")
.build()) .build())
.paymentMethod("CREDIT_CARD") .paymentMethod(PaymentMethod.CREDIT_CARD)
.build(); .build();
orderRepository.save(order); orderRepository.save(order);
} }
@When("I retrieve the order by ID {string}") @When("I retrieve the order by ID {string}")
public void iRetrieveTheOrderByID(String orderId) { public void iRetrieveTheOrderByID(String orderId) {
UUID orderUUID = UUID.fromString(orderId);
try { try {
UUID orderUUID = UUID.fromString(orderId);
orderByUUID = orderUseCase.findOrderById(orderUUID); orderByUUID = orderUseCase.findOrderById(orderUUID);
exception = null; exception = null;
} catch (IllegalArgumentException e) {
exception = new OrderNotFoundException("Order not found");
orderByUUID = Optional.empty();
} catch (Exception e) { } catch (Exception e) {
exception = e; exception = e;
orderByUUID = Optional.empty(); orderByUUID = Optional.empty();
@@ -269,14 +293,15 @@ public class OrderSteps {
assertTrue(orderByUUID.isPresent(), "The order should be found by ID"); assertTrue(orderByUUID.isPresent(), "The order should be found by ID");
OrderDTO order = orderByUUID.get(); OrderDTO order = orderByUUID.get();
assertNotNull(order, "The retrieved order should not be null"); 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}") @When("I request all orders for customer {string}")
public void iRequestAllOrdersForCustomer(String customerId) { public void iRequestAllOrdersForCustomer(String customerId) {
UUID customerUUID = UUID.fromString(customerId); UUID customerUUID = UUID.fromString(customerId);
List<OrderDTO> orders;
try { try {
orders = orderUseCase.findOrdersByCustomerId(customerUUID); orders = orderUseCase.findOrdersByCustomerId(customerUUID);
exception = null; exception = null;

View File

@@ -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<OrderLineDTO> 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<OrderLineDTO> 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));
}
}

View File

@@ -60,12 +60,11 @@ class OrderTest {
OrderLineDTO.builder().bookId("1234567890123").quantity(2).build(), OrderLineDTO.builder().bookId("1234567890123").quantity(2).build(),
OrderLineDTO.builder().bookId("9876543210123").quantity(1).build() OrderLineDTO.builder().bookId("9876543210123").quantity(1).build()
); );
// Supposons prix 39.99 et 49.99
double price1 = 39.99; double price1 = 39.99;
double price2 = 49.99; double price2 = 49.99;
double expectedTotal = 2 * price1 + 1 * price2; 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; double total = 2 * price1 + 1 * price2;
assertEquals(expectedTotal, total); assertEquals(expectedTotal, total);
} }
@@ -106,7 +105,7 @@ class OrderTest {
@Test @Test
@DisplayName("Create order with credit card - success") @DisplayName("Create order with credit card - success")
void testCreateOrderWithCreditCard() { void testCreateOrderWithCreditCard() {
// Arrange
UUID customerId = UUID.fromString("11111111-1111-1111-1111-111111111111"); UUID customerId = UUID.fromString("11111111-1111-1111-1111-111111111111");
List<OrderLineDTO> orderLines = List.of( List<OrderLineDTO> orderLines = List.of(
OrderLineDTO.builder().bookId("1234567890123").quantity(2).build() OrderLineDTO.builder().bookId("1234567890123").quantity(2).build()
@@ -114,7 +113,7 @@ class OrderTest {
AddressDTO address = AddressDTO.builder() AddressDTO address = AddressDTO.builder()
.street("12 Main St.").city("Paris").postalCode("75000").country("France").build(); .street("12 Main St.").city("Paris").postalCode("75000").country("France").build();
double expectedTotal = 79.98; double expectedTotal = 79.98;
// Act
OrderDTO order = OrderDTO.builder() OrderDTO order = OrderDTO.builder()
.id(UUID.randomUUID()) .id(UUID.randomUUID())
.customerId(customerId) .customerId(customerId)
@@ -124,7 +123,7 @@ class OrderTest {
.address(address) .address(address)
.paymentMethod(PaymentMethod.CREDIT_CARD) .paymentMethod(PaymentMethod.CREDIT_CARD)
.build(); .build();
// Assert
assertEquals(expectedTotal, order.getTotalPrice()); assertEquals(expectedTotal, order.getTotalPrice());
assertEquals(PaymentMethod.CREDIT_CARD, order.getPaymentMethod()); assertEquals(PaymentMethod.CREDIT_CARD, order.getPaymentMethod());
assertEquals(customerId, order.getCustomerId()); assertEquals(customerId, order.getCustomerId());

View File

@@ -14,7 +14,7 @@ class OrderNotFoundExceptionTest {
@DisplayName("Exception message should contain the UUID provided") @DisplayName("Exception message should contain the UUID provided")
void testExceptionMessageContainsUUID() { void testExceptionMessageContainsUUID() {
UUID uuid = UUID.randomUUID(); 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); String expectedMessage = String.format("The order with id %s does not exist", uuid);
assertEquals(expectedMessage, exception.getMessage()); assertEquals(expectedMessage, exception.getMessage());
} }
@@ -23,7 +23,7 @@ class OrderNotFoundExceptionTest {
@DisplayName("Exception should use the correct constant message format") @DisplayName("Exception should use the correct constant message format")
void testExceptionUsesConstantMessageFormat() { void testExceptionUsesConstantMessageFormat() {
UUID uuid = UUID.randomUUID(); 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"; String expectedFormatWithPlaceholder = "The order with id {0} does not exist";
assertEquals(OrderNotFoundException.THE_ORDER_WITH_ID_DOES_NOT_EXIST_MESSAGE, expectedFormatWithPlaceholder); assertEquals(OrderNotFoundException.THE_ORDER_WITH_ID_DOES_NOT_EXIST_MESSAGE, expectedFormatWithPlaceholder);
assertTrue(exception.getMessage().contains(uuid.toString())); assertTrue(exception.getMessage().contains(uuid.toString()));
@@ -34,7 +34,7 @@ class OrderNotFoundExceptionTest {
void testExceptionCanBeThrownAndCaught() { void testExceptionCanBeThrownAndCaught() {
UUID uuid = UUID.randomUUID(); UUID uuid = UUID.randomUUID();
try { try {
throw new OrderNotFoundException(uuid); throw new OrderNotFoundException(uuid.toString());
} catch (OrderNotFoundException e) { } catch (OrderNotFoundException e) {
String expectedMessage = String.format("The order with id %s does not exist", uuid); String expectedMessage = String.format("The order with id %s does not exist", uuid);
assertEquals(expectedMessage, e.getMessage()); assertEquals(expectedMessage, e.getMessage());

View File

@@ -14,7 +14,7 @@ class UserNotFoundExceptionTest {
@DisplayName("Exception message should contain the UUID provided") @DisplayName("Exception message should contain the UUID provided")
void testExceptionMessageContainsUUID() { void testExceptionMessageContainsUUID() {
UUID uuid = UUID.randomUUID(); 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); String expectedMessage = String.format("The customer with id %s does not exist", uuid);
assertEquals(expectedMessage, exception.getMessage()); assertEquals(expectedMessage, exception.getMessage());
} }
@@ -23,7 +23,7 @@ class UserNotFoundExceptionTest {
@DisplayName("Exception should use the correct constant message format") @DisplayName("Exception should use the correct constant message format")
void testExceptionUsesConstantMessageFormat() { void testExceptionUsesConstantMessageFormat() {
UUID uuid = UUID.randomUUID(); 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"; String expectedFormatWithPlaceholder = "The customer with id {0} does not exist";
assertEquals(UserNotFoundException.THE_CUSTOMER_WITH_ID_DOES_NOT_EXIST_MESSAGE, expectedFormatWithPlaceholder); assertEquals(UserNotFoundException.THE_CUSTOMER_WITH_ID_DOES_NOT_EXIST_MESSAGE, expectedFormatWithPlaceholder);
assertTrue(exception.getMessage().contains(uuid.toString())); assertTrue(exception.getMessage().contains(uuid.toString()));
@@ -34,7 +34,7 @@ class UserNotFoundExceptionTest {
void testExceptionCanBeThrownAndCaught() { void testExceptionCanBeThrownAndCaught() {
UUID uuid = UUID.randomUUID(); UUID uuid = UUID.randomUUID();
try { try {
throw new UserNotFoundException(uuid); throw new UserNotFoundException(uuid.toString());
} catch (UserNotFoundException e) { } catch (UserNotFoundException e) {
String expectedMessage = String.format("The customer with id %s does not exist", uuid); String expectedMessage = String.format("The customer with id %s does not exist", uuid);
assertEquals(expectedMessage, e.getMessage()); assertEquals(expectedMessage, e.getMessage());

View File

@@ -1,9 +1,7 @@
package fr.iut_fbleau.but3.dev62.mylibrary.order.repository; 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.*;
import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order;
import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.OrderNotFoundException;
import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.UserNotFoundException;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
@@ -14,8 +12,8 @@ import static org.junit.jupiter.api.Assertions.*;
class OrderRepositoryTest { class OrderRepositoryTest {
private OrderRepository repository; private OrderRepository repository;
private OrderDTO order1; private Order order1;
private OrderDTO order2; private Order order2;
private UUID customerId1; private UUID customerId1;
private UUID customerId2; private UUID customerId2;
@@ -24,7 +22,7 @@ class OrderRepositoryTest {
repository = new OrderRepository(); repository = new OrderRepository();
customerId1 = UUID.fromString("11111111-1111-1111-1111-111111111111"); customerId1 = UUID.fromString("11111111-1111-1111-1111-111111111111");
customerId2 = UUID.fromString("22222222-2222-2222-2222-222222222222"); customerId2 = UUID.fromString("22222222-2222-2222-2222-222222222222");
order1 = OrderDTO.builder() order1 = Order.builder()
.id(UUID.randomUUID()) .id(UUID.randomUUID())
.customerId(customerId1) .customerId(customerId1)
.orderLines(List.of(OrderLineDTO.builder().bookId("1234567890123").quantity(2).build())) .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()) .address(AddressDTO.builder().street("12 Main St.").city("Paris").postalCode("75000").country("France").build())
.paymentMethod(PaymentMethod.CREDIT_CARD) .paymentMethod(PaymentMethod.CREDIT_CARD)
.build(); .build();
order2 = OrderDTO.builder() order2 = Order.builder()
.id(UUID.randomUUID()) .id(UUID.randomUUID())
.customerId(customerId2) .customerId(customerId2)
.orderLines(List.of(OrderLineDTO.builder().bookId("9876543210123").quantity(1).build())) .orderLines(List.of(OrderLineDTO.builder().bookId("9876543210123").quantity(1).build()))
@@ -47,7 +45,7 @@ class OrderRepositoryTest {
@Test @Test
@DisplayName("New repository should be empty") @DisplayName("New repository should be empty")
void testNewRepositoryIsEmpty() { void testNewRepositoryIsEmpty() {
List<OrderDTO> orders = repository.findAll(); List<Order> orders = repository.findAll();
assertTrue(orders.isEmpty()); assertTrue(orders.isEmpty());
assertEquals(0, orders.size()); assertEquals(0, orders.size());
} }
@@ -58,8 +56,9 @@ class OrderRepositoryTest {
@Test @Test
@DisplayName("Save should add a new order") @DisplayName("Save should add a new order")
void testSaveNewOrder() { void testSaveNewOrder() {
OrderDTO savedOrder = repository.save(order1); Order savedOrder = repository.save(order1);
assertEquals(1, repository.findAll().size()); Optional<Order> foundOrder = repository.findById(order1.getId());
assertTrue(foundOrder.isPresent());
assertEquals(order1.getId(), savedOrder.getId()); assertEquals(order1.getId(), savedOrder.getId());
assertEquals(order1.getCustomerId(), savedOrder.getCustomerId()); assertEquals(order1.getCustomerId(), savedOrder.getCustomerId());
} }
@@ -69,7 +68,7 @@ class OrderRepositoryTest {
void testSaveMultipleOrders() { void testSaveMultipleOrders() {
repository.save(order1); repository.save(order1);
repository.save(order2); repository.save(order2);
List<OrderDTO> orders = repository.findAll(); List<Order> orders = repository.findAll();
assertEquals(2, orders.size()); assertEquals(2, orders.size());
assertTrue(orders.contains(order1)); assertTrue(orders.contains(order1));
assertTrue(orders.contains(order2)); assertTrue(orders.contains(order2));
@@ -88,108 +87,18 @@ class OrderRepositoryTest {
@Test @Test
@DisplayName("FindById should return order with matching ID") @DisplayName("FindById should return order with matching ID")
void testFindById() { void testFindById() {
Optional<OrderDTO> foundOrder = repository.findById(order1.getId()); Optional<Order> foundOrder = repository.findById(order1.getId());
assertTrue(foundOrder.isPresent()); assertTrue(foundOrder.isPresent());
assertEquals(order1.getCustomerId(), foundOrder.get().getCustomerId()); 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 @Test
@DisplayName("FindByCustomerId should return all orders for a customer") @DisplayName("FindByCustomerId should return all orders for a customer")
void testFindByCustomerId() { void testFindByCustomerId() {
List<OrderDTO> orders = repository.findByCustomerId(customerId1); List<Order> orders = repository.findByCustomerId(customerId1);
assertFalse(orders.isEmpty()); assertFalse(orders.isEmpty());
assertTrue(orders.stream().allMatch(o -> o.getCustomerId().equals(customerId1))); 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);
});
}
} }
} }

View File

@@ -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<OrderDTO> 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<OrderDTO> orders = List.of(validOrderDTO);
when(orderRepository.findByCustomerId(customerId)).thenReturn(List.of(order));
List<OrderDTO> 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);
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -6,7 +6,7 @@ Feature: Manage customer orders
| isbn | titre | auteur | editeur | datePublication | prix | stockInitial | categories | description | langue | | 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 | | 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 | | 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 | | id | firstName | lastName | phoneNumber | loyaltyPoints |
| 11111111-1111-1111-1111-111111111111 | Alice | Smith | 0612345678 | 100 | | 11111111-1111-1111-1111-111111111111 | Alice | Smith | 0612345678 | 100 |
| 22222222-2222-2222-2222-222222222222 | Bob | Martin | 0698765432 | 10 | | 22222222-2222-2222-2222-222222222222 | Bob | Martin | 0698765432 | 10 |
@@ -25,7 +25,7 @@ Feature: Manage customer orders
| 12 Main St. | Paris | 75000 | France | | 12 Main St. | Paris | 75000 | France |
Then a new order is created Then a new order is created
And the total price is 79.98 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 Scenario: Create an order using loyalty points
When I create a new order with the following information: When I create a new order with the following information:
@@ -39,8 +39,8 @@ Feature: Manage customer orders
| 42 Book Street | Lyon | 69000 | France | | 42 Book Street | Lyon | 69000 | France |
Then a new order is created Then a new order is created
And the total price is 49.99 And the total price is 49.99
And 49 loyalty points are deducted And 50 loyalty points are deducted
And customer "11111111-1111-1111-1111-111111111111" now has 51 loyalty points And The customer "11111111-1111-1111-1111-111111111111" now has 50 loyalty points
Scenario: Create an order with multiple books Scenario: Create an order with multiple books
When I create a new order with the following information: When I create a new order with the following information:
@@ -55,7 +55,7 @@ Feature: Manage customer orders
| 12 Main St. | Paris | 75000 | France | | 12 Main St. | Paris | 75000 | France |
Then a new order is created Then a new order is created
And the total price is 239.92 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 Scenario: Attempt to create an order with invalid address
When I create a new order with the following information: When I create a new order with the following information:
@@ -67,7 +67,7 @@ Feature: Manage customer orders
And the delivery address is: And the delivery address is:
| street | city | postalCode | country | | 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" And I receive an error for validation order message containing "Address fields are required"
Scenario: Attempt to create an order with unknown customer Scenario: Attempt to create an order with unknown customer
@@ -80,7 +80,7 @@ Feature: Manage customer orders
And the delivery address is: And the delivery address is:
| street | city | postalCode | country | | street | city | postalCode | country |
| 12 Main St. | Paris | 75000 | France | | 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" 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 Scenario: Attempt to create an order with insufficient loyalty points
@@ -93,7 +93,7 @@ Feature: Manage customer orders
And the delivery address is: And the delivery address is:
| street | city | postalCode | country | | street | city | postalCode | country |
| 42 Book Street | Lyon | 69000 | France | | 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" And I receive an error for validation order message containing "Not enough loyalty points"
Scenario: Attempt to order more books than available stock Scenario: Attempt to order more books than available stock
@@ -106,7 +106,7 @@ Feature: Manage customer orders
And the delivery address is: And the delivery address is:
| street | city | postalCode | country | | street | city | postalCode | country |
| 12 Main St. | Paris | 75000 | France | | 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" And I receive an error for validation order message containing "Insufficient book stock"
Scenario: Attempt to create an order with invalid payment method Scenario: Attempt to create an order with invalid payment method
@@ -119,7 +119,7 @@ Feature: Manage customer orders
And the delivery address is: And the delivery address is:
| street | city | postalCode | country | | street | city | postalCode | country |
| 12 Main St. | Paris | 75000 | France | | 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" And I receive an error for validation order message containing "Payment method is not valid"
Scenario: Attempt to create an order with unknown book Scenario: Attempt to create an order with unknown book
@@ -132,7 +132,7 @@ Feature: Manage customer orders
And the delivery address is: And the delivery address is:
| street | city | postalCode | country | | street | city | postalCode | country |
| 12 Main St. | Paris | 75000 | France | | 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" And I receive an error for not found exception message containing "Book not found"
Scenario: Attempt to create an order with empty book list Scenario: Attempt to create an order with empty book list
@@ -143,7 +143,7 @@ Feature: Manage customer orders
And the delivery address is: And the delivery address is:
| street | city | postalCode | country | | street | city | postalCode | country |
| 12 Main St. | Paris | 75000 | France | | 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" 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 Scenario: Attempt to create an order with a negative quantity
@@ -156,7 +156,7 @@ Feature: Manage customer orders
And the delivery address is: And the delivery address is:
| street | city | postalCode | country | | street | city | postalCode | country |
| 12 Main St. | Paris | 75000 | France | | 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" And I receive an error for validation order message containing "Quantity must be positive"
#Get orders #Get orders