From 3a7b9aba568b426ae8aac4d78624789cc32e2d95 Mon Sep 17 00:00:00 2001 From: fauvet Date: Sat, 14 Jun 2025 22:38:49 +0200 Subject: [PATCH] :tada: Push every file from windaube computers --- .idea/.gitignore | 10 + .idea/encodings.xml | 7 + .idea/misc.xml | 12 + .idea/vcs.xml | 6 + .../but3/dev62/mylibrary/book/BookDTO.java | 24 ++ .../but3/dev62/mylibrary/book/BookInfo.java | 17 + .../but3/dev62/mylibrary/book/Category.java | 24 ++ .../book/converter/BookConverter.java | 40 ++ .../dev62/mylibrary/book/entity/Book.java | 33 ++ .../book/exception/BookNotFoundException.java | 13 + .../book/exception/NotValidBookException.java | 7 + .../book/repository/BookRepository.java | 78 ++++ .../mylibrary/book/usecase/BookUseCase.java | 163 ++++++++ .../book/validator/BookValidator.java | 65 ++++ .../customer/converter/CustomerConverter.java | 28 +- .../customer/usecase/CustomerUseCase.java | 86 ++--- .../customer/validator/CustomerValidator.java | 41 +- .../dev62/mylibrary/order/AddressDTO.java | 14 + .../but3/dev62/mylibrary/order/OrderDTO.java | 19 + .../but3/dev62/mylibrary/order/OrderInfo.java | 16 + .../dev62/mylibrary/order/OrderLineDTO.java | 12 + .../dev62/mylibrary/order/PaymentMethod.java | 6 + .../order/converter/OrderConverter.java | 97 +++++ .../dev62/mylibrary/order/entity/Address.java | 14 + .../dev62/mylibrary/order/entity/Order.java | 19 + .../mylibrary/order/entity/OrderLine.java | 12 + .../exception/IllegalOrderPointException.java | 7 + .../InvalidPaymentMethodException.java | 7 + .../exception/NotValidOrderException.java | 7 + .../exception/OrderNotFoundException.java | 8 + .../exception/OrderQuantityException.java | 7 + .../exception/WrongAddressException.java | 7 + .../order/repository/OrderRepository.java | 60 +++ .../mylibrary/order/usecase/OrderUseCase.java | 158 ++++++++ .../order/validator/OrderValidator.java | 66 ++++ .../dev62/mylibrary/review/ReviewDto.java | 19 + .../dev62/mylibrary/review/ReviewInfo.java | 16 + .../review/converter/ReviewConverter.java | 29 ++ .../dev62/mylibrary/review/entity/Review.java | 19 + .../InvalidReviewRatingException.java | 8 + .../ReviewAlreadyExistsException.java | 8 + .../exception/ReviewNotFoundException.java | 8 + .../review/repository/ReviewRepository.java | 44 +++ .../review/usecase/ReviewUseCase.java | 71 ++++ .../review/validator/ReviewValidator.java | 30 ++ .../subscription/SubscriptionDTO.java | 15 + .../subscription/SubscriptionInfo.java | 7 + .../converter/SubscriptionConverter.java | 31 ++ .../subscription/entity/Subscription.java | 24 ++ .../NotValidSubscriptionException.java | 8 + .../SubscriptionNotFoundException.java | 13 + .../repository/SubscriptionRepository.java | 40 ++ .../usecase/SubscriptionUseCase.java | 45 +++ .../validator/SubscriptionValidator.java | 37 ++ .../book/converter/BookConverterTest.java | 140 +++++++ .../dev62/mylibrary/book/entity/BookTest.java | 126 ++++++ .../exception/BookNotFoundExceptionTest.java | 44 +++ .../exception/NotValidBookExceptionTest.java | 60 +++ .../book/usecase/BookUseCaseTest.java | 246 ++++++++++++ .../book/validator/BookValidatorTest.java | 213 ++++++++++ .../converter/CustomerConverterTest.java | 113 +++--- .../customer/entity/CustomerTest.java | 147 ++++--- .../CustomerNotFoundExceptionTest.java | 66 ++-- .../IllegalCustomerPointExceptionTest.java | 79 ++-- .../NotValidCustomerExceptionTest.java | 71 ++-- .../repository/CustomerRepositoryTest.java | 227 ----------- .../customer/usecase/CustomerUseCaseTest.java | 249 +++++------- .../validator/CustomerValidatorTest.java | 138 +++---- .../mylibrary/features/book/BookSteps.java | 320 +++++++++++++++ .../mylibrary/features/order/OrderSteps.java | 364 ++++++++++++++++++ .../features/review/ReviewSteps.java | 282 ++++++++++++++ .../subscription/SubscriptionSteps.java | 161 ++++++++ .../order/converter/OrderConverterTest.java | 131 +++++++ .../mylibrary/order/entity/OrderTest.java | 81 ++++ .../IllegalOrderPointExceptionTest.java | 22 ++ .../exception/OrderNotFoundExceptionTest.java | 22 ++ .../exception/OrderQuantityExceptionTest.java | 22 ++ .../exception/WrongAddressExceptionTest.java | 22 ++ .../order/repository/OrderRepositoryTest.java | 84 ++++ .../order/usecase/OrderUseCaseTest.java | 170 ++++++++ .../order/validator/OrderValidatorTest.java | 263 +++++++++++++ .../review/converter/ReviewConverterTest.java | 51 +++ .../mylibrary/review/entity/ReviewTest.java | 87 +++++ .../InvalidReviewRatingExceptionTest.java | 15 + .../ReviewAlreadyExistsExceptionTest.java | 15 + .../ReviewNotFoundExceptionTest.java | 15 + .../repository/ReviewRepositoryTest.java | 134 +++++++ .../review/usecase/ReviewUseCaseTest.java | 134 +++++++ .../review/validator/ReviewValidatorTest.java | 69 ++++ .../converter/SubscriptionConverterTest.java | 98 +++++ .../subscription/entity/SubscriptionTest.java | 47 +++ .../NotValidSubscriptionExceptionTest.java | 53 +++ .../SubscriptionNotFoundExceptionTest.java | 51 +++ .../SubscriptionRepositoryTest.java | 148 +++++++ .../usecase/SubscribeUseCaseTest.java | 112 ++++++ .../validator/SubscriptionValidatorTest.java | 93 +++++ src/test/resources/features/book.feature | 66 ++++ src/test/resources/features/order.feature | 201 ++++++++++ src/test/resources/features/review.feature | 71 ++++ .../resources/features/subscription.feature | 44 +++ 100 files changed, 6220 insertions(+), 809 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/BookDTO.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/BookInfo.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/Category.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/converter/BookConverter.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/entity/Book.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/BookNotFoundException.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/NotValidBookException.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/repository/BookRepository.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCase.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/validator/BookValidator.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/AddressDTO.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderDTO.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderInfo.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderLineDTO.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/PaymentMethod.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverter.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/Address.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/Order.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/OrderLine.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/IllegalOrderPointException.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/InvalidPaymentMethodException.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/NotValidOrderException.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundException.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderQuantityException.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/WrongAddressException.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepository.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCase.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/validator/OrderValidator.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewDto.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewInfo.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverter.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/Review.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/InvalidReviewRatingException.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewAlreadyExistsException.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundException.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepository.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCase.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidator.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/SubscriptionDTO.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/SubscriptionInfo.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/converter/SubscriptionConverter.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/entity/Subscription.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/NotValidSubscriptionException.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/SubscriptionNotFoundException.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/repository/SubscriptionRepository.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/usecase/SubscriptionUseCase.java create mode 100644 src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/validator/SubscriptionValidator.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/converter/BookConverterTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/entity/BookTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/BookNotFoundExceptionTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/NotValidBookExceptionTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCaseTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/validator/BookValidatorTest.java delete mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/repository/CustomerRepositoryTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/book/BookSteps.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/order/OrderSteps.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/review/ReviewSteps.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/subscription/SubscriptionSteps.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverterTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/OrderTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/IllegalOrderPointExceptionTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundExceptionTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderQuantityExceptionTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/WrongAddressExceptionTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepositoryTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCaseTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/validator/OrderValidatorTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverterTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/ReviewTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/InvalidReviewRatingExceptionTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewAlreadyExistsExceptionTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundExceptionTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepositoryTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCaseTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidatorTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/converter/SubscriptionConverterTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/entity/SubscriptionTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/NotValidSubscriptionExceptionTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/SubscriptionNotFoundExceptionTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/repository/SubscriptionRepositoryTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/usecase/SubscribeUseCaseTest.java create mode 100644 src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/validator/SubscriptionValidatorTest.java create mode 100644 src/test/resources/features/book.feature create mode 100644 src/test/resources/features/order.feature create mode 100644 src/test/resources/features/review.feature create mode 100644 src/test/resources/features/subscription.feature diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..7bc07ec --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Environment-dependent path to Maven home directory +/mavenHomeManager.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..2ee03b5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/BookDTO.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/BookDTO.java new file mode 100644 index 0000000..b85234b --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/BookDTO.java @@ -0,0 +1,24 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book; + +import java.time.LocalDate; +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +@AllArgsConstructor +public class BookDTO { + private final String isbn; + private final String title; + private final String author; + private final String publisher; + private final LocalDate publicationDate; + private final double price; + private final int stock; + private final List categories; + private final String description; + private final String language; +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/BookInfo.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/BookInfo.java new file mode 100644 index 0000000..062a2f5 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/BookInfo.java @@ -0,0 +1,17 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book; + +import java.time.LocalDate; +import java.util.List; + +public record BookInfo( + String isbn, + String title, + String author, + String publisher, + LocalDate publicationDate, + double price, + int stock, + List categories, + String description, + String language +) {} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/Category.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/Category.java new file mode 100644 index 0000000..c86eff7 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/Category.java @@ -0,0 +1,24 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book; + +public enum Category { + FICTION, + NON_FICTION, + SCIENCE_FICTION, + FANTASY, + MYSTERY, + THRILLER, + ROMANCE, + BIOGRAPHY, + HISTORY, + POETRY, + CHILDREN, + YOUNG_ADULT, + SCIENCE, + PHILOSOPHY, + SELF_HELP, + TRAVEL, + COOKING, + ART, + RELIGION, + REFERENCE +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/converter/BookConverter.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/converter/BookConverter.java new file mode 100644 index 0000000..5c09b6a --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/converter/BookConverter.java @@ -0,0 +1,40 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.converter; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.BookDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.book.BookInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; + +public final class BookConverter { + + private BookConverter() {} + + public static Book toDomain(BookInfo info) { + return Book.builder() + .isbn(info.isbn()) + .title(info.title()) + .author(info.author()) + .publisher(info.publisher()) + .publicationDate(info.publicationDate()) + .price(info.price()) + .stock(info.stock()) + .categories(info.categories()) + .description(info.description()) + .language(info.language()) + .build(); + } + + public static BookDTO toDTO(Book book) { + return BookDTO.builder() + .isbn(book.getIsbn()) + .title(book.getTitle()) + .author(book.getAuthor()) + .publisher(book.getPublisher()) + .publicationDate(book.getPublicationDate()) + .price(book.getPrice()) + .stock(book.getStock()) + .categories(book.getCategories()) + .description(book.getDescription()) + .language(book.getLanguage()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/entity/Book.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/entity/Book.java new file mode 100644 index 0000000..7a38c73 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/entity/Book.java @@ -0,0 +1,33 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.entity; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.Category; +import java.time.LocalDate; +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class Book { + private String isbn; + private String title; + private String author; + private String publisher; + private LocalDate publicationDate; + private double price; + private int stock; + private List categories; + private String description; + private String language; + + public void addStock(int quantityToAdd) { + this.stock += quantityToAdd; + } + + public void removeStock(int quantityToRemove) { + if (quantityToRemove > this.stock) { + throw new IllegalArgumentException("Pas assez de stock pour retirer : " + quantityToRemove); + } + this.stock -= quantityToRemove; + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/BookNotFoundException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/BookNotFoundException.java new file mode 100644 index 0000000..9f278f0 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/BookNotFoundException.java @@ -0,0 +1,13 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.exception; + +public class BookNotFoundException extends RuntimeException { + public static final String THE_BOOK_WITH_ISBN_DOES_NOT_EXIST_MESSAGE = "The book with isbn %s does not exist"; + + public BookNotFoundException(String message) { + super(message); + } + + public static BookNotFoundException forIsbn(String isbn) { + return new BookNotFoundException(String.format(THE_BOOK_WITH_ISBN_DOES_NOT_EXIST_MESSAGE, isbn)); + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/NotValidBookException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/NotValidBookException.java new file mode 100644 index 0000000..bec0172 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/NotValidBookException.java @@ -0,0 +1,7 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.exception; + +public class NotValidBookException extends RuntimeException { + public NotValidBookException(String message) { + super(message); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/repository/BookRepository.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/repository/BookRepository.java new file mode 100644 index 0000000..37b1fdb --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/repository/BookRepository.java @@ -0,0 +1,78 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; +import fr.iut_fbleau.but3.dev62.mylibrary.book.Category; +import java.util.*; +import java.util.stream.Collectors; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public final class BookRepository { + private final List books = new ArrayList<>(); + + public List findAll() { + return books; + } + + public void deleteAll() { + books.clear(); + } + + public Book save(Book newBook) { + Optional optionalBookWithSameIsbn = this.findByIsbn(newBook.getIsbn()); + optionalBookWithSameIsbn.ifPresent(books::remove); + books.add(newBook); + return newBook; + } + + public Optional findByIsbn(String isbn) { + return books.stream() + .filter(book -> Objects.equals(book.getIsbn(), isbn)) + .findFirst(); + } + + public boolean existsByIsbn(String isbn) { + return books.stream() + .anyMatch(book -> Objects.equals(book.getIsbn(), isbn)); + } + + public void delete(Book book) { + books.remove(book); + } + + public List findByTitleContaining(String titlePart) { + return books.stream() + .filter(book -> book.getTitle() != null && book.getTitle().toLowerCase().contains(titlePart.toLowerCase())) + .collect(Collectors.toList()); + } + + public List findByAuthor(String author) { + return books.stream() + .filter(book -> book.getAuthor() != null && book.getAuthor().equalsIgnoreCase(author)) + .collect(Collectors.toList()); + } + + public List findByPublisher(String publisher) { + return books.stream() + .filter(book -> book.getPublisher() != null && book.getPublisher().equalsIgnoreCase(publisher)) + .collect(Collectors.toList()); + } + + public List findByCategory(Category category) { + return books.stream() + .filter(book -> book.getCategories() != null && book.getCategories().contains(category)) + .collect(Collectors.toList()); + } + + public List findPublishedAfter(java.time.LocalDate date) { + return books.stream() + .filter(book -> book.getPublicationDate() != null && book.getPublicationDate().isAfter(date)) + .collect(Collectors.toList()); + } + + public List findByPriceRange(double min, double max) { + return books.stream() + .filter(book -> book.getPrice() >= min && book.getPrice() <= max) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCase.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCase.java new file mode 100644 index 0000000..b3decb6 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCase.java @@ -0,0 +1,163 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.*; +import fr.iut_fbleau.but3.dev62.mylibrary.book.converter.BookConverter; +import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.BookNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.NotValidBookException; +import fr.iut_fbleau.but3.dev62.mylibrary.book.repository.BookRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.book.validator.BookValidator; + +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + +public final class BookUseCase { + + private final BookRepository repository; + + public BookUseCase(BookRepository repository) { + this.repository = repository; + } + + public String registerBook(BookInfo info) throws NotValidBookException { + BookValidator.validate(info); + + if (repository.existsByIsbn(info.isbn())) { + throw new NotValidBookException("A book with this ISBN already exists"); + } + + Book book = BookConverter.toDomain(info); + Book stored = repository.save(book); + return stored.getIsbn(); + } + + public BookDTO updateBook(String isbn, BookInfo data) throws BookNotFoundException, NotValidBookException { + BookValidator.validate(data); + ensureBookExists(isbn); + + Book updated = Book.builder() + .isbn(isbn) + .title(data.title()) + .author(data.author()) + .publisher(data.publisher()) + .publicationDate(data.publicationDate()) + .price(data.price()) + .stock(data.stock()) + .categories(data.categories()) + .description(data.description()) + .language(data.language()) + .build(); + + return BookConverter.toDTO(repository.save(updated)); + } + + public int addStock(String isbn, int quantity) throws BookNotFoundException { + Book book = ensureBookExists(isbn); + book.addStock(quantity); + repository.save(book); + return book.getStock(); + } + + public int removeStock(String isbn, int quantity) throws BookNotFoundException { + Book book = ensureBookExists(isbn); + book.removeStock(quantity); + repository.save(book); + return book.getStock(); + } + + public void deleteBook(String isbn) throws BookNotFoundException { + Book target = ensureBookExists(isbn); + repository.delete(target); + } + + public BookDTO findBookByIsbn(String isbn) throws BookNotFoundException { + return repository.findByIsbn(isbn) + .map(BookConverter::toDTO) + .orElseThrow(() -> new BookNotFoundException(isbn)); + } + + public List findBooksByAuthor(String author) { + return repository.findByAuthor(author) + .stream() + .map(BookConverter::toDTO) + .collect(Collectors.toList()); + } + + public List findBooksByCategory(Category category) { + return repository.findByCategory(category) + .stream() + .map(BookConverter::toDTO) + .collect(Collectors.toList()); + } + + public List findBooksByTitleContaining(String title) { + return repository.findByTitleContaining(title) + .stream() + .map(BookConverter::toDTO) + .collect(Collectors.toList()); + } + + public List findBooksByPublisher(String publisher) { + return repository.findByPublisher(publisher) + .stream() + .map(BookConverter::toDTO) + .collect(Collectors.toList()); + } + + public List findBooksPublishedAfter(LocalDate date) { + return repository.findPublishedAfter(date) + .stream() + .map(BookConverter::toDTO) + .collect(Collectors.toList()); + } + + public List findBooksByPriceRange(double min, double max) { + return repository.findByPriceRange(min, max) + .stream() + .map(BookConverter::toDTO) + .collect(Collectors.toList()); + } + + public Map findBooksPageSorted(int page, int size, String sortBy) { + List books = repository.findAll(); + + books.sort(Comparator.comparing(book -> { + switch (sortBy.toLowerCase()) { + case "title": + case "titre": + return book.getTitle() == null ? "" : book.getTitle().toLowerCase(); + case "author": + case "auteur": + return book.getAuthor() == null ? "" : book.getAuthor().toLowerCase(); + case "publisher": + case "editeur": + return book.getPublisher() == null ? "" : book.getPublisher().toLowerCase(); + default: + return book.getTitle() == null ? "" : book.getTitle().toLowerCase(); + } + })); + + int total = books.size(); + int pages = (int) Math.ceil((double) total / size); + int start = Math.min(page * size, total); + int end = Math.min(start + size, total); + + List content = books.subList(start, end) + .stream() + .map(BookConverter::toDTO) + .collect(Collectors.toList()); + + Map result = new HashMap<>(); + result.put("content", content); + result.put("totalElements", total); + result.put("totalPages", pages); + + return result; + } + + private Book ensureBookExists(String isbn) throws BookNotFoundException { + return repository.findByIsbn(isbn) + .orElseThrow(() -> new BookNotFoundException(isbn)); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/validator/BookValidator.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/validator/BookValidator.java new file mode 100644 index 0000000..ff53167 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/validator/BookValidator.java @@ -0,0 +1,65 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.BookInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.NotValidBookException; + +public final class BookValidator { + + public static final String ERROR_ISBN_NULL = "ISBN cannot be null"; + public static final String ERROR_INVALID_ISBN = "ISBN is not valid"; + public static final String ERROR_TITLE_EMPTY = "Title cannot be empty"; + public static final String ERROR_AUTHOR_EMPTY = "Author cannot be empty"; + public static final String ERROR_NEGATIVE_PRICE = "Price must be positive"; + public static final String ERROR_NEGATIVE_STOCK = "Stock cannot be negative"; + public static final String ERROR_PUBLISHER_EMPTY = "Publisher cannot be empty"; + public static final String ERROR_PUBLICATION_DATE_NULL = "Publication date cannot be null"; + public static final String ERROR_CATEGORIES_EMPTY = "Categories list cannot be empty"; + public static final String ERROR_DESCRIPTION_EMPTY = "Description cannot be empty"; + public static final String ERROR_LANGUAGE_EMPTY = "Language cannot be empty"; + + private BookValidator() { + // Prevent instantiation + } + + public static void validate(BookInfo info) throws NotValidBookException { + checkPrice(info); + checkIsbn(info); + checkTitle(info); + checkAuthor(info); + checkStock(info); + } + + private static void checkIsbn(BookInfo info) throws NotValidBookException { + String isbn = info.isbn(); + if (isbn == null) { + throw new NotValidBookException(ERROR_ISBN_NULL); + } + if (!isbn.matches("\\d{13}")) { + throw new NotValidBookException(ERROR_INVALID_ISBN); + } + } + + private static void checkTitle(BookInfo info) throws NotValidBookException { + if (info.title() == null || info.title().isBlank()) { + throw new NotValidBookException(ERROR_TITLE_EMPTY); + } + } + + private static void checkAuthor(BookInfo info) throws NotValidBookException { + if (info.author() == null || info.author().isBlank()) { + throw new NotValidBookException(ERROR_AUTHOR_EMPTY); + } + } + + private static void checkPrice(BookInfo info) throws NotValidBookException { + if (info.price() < 0) { + throw new NotValidBookException(ERROR_NEGATIVE_PRICE); + } + } + + private static void checkStock(BookInfo info) throws NotValidBookException { + if (info.stock() < 0) { + throw new NotValidBookException(ERROR_NEGATIVE_STOCK); + } + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/converter/CustomerConverter.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/converter/CustomerConverter.java index e420020..c5e1202 100644 --- a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/converter/CustomerConverter.java +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/converter/CustomerConverter.java @@ -3,28 +3,30 @@ package fr.iut_fbleau.but3.dev62.mylibrary.customer.converter; import fr.iut_fbleau.but3.dev62.mylibrary.customer.CustomerDTO; import fr.iut_fbleau.but3.dev62.mylibrary.customer.CustomerInfo; import fr.iut_fbleau.but3.dev62.mylibrary.customer.entity.Customer; +import java.util.UUID; public final class CustomerConverter { - private CustomerConverter(){ - + private CustomerConverter() { + // util class } public static Customer toDomain(CustomerInfo newCustomer) { return Customer.builder() - .firstName(newCustomer.firstName()) - .lastName(newCustomer.lastName()) - .phoneNumber(newCustomer.phoneNumber()) - .loyaltyPoints(0) - .build(); + .id(UUID.randomUUID()) // ← génération automatique + .firstName(newCustomer.firstName()) + .lastName(newCustomer.lastName()) + .phoneNumber(newCustomer.phoneNumber()) + .loyaltyPoints(0) + .build(); } public static CustomerDTO toDTO(Customer customer) { return CustomerDTO.builder() - .id(customer.getId()) - .firstName(customer.getFirstName()) - .lastName(customer.getLastName()) - .phoneNumber(customer.getPhoneNumber()) - .loyaltyPoints(customer.getLoyaltyPoints()) - .build(); + .id(customer.getId()) + .firstName(customer.getFirstName()) + .lastName(customer.getLastName()) + .phoneNumber(customer.getPhoneNumber()) + .loyaltyPoints(customer.getLoyaltyPoints()) + .build(); } } diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/usecase/CustomerUseCase.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/usecase/CustomerUseCase.java index 3241c30..95b793a 100644 --- a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/usecase/CustomerUseCase.java +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/usecase/CustomerUseCase.java @@ -14,68 +14,58 @@ import java.util.UUID; public final class CustomerUseCase { - private final CustomerRepository customerRepository; + private final CustomerRepository repo; - public CustomerUseCase(CustomerRepository customerRepository) { - this.customerRepository = customerRepository; + public CustomerUseCase(CustomerRepository repo) { + this.repo = repo; } - public UUID registerCustomer(CustomerInfo newCustomer) throws NotValidCustomerException { - CustomerValidator.validate(newCustomer); - Customer customerToRegister = CustomerConverter.toDomain(newCustomer); - Customer customerToRegistered = customerRepository.save(customerToRegister); - return customerToRegistered.getId(); + public Optional getByPhone(String phone) { + return repo.findByPhoneNumber(phone).map(CustomerConverter::toDTO); } - public Optional findCustomerByPhoneNumber(String phoneNumber) { - Optional optionalCustomer = customerRepository.findByPhoneNumber(phoneNumber); - return optionalCustomer.map(CustomerConverter::toDTO); + public UUID create(CustomerInfo input) throws NotValidCustomerException { + CustomerValidator.validate(input); + Customer newCustomer = CustomerConverter.toDomain(input); + Customer saved = repo.save(newCustomer); + return saved.getId(); } - public CustomerDTO updateCustomer(UUID uuid, CustomerInfo customerInfo) - throws CustomerNotFoundException, NotValidCustomerException { - CustomerValidator.validate(customerInfo); - Customer customerByUUID = getCustomerIfDoesNotExistThrowCustomerNotFoundException( - uuid); - Customer customer = Customer.builder() - .id(uuid) - .firstName(customerInfo.firstName()) - .lastName(customerInfo.lastName()) - .phoneNumber(customerInfo.phoneNumber()) - .loyaltyPoints(customerByUUID.getLoyaltyPoints()) - .build(); - Customer updatedCustomer = customerRepository.save(customer); - return CustomerConverter.toDTO(updatedCustomer); + public int increasePoints(UUID id, int points) throws CustomerNotFoundException { + Customer c = resolveOrFail(id); + c.addLoyaltyPoints(points); + repo.save(c); + return c.getLoyaltyPoints(); } - public void deleteCustomer(UUID uuid) throws CustomerNotFoundException { - Customer customerToDelete = getCustomerIfDoesNotExistThrowCustomerNotFoundException(uuid); - this.customerRepository.delete(customerToDelete); + public int decreasePoints(UUID id, int points) + throws CustomerNotFoundException, IllegalCustomerPointException { + Customer c = resolveOrFail(id); + c.removeLoyaltyPoints(points); + repo.save(c); + return c.getLoyaltyPoints(); } - public int addLoyaltyPoints(UUID uuid, int loyaltyPointToAdd) throws CustomerNotFoundException { - Customer customerToAddLoyaltyPoints = getCustomerIfDoesNotExistThrowCustomerNotFoundException( - uuid); - customerToAddLoyaltyPoints.addLoyaltyPoints(loyaltyPointToAdd); - customerRepository.save(customerToAddLoyaltyPoints); - return customerToAddLoyaltyPoints.getLoyaltyPoints(); + public void remove(UUID id) throws CustomerNotFoundException { + Customer c = resolveOrFail(id); + repo.delete(c); } - public int subtractLoyaltyPoints(UUID uuid, int loyaltyPointToRemove) - throws CustomerNotFoundException, IllegalCustomerPointException { - Customer customerToSubtractLoyaltyPoints = getCustomerIfDoesNotExistThrowCustomerNotFoundException( - uuid); - customerToSubtractLoyaltyPoints.removeLoyaltyPoints(loyaltyPointToRemove); - customerRepository.save(customerToSubtractLoyaltyPoints); - return customerToSubtractLoyaltyPoints.getLoyaltyPoints(); + public CustomerDTO modify(UUID id, CustomerInfo input) + throws CustomerNotFoundException, NotValidCustomerException { + CustomerValidator.validate(input); + Customer existing = resolveOrFail(id); + Customer updated = Customer.builder() + .id(id) + .firstName(input.firstName()) + .lastName(input.lastName()) + .phoneNumber(input.phoneNumber()) + .loyaltyPoints(existing.getLoyaltyPoints()) + .build(); + return CustomerConverter.toDTO(repo.save(updated)); } - private Customer getCustomerIfDoesNotExistThrowCustomerNotFoundException(UUID uuid) - throws CustomerNotFoundException { - Optional optionalCustomerById = customerRepository.findById(uuid); - if (optionalCustomerById.isEmpty()) { - throw new CustomerNotFoundException(uuid); - } - return optionalCustomerById.get(); + private Customer resolveOrFail(UUID id) throws CustomerNotFoundException { + return repo.findById(id).orElseThrow(() -> new CustomerNotFoundException(id)); } } diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/validator/CustomerValidator.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/validator/CustomerValidator.java index d9bd9c1..b15320b 100644 --- a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/validator/CustomerValidator.java +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/validator/CustomerValidator.java @@ -5,40 +5,37 @@ import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.NotValidCustomerExc public final class CustomerValidator { - public static final String PHONE_NUMBER_IS_NOT_VALID = "Phone number is not valid"; - public static final String LAST_NAME_CANNOT_BE_BLANK = "Last name cannot be blank"; - public static final String FIRST_NAME_CANNOT_BE_BLANK = "First name cannot be blank"; - public static final String PHONE_NUMBER_REGEX = "0([67])\\d{8}"; + public static final String ERROR_PHONE_INVALID = "Phone number is not valid"; + public static final String ERROR_LASTNAME_EMPTY = "Last name cannot be blank"; + public static final String ERROR_FIRSTNAME_EMPTY = "First name cannot be blank"; + public static final String PHONE_PATTERN = "0([67])\\d{8}"; - private CustomerValidator() { + private CustomerValidator() {} + public static void check(CustomerInfo input) throws NotValidCustomerException { + ensureFirstName(input); + ensureLastName(input); + ensurePhone(input); } - public static void validate(CustomerInfo newCustomer) throws NotValidCustomerException { - validateFirstName(newCustomer); - validateLastName(newCustomer); - validatePhoneNumber(newCustomer); - } - - private static void validatePhoneNumber(CustomerInfo newCustomer) - throws NotValidCustomerException { - if (newCustomer.phoneNumber().isBlank()) { + private static void ensurePhone(CustomerInfo input) throws NotValidCustomerException { + if (input.phoneNumber().isBlank()) { throw new NotValidCustomerException("Phone number cannot be blank"); } - if (!newCustomer.phoneNumber().matches(PHONE_NUMBER_REGEX)) { - throw new NotValidCustomerException(PHONE_NUMBER_IS_NOT_VALID); + if (!input.phoneNumber().matches(PHONE_PATTERN)) { + throw new NotValidCustomerException(ERROR_PHONE_INVALID); } } - private static void validateLastName(CustomerInfo newCustomer) throws NotValidCustomerException { - if (newCustomer.lastName().isBlank()) { - throw new NotValidCustomerException(LAST_NAME_CANNOT_BE_BLANK); + private static void ensureLastName(CustomerInfo input) throws NotValidCustomerException { + if (input.lastName().isBlank()) { + throw new NotValidCustomerException(ERROR_LASTNAME_EMPTY); } } - private static void validateFirstName(CustomerInfo newCustomer) throws NotValidCustomerException { - if (newCustomer.firstName().isBlank()) { - throw new NotValidCustomerException(FIRST_NAME_CANNOT_BE_BLANK); + private static void ensureFirstName(CustomerInfo input) throws NotValidCustomerException { + if (input.firstName().isBlank()) { + throw new NotValidCustomerException(ERROR_FIRSTNAME_EMPTY); } } } diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/AddressDTO.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/AddressDTO.java new file mode 100644 index 0000000..53243f9 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/AddressDTO.java @@ -0,0 +1,14 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class AddressDTO { + private final String street; + private final String city; + private final String postalCode; + private final String country; +} + diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderDTO.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderDTO.java new file mode 100644 index 0000000..690fd04 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderDTO.java @@ -0,0 +1,19 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order; + +import lombok.Builder; +import lombok.Getter; +import java.util.List; +import java.util.UUID; + +@Builder +@Getter +public class OrderDTO { + private final UUID id; + private final UUID customerId; + private final String paymentMethod; + private final List orderLines; + private final AddressDTO shippingAddress; + private final double totalPrice; + private final double totalPriceToPay; +} + 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..406cc00 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderInfo.java @@ -0,0 +1,16 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import java.util.List; + +@Getter +@Setter +@Builder +public class OrderInfo { + private String customerId; + private String paymentMethod; + private List orderLines; + private AddressDTO address; +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderLineDTO.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderLineDTO.java new file mode 100644 index 0000000..18735d9 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderLineDTO.java @@ -0,0 +1,12 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class OrderLineDTO { + private final String bookId; + private final int quantity; +} + diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/PaymentMethod.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/PaymentMethod.java new file mode 100644 index 0000000..a4b3ed4 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/PaymentMethod.java @@ -0,0 +1,6 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order; + +public enum PaymentMethod { + CREDIT_CARD, + LOYALTY_POINTS; +} 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..99fbda8 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverter.java @@ -0,0 +1,97 @@ +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.Address; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.OrderLine; + +import java.util.UUID; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public final class OrderConverter { + + private OrderConverter() { + throw new AssertionError("This utility class should not be instantiated"); + } + + public static Order toDomain(OrderInfo info) { + if (info == null) return null; + + return Order.builder() + .customerId(Optional.ofNullable(info.getCustomerId()) + .map(UUID::fromString) + .orElse(null)) + .paymentMethod(Optional.ofNullable(info.getPaymentMethod()) + .map(PaymentMethod::valueOf) + .orElse(null)) + .orderLines(convertOrderLines(info.getOrderLines())) + .shippingAddress(convertAddress(info.getAddress())) + .totalPrice(0.0) + .build(); + } + + public static OrderDTO toDTO(Order order) { + if (order == null) return null; + + return OrderDTO.builder() + .id(order.getId()) + .customerId(order.getCustomerId()) + .paymentMethod(Optional.ofNullable(order.getPaymentMethod()) + .map(PaymentMethod::name) + .orElse(null)) + .orderLines(convertOrderLinesToDTO(order.getOrderLines())) + .shippingAddress(convertAddressToDTO(order.getShippingAddress())) + .totalPrice(order.getTotalPrice()) + .totalPriceToPay(order.getTotalPriceToPay()) + .build(); + } + + private static Address convertAddress(AddressDTO addressDTO) { + return Optional.ofNullable(addressDTO) + .map(addr -> Address.builder() + .street(addr.getStreet()) + .city(addr.getCity()) + .postalCode(addr.getPostalCode()) + .country(addr.getCountry()) + .build()) + .orElse(null); + } + + private static AddressDTO convertAddressToDTO(Address address) { + return Optional.ofNullable(address) + .map(addr -> AddressDTO.builder() + .street(addr.getStreet()) + .city(addr.getCity()) + .postalCode(addr.getPostalCode()) + .country(addr.getCountry()) + .build()) + .orElse(null); + } + + private static List convertOrderLinesToDTO(List orderLines) { + return Optional.ofNullable(orderLines) + .map(lines -> lines.stream() + .filter(Objects::nonNull) + .map(line -> OrderLineDTO.builder() + .bookId(line.getBookId()) + .quantity(line.getQuantity()) + .build()) + .toList()) + .orElse(null); + } + + private static List convertOrderLines(List orderLines) { + return Optional.ofNullable(orderLines) + .map(lines -> lines.stream() + .filter(Objects::nonNull) + .map(line -> OrderLine.builder() + .bookId(line.getBookId()) + .quantity(line.getQuantity()) + .build()) + .toList()) + .orElse(new ArrayList<>()); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/Address.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/Address.java new file mode 100644 index 0000000..c15d2d9 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/Address.java @@ -0,0 +1,14 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.entity; + +import lombok.Builder; +import lombok.Getter; +import lombok.AllArgsConstructor; + +@Getter +@Builder +public class Address { + private final String street; + private final String city; + private final String postalCode; + private final String country; +} 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..24974c9 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/Order.java @@ -0,0 +1,19 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.entity; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.PaymentMethod; +import lombok.Builder; +import lombok.Getter; +import java.util.List; +import java.util.UUID; + +@Getter +@Builder +public class Order { + private final UUID id; + private final UUID customerId; + private final PaymentMethod paymentMethod; + private final List orderLines; + private final Address shippingAddress; + private final double totalPrice; + private final double totalPriceToPay; +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/OrderLine.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/OrderLine.java new file mode 100644 index 0000000..b191b9f --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/OrderLine.java @@ -0,0 +1,12 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.entity; + +import lombok.Builder; +import lombok.Getter; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +@Getter +@Builder +public class OrderLine { + private final String bookId; + private final int quantity; +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/IllegalOrderPointException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/IllegalOrderPointException.java new file mode 100644 index 0000000..fe8d023 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/IllegalOrderPointException.java @@ -0,0 +1,7 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +public class IllegalOrderPointException extends RuntimeException { + public IllegalOrderPointException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/InvalidPaymentMethodException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/InvalidPaymentMethodException.java new file mode 100644 index 0000000..03adfc3 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/InvalidPaymentMethodException.java @@ -0,0 +1,7 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +public class InvalidPaymentMethodException extends RuntimeException { + public InvalidPaymentMethodException(String message) { + super(message); + } +} \ No newline at end of file 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..87db9f4 --- /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 RuntimeException { + public NotValidOrderException(String message) { + super(message); + } +} \ No newline at end of file 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..45ab3e4 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundException.java @@ -0,0 +1,8 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + + +public class OrderNotFoundException extends RuntimeException { + public OrderNotFoundException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderQuantityException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderQuantityException.java new file mode 100644 index 0000000..69291dd --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderQuantityException.java @@ -0,0 +1,7 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +public class OrderQuantityException extends RuntimeException { + public OrderQuantityException(String message) { + super(message); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/WrongAddressException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/WrongAddressException.java new file mode 100644 index 0000000..ea4e10f --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/WrongAddressException.java @@ -0,0 +1,7 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +public class WrongAddressException extends RuntimeException { + public WrongAddressException(String message) { + super(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..f799a71 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepository.java @@ -0,0 +1,60 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import java.util.*; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public final class OrderRepository { + private final List orders = new ArrayList<>(); + + public void deleteAll() { + orders.clear(); + } + + public Optional findById(UUID id) { + return this.orders.stream() + .filter(order -> Objects.equals(order.getId(), id)) + .findFirst(); + } + + public boolean existsById(UUID id) { + return this.orders.stream() + .anyMatch(order -> Objects.equals(order.getId(), id)); + } + + public void delete(Order order) { + this.orders.remove(order); + } + + public Order save(Order newOrder) { + if (newOrder.getId() == null) { + Order orderWithId = Order.builder() + .id(UUID.randomUUID()) + .customerId(newOrder.getCustomerId()) + .paymentMethod(newOrder.getPaymentMethod()) + .orderLines(newOrder.getOrderLines()) + .shippingAddress(newOrder.getShippingAddress()) + .totalPrice(newOrder.getTotalPrice()) + .totalPriceToPay(newOrder.getTotalPriceToPay()) + .build(); + this.orders.add(orderWithId); + return orderWithId; + } + + Optional optionalOrderWithSameId = this.findById(newOrder.getId()); + optionalOrderWithSameId.ifPresent(orders::remove); + this.orders.add(newOrder); + return newOrder; + } + + public List findByCustomerId(UUID customerId) { + return this.orders.stream() + .filter(order -> Objects.equals(order.getCustomerId(), customerId)) + .toList(); + } + + public List findAll() { + return orders; + } +} 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..b0e29bd --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCase.java @@ -0,0 +1,158 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.BookNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.book.repository.BookRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.CustomerNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.IllegalCustomerPointException; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.repository.CustomerRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.entity.Customer; +import fr.iut_fbleau.but3.dev62.mylibrary.order.*; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.converter.OrderConverter; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.*; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.repository.OrderRepository; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +public final class OrderUseCase { + private final OrderRepository orderRepository; + private final CustomerRepository customerRepository; + private final BookRepository bookRepository; + + public OrderUseCase(OrderRepository orderRepository, CustomerRepository customerRepository, BookRepository bookRepository) { + this.orderRepository = orderRepository; + this.customerRepository = customerRepository; + this.bookRepository = bookRepository; + } + + public UUID createOrder(OrderInfo orderInfo) throws CustomerNotFoundException, IllegalCustomerPointException, NotValidOrderException { + validateOrderInfo(orderInfo); + + PaymentMethod paymentMethod = parsePaymentMethod(orderInfo.getPaymentMethod()); + UUID customerId = parseCustomerId(orderInfo.getCustomerId()); + + Customer customer = customerRepository.findById(customerId) + .orElseThrow(() -> new CustomerNotFoundException(customerId)); + + Map books = orderInfo.getOrderLines().stream() + .map(line -> bookRepository.findByIsbn(line.getBookId()) + .orElseThrow(() -> new BookNotFoundException("Book with ISBN " + line.getBookId() + " was not found"))) + .collect(Collectors.toMap(fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book::getIsbn, b -> b)); + + validateOrderLines(orderInfo, books); + + double totalPrice = calculateTotalPrice(orderInfo, books); + double totalPriceToPay = calculateTotalPriceToPay(orderInfo, books); + + Order domainOrder = OrderConverter.toDomain(orderInfo); + + Order order = Order.builder() + .customerId(customerId) + .paymentMethod(paymentMethod) + .orderLines(domainOrder.getOrderLines()) + .shippingAddress(domainOrder.getShippingAddress()) + .totalPrice(totalPrice) + .totalPriceToPay(totalPriceToPay) + .build(); + + if (paymentMethod == PaymentMethod.LOYALTY_POINTS) { + handleLoyaltyPointsPayment(customer, totalPrice); + } + + return orderRepository.save(order).getId(); + } + + public OrderDTO findOrderById(String orderId) { + UUID uuid = parseOrderId(orderId); + return orderRepository.findById(uuid) + .map(OrderConverter::toDTO) + .orElseThrow(() -> new OrderNotFoundException("Order not found")); + } + + public List findOrdersByCustomerId(String customerId) throws CustomerNotFoundException { + UUID uuid = parseCustomerId(customerId); + customerRepository.findById(uuid) + .orElseThrow(() -> new CustomerNotFoundException(uuid)); + + return orderRepository.findByCustomerId(uuid) + .stream() + .map(OrderConverter::toDTO) + .toList(); + } + + private void validateOrderLines(OrderInfo orderInfo, Map books) { + orderInfo.getOrderLines().forEach(line -> { + if (line.getQuantity() <= 0) { + throw new OrderQuantityException("Quantity must be greater than 0 for book " + line.getBookId()); + } + var book = books.get(line.getBookId()); + if (book.getStock() < line.getQuantity()) { + throw new OrderQuantityException("Insufficient stock for book " + line.getBookId()); + } + }); + } + + private void validateOrderInfo(OrderInfo orderInfo) throws NotValidOrderException { + if (orderInfo == null) { + throw new NotValidOrderException("Order cannot be null"); + } + if (orderInfo.getOrderLines() == null || orderInfo.getOrderLines().isEmpty()) { + throw new OrderQuantityException("Order must contain at least one book"); + } + if (orderInfo.getAddress() == null) { + throw new NotValidOrderException("Shipping address is required"); + } + } + + private double calculateTotalPriceToPay(OrderInfo orderInfo, Map books) { + // Apply any potential discounts here + return calculateTotalPrice(orderInfo, books); + } + + private UUID parseCustomerId(String customerId) throws CustomerNotFoundException { + if (customerId == null || customerId.trim().isEmpty()) { + throw new CustomerNotFoundException(null); + } + try { + return UUID.fromString(customerId); + } catch (IllegalArgumentException e) { + throw new CustomerNotFoundException(null); + } + } + + private PaymentMethod parsePaymentMethod(String paymentMethod) { + try { + return PaymentMethod.valueOf(paymentMethod); + } catch (IllegalArgumentException | NullPointerException e) { + throw new InvalidPaymentMethodException("Payment method " + paymentMethod + " is not valid"); + } + } + + private UUID parseOrderId(String orderId) { + try { + return UUID.fromString(orderId); + } catch (IllegalArgumentException e) { + throw new OrderNotFoundException("Order with ID " + orderId + " was not found"); + } + } + + private double calculateTotalPrice(OrderInfo orderInfo, Map books) { + return orderInfo.getOrderLines().stream() + .mapToDouble(line -> books.get(line.getBookId()).getPrice() * line.getQuantity()) + .sum(); + } + + private void handleLoyaltyPointsPayment(Customer customer, double totalPrice) throws IllegalCustomerPointException { + int requiredPoints = (int) (totalPrice * 100); + if (customer.getLoyaltyPoints() < requiredPoints) { + throw new IllegalOrderPointException("Not enough loyalty points to pay for the order"); + } + customer.removeLoyaltyPoints(requiredPoints); + customerRepository.save(customer); + } +} 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..35c9edf --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/validator/OrderValidator.java @@ -0,0 +1,66 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Address; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; + +public class OrderValidator { + + public static void validate(Order order) { + if (order == null) { + throw new NotValidOrderException("Order cannot be null"); + } + validatePrices(order); + validateAddress(order); + validatePaymentMethod(order); + validateOrderLines(order); + } + + private static void validatePrices(Order order) { + if (order.getTotalPrice() <= 0) { + throw new NotValidOrderException("Total price must be positive"); + } + if (order.getTotalPriceToPay() <= 0) { + throw new NotValidOrderException("Total price to pay must be positive"); + } + if (order.getTotalPriceToPay() > order.getTotalPrice()) { + throw new NotValidOrderException("Total price to pay cannot exceed total price"); + } + } + + private static void validateOrderLines(Order order) { + if (order.getOrderLines() == null || order.getOrderLines().isEmpty()) { + throw new NotValidOrderException("Order must contain at least one line"); + } + + order.getOrderLines().forEach(line -> { + if (line.getQuantity() <= 0) { + throw new NotValidOrderException("Quantity must be positive"); + } + }); + } + + private static void validateAddress(Order order) { + Address address = order.getShippingAddress(); + if (address == null) { + throw new NotValidOrderException("Shipping address is required"); + } + + if (isNullOrBlank(address.getStreet()) || + isNullOrBlank(address.getCity()) || + isNullOrBlank(address.getPostalCode()) || + isNullOrBlank(address.getCountry())) { + throw new NotValidOrderException("All address fields are required"); + } + } + + private static void validatePaymentMethod(Order order) { + if (order.getPaymentMethod() == null) { + throw new NotValidOrderException("Payment method cannot be null"); + } + } + + private static boolean isNullOrBlank(String value) { + return value == null || value.trim().isEmpty(); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewDto.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewDto.java new file mode 100644 index 0000000..232a984 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewDto.java @@ -0,0 +1,19 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.util.UUID; + +@Builder +@Getter +@AllArgsConstructor +public class ReviewDto { + private final UUID reviewId; + private final long bookId; + private final String customerName; + private final String comment; + private final int rating; +} + diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewInfo.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewInfo.java new file mode 100644 index 0000000..df02df8 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewInfo.java @@ -0,0 +1,16 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +@AllArgsConstructor +public class ReviewInfo { + private final String customerId; + private final long isbn; + private final int rating; + private final String comment; +} + diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverter.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverter.java new file mode 100644 index 0000000..a7584d2 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverter.java @@ -0,0 +1,29 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.converter; + +import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewDto; +import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; + +import java.util.UUID; + +public class ReviewConverter { + public static ReviewDto toDto(Review review, UUID reviewId, String customerName) { + return ReviewDto.builder() + .reviewId(reviewId) + .bookId(review.getIsbn()) + .customerName(customerName) + .comment(review.getComment()) + .rating(review.getRating()) + .build(); + } + + public static Review toDomain(ReviewInfo info) { + return Review.builder() + .customerId(UUID.fromString(info.getCustomerId())) + .isbn(info.getIsbn()) + .rating(info.getRating()) + .comment(info.getComment()) + .build(); + } +} + diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/Review.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/Review.java new file mode 100644 index 0000000..20376e5 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/Review.java @@ -0,0 +1,19 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class Review { + private UUID customerId; + private long isbn; + private int rating; + private String comment; +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/InvalidReviewRatingException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/InvalidReviewRatingException.java new file mode 100644 index 0000000..0b07448 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/InvalidReviewRatingException.java @@ -0,0 +1,8 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.exception; + +public class InvalidReviewRatingException extends RuntimeException { + public InvalidReviewRatingException(String message) { + super(message); + } +} + diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewAlreadyExistsException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewAlreadyExistsException.java new file mode 100644 index 0000000..5f92fa6 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewAlreadyExistsException.java @@ -0,0 +1,8 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.exception; + +public class ReviewAlreadyExistsException extends RuntimeException { + public ReviewAlreadyExistsException(String message) { + super(message); + } +} + diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundException.java new file mode 100644 index 0000000..4b3268a --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundException.java @@ -0,0 +1,8 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.exception; + +public class ReviewNotFoundException extends RuntimeException { + public ReviewNotFoundException(String message) { + super(message); + } +} + diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepository.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepository.java new file mode 100644 index 0000000..2e5e26e --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepository.java @@ -0,0 +1,44 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; + +import java.util.*; +import java.util.stream.Collectors; + +public class ReviewRepository { + private final List reviews = new ArrayList<>(); + + public void save(Review review) { + reviews.add(review); + } + + public List findAll() { + return new ArrayList<>(reviews); + } + + public List findByIsbn(long isbn) { + return reviews.stream().filter(r -> r.getIsbn() == isbn).collect(Collectors.toList()); + } + + public List findByCustomerId(UUID customerId) { + return reviews.stream().filter(r -> r.getCustomerId().equals(customerId)).collect(Collectors.toList()); + } + + public boolean existsByCustomerIdAndIsbn(UUID customerId, long isbn) { + return reviews.stream().anyMatch(r -> r.getCustomerId().equals(customerId) && r.getIsbn() == isbn); + } + + public void deleteByCustomerIdAndIsbn(UUID customerId, long isbn) { + reviews.removeIf(r -> r.getCustomerId().equals(customerId) && r.getIsbn() == isbn); + } + + public void update(Review review) { + deleteByCustomerIdAndIsbn(review.getCustomerId(), review.getIsbn()); + save(review); + } + + public void deleteAll() { + reviews.clear(); + } +} + diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCase.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCase.java new file mode 100644 index 0000000..939ce93 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCase.java @@ -0,0 +1,71 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.repository.BookRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.repository.CustomerRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; +import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.InvalidReviewRatingException; +import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.ReviewAlreadyExistsException; +import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.ReviewNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.BookNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.CustomerNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.review.repository.ReviewRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.review.validator.ReviewValidator; + +import java.util.List; +import java.util.UUID; + +public class ReviewUseCase { + + private final ReviewRepository reviewRepository; + private final BookRepository bookRepository; + private final CustomerRepository customerRepository; + + public ReviewUseCase(ReviewRepository reviewRepository, BookRepository bookRepository, CustomerRepository customerRepository) { + this.reviewRepository = reviewRepository; + this.bookRepository = bookRepository; + this.customerRepository = customerRepository; + } + + public void deleteReview(long isbn, UUID customerId) { + if (!reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)) { + throw new ReviewNotFoundException("No review found for the given customer and book."); + } + reviewRepository.deleteByCustomerIdAndIsbn(customerId, isbn); + } + + public void submitReview(Review review) throws CustomerNotFoundException { + ReviewValidator.validate(review); + + if (!bookRepository.existsByIsbn(String.valueOf(review.getIsbn()))) { + throw new BookNotFoundException("The book with ISBN " + review.getIsbn() + " was not found."); + } + + if (!customerRepository.existsById(review.getCustomerId())) { + throw new CustomerNotFoundException(review.getCustomerId()); + } + + if (reviewRepository.existsByCustomerIdAndIsbn(review.getCustomerId(), review.getIsbn())) { + throw new ReviewAlreadyExistsException("A review already exists for this customer and book."); + } + + reviewRepository.save(review); + } + + public List getReviewsByCustomer(UUID customerId) { + return reviewRepository.findByCustomerId(customerId); + } + + public void updateReview(Review review) { + ReviewValidator.validate(review); + + if (!reviewRepository.existsByCustomerIdAndIsbn(review.getCustomerId(), review.getIsbn())) { + throw new ReviewNotFoundException("Cannot update: no existing review found for this customer and book."); + } + + reviewRepository.update(review); + } + + public List getReviewsByBook(long isbn) { + return reviewRepository.findByIsbn(isbn); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidator.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidator.java new file mode 100644 index 0000000..e82572f --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidator.java @@ -0,0 +1,30 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; +import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.InvalidReviewRatingException; + +public class ReviewValidator { + + public static void validate(Review review) { + if (review == null) { + throw new IllegalArgumentException("The review provided is null."); + } + + if (review.getCustomerId() == null) { + throw new IllegalArgumentException("Customer ID is required."); + } + + if (review.getIsbn() <= 0) { + throw new IllegalArgumentException("ISBN must be a positive number."); + } + + if (review.getComment() == null || review.getComment().trim().isEmpty()) { + throw new IllegalArgumentException("A review must contain a comment."); + } + + int rating = review.getRating(); + if (rating < 1 || rating > 5) { + throw new InvalidReviewRatingException("Rating must be within the range of 1 to 5."); + } + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/SubscriptionDTO.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/SubscriptionDTO.java new file mode 100644 index 0000000..6447380 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/SubscriptionDTO.java @@ -0,0 +1,15 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription; + +import lombok.Builder; +import lombok.Getter; +import java.util.UUID; + +@Builder +@Getter +public class SubscriptionDTO { + private final UUID id; + private final UUID customerId; + private final Integer duration; + private final String paymentMethod; + private final String debutDate; +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/SubscriptionInfo.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/SubscriptionInfo.java new file mode 100644 index 0000000..2bf5638 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/SubscriptionInfo.java @@ -0,0 +1,7 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription; + +import java.util.UUID; + +public record SubscriptionInfo(UUID customerId, Integer duration, String paymentMethod) { + +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/converter/SubscriptionConverter.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/converter/SubscriptionConverter.java new file mode 100644 index 0000000..9f8aaca --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/converter/SubscriptionConverter.java @@ -0,0 +1,31 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.converter; + + +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity.Subscription; + +public final class SubscriptionConverter { + private SubscriptionConverter() { + + } + + public static Subscription toDomain(SubscriptionInfo newSubscription) { + return Subscription.builder() + .customerId(newSubscription.customerId()) + .duration(newSubscription.duration()) + .paymentMethod(newSubscription.paymentMethod()) + .build(); + } + + public static SubscriptionDTO toDTO(Subscription subscription) { + return SubscriptionDTO.builder() + .id(subscription.getId()) + .customerId(subscription.getCustomerId()) + .duration(subscription.getDuration()) + .paymentMethod(subscription.getPaymentMethod()) + .debutDate(subscription.getDebutDate()) + .build(); + } + +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/entity/Subscription.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/entity/Subscription.java new file mode 100644 index 0000000..7d0efe3 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/entity/Subscription.java @@ -0,0 +1,24 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity; + +import java.util.UUID; +import lombok.Builder; +import lombok.Getter; +import java.time.LocalDate; + +@Builder +@Getter +public class Subscription { + private UUID id; + private UUID customerId; + private Integer duration; + private String paymentMethod; + private String debutDate; + + public void setRandomUUID() { + this.id = UUID.randomUUID(); + } + + public void setDebutDate(String debutDate) { + this.debutDate = LocalDate.now().toString(); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/NotValidSubscriptionException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/NotValidSubscriptionException.java new file mode 100644 index 0000000..4ab97d2 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/NotValidSubscriptionException.java @@ -0,0 +1,8 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.exception; + +public class NotValidSubscriptionException extends Exception { + + public NotValidSubscriptionException(String message) { + super(message); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/SubscriptionNotFoundException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/SubscriptionNotFoundException.java new file mode 100644 index 0000000..3b71ab1 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/SubscriptionNotFoundException.java @@ -0,0 +1,13 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.exception; + +import java.text.MessageFormat; +import java.util.UUID; + +public class SubscriptionNotFoundException extends Exception { + + public static final String THE_SUBSCRIPTION_WITH_ID_DOES_NOT_EXIST_MESSAGE = "The subscription with id {0} does not exist"; + + public SubscriptionNotFoundException(UUID uuid) { + super(MessageFormat.format(THE_SUBSCRIPTION_WITH_ID_DOES_NOT_EXIST_MESSAGE, uuid)); + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/repository/SubscriptionRepository.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/repository/SubscriptionRepository.java new file mode 100644 index 0000000..655aef2 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/repository/SubscriptionRepository.java @@ -0,0 +1,40 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.repository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity.Subscription; + +public class SubscriptionRepository { + private final List subscriptions = new ArrayList<>(); + + public Optional findById(UUID id) { + return subscriptions.stream() + .filter(subscription -> subscription.getId().equals(id)) + .findFirst(); + } + + public Optional findByCustomerId(UUID uuid) { + return subscriptions.stream() + .filter(subscription -> subscription.getCustomerId().equals(uuid)) + .findFirst(); + } + + public List findAll() { + return subscriptions; + } + + public boolean existsById(UUID uuid) { + return subscriptions.stream() + .anyMatch(subscription -> subscription.getId().equals(uuid)); + } + + public Subscription save(Subscription newSubscription) { + Optional existing = this.findByCustomerId(newSubscription.getCustomerId()); + existing.ifPresentOrElse(subscriptions::remove, newSubscription::setRandomUUID); + subscriptions.add(newSubscription); + return newSubscription; + } +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/usecase/SubscriptionUseCase.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/usecase/SubscriptionUseCase.java new file mode 100644 index 0000000..0069a92 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/usecase/SubscriptionUseCase.java @@ -0,0 +1,45 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.converter.SubscriptionConverter; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity.Subscription; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.exception.NotValidSubscriptionException; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.exception.SubscriptionNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.repository.SubscriptionRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.validator.SubscriptionValidator; + +import java.util.Optional; +import java.util.UUID; + +public class SubscriptionUseCase { + + public Optional findSubscriptionByCustomerId(UUID customerId) { + return subscriptionRepository.findByCustomerId(customerId) + .map(SubscriptionConverter::toDTO); + } + + public UUID registerSubscription(SubscriptionInfo info) throws NotValidSubscriptionException { + SubscriptionValidator.validate(info); + Subscription entity = SubscriptionConverter.toDomain(info); + Subscription saved = subscriptionRepository.save(entity); + if (saved.getDuration() <= 0) { + throw new NotValidSubscriptionException("Duration must be positive"); + } + return saved.getId(); + } + + private boolean getSubscriptionIfDoesNotExistThrowSubscriptionNotFoundException(UUID uuid) + throws SubscriptionNotFoundException { + if (subscriptionRepository.existsById(uuid)) { + throw new SubscriptionNotFoundException(uuid); + } + return subscriptionRepository.existsById(uuid); + } + + public SubscriptionUseCase(SubscriptionRepository subscriptionRepository) { + this.subscriptionRepository = subscriptionRepository; + } + + private final SubscriptionRepository subscriptionRepository; +} diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/validator/SubscriptionValidator.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/validator/SubscriptionValidator.java new file mode 100644 index 0000000..efab9ca --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/validator/SubscriptionValidator.java @@ -0,0 +1,37 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.exception.NotValidSubscriptionException; + +public class SubscriptionValidator { + + public static final String CUSTOMER_ID_NULL_MESSAGE = "Customer ID must not be null"; + public static final String DURATION_NULL_MESSAGE = "Invalid duration value"; + public static final String PAYMENT_METHOD_EMPTY_MESSAGE = "Payment method cannot be empty"; + + public static void validate(SubscriptionInfo info) throws NotValidSubscriptionException { + checkCustomerId(info); + checkDuration(info); + checkPayment(info); + } + + private static void checkDuration(SubscriptionInfo info) throws NotValidSubscriptionException { + if (info.duration() == null) { + throw new NotValidSubscriptionException(DURATION_NULL_MESSAGE); + } + } + + private static void checkCustomerId(SubscriptionInfo info) throws NotValidSubscriptionException { + if (info.customerId() == null) { + throw new NotValidSubscriptionException(CUSTOMER_ID_NULL_MESSAGE); + } + } + + private static void checkPayment(SubscriptionInfo info) throws NotValidSubscriptionException { + if (info.paymentMethod().isBlank()) { + throw new NotValidSubscriptionException(PAYMENT_METHOD_EMPTY_MESSAGE); + } + } + + private SubscriptionValidator() {} +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/converter/BookConverterTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/converter/BookConverterTest.java new file mode 100644 index 0000000..ab15a07 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/converter/BookConverterTest.java @@ -0,0 +1,140 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.converter; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.*; +import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; +import org.junit.jupiter.api.*; + +import java.time.LocalDate; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Unit tests for BookConverter") +class BookConverterTest { + + @Test + @DisplayName("Should convert empty strings correctly during conversion") + void shouldPreserveEmptyStrings() { + BookInfo info = new BookInfo( + "1111111111111", + "", + "", + "", + LocalDate.of(2022, 2, 2), + 0.0, + 0, + Collections.emptyList(), + "", + "" + ); + + Book domain = BookConverter.toDomain(info); + BookDTO dto = BookConverter.toDTO(domain); + + assertEquals("", dto.getTitle()); + assertEquals("", dto.getAuthor()); + assertEquals("", dto.getPublisher()); + assertEquals("", dto.getDescription()); + assertEquals("", dto.getLanguage()); + } + + @Nested + @DisplayName("Tests for toDTO() method") + class ToDTO { + + @Test + @DisplayName("Should map all fields from Book to BookDTO correctly") + void shouldConvertBookToDTO() { + Book book = Book.builder() + .isbn("9876543210123") + .title("Title2") + .author("Author2") + .publisher("Publisher2") + .publicationDate(LocalDate.of(2021, 5, 10)) + .price(25.5) + .stock(5) + .categories(List.of(Category.FANTASY)) + .description("Desc2") + .language("English") + .build(); + + BookDTO result = BookConverter.toDTO(book); + + assertNotNull(result); + assertEquals(book.getIsbn(), result.getIsbn()); + assertEquals(book.getTitle(), result.getTitle()); + assertEquals(book.getAuthor(), result.getAuthor()); + assertEquals(book.getPublisher(), result.getPublisher()); + assertEquals(book.getPublicationDate(), result.getPublicationDate()); + assertEquals(book.getPrice(), result.getPrice()); + assertEquals(book.getStock(), result.getStock()); + assertEquals(book.getCategories(), result.getCategories()); + assertEquals(book.getDescription(), result.getDescription()); + assertEquals(book.getLanguage(), result.getLanguage()); + } + } + + @Test + @DisplayName("Should handle null values safely during conversion") + void shouldHandleNullValuesGracefully() { + Book book = Book.builder() + .isbn("0") + .title(null) + .author(null) + .publisher(null) + .publicationDate(null) + .price(0.0) + .stock(0) + .categories(Collections.emptyList()) + .description(null) + .language(null) + .build(); + + BookDTO result = BookConverter.toDTO(book); + + assertNotNull(result); + assertNull(result.getTitle()); + assertNull(result.getAuthor()); + assertNull(result.getPublisher()); + assertNull(result.getPublicationDate()); + assertNull(result.getDescription()); + assertNull(result.getLanguage()); + assertEquals(Collections.emptyList(), result.getCategories()); + } + + @Nested + @DisplayName("Tests for toDomain() method") + class ToDomain { + + @Test + @DisplayName("Should convert BookInfo to Book domain object with default values") + void shouldConvertBookInfoToDomain() { + BookInfo info = new BookInfo( + "1234567890123", + "Title", + "Author", + "Publisher", + LocalDate.of(2020, 1, 1), + 19.99, + 10, + List.of(Category.FICTION), + "Description", + "French" + ); + + Book result = BookConverter.toDomain(info); + + assertNotNull(result); + assertEquals(info.isbn(), result.getIsbn()); + assertEquals(info.title(), result.getTitle()); + assertEquals(info.author(), result.getAuthor()); + assertEquals(info.publisher(), result.getPublisher()); + assertEquals(info.publicationDate(), result.getPublicationDate()); + assertEquals(info.price(), result.getPrice()); + assertEquals(info.stock(), result.getStock()); + assertEquals(info.categories(), result.getCategories()); + assertEquals(info.description(), result.getDescription()); + assertEquals(info.language(), result.getLanguage()); + } + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/entity/BookTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/entity/BookTest.java new file mode 100644 index 0000000..f176326 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/entity/BookTest.java @@ -0,0 +1,126 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.entity; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.Category; +import org.junit.jupiter.api.*; + +import java.time.LocalDate; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Unit tests for Book class") +class BookTest { + + @Test + @DisplayName("Builder should create a valid Book instance") + void shouldBuildValidBook() { + String isbn = "9780987654321"; + String title = "AI Secrets Unveiled"; + String author = "Alice Newton"; + String publisher = "FutureBooks"; + LocalDate publicationDate = LocalDate.of(2012, 6, 20); + double price = 39.95; + int stock = 7; + List categories = List.of(Category.TECHNOLOGY); + String description = "A deep dive into the evolution of AI."; + String language = "English"; + + Book book = Book.builder() + .isbn(isbn) + .title(title) + .author(author) + .publisher(publisher) + .publicationDate(publicationDate) + .price(price) + .stock(stock) + .categories(categories) + .description(description) + .language(language) + .build(); + + assertEquals(isbn, book.getIsbn()); + assertEquals(title, book.getTitle()); + assertEquals(author, book.getAuthor()); + assertEquals(publisher, book.getPublisher()); + assertEquals(publicationDate, book.getPublicationDate()); + assertEquals(price, book.getPrice()); + assertEquals(stock, book.getStock()); + assertEquals(categories, book.getCategories()); + assertEquals(description, book.getDescription()); + assertEquals(language, book.getLanguage()); + } + + @Nested + @DisplayName("Stock management tests") + class StockTests { + + @Test + @DisplayName("addStock should handle adding zero properly") + void shouldHandleAddingZeroStock() { + Book book = Book.builder().stock(8).build(); + + book.addStock(0); + + assertEquals(8, book.getStock()); + } + + @Test + @DisplayName("addStock should increment the stock correctly") + void shouldAddStockCorrectly() { + Book book = Book.builder().stock(12).build(); + int toAdd = 6; + int expected = 18; + + book.addStock(toAdd); + + assertEquals(expected, book.getStock()); + } + + @Test + @DisplayName("removeStock should throw if more than available is removed") + void shouldThrowWhenRemovingTooMuchStock() { + Book book = Book.builder().stock(5).build(); + int toRemove = 9; + + Exception exception = assertThrows( + IllegalArgumentException.class, + () -> book.removeStock(toRemove) + ); + + assertEquals(5, book.getStock()); + assertTrue(exception.getMessage().contains(String.valueOf(toRemove))); + } + + @Test + @DisplayName("removeStock should handle zero removal correctly") + void shouldHandleZeroRemoval() { + Book book = Book.builder().stock(3).build(); + + book.removeStock(0); + + assertEquals(3, book.getStock()); + } + + @Test + @DisplayName("removeStock should remove all available stock") + void shouldRemoveAllStock() { + Book book = Book.builder().stock(4).build(); + + book.removeStock(4); + + assertEquals(0, book.getStock()); + } + + @Test + @DisplayName("removeStock should decrease stock properly") + void shouldRemoveStockCorrectly() { + Book book = Book.builder().stock(9).build(); + int toRemove = 2; + int expected = 7; + + book.removeStock(toRemove); + + assertEquals(expected, book.getStock()); + } + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/BookNotFoundExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/BookNotFoundExceptionTest.java new file mode 100644 index 0000000..b528aa9 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/BookNotFoundExceptionTest.java @@ -0,0 +1,44 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.exception; + +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Unit tests for BookNotFoundException") +class BookNotFoundExceptionTest { + + @Test + @DisplayName("Should throw and catch the exception properly") + void shouldThrowAndCatchProperly() { + String isbn = "9780001112223"; + + try { + throw new BookNotFoundException(isbn); + } catch (BookNotFoundException e) { + String expected = String.format("The book with isbn %s does not exist", isbn); + assertEquals(expected, e.getMessage()); + } + } + + @Test + @DisplayName("Should contain the ISBN in the exception message") + void shouldIncludeIsbnInMessage() { + String isbn = "9780001112223"; + + BookNotFoundException exception = new BookNotFoundException(isbn); + + assertTrue(exception.getMessage().contains(isbn)); + assertEquals(String.format("The book with isbn %s does not exist", isbn), exception.getMessage()); + } + + @Test + @DisplayName("Should use the constant message format defined in the class") + void shouldUseCorrectConstantMessageFormat() { + String isbn = "9780001112223"; + + BookNotFoundException ex = new BookNotFoundException(isbn); + + assertEquals("The book with isbn {0} does not exist", BookNotFoundException.THE_BOOK_WITH_ISBN_DOES_NOT_EXIST_MESSAGE); + assertTrue(ex.getMessage().contains(isbn)); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/NotValidBookExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/NotValidBookExceptionTest.java new file mode 100644 index 0000000..7622bc2 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/NotValidBookExceptionTest.java @@ -0,0 +1,60 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.exception; + +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Unit tests for NotValidBookException") +class NotValidBookExceptionTest { + + @Test + @DisplayName("Should allow creation with provided message") + void shouldCreateExceptionWithMessage() { + String message = "Invalid book data detected"; + + NotValidBookException ex = new NotValidBookException(message); + + assertEquals(message, ex.getMessage()); + } + + @Test + @DisplayName("Should be catchable as general Exception") + void shouldBeCatchableAsException() { + String message = "Improper book input"; + + try { + throw new NotValidBookException(message); + } catch (Exception e) { + assertEquals(NotValidBookException.class, e.getClass()); + assertEquals(message, e.getMessage()); + } + } + + @Test + @DisplayName("Should be thrown and caught properly") + void shouldThrowAndCatchProperly() { + String msg = "Missing required field"; + + Exception thrown = assertThrows(NotValidBookException.class, () -> { + throw new NotValidBookException(msg); + }); + + assertEquals(msg, thrown.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = { + "Title must not be empty", + "ISBN format is invalid", + "Price cannot be negative", + "Stock cannot be less than zero" + }) + @DisplayName("Should handle multiple validation messages") + void shouldHandleMultipleMessages(String msg) { + NotValidBookException ex = new NotValidBookException(msg); + + assertEquals(msg, ex.getMessage()); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCaseTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCaseTest.java new file mode 100644 index 0000000..1dc7315 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCaseTest.java @@ -0,0 +1,246 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.*; +import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.BookNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.NotValidBookException; +import fr.iut_fbleau.but3.dev62.mylibrary.book.repository.BookRepository; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDate; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class BookUseCaseTest { + + @Mock + private BookRepository bookRepository; + + @InjectMocks + private BookUseCase bookUseCase; + + private String isbn; + private Book mockBook; + private BookInfo validBookInfo; + + @BeforeEach + void setup() { + isbn = "9780001112223"; + mockBook = Book.builder() + .isbn(isbn) + .title("Code Craft") + .author("Jane Doe") + .publisher("CodePress") + .publicationDate(LocalDate.of(2012, 3, 12)) + .price(27.5) + .stock(10) + .categories(List.of(Category.MYSTERY)) + .description("Deep insights into software practices.") + .language("English") + .build(); + + validBookInfo = new BookInfo( + isbn, + "Code Craft", + "Jane Doe", + "CodePress", + LocalDate.of(2012, 3, 12), + 27.5, + 10, + List.of(Category.MYSTERY), + "Deep insights into software practices.", + "English" + ); + } + + @Nested + @DisplayName("Search functionality") + class SearchTests { + @Test + @DisplayName("Should return books matching given title fragment") + void shouldFindBooksByTitle() { + when(bookRepository.findByTitleContaining("Code")).thenReturn(List.of(mockBook)); + + List result = bookUseCase.findBooksByTitleContaining("Code"); + + assertEquals(1, result.size()); + assertEquals("Code Craft", result.get(0).getTitle()); + verify(bookRepository).findByTitleContaining("Code"); + } + } + + @Nested + @DisplayName("Stock operations") + class StockManagementTests { + + @Test + @DisplayName("Should add stock successfully") + void shouldAddStock() { + when(bookRepository.findByIsbn(isbn)).thenReturn(Optional.of(mockBook)); + when(bookRepository.save(any(Book.class))).thenReturn(mockBook); + + int result = bookUseCase.addStock(isbn, 5); + + assertEquals(15, result); + verify(bookRepository).save(mockBook); + } + + @Test + @DisplayName("Should remove stock successfully") + void shouldRemoveStock() { + when(bookRepository.findByIsbn(isbn)).thenReturn(Optional.of(mockBook)); + when(bookRepository.save(any(Book.class))).thenReturn(mockBook); + + int result = bookUseCase.removeStock(isbn, 5); + + assertEquals(5, result); + verify(bookRepository).save(mockBook); + } + } + + @Nested + @DisplayName("Book deletion") + class DeleteBookTests { + + @Test + @DisplayName("Should delete book when it exists") + void shouldDeleteExistingBook() throws BookNotFoundException { + when(bookRepository.findByIsbn(isbn)).thenReturn(Optional.of(mockBook)); + doNothing().when(bookRepository).delete(mockBook); + + bookUseCase.deleteBook(isbn); + + verify(bookRepository).delete(mockBook); + } + + @Test + @DisplayName("Should throw when trying to delete non-existent book") + void shouldThrowWhenBookToDeleteDoesNotExist() { + when(bookRepository.findByIsbn("unknown")).thenReturn(Optional.empty()); + + assertThrows(BookNotFoundException.class, + () -> bookUseCase.deleteBook("unknown")); + } + } + + @Nested + @DisplayName("Book update") + class UpdateBookTests { + + @Test + @DisplayName("Should update existing book with valid info") + void shouldUpdateBookCorrectly() throws BookNotFoundException, NotValidBookException { + when(bookRepository.findByIsbn(isbn)).thenReturn(Optional.of(mockBook)); + + Book updatedBook = Book.builder() + .isbn(isbn) + .title("Code Craft - Revised") + .author("Jane Doe") + .publisher("CodePress") + .publicationDate(LocalDate.of(2012, 3, 12)) + .price(30.0) + .stock(12) + .categories(List.of(Category.MYSTERY)) + .description("Revised edition of software insights.") + .language("English") + .build(); + + when(bookRepository.save(any(Book.class))).thenReturn(updatedBook); + + BookInfo updatedInfo = new BookInfo( + isbn, + "Code Craft - Revised", + "Jane Doe", + "CodePress", + LocalDate.of(2012, 3, 12), + 30.0, + 12, + List.of(Category.MYSTERY), + "Revised edition of software insights.", + "English" + ); + + BookDTO result = bookUseCase.updateBook(isbn, updatedInfo); + + assertNotNull(result); + assertEquals("Code Craft - Revised", result.getTitle()); + } + + @Test + @DisplayName("Should throw when trying to update non-existent book") + void shouldThrowWhenUpdatingUnknownBook() { + when(bookRepository.findByIsbn("invalid")).thenReturn(Optional.empty()); + + assertThrows(BookNotFoundException.class, + () -> bookUseCase.updateBook("invalid", validBookInfo)); + } + + @Test + @DisplayName("Should throw when update info is invalid") + void shouldThrowForInvalidUpdateInfo() { + BookInfo invalidInfo = new BookInfo( + null, "", "", "", null, 0.0, 0, Collections.emptyList(), "", "" + ); + + assertThrows(NotValidBookException.class, + () -> bookUseCase.updateBook(isbn, invalidInfo)); + } + } + + @Nested + @DisplayName("Book lookup") + class FindBookTests { + + @Test + @DisplayName("Should return DTO if ISBN matches a book") + void shouldReturnBookDTO() { + when(bookRepository.findByIsbn(isbn)).thenReturn(Optional.of(mockBook)); + + BookDTO result = bookUseCase.findBookByIsbn(isbn); + + assertNotNull(result); + assertEquals(isbn, result.getIsbn()); + } + + @Test + @DisplayName("Should throw if book not found by ISBN") + void shouldThrowIfBookNotFound() { + when(bookRepository.findByIsbn("notfound")).thenReturn(Optional.empty()); + + assertThrows(BookNotFoundException.class, + () -> bookUseCase.findBookByIsbn("notfound")); + } + } + + @Nested + @DisplayName("Book registration") + class RegisterBookTests { + + @Test + @DisplayName("Should register book if valid") + void shouldRegisterBookSuccessfully() throws NotValidBookException { + when(bookRepository.save(any(Book.class))).thenReturn(mockBook); + + String resultIsbn = bookUseCase.registerBook(validBookInfo); + + assertEquals(isbn, resultIsbn); + } + + @Test + @DisplayName("Should reject invalid book data") + void shouldThrowOnInvalidRegistration() { + BookInfo invalidInfo = new BookInfo( + null, "", "", "", null, 0.0, 0, Collections.emptyList(), "", "" + ); + + assertThrows(NotValidBookException.class, + () -> bookUseCase.registerBook(invalidInfo)); + } + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/validator/BookValidatorTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/validator/BookValidatorTest.java new file mode 100644 index 0000000..759b219 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/validator/BookValidatorTest.java @@ -0,0 +1,213 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.BookInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.book.Category; +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.NotValidBookException; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.time.LocalDate; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Unit tests for BookValidator") +class BookValidatorTest { + + @Test + @DisplayName("Should validate a properly filled book without throwing") + void shouldValidateValidBook() { + BookInfo validBook = new BookInfo( + "9780001234567", + "Mastering Java", + "Alice Newton", + "DevPress", + LocalDate.of(2012, 10, 5), + 45.0, + 7, + List.of(Category.MYSTERY), + "Advanced guide to Java programming.", + "English" + ); + assertDoesNotThrow(() -> BookValidator.validate(validBook)); + } + + @Nested + @DisplayName("ISBN validation") + class IsbnTests { + + @Test + @DisplayName("Should fail if ISBN is null") + void shouldFailOnNullIsbn() { + BookInfo book = new BookInfo( + null, "Title", "Author", "Publisher", LocalDate.now(), 10.0, 2, + List.of(Category.SCIENCE), "Details", "English" + ); + NotValidBookException ex = assertThrows(NotValidBookException.class, () -> BookValidator.validate(book)); + assertEquals(BookValidator.ERROR_ISBN_NULL, ex.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {"", "1", "123456", "abc"}) + @DisplayName("Should fail if ISBN is not 13 digits") + void shouldFailOnInvalidIsbn(String isbn) { + BookInfo book = new BookInfo( + isbn, "Title", "Author", "Publisher", LocalDate.now(), 10.0, 2, + List.of(Category.SCIENCE), "Details", "English" + ); + NotValidBookException ex = assertThrows(NotValidBookException.class, () -> BookValidator.validate(book)); + assertEquals(BookValidator.ERROR_INVALID_ISBN, ex.getMessage()); + } + } + + @Nested + @DisplayName("Title validation") + class TitleTests { + + @Test + @DisplayName("Should fail if title is blank") + void shouldFailOnBlankTitle() { + BookInfo book = new BookInfo( + "9780001234567", "", "Author", "Publisher", LocalDate.now(), 10.0, 2, + List.of(Category.SCIENCE), "Details", "English" + ); + NotValidBookException ex = assertThrows(NotValidBookException.class, () -> BookValidator.validate(book)); + assertEquals(BookValidator.ERROR_TITLE_EMPTY, ex.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {" ", "\t", "\n"}) + @DisplayName("Should fail if title is only whitespace") + void shouldFailOnWhitespaceOnlyTitle(String value) { + BookInfo book = new BookInfo( + "9780001234567", value, "Author", "Publisher", LocalDate.now(), 10.0, 2, + List.of(Category.SCIENCE), "Details", "English" + ); + NotValidBookException ex = assertThrows(NotValidBookException.class, () -> BookValidator.validate(book)); + assertEquals(BookValidator.ERROR_TITLE_EMPTY, ex.getMessage()); + } + } + + @Nested + @DisplayName("Author validation") + class AuthorTests { + @Test + @DisplayName("Should fail if author is blank") + void shouldFailOnBlankAuthor() { + BookInfo book = new BookInfo( + "9780001234567", "Title", "", "Publisher", LocalDate.now(), 10.0, 2, + List.of(Category.SCIENCE), "Details", "English" + ); + NotValidBookException ex = assertThrows(NotValidBookException.class, () -> BookValidator.validate(book)); + assertEquals(BookValidator.ERROR_AUTHOR_EMPTY, ex.getMessage()); + } + } + + @Nested + @DisplayName("Publisher validation") + class PublisherTests { + @Test + @DisplayName("Should fail if publisher is blank") + void shouldFailOnBlankPublisher() { + BookInfo book = new BookInfo( + "9780001234567", "Title", "Author", "", LocalDate.now(), 10.0, 2, + List.of(Category.SCIENCE), "Details", "English" + ); + NotValidBookException ex = assertThrows(NotValidBookException.class, () -> BookValidator.validate(book)); + assertEquals(BookValidator.ERROR_PUBLISHER_EMPTY, ex.getMessage()); + } + } + + @Nested + @DisplayName("Publication date validation") + class PublicationDateTests { + @Test + @DisplayName("Should fail if publication date is null") + void shouldFailOnNullPublicationDate() { + BookInfo book = new BookInfo( + "9780001234567", "Title", "Author", "Publisher", null, 10.0, 2, + List.of(Category.SCIENCE), "Details", "English" + ); + NotValidBookException ex = assertThrows(NotValidBookException.class, () -> BookValidator.validate(book)); + assertEquals(BookValidator.ERROR_PUBLICATION_DATE_NULL, ex.getMessage()); + } + } + + @Nested + @DisplayName("Price validation") + class PriceTests { + @ParameterizedTest + @ValueSource(doubles = {0.0, -5.5, -100.0}) + @DisplayName("Should fail if price is zero or negative") + void shouldFailOnInvalidPrice(double price) { + BookInfo book = new BookInfo( + "9780001234567", "Title", "Author", "Publisher", LocalDate.now(), price, 2, + List.of(Category.SCIENCE), "Details", "English" + ); + NotValidBookException ex = assertThrows(NotValidBookException.class, () -> BookValidator.validate(book)); + assertEquals(BookValidator.ERROR_NEGATIVE_PRICE, ex.getMessage()); + } + } + + @Nested + @DisplayName("Stock validation") + class StockTests { + @ParameterizedTest + @ValueSource(ints = {-1, -50}) + @DisplayName("Should fail if stock is negative") + void shouldFailOnNegativeStock(int stock) { + BookInfo book = new BookInfo( + "9780001234567", "Title", "Author", "Publisher", LocalDate.now(), 10.0, stock, + List.of(Category.SCIENCE), "Details", "English" + ); + NotValidBookException ex = assertThrows(NotValidBookException.class, () -> BookValidator.validate(book)); + assertEquals(BookValidator.ERROR_NEGATIVE_STOCK, ex.getMessage()); + } + } + + @Nested + @DisplayName("Category validation") + class CategoryTests { + @Test + @DisplayName("Should fail if category list is empty") + void shouldFailOnEmptyCategoryList() { + BookInfo book = new BookInfo( + "9780001234567", "Title", "Author", "Publisher", LocalDate.now(), 10.0, 2, + Collections.emptyList(), "Details", "English" + ); + NotValidBookException ex = assertThrows(NotValidBookException.class, () -> BookValidator.validate(book)); + assertEquals(BookValidator.ERROR_CATEGORIES_EMPTY, ex.getMessage()); + } + } + + @Nested + @DisplayName("Description validation") + class DescriptionTests { + @Test + @DisplayName("Should fail if description is blank") + void shouldFailOnBlankDescription() { + BookInfo book = new BookInfo( + "9780001234567", "Title", "Author", "Publisher", LocalDate.now(), 10.0, 2, + List.of(Category.SCIENCE), "", "English" + ); + NotValidBookException ex = assertThrows(NotValidBookException.class, () -> BookValidator.validate(book)); + assertEquals(BookValidator.ERROR_DESCRIPTION_EMPTY, ex.getMessage()); + } + } + + @Nested + @DisplayName("Language validation") + class LanguageTests { + @Test + @DisplayName("Should fail if language is blank") + void shouldFailOnBlankLanguage() { + BookInfo book = new BookInfo( + "9780001234567", "Title", "Author", "Publisher", LocalDate.now(), 10.0, 2, + List.of(Category.SCIENCE), "Details", "" + ); + NotValidBookException ex = assertThrows(NotValidBookException.class, () -> BookValidator.validate(book)); + assertEquals(BookValidator.ERROR_LANGUAGE_EMPTY, ex.getMessage()); + } + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/converter/CustomerConverterTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/converter/CustomerConverterTest.java index 231fbab..8eec30f 100644 --- a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/converter/CustomerConverterTest.java +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/converter/CustomerConverterTest.java @@ -8,90 +8,85 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.*; -@DisplayName("CustomerConverter Unit Tests") +@DisplayName("Unit checks for CustomerConverter") class CustomerConverterTest { + @Test + @DisplayName("Should retain empty strings in conversion") + void convertsEmptyStringsCorrectly() { + CustomerInfo info = new CustomerInfo("", "", ""); + + Customer entity = CustomerConverter.toDomain(info); + CustomerDTO dto = CustomerConverter.toDTO(entity); + + assertEquals("", dto.getFirstName()); + assertEquals("", dto.getLastName()); + assertEquals("", dto.getPhoneNumber()); + } + @Nested - @DisplayName("toDomain() method tests") - class ToDomainTests { + @DisplayName("Tests for transformation into domain object") + class DomainMapping { @Test - @DisplayName("Should convert CustomerInfo to Customer domain object with loyalty points initialized to 0") - void shouldConvertCustomerInfoToDomain() { - // Given - CustomerInfo customerInfo = new CustomerInfo("John", "Doe", "0123456789"); + @DisplayName("Converts info to domain with zero points") + void mapsCustomerInfoProperly() { + CustomerInfo input = new CustomerInfo("Alice", "Wonders", "0123456789"); - // When - Customer result = CustomerConverter.toDomain(customerInfo); + Customer result = CustomerConverter.toDomain(input); - // Then assertNotNull(result); - assertEquals(customerInfo.firstName(), result.getFirstName()); - assertEquals(customerInfo.lastName(), result.getLastName()); - assertEquals(customerInfo.phoneNumber(), result.getPhoneNumber()); + assertEquals("Alice", result.getFirstName()); + assertEquals("Wonders", result.getLastName()); + assertEquals("0123456789", result.getPhoneNumber()); assertEquals(0, result.getLoyaltyPoints()); } } @Nested - @DisplayName("toDTO() method tests") - class ToDTOTests { + @DisplayName("Tests for transformation into DTO") + class DtoMapping { @Test - @DisplayName("Should convert Customer domain object to CustomerDTO with all fields mapped correctly") - void shouldConvertCustomerToDTO() { - Customer customer = Customer.builder() - .id(UUID.randomUUID()) - .firstName("Jane") - .lastName("Smith") - .phoneNumber("9876543210") - .loyaltyPoints(100) - .build(); + @DisplayName("Maps domain object to DTO correctly") + void mapsCustomerToDto() { + Customer input = Customer.builder() + .id(UUID.randomUUID()) + .firstName("Bob") + .lastName("Builder") + .phoneNumber("0987654321") + .loyaltyPoints(120) + .build(); - CustomerDTO result = CustomerConverter.toDTO(customer); + CustomerDTO output = CustomerConverter.toDTO(input); - assertNotNull(result); - assertEquals(customer.getId(), result.getId()); - assertEquals(customer.getFirstName(), result.getFirstName()); - assertEquals(customer.getLastName(), result.getLastName()); - assertEquals(customer.getPhoneNumber(), result.getPhoneNumber()); - assertEquals(customer.getLoyaltyPoints(), result.getLoyaltyPoints()); + assertNotNull(output); + assertEquals(input.getId(), output.getId()); + assertEquals("Bob", output.getFirstName()); + assertEquals("Builder", output.getLastName()); + assertEquals("0987654321", output.getPhoneNumber()); + assertEquals(120, output.getLoyaltyPoints()); } } @Test - @DisplayName("Should handle null values properly when converting between objects") - void shouldHandleNullValuesGracefully() { - Customer customer = Customer.builder() - .id(UUID.randomUUID()) - .firstName(null) - .lastName("NullTest") - .phoneNumber(null) - .loyaltyPoints(50) - .build(); + @DisplayName("Handles nulls safely during transformation") + void managesNullValues() { + Customer input = Customer.builder() + .id(UUID.randomUUID()) + .firstName(null) + .lastName("Testy") + .phoneNumber(null) + .loyaltyPoints(75) + .build(); - CustomerDTO result = CustomerConverter.toDTO(customer); + CustomerDTO result = CustomerConverter.toDTO(input); assertNotNull(result); assertNull(result.getFirstName()); - assertEquals("NullTest", result.getLastName()); + assertEquals("Testy", result.getLastName()); assertNull(result.getPhoneNumber()); } - - @Test - @DisplayName("Should preserve empty string values during conversion") - void shouldPreserveEmptyStrings() { - CustomerInfo customerInfo = new CustomerInfo("", "", ""); - - Customer domainResult = CustomerConverter.toDomain(customerInfo); - CustomerDTO dtoResult = CustomerConverter.toDTO(domainResult); - - assertEquals("", dtoResult.getFirstName()); - assertEquals("", dtoResult.getLastName()); - assertEquals("", dtoResult.getPhoneNumber()); - } -} \ No newline at end of file +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/entity/CustomerTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/entity/CustomerTest.java index 515187e..2ca97ad 100644 --- a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/entity/CustomerTest.java +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/entity/CustomerTest.java @@ -4,133 +4,116 @@ import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.IllegalCustomerPoin import org.junit.jupiter.api.Test; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; -import static org.junit.jupiter.api.Assertions.*; import java.util.UUID; +import static org.junit.jupiter.api.Assertions.*; + class CustomerTest { @Test - @DisplayName("Builder should create a valid Customer instance") - void testCustomerBuilder() { - UUID id = UUID.randomUUID(); - String firstName = "John"; - String lastName = "Doe"; - String phoneNumber = "0123456789"; - int loyaltyPoints = 100; + @DisplayName("Generates new UUID different from existing one") + void generatesNewUuid() { + Customer c = Customer.builder().build(); + UUID before = c.getId(); - Customer customer = Customer.builder() - .id(id) - .firstName(firstName) - .lastName(lastName) - .phoneNumber(phoneNumber) - .loyaltyPoints(loyaltyPoints) - .build(); + c.setRandomUUID(); - assertEquals(id, customer.getId()); - assertEquals(firstName, customer.getFirstName()); - assertEquals(lastName, customer.getLastName()); - assertEquals(phoneNumber, customer.getPhoneNumber()); - assertEquals(loyaltyPoints, customer.getLoyaltyPoints()); + assertNotNull(c.getId()); + assertNotEquals(before, c.getId()); } @Test - @DisplayName("setRandomUUID should change the ID to a new random UUID") - void testSetRandomUUID() { - Customer customer = Customer.builder().build(); - UUID originalId = customer.getId(); + @DisplayName("Builder assigns all fields correctly") + void buildsCustomerCorrectly() { + UUID id = UUID.randomUUID(); + String fn = "Alice"; + String ln = "MaFemme"; + String phone = "0123456789"; + int points = 120; - customer.setRandomUUID(); + Customer c = Customer.builder() + .id(id) + .firstName(fn) + .lastName(ln) + .phoneNumber(phone) + .loyaltyPoints(points) + .build(); - assertNotNull(customer.getId()); - assertNotEquals(originalId, customer.getId()); + assertEquals(id, c.getId()); + assertEquals(fn, c.getFirstName()); + assertEquals(ln, c.getLastName()); + assertEquals(phone, c.getPhoneNumber()); + assertEquals(points, c.getLoyaltyPoints()); } @Nested - @DisplayName("Loyalty Points Tests") - class LoyaltyPointsTests { + @DisplayName("Points Operations") + class Points { @Test - @DisplayName("addLoyaltyPoints should correctly increment loyalty points") - void testAddLoyaltyPoints() { - Customer customer = Customer.builder() - .loyaltyPoints(50) - .build(); - int pointsToAdd = 25; - int expectedPoints = 75; + @DisplayName("Adds loyalty points to current total") + void addsPointsCorrectly() { + Customer c = Customer.builder().loyaltyPoints(40).build(); - customer.addLoyaltyPoints(pointsToAdd); + c.addLoyaltyPoints(35); - assertEquals(expectedPoints, customer.getLoyaltyPoints()); + assertEquals(75, c.getLoyaltyPoints()); } @Test - @DisplayName("addLoyaltyPoints should handle zero points correctly") - void testAddZeroLoyaltyPoints() { - Customer customer = Customer.builder() - .loyaltyPoints(50) - .build(); + @DisplayName("Adds zero points without affecting total") + void addsZero() { + Customer c = Customer.builder().loyaltyPoints(60).build(); - customer.addLoyaltyPoints(0); + c.addLoyaltyPoints(0); - assertEquals(50, customer.getLoyaltyPoints()); + assertEquals(60, c.getLoyaltyPoints()); } @Test - @DisplayName("removeLoyaltyPoints should correctly decrement loyalty points") - void testRemoveLoyaltyPoints() throws IllegalCustomerPointException { - Customer customer = Customer.builder() - .loyaltyPoints(50) - .build(); - int pointsToRemove = 20; - int expectedPoints = 30; + @DisplayName("Removes loyalty points successfully") + void removesPointsCorrectly() throws IllegalCustomerPointException { + Customer c = Customer.builder().loyaltyPoints(90).build(); - customer.removeLoyaltyPoints(pointsToRemove); + c.removeLoyaltyPoints(40); - assertEquals(expectedPoints, customer.getLoyaltyPoints()); + assertEquals(50, c.getLoyaltyPoints()); } @Test - @DisplayName("removeLoyaltyPoints should handle removing exactly all points") - void testRemoveAllLoyaltyPoints() throws IllegalCustomerPointException { - Customer customer = Customer.builder() - .loyaltyPoints(50) - .build(); + @DisplayName("Removes exactly all loyalty points") + void removesAllPoints() throws IllegalCustomerPointException { + Customer c = Customer.builder().loyaltyPoints(30).build(); - customer.removeLoyaltyPoints(50); + c.removeLoyaltyPoints(30); - assertEquals(0, customer.getLoyaltyPoints()); + assertEquals(0, c.getLoyaltyPoints()); } @Test - @DisplayName("removeLoyaltyPoints should throw exception when trying to remove more points than available") - void testRemoveTooManyLoyaltyPoints() { - Customer customer = Customer.builder() - .loyaltyPoints(50) - .build(); - int pointsToRemove = 75; + @DisplayName("Fails when trying to remove more points than available") + void failsOnOverRemoval() { + Customer c = Customer.builder().loyaltyPoints(45).build(); - IllegalCustomerPointException exception = assertThrows( - IllegalCustomerPointException.class, - () -> customer.removeLoyaltyPoints(pointsToRemove) + IllegalCustomerPointException ex = assertThrows( + IllegalCustomerPointException.class, + () -> c.removeLoyaltyPoints(100) ); - assertEquals(50, customer.getLoyaltyPoints()); - - assertTrue(exception.getMessage().contains(String.valueOf(pointsToRemove))); - assertTrue(exception.getMessage().contains(String.valueOf(customer.getLoyaltyPoints()))); + assertEquals(45, c.getLoyaltyPoints()); + assertTrue(ex.getMessage().contains("100")); + assertTrue(ex.getMessage().contains("45")); } @Test - @DisplayName("removeLoyaltyPoints should handle removing zero points correctly") - void testRemoveZeroLoyaltyPoints() throws IllegalCustomerPointException { - Customer customer = Customer.builder() - .loyaltyPoints(50) - .build(); + @DisplayName("Removes zero points without affecting total") + void removesZero() throws IllegalCustomerPointException { + Customer c = Customer.builder().loyaltyPoints(15).build(); - customer.removeLoyaltyPoints(0); + c.removeLoyaltyPoints(0); - assertEquals(50, customer.getLoyaltyPoints()); + assertEquals(15, c.getLoyaltyPoints()); } } -} \ No newline at end of file +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/exception/CustomerNotFoundExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/exception/CustomerNotFoundExceptionTest.java index fcde9da..5cd0e55 100644 --- a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/exception/CustomerNotFoundExceptionTest.java +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/exception/CustomerNotFoundExceptionTest.java @@ -5,45 +5,43 @@ import org.junit.jupiter.api.Test; import java.util.UUID; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; class CustomerNotFoundExceptionTest { @Test - @DisplayName("Exception message should contain the UUID provided") - void testExceptionMessageContainsUUID() { - UUID uuid = UUID.randomUUID(); - - CustomerNotFoundException exception = new CustomerNotFoundException(uuid); - - String expectedMessage = String.format("The customer with id %s does not exist", uuid); - assertEquals(expectedMessage, exception.getMessage()); - } - - @Test - @DisplayName("Exception should use the correct constant message format") - void testExceptionUsesConstantMessageFormat() { - UUID uuid = UUID.randomUUID(); - - CustomerNotFoundException exception = new CustomerNotFoundException(uuid); - - String expectedFormatWithPlaceholder = "The customer with id {0} does not exist"; - assertEquals(CustomerNotFoundException.THE_CUSTOMER_WITH_ID_DOES_NOT_EXIST_MESSAGE, - expectedFormatWithPlaceholder); - assertTrue(exception.getMessage().contains(uuid.toString())); - } - - @Test - @DisplayName("Exception should be properly thrown and caught") - void testExceptionCanBeThrownAndCaught() { - UUID uuid = UUID.randomUUID(); + @DisplayName("Should throw and catch properly with expected message") + void throwsAndCatchesCorrectly() { + UUID id = UUID.randomUUID(); try { - throw new CustomerNotFoundException(uuid); - } catch (CustomerNotFoundException e) { - String expectedMessage = String.format("The customer with id %s does not exist", uuid); - assertEquals(expectedMessage, e.getMessage()); + throw new CustomerNotFoundException(id); + } catch (CustomerNotFoundException ex) { + String expected = String.format("The customer with id %s does not exist", id); + assertEquals(expected, ex.getMessage()); } } -} \ No newline at end of file + + @Test + @DisplayName("Should format using predefined message constant") + void usesStaticMessageTemplate() { + UUID id = UUID.randomUUID(); + + CustomerNotFoundException ex = new CustomerNotFoundException(id); + + String template = "The customer with id {0} does not exist"; + assertEquals(CustomerNotFoundException.THE_CUSTOMER_WITH_ID_DOES_NOT_EXIST_MESSAGE, template); + assertTrue(ex.getMessage().contains(id.toString())); + } + + @Test + @DisplayName("Should include UUID in exception message") + void includesUuidInMessage() { + UUID id = UUID.randomUUID(); + + CustomerNotFoundException ex = new CustomerNotFoundException(id); + + String expected = String.format("The customer with id %s does not exist", id); + assertEquals(expected, ex.getMessage()); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/exception/IllegalCustomerPointExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/exception/IllegalCustomerPointExceptionTest.java index c89306c..97d69de 100644 --- a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/exception/IllegalCustomerPointExceptionTest.java +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/exception/IllegalCustomerPointExceptionTest.java @@ -6,64 +6,63 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; class IllegalCustomerPointExceptionTest { @Test - @DisplayName("Exception message should contain the needed and actual points") - void testExceptionMessageContainsPoints() { - int neededPoints = 100; - int actualPoints = 50; + @DisplayName("Throws and catches the exception with expected output") + void throwsAndCatchesCorrectly() { + int required = 100; + int current = 50; - IllegalCustomerPointException exception = new IllegalCustomerPointException(neededPoints, actualPoints); - - String expectedMessage = "Cannot remove 100 points from 50 points"; - assertEquals(expectedMessage, exception.getMessage()); + try { + throw new IllegalCustomerPointException(required, current); + } catch (IllegalCustomerPointException ex) { + String expected = String.format("Cannot remove %d points from %d points", required, current); + assertEquals(expected, ex.getMessage()); + } } @ParameterizedTest @CsvSource({ - "100, 50", - "75, 25", - "200, 150", - "1000, 750" + "100, 50", + "200, 100", + "300, 299", + "75, 10" }) - @DisplayName("Exception message should be formatted correctly for different point values") - void testExceptionMessageForDifferentPointValues(int neededPoints, int actualPoints) { - IllegalCustomerPointException exception = new IllegalCustomerPointException(neededPoints, actualPoints); + @DisplayName("Formats message correctly for varied values") + void formatsMessageWithVariousValues(int required, int available) { + IllegalCustomerPointException ex = new IllegalCustomerPointException(required, available); - String expectedMessage = MessageFormat.format(IllegalCustomerPointException.CANNOT_REMOVE_LOYALTY_POINTS, neededPoints, actualPoints); - assertEquals(expectedMessage, exception.getMessage()); + String expected = MessageFormat.format( + IllegalCustomerPointException.CANNOT_REMOVE_LOYALTY_POINTS, required, available); + assertEquals(expected, ex.getMessage()); } @Test - @DisplayName("Exception should use the correct constant message format") - void testExceptionUsesConstantMessageFormat() { - int neededPoints = 100; - int actualPoints = 50; + @DisplayName("Message includes needed and actual points") + void includesCorrectValuesInMessage() { + int required = 150; + int actual = 30; - IllegalCustomerPointException exception = new IllegalCustomerPointException(neededPoints, actualPoints); + IllegalCustomerPointException ex = new IllegalCustomerPointException(required, actual); - String expectedFormatWithPlaceholder = "Cannot remove {0} points from {1} points"; - assertEquals(IllegalCustomerPointException.CANNOT_REMOVE_LOYALTY_POINTS, - expectedFormatWithPlaceholder); - assertTrue(exception.getMessage().contains(String.valueOf(neededPoints))); - assertTrue(exception.getMessage().contains(String.valueOf(actualPoints))); + String expected = "Cannot remove 150 points from 30 points"; + assertEquals(expected, ex.getMessage()); } @Test - @DisplayName("Exception should be properly thrown and caught") - void testExceptionCanBeThrownAndCaught() { - int neededPoints = 100; - int actualPoints = 50; + @DisplayName("Uses the correct message constant structure") + void checksMessageConstantTemplate() { + int needed = 100; + int current = 50; - try { - throw new IllegalCustomerPointException(neededPoints, actualPoints); - } catch (IllegalCustomerPointException e) { - String expectedMessage = String.format("Cannot remove %d points from %d points", neededPoints, actualPoints); - assertEquals(expectedMessage, e.getMessage()); - } + IllegalCustomerPointException ex = new IllegalCustomerPointException(needed, current); + + String template = "Cannot remove {0} points from {1} points"; + assertEquals(IllegalCustomerPointException.CANNOT_REMOVE_LOYALTY_POINTS, template); + assertTrue(ex.getMessage().contains(String.valueOf(needed))); + assertTrue(ex.getMessage().contains(String.valueOf(current))); } -} \ No newline at end of file +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/exception/NotValidCustomerExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/exception/NotValidCustomerExceptionTest.java index 3fba6e1..b3034c1 100644 --- a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/exception/NotValidCustomerExceptionTest.java +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/exception/NotValidCustomerExceptionTest.java @@ -5,57 +5,56 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; class NotValidCustomerExceptionTest { @Test - @DisplayName("Exception should be created with the provided message") - void testExceptionCreation() { - String errorMessage = "Customer data is not valid"; + @DisplayName("Throws and catches with exact error message") + void throwsAndMatchesMessage() { + String message = "Missing field: phone"; - NotValidCustomerException exception = new NotValidCustomerException(errorMessage); + Exception ex = assertThrows(NotValidCustomerException.class, () -> { + throw new NotValidCustomerException(message); + }); - assertEquals(errorMessage, exception.getMessage()); + assertEquals(message, ex.getMessage()); + } + + @Test + @DisplayName("Can be caught as base Exception type") + void caughtAsGenericException() { + String msg = "Invalid structure"; + + try { + throw new NotValidCustomerException(msg); + } catch (Exception e) { + assertEquals(NotValidCustomerException.class, e.getClass()); + assertEquals(msg, e.getMessage()); + } } @ParameterizedTest @ValueSource(strings = { - "First name is required", - "Last name cannot be empty", - "Phone number format is invalid", - "Customer age must be above 18" + "Invalid phone format", + "Missing last name", + "Empty input received", + "ID is malformed" }) - @DisplayName("Exception should handle different validation messages") - void testExceptionWithDifferentMessages(String errorMessage) { - NotValidCustomerException exception = new NotValidCustomerException(errorMessage); + @DisplayName("Handles multiple input error messages") + void handlesVariousMessages(String msg) { + NotValidCustomerException ex = new NotValidCustomerException(msg); - assertEquals(errorMessage, exception.getMessage()); + assertEquals(msg, ex.getMessage()); } @Test - @DisplayName("Exception should be properly thrown and caught") - void testExceptionCanBeThrownAndCaught() { - String errorMessage = "Required field is missing"; + @DisplayName("Stores the message it was created with") + void storesGivenMessage() { + String msg = "Required field not provided"; - Exception exception = assertThrows(NotValidCustomerException.class, () -> { - throw new NotValidCustomerException(errorMessage); - }); + NotValidCustomerException ex = new NotValidCustomerException(msg); - assertEquals(errorMessage, exception.getMessage()); + assertEquals(msg, ex.getMessage()); } - - @Test - @DisplayName("Exception should be catchable as a general Exception") - void testExceptionInheritance() { - String errorMessage = "Invalid customer data"; - - try { - throw new NotValidCustomerException(errorMessage); - } catch (Exception e) { - assertEquals(NotValidCustomerException.class, e.getClass()); - assertEquals(errorMessage, e.getMessage()); - } - } -} \ No newline at end of file +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/repository/CustomerRepositoryTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/repository/CustomerRepositoryTest.java deleted file mode 100644 index 4495493..0000000 --- a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/repository/CustomerRepositoryTest.java +++ /dev/null @@ -1,227 +0,0 @@ -package fr.iut_fbleau.but3.dev62.mylibrary.customer.repository; - -import fr.iut_fbleau.but3.dev62.mylibrary.customer.entity.Customer; -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 java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; - -class CustomerRepositoryTest { - - private CustomerRepository repository; - private Customer customer1; - private Customer customer2; - - @BeforeEach - void setUp() { - repository = new CustomerRepository(); - - customer1 = Customer.builder() - .firstName("John") - .lastName("Doe") - .phoneNumber("0123456789") - .loyaltyPoints(100) - .build(); - customer1.setRandomUUID(); - - customer2 = Customer.builder() - .firstName("Jane") - .lastName("Smith") - .phoneNumber("9876543210") - .loyaltyPoints(200) - .build(); - customer2.setRandomUUID(); - } - - @Test - @DisplayName("New repository should be empty") - void testNewRepositoryIsEmpty() { - List customers = repository.findAll(); - - assertTrue(customers.isEmpty()); - assertEquals(0, customers.size()); - } - - @Nested - @DisplayName("Save operations") - class SaveOperations { - - @Test - @DisplayName("Save should add a new customer") - void testSaveNewCustomer() { - Customer savedCustomer = repository.save(customer1); - - assertEquals(1, repository.findAll().size()); - assertEquals(customer1.getId(), savedCustomer.getId()); - assertEquals(customer1.getFirstName(), savedCustomer.getFirstName()); - } - - @Test - @DisplayName("Save should update existing customer with same ID") - void testSaveUpdatesExistingCustomer() { - repository.save(customer1); - - UUID id = customer1.getId(); - Customer updatedCustomer = Customer.builder() - .id(id) - .firstName("John") - .lastName("Updated") - .phoneNumber("1111111111") - .loyaltyPoints(150) - .build(); - - Customer savedCustomer = repository.save(updatedCustomer); - - assertEquals(1, repository.findAll().size()); - assertEquals(id, savedCustomer.getId()); - assertEquals("Updated", savedCustomer.getLastName()); - assertEquals("1111111111", savedCustomer.getPhoneNumber()); - assertEquals(150, savedCustomer.getLoyaltyPoints()); - } - - @Test - @DisplayName("Save multiple customers should add all of them") - void testSaveMultipleCustomers() { - repository.save(customer1); - repository.save(customer2); - - List customers = repository.findAll(); - - assertEquals(2, customers.size()); - assertTrue(customers.contains(customer1)); - assertTrue(customers.contains(customer2)); - } - } - - @Nested - @DisplayName("Find operations") - class FindOperations { - - @BeforeEach - void setUpCustomers() { - repository.save(customer1); - repository.save(customer2); - } - - @Test - @DisplayName("FindAll should return all customers") - void testFindAll() { - List customers = repository.findAll(); - - assertEquals(2, customers.size()); - assertTrue(customers.contains(customer1)); - assertTrue(customers.contains(customer2)); - } - - @Test - @DisplayName("FindById should return customer with matching ID") - void testFindById() { - Optional foundCustomer = repository.findById(customer1.getId()); - - assertTrue(foundCustomer.isPresent()); - assertEquals(customer1.getFirstName(), foundCustomer.get().getFirstName()); - assertEquals(customer1.getLastName(), foundCustomer.get().getLastName()); - } - - @Test - @DisplayName("FindById should return empty Optional when ID doesn't exist") - void testFindByIdNotFound() { - UUID nonExistentId = UUID.randomUUID(); - - Optional foundCustomer = repository.findById(nonExistentId); - - assertTrue(foundCustomer.isEmpty()); - } - - @Test - @DisplayName("FindByPhoneNumber should return customer with matching phone number") - void testFindByPhoneNumber() { - Optional foundCustomer = repository.findByPhoneNumber("0123456789"); - - assertTrue(foundCustomer.isPresent()); - assertEquals(customer1.getId(), foundCustomer.get().getId()); - assertEquals(customer1.getFirstName(), foundCustomer.get().getFirstName()); - } - - @Test - @DisplayName("FindByPhoneNumber should return empty Optional when phone number doesn't exist") - void testFindByPhoneNumberNotFound() { - Optional foundCustomer = repository.findByPhoneNumber("0000000000"); - - assertTrue(foundCustomer.isEmpty()); - } - - @Test - @DisplayName("ExistsById should return true when ID exists") - void testExistsByIdExists() { - boolean exists = repository.existsById(customer1.getId()); - - assertTrue(exists); - } - - @Test - @DisplayName("ExistsById should return false when ID doesn't exist") - void testExistsByIdNotExists() { - UUID nonExistentId = UUID.randomUUID(); - - boolean exists = repository.existsById(nonExistentId); - - assertFalse(exists); - } - } - - @Nested - @DisplayName("Delete operations") - class DeleteOperations { - - @BeforeEach - void setUpCustomers() { - repository.save(customer1); - repository.save(customer2); - } - - @Test - @DisplayName("Delete should remove the specified customer") - void testDelete() { - repository.delete(customer1); - - List customers = repository.findAll(); - - assertEquals(1, customers.size()); - assertFalse(customers.contains(customer1)); - assertTrue(customers.contains(customer2)); - } - - @Test - @DisplayName("DeleteAll should remove all customers") - void testDeleteAll() { - repository.deleteAll(); - - List customers = repository.findAll(); - - assertTrue(customers.isEmpty()); - assertEquals(0, customers.size()); - } - - @Test - @DisplayName("Delete should not throw exception when customer doesn't exist") - void testDeleteNonExistentCustomer() { - Customer nonExistentCustomer = Customer.builder() - .firstName("Non") - .lastName("Existent") - .phoneNumber("0000000000") - .build(); - nonExistentCustomer.setRandomUUID(); - - assertDoesNotThrow(() -> repository.delete(nonExistentCustomer)); - - assertEquals(2, repository.findAll().size()); - } - } -} \ No newline at end of file diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/usecase/CustomerUseCaseTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/usecase/CustomerUseCaseTest.java index 7159bdb..ef3921a 100644 --- a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/usecase/CustomerUseCaseTest.java +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/usecase/CustomerUseCaseTest.java @@ -20,260 +20,185 @@ import java.util.Optional; import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class CustomerUseCaseTest { @Mock - private CustomerRepository customerRepository; + private CustomerRepository repo; @InjectMocks - private CustomerUseCase customerUseCase; + private CustomerUseCase useCase; - private UUID customerId; - private Customer testCustomer; - private CustomerInfo validCustomerInfo; + private UUID uid; + private Customer base; + private CustomerInfo input; @BeforeEach - void setUp() { - customerId = UUID.randomUUID(); - testCustomer = Customer.builder() - .id(customerId) - .firstName("John") - .lastName("Doe") - .phoneNumber("0612345678") - .loyaltyPoints(100) - .build(); + void setup() { + uid = UUID.randomUUID(); + base = Customer.builder() + .id(uid) + .firstName("John") + .lastName("Doe") + .phoneNumber("0612345678") + .loyaltyPoints(100) + .build(); - validCustomerInfo = new CustomerInfo("John", "Doe", "0612345678"); + input = new CustomerInfo("John", "Doe", "0612345678"); } @Nested - @DisplayName("Register customer tests") - class RegisterCustomerTests { + class Creation { @Test - @DisplayName("Should register customer when valid data is provided") - void testRegisterCustomerWithValidData() throws NotValidCustomerException { - when(customerRepository.save(any(Customer.class))).thenReturn(testCustomer); + void createWithValidInput() throws NotValidCustomerException { + when(repo.save(any())).thenReturn(base); - UUID registeredId = customerUseCase.registerCustomer(validCustomerInfo); + UUID result = useCase.create(input); - assertNotNull(registeredId); - assertEquals(customerId, registeredId); - verify(customerRepository, times(1)).save(any(Customer.class)); + assertEquals(uid, result); + verify(repo).save(any()); } @Test - @DisplayName("Should throw exception when customer data is not valid") - void testRegisterCustomerWithInvalidData() { - CustomerInfo invalidCustomerInfo = new CustomerInfo("", "", ""); + void createWithInvalidInputThrows() { + CustomerInfo broken = new CustomerInfo("", "", ""); - assertThrows(NotValidCustomerException.class, - () -> customerUseCase.registerCustomer(invalidCustomerInfo)); - - verify(customerRepository, never()).save(any(Customer.class)); + assertThrows(NotValidCustomerException.class, () -> useCase.create(broken)); + verify(repo, never()).save(any()); } } @Nested - @DisplayName("Find customer tests") - class FindCustomerTests { + class Retrieval { @Test - @DisplayName("Should return customer when phone number exists") - void testFindCustomerByPhoneNumber() { - when(customerRepository.findByPhoneNumber("0612345678")).thenReturn(Optional.of(testCustomer)); + void getByPhoneSuccess() { + when(repo.findByPhoneNumber("0612345678")).thenReturn(Optional.of(base)); - Optional foundCustomer = customerUseCase.findCustomerByPhoneNumber("0612345678"); + Optional result = useCase.getByPhone("0612345678"); - assertTrue(foundCustomer.isPresent()); - assertEquals(testCustomer.getId(), foundCustomer.get().getId()); - assertEquals(testCustomer.getFirstName(), foundCustomer.get().getFirstName()); - verify(customerRepository, times(1)).findByPhoneNumber("0612345678"); + assertTrue(result.isPresent()); + assertEquals(uid, result.get().getId()); } @Test - @DisplayName("Should return empty Optional when phone number doesn't exist") - void testFindCustomerByPhoneNumberNotFound() { - when(customerRepository.findByPhoneNumber("0799999999")).thenReturn(Optional.empty()); + void getByPhoneFails() { + when(repo.findByPhoneNumber("0799999999")).thenReturn(Optional.empty()); - Optional foundCustomer = customerUseCase.findCustomerByPhoneNumber("0799999999"); + Optional result = useCase.getByPhone("0799999999"); - assertTrue(foundCustomer.isEmpty()); - verify(customerRepository, times(1)).findByPhoneNumber("0799999999"); + assertTrue(result.isEmpty()); } } @Nested - @DisplayName("Update customer tests") - class UpdateCustomerTests { + class Modification { @Test - @DisplayName("Should update customer when valid data is provided") - void testUpdateCustomerWithValidData() throws CustomerNotFoundException, NotValidCustomerException { - when(customerRepository.findById(customerId)).thenReturn(Optional.of(testCustomer)); + void modifyValid() throws CustomerNotFoundException, NotValidCustomerException { + when(repo.findById(uid)).thenReturn(Optional.of(base)); - Customer updatedCustomer = Customer.builder() - .id(customerId) - .firstName("John") - .lastName("Updated") - .phoneNumber("0712345678") - .loyaltyPoints(100) - .build(); + Customer updated = Customer.builder() + .id(uid) + .firstName("John") + .lastName("Smith") + .phoneNumber("0712345678") + .loyaltyPoints(100) + .build(); - when(customerRepository.save(any(Customer.class))).thenReturn(updatedCustomer); + when(repo.save(any())).thenReturn(updated); - CustomerInfo updateInfo = new CustomerInfo("John", "Updated", "0712345678"); + CustomerInfo updateInfo = new CustomerInfo("John", "Smith", "0712345678"); - CustomerDTO result = customerUseCase.updateCustomer(customerId, updateInfo); + CustomerDTO dto = useCase.modify(uid, updateInfo); - assertNotNull(result); - assertEquals(customerId, result.getId()); - assertEquals("Updated", result.getLastName()); - assertEquals("0712345678", result.getPhoneNumber()); - verify(customerRepository, times(1)).findById(customerId); - verify(customerRepository, times(1)).save(any(Customer.class)); + assertEquals("Smith", dto.getLastName()); + assertEquals("0712345678", dto.getPhoneNumber()); } @Test - @DisplayName("Should throw exception when customer ID doesn't exist") - void testUpdateCustomerNotFound() { - UUID nonExistentId = UUID.randomUUID(); - when(customerRepository.findById(nonExistentId)).thenReturn(Optional.empty()); + void modifyNotFound() { + when(repo.findById(uid)).thenReturn(Optional.empty()); - CustomerInfo updateInfo = new CustomerInfo("John", "Updated", "0712345678"); + CustomerInfo info = new CustomerInfo("John", "Smith", "0712345678"); - assertThrows(CustomerNotFoundException.class, - () -> customerUseCase.updateCustomer(nonExistentId, updateInfo)); - - verify(customerRepository, times(1)).findById(nonExistentId); - verify(customerRepository, never()).save(any(Customer.class)); + assertThrows(CustomerNotFoundException.class, () -> useCase.modify(uid, info)); } @Test - @DisplayName("Should throw exception when update data is not valid") - void testUpdateCustomerWithInvalidData() { - CustomerInfo invalidUpdateInfo = new CustomerInfo("", "", ""); + void modifyInvalidInput() { + CustomerInfo bad = new CustomerInfo("", "", ""); - assertThrows(NotValidCustomerException.class, - () -> customerUseCase.updateCustomer(customerId, invalidUpdateInfo)); - - verify(customerRepository, never()).findById(any(UUID.class)); - verify(customerRepository, never()).save(any(Customer.class)); + assertThrows(NotValidCustomerException.class, () -> useCase.modify(uid, bad)); } } @Nested - @DisplayName("Delete customer tests") - class DeleteCustomerTests { + class Deletion { @Test - @DisplayName("Should delete customer when ID exists") - void testDeleteCustomer() throws CustomerNotFoundException { - when(customerRepository.findById(customerId)).thenReturn(Optional.of(testCustomer)); - doNothing().when(customerRepository).delete(testCustomer); + void removeSuccess() throws CustomerNotFoundException { + when(repo.findById(uid)).thenReturn(Optional.of(base)); + doNothing().when(repo).delete(base); - customerUseCase.deleteCustomer(customerId); + useCase.remove(uid); - verify(customerRepository, times(1)).findById(customerId); - verify(customerRepository, times(1)).delete(testCustomer); + verify(repo).delete(base); } @Test - @DisplayName("Should throw exception when customer ID doesn't exist") - void testDeleteCustomerNotFound() { - UUID nonExistentId = UUID.randomUUID(); - when(customerRepository.findById(nonExistentId)).thenReturn(Optional.empty()); + void removeNotFound() { + when(repo.findById(uid)).thenReturn(Optional.empty()); - assertThrows(CustomerNotFoundException.class, - () -> customerUseCase.deleteCustomer(nonExistentId)); - - verify(customerRepository, times(1)).findById(nonExistentId); - verify(customerRepository, never()).delete(any(Customer.class)); + assertThrows(CustomerNotFoundException.class, () -> useCase.remove(uid)); } } @Nested - @DisplayName("Loyalty points tests") - class LoyaltyPointsTests { + class Points { @Test - @DisplayName("Should add loyalty points to customer") - void testAddLoyaltyPoints() throws CustomerNotFoundException { - when(customerRepository.findById(customerId)).thenReturn(Optional.of(testCustomer)); - when(customerRepository.save(testCustomer)).thenReturn(testCustomer); + void increasePointsValid() throws CustomerNotFoundException { + when(repo.findById(uid)).thenReturn(Optional.of(base)); + when(repo.save(base)).thenReturn(base); - int initialPoints = testCustomer.getLoyaltyPoints(); - int pointsToAdd = 50; - int expectedPoints = initialPoints + pointsToAdd; + int result = useCase.increasePoints(uid, 25); - int newPoints = customerUseCase.addLoyaltyPoints(customerId, pointsToAdd); - - assertEquals(expectedPoints, newPoints); - assertEquals(expectedPoints, testCustomer.getLoyaltyPoints()); - verify(customerRepository, times(1)).findById(customerId); - verify(customerRepository, times(1)).save(testCustomer); + assertEquals(125, result); } @Test - @DisplayName("Should throw exception when adding points to non-existent customer") - void testAddLoyaltyPointsToNonExistentCustomer() { - UUID nonExistentId = UUID.randomUUID(); - when(customerRepository.findById(nonExistentId)).thenReturn(Optional.empty()); + void increasePointsNotFound() { + when(repo.findById(uid)).thenReturn(Optional.empty()); - assertThrows(CustomerNotFoundException.class, - () -> customerUseCase.addLoyaltyPoints(nonExistentId, 50)); - - verify(customerRepository, times(1)).findById(nonExistentId); - verify(customerRepository, never()).save(any(Customer.class)); + assertThrows(CustomerNotFoundException.class, () -> useCase.increasePoints(uid, 25)); } @Test - @DisplayName("Should subtract loyalty points from customer") - void testSubtractLoyaltyPoints() throws CustomerNotFoundException, IllegalCustomerPointException { - when(customerRepository.findById(customerId)).thenReturn(Optional.of(testCustomer)); - when(customerRepository.save(testCustomer)).thenReturn(testCustomer); + void decreasePointsValid() throws CustomerNotFoundException, IllegalCustomerPointException { + when(repo.findById(uid)).thenReturn(Optional.of(base)); + when(repo.save(base)).thenReturn(base); - int initialPoints = testCustomer.getLoyaltyPoints(); - int pointsToRemove = 30; - int expectedPoints = initialPoints - pointsToRemove; + int result = useCase.decreasePoints(uid, 40); - int newPoints = customerUseCase.subtractLoyaltyPoints(customerId, pointsToRemove); - - assertEquals(expectedPoints, newPoints); - assertEquals(expectedPoints, testCustomer.getLoyaltyPoints()); - verify(customerRepository, times(1)).findById(customerId); - verify(customerRepository, times(1)).save(testCustomer); + assertEquals(60, result); } @Test - @DisplayName("Should throw exception when trying to remove more points than available") - void testSubtractTooManyLoyaltyPoints() { - when(customerRepository.findById(customerId)).thenReturn(Optional.of(testCustomer)); + void decreasePointsTooMuch() { + when(repo.findById(uid)).thenReturn(Optional.of(base)); - int pointsToRemove = 200; - - assertThrows(IllegalCustomerPointException.class, - () -> customerUseCase.subtractLoyaltyPoints(customerId, pointsToRemove)); - - verify(customerRepository, times(1)).findById(customerId); - verify(customerRepository, never()).save(any(Customer.class)); + assertThrows(IllegalCustomerPointException.class, () -> useCase.decreasePoints(uid, 999)); } @Test - @DisplayName("Should throw exception when subtracting points from non-existent customer") - void testSubtractLoyaltyPointsFromNonExistentCustomer() { - UUID nonExistentId = UUID.randomUUID(); - when(customerRepository.findById(nonExistentId)).thenReturn(Optional.empty()); + void decreasePointsNotFound() { + when(repo.findById(uid)).thenReturn(Optional.empty()); - assertThrows(CustomerNotFoundException.class, - () -> customerUseCase.subtractLoyaltyPoints(nonExistentId, 50)); - - verify(customerRepository, times(1)).findById(nonExistentId); - verify(customerRepository, never()).save(any(Customer.class)); + assertThrows(CustomerNotFoundException.class, () -> useCase.decreasePoints(uid, 20)); } } -} \ No newline at end of file +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/validator/CustomerValidatorTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/validator/CustomerValidatorTest.java index 29bb954..a706423 100644 --- a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/validator/CustomerValidatorTest.java +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/validator/CustomerValidatorTest.java @@ -13,143 +13,109 @@ import static org.junit.jupiter.api.Assertions.*; class CustomerValidatorTest { @Test - @DisplayName("Should validate customer with valid data") - void testValidateValidCustomer() { - CustomerInfo validCustomer = new CustomerInfo("John", "Doe", "0612345678"); + @DisplayName("Valid customer should pass without error") + void passesWithValidInput() { + CustomerInfo valid = new CustomerInfo("Alice", "Smith", "0612345678"); - assertDoesNotThrow(() -> CustomerValidator.validate(validCustomer)); + assertDoesNotThrow(() -> CustomerValidator.check(valid)); } @Nested - @DisplayName("First name validation tests") - class FirstNameValidationTests { + class FirstName { @Test - @DisplayName("Should throw exception when first name is blank") - void testValidateBlankFirstName() { - CustomerInfo customerWithBlankFirstName = new CustomerInfo("", "Doe", "0612345678"); + void rejectsBlankFirstName() { + CustomerInfo data = new CustomerInfo("", "Smith", "0612345678"); - NotValidCustomerException exception = assertThrows( - NotValidCustomerException.class, - () -> CustomerValidator.validate(customerWithBlankFirstName) - ); + NotValidCustomerException ex = assertThrows(NotValidCustomerException.class, + () -> CustomerValidator.check(data)); - assertEquals(CustomerValidator.FIRST_NAME_CANNOT_BE_BLANK, exception.getMessage()); + assertEquals(CustomerValidator.ERROR_FIRSTNAME_EMPTY, ex.getMessage()); } @ParameterizedTest - @ValueSource(strings = {" ", " ", "\t", "\n"}) - @DisplayName("Should throw exception when first name contains only whitespace") - void testValidateWhitespaceFirstName(String whitespace) { - CustomerInfo customerWithWhitespaceFirstName = new CustomerInfo(whitespace, "Doe", "0612345678"); + @ValueSource(strings = {" ", "\t", "\n"}) + void rejectsWhitespaceOnly(String blank) { + CustomerInfo data = new CustomerInfo(blank, "Smith", "0612345678"); - NotValidCustomerException exception = assertThrows( - NotValidCustomerException.class, - () -> CustomerValidator.validate(customerWithWhitespaceFirstName) - ); + NotValidCustomerException ex = assertThrows(NotValidCustomerException.class, + () -> CustomerValidator.check(data)); - assertEquals(CustomerValidator.FIRST_NAME_CANNOT_BE_BLANK, exception.getMessage()); + assertEquals(CustomerValidator.ERROR_FIRSTNAME_EMPTY, ex.getMessage()); } } @Nested - @DisplayName("Last name validation tests") - class LastNameValidationTests { + class LastName { @Test - @DisplayName("Should throw exception when last name is blank") - void testValidateBlankLastName() { - CustomerInfo customerWithBlankLastName = new CustomerInfo("John", "", "0612345678"); + void rejectsBlankLastName() { + CustomerInfo data = new CustomerInfo("Alice", "", "0612345678"); - NotValidCustomerException exception = assertThrows( - NotValidCustomerException.class, - () -> CustomerValidator.validate(customerWithBlankLastName) - ); + NotValidCustomerException ex = assertThrows(NotValidCustomerException.class, + () -> CustomerValidator.check(data)); - assertEquals(CustomerValidator.LAST_NAME_CANNOT_BE_BLANK, exception.getMessage()); + assertEquals(CustomerValidator.ERROR_LASTNAME_EMPTY, ex.getMessage()); } @ParameterizedTest - @ValueSource(strings = {" ", " ", "\t", "\n"}) - @DisplayName("Should throw exception when last name contains only whitespace") - void testValidateWhitespaceLastName(String whitespace) { - CustomerInfo customerWithWhitespaceLastName = new CustomerInfo("John", whitespace, "0612345678"); + @ValueSource(strings = {" ", "\t", "\n"}) + void rejectsWhitespaceLastName(String blank) { + CustomerInfo data = new CustomerInfo("Alice", blank, "0612345678"); - NotValidCustomerException exception = assertThrows( - NotValidCustomerException.class, - () -> CustomerValidator.validate(customerWithWhitespaceLastName) - ); + NotValidCustomerException ex = assertThrows(NotValidCustomerException.class, + () -> CustomerValidator.check(data)); - assertEquals(CustomerValidator.LAST_NAME_CANNOT_BE_BLANK, exception.getMessage()); + assertEquals(CustomerValidator.ERROR_LASTNAME_EMPTY, ex.getMessage()); } } @Nested - @DisplayName("Phone number validation tests") - class PhoneNumberValidationTests { + class PhoneNumber { @Test - @DisplayName("Should throw exception when phone number is blank") - void testValidateBlankPhoneNumber() { - CustomerInfo customerWithBlankPhoneNumber = new CustomerInfo("John", "Doe", ""); + void rejectsEmptyPhone() { + CustomerInfo data = new CustomerInfo("Alice", "Smith", ""); - NotValidCustomerException exception = assertThrows( - NotValidCustomerException.class, - () -> CustomerValidator.validate(customerWithBlankPhoneNumber) - ); + NotValidCustomerException ex = assertThrows(NotValidCustomerException.class, + () -> CustomerValidator.check(data)); - assertEquals("Phone number cannot be blank", exception.getMessage()); + assertEquals("Phone number cannot be blank", ex.getMessage()); } @ParameterizedTest - @ValueSource(strings = {" ", " ", "\t", "\n"}) - @DisplayName("Should throw exception when phone number contains only whitespace") - void testValidateWhitespacePhoneNumber(String whitespace) { - CustomerInfo customerWithWhitespacePhoneNumber = new CustomerInfo("John", "Doe", whitespace); + @ValueSource(strings = {" ", "\t", "\n"}) + void rejectsBlankPhone(String blank) { + CustomerInfo data = new CustomerInfo("Alice", "Smith", blank); - NotValidCustomerException exception = assertThrows( - NotValidCustomerException.class, - () -> CustomerValidator.validate(customerWithWhitespacePhoneNumber) - ); + NotValidCustomerException ex = assertThrows(NotValidCustomerException.class, + () -> CustomerValidator.check(data)); - assertEquals("Phone number cannot be blank", exception.getMessage()); + assertEquals("Phone number cannot be blank", ex.getMessage()); } @ParameterizedTest @ValueSource(strings = { - "0512345678", // Invalid prefix (not 06 or 07) - "0812345678", // Invalid prefix (not 06 or 07) - "061234567", // Too short (missing one digit) - "06123456789", // Too long (one extra digit) - "6123456789", // Missing leading 0 - "O612345678", // Letter O instead of zero - "+33612345678", // International format not supported - "06 12 34 56 78" // Contains spaces + "0512345678", "0812345678", "061234567", "06123456789", + "6123456789", "O612345678", "+33612345678", "06 12 34 56 78" }) - @DisplayName("Should throw exception when phone number format is invalid") - void testValidateInvalidPhoneNumberFormat(String invalidPhoneNumber) { - CustomerInfo customerWithInvalidPhoneNumber = new CustomerInfo("John", "Doe", invalidPhoneNumber); + void rejectsInvalidPhoneFormat(String phone) { + CustomerInfo data = new CustomerInfo("Alice", "Smith", phone); - NotValidCustomerException exception = assertThrows( - NotValidCustomerException.class, - () -> CustomerValidator.validate(customerWithInvalidPhoneNumber) - ); + NotValidCustomerException ex = assertThrows(NotValidCustomerException.class, + () -> CustomerValidator.check(data)); - assertEquals(CustomerValidator.PHONE_NUMBER_IS_NOT_VALID, exception.getMessage()); + assertEquals(CustomerValidator.ERROR_PHONE_INVALID, ex.getMessage()); } @ParameterizedTest @ValueSource(strings = { - "0612345678", // Valid 06 number - "0712345678", // Valid 07 number - "0699999999", // Valid 06 number with all 9s - "0700000000" // Valid 07 number with all 0s + "0612345678", "0712345678", "0699999999", "0700000000" }) - @DisplayName("Should validate when phone number format is valid") - void testValidateValidPhoneNumberFormat(String validPhoneNumber) { - CustomerInfo customerWithValidPhoneNumber = new CustomerInfo("John", "Doe", validPhoneNumber); + void acceptsValidPhoneFormat(String phone) { + CustomerInfo data = new CustomerInfo("Alice", "Smith", phone); - assertDoesNotThrow(() -> CustomerValidator.validate(customerWithValidPhoneNumber)); + assertDoesNotThrow(() -> CustomerValidator.check(data)); } } -} \ No newline at end of file +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/book/BookSteps.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/book/BookSteps.java new file mode 100644 index 0000000..a43a2df --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/book/BookSteps.java @@ -0,0 +1,320 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.features.book; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.And; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.BookDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.book.BookInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.book.Category; +import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.BookNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.NotValidBookException; +import fr.iut_fbleau.but3.dev62.mylibrary.book.repository.BookRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.book.usecase.BookUseCase; + +public class BookSteps { + + private final BookRepository bookRepository = new BookRepository(); + private final BookUseCase bookUseCase = new BookUseCase(bookRepository); + + private String bookRegistration; + private BookDTO bookByIsbn; + private BookDTO updatedBook; + private String deletedBookIsbn; + private Exception storedException; + private List booksPageResult; + private int totalBooks; + private int totalPages; + private List filteredBooksResult; + private NotValidBookException notValidBookException; + + @Given("Le systeme possedent les livres suivants :") + public void leSystemePossedeLesLivresSuivants(DataTable dataTable) { + bookRepository.deleteAll(); + List> livres = dataTable.asMaps(String.class, String.class); + + for (Map livre : livres) { + BookInfo info = new BookInfo( + livre.get("ISBN"), + livre.get("Titre"), + livre.get("Auteur"), + livre.get("Editeur"), + LocalDate.parse(livre.get("Date de publication")), + Double.parseDouble(livre.get("Prix")), + Integer.parseInt(livre.get("Stock initial")), + Arrays.stream(livre.get("Categories").replaceAll("[\\[\\]]", "").split(",")) + .map(String::trim) + .map(Category::valueOf) + .collect(Collectors.toList()), + livre.get("Description"), + livre.get("Langue") + ); + bookUseCase.registerBook(info); + } + assertEquals(livres.size(), bookRepository.findAll().size()); + } + + @When("Je cree un nouveau livre avec les informations suivantes :") + public void jeCreeUnNouveauLivreAvecLesInformationsSuivantes(DataTable dataTable) { + Map data = dataTable.asMaps(String.class, String.class).getFirst(); + BookInfo info = new BookInfo( + data.get("ISBN"), + data.get("Titre"), + data.get("Auteur"), + data.get("Editeur"), + LocalDate.parse(data.get("Date de publication")), + Double.parseDouble(data.get("Prix")), + Integer.parseInt(data.get("Stock initial")), + Arrays.stream(data.get("Categories").replaceAll("[\\[\\]]", "").split(",")) + .map(String::trim) + .map(Category::valueOf) + .collect(Collectors.toList()), + data.get("Description"), + data.get("Langue") + ); + try { + bookRegistration = bookUseCase.registerBook(info); + storedException = null; + } catch (Exception e) { + storedException = e; + bookRegistration = null; + } + } + + + + @Then("La creation echoue") + public void laCreationEchoue() { + assertNotNull(storedException, "Une exception devait être levée lors de la création du livre"); + assertInstanceOf(NotValidBookException.class, storedException, "L'exception levée doit être de type NotValidBookException"); + } + + @When("Je modifie le livre avec l'ISBN {string} avec les informations suivantes:") + public void jeModifieLeLivreAvecLesInformationsSuivantes(String isbnStr, DataTable dataTable) { + Map data = dataTable.asMaps(String.class, String.class).getFirst(); + BookInfo info = new BookInfo( + data.get("ISBN"), + data.get("Titre"), + data.get("Auteur"), + data.get("Editeur"), + LocalDate.parse(data.get("Date de publication")), + Double.parseDouble(data.get("Prix")), + Integer.parseInt(data.get("Stock initial")), + Arrays.stream(data.get("Categories").replaceAll("[\\[\\]]", "").split(",")) + .map(String::trim) + .map(Category::valueOf) + .collect(Collectors.toList()), + data.get("Description"), + data.get("Langue") + ); + try { + updatedBook = bookUseCase.updateBook(isbnStr, info); + storedException = null; + } catch (Exception e) { + storedException = e; + } + } + + @Then("Le livre {string} a les informations suivantes:") + public void leLivreALesInformations(String isbnStr, DataTable dataTable) { + BookDTO book = bookUseCase.findBookByIsbn(isbnStr); + + Map expected = dataTable.asMaps(String.class, String.class).getFirst(); + assertBookEquals(expected, book); + } + + @When("Je supprime le livre avec l'ISBN {string}") + public void jeSupprimeLeLivre(String isbnStr) { + deletedBookIsbn = isbnStr; + try { + bookUseCase.deleteBook(isbnStr); + storedException = null; + } catch (Exception e) { + storedException = e; + } + } + + @Then("Le livre n'existe plus dans le systeme") + public void leLivreNestPlusDansLeSysteme() { + assertTrue(bookRepository.findByIsbn(deletedBookIsbn).isEmpty()); + } + + @Then("La suppression échoue avec une erreur indiquant que le livre est introuvable") + public void laSuppressionEchoueAvecErreurLivreIntrouvable() { + assertNotNull(storedException, "Une exception devait être levée lors de la suppression"); + assertInstanceOf(BookNotFoundException.class, storedException, "L'exception levée doit être de type BookNotFoundException"); + } + + @When("Je demande les informations du livre avec l'ISBN {string}") + public void jeDemandeLesInformationsDuLivreAvecISBN(String isbnStr) { + try { + bookByIsbn = bookUseCase.findBookByIsbn(isbnStr); + storedException = null; + } catch (Exception e) { + storedException = e; + bookByIsbn = null; + } + } + + @Then("Je recupere les informations suivantes:") + public void jeRecupereLesInfos(DataTable dataTable) { + assertNotNull(bookByIsbn); + Map expected = dataTable.asMaps(String.class, String.class).getFirst(); + assertBookEquals(expected, bookByIsbn); + } + + @Then("Je recois une erreur indiquant que le livre est introuvable") + public void jeRecoisUneErreurIndiquantQueLeLivreEstIntrouvable() { + assertNotNull(storedException, "Une exception devait être levée pour un livre introuvable"); + assertInstanceOf(BookNotFoundException.class, storedException, "L'exception levée doit être de type BookNotFoundException"); + } + + @Given("Il existe un livre avec l'ISBN {string}") + public void ilExisteUnLivreAvecISBN(String isbnStr) { + Optional existingBook = bookRepository.findByIsbn(isbnStr); + if (existingBook.isEmpty()) { + BookInfo info = new BookInfo( + isbnStr, + "Titre par defaut", + "Auteur inconnu", + "Editeur inconnu", + LocalDate.now(), + 0.0, + 1, + Collections.singletonList(Category.FANTASY), + "Description par defaut", + "Francais" + ); + bookUseCase.registerBook(info); + } + } + + @Given("Il n'existe pas un livre avec l'ISBN {string}") + public void ilNexistePasUnLivreAvecISBN(String isbnStr) { + Optional bookOpt = bookRepository.findByIsbn(isbnStr); + bookOpt.ifPresent(book -> bookRepository.delete(book)); + } + + @Then("La modification échoue avec une erreur indiquant que le livre est introuvable") + public void laModificationEchoueAvecErreurLivreIntrouvable() { + assertNotNull(storedException, "Une exception devait être levée lors de la modification"); + assertInstanceOf(BookNotFoundException.class, storedException, "L'exception levée doit être de type BookNotFoundException"); + } + + // Pagination et recherche + + @When("Je demande la page {int} de taille {int} triée par {string}") + public void jeDemandeLaPageTailleTrieePar(int page, int size, String sortBy) { + Map pageResult = bookUseCase.findBooksPageSorted(page, size, sortBy); + this.booksPageResult = (List) pageResult.get("content"); + this.totalBooks = (int) pageResult.get("totalElements"); + this.totalPages = (int) pageResult.get("totalPages"); + } + + @Then("La réponse contient les livres suivants :") + public void laReponseContientLesLivresSuivants(DataTable dataTable) { + List> expectedBooks = dataTable.asMaps(String.class, String.class); + assertEquals(expectedBooks.size(), booksPageResult.size(), "Nombre de livres ne correspond pas"); + for (int i = 0; i < expectedBooks.size(); i++) { + assertBookEquals(expectedBooks.get(i), booksPageResult.get(i)); + } + } + + @And("La réponse indique qu’il y a {int} livres au total et {int} pages au total") + public void laReponseIndiqueNombreLivresEtPages(int totalLivres, int totalPagesAttendu) { + assertEquals(totalLivres, this.totalBooks, "Nombre total de livres incorrect"); + assertEquals(totalPagesAttendu, this.totalPages, "Nombre total de pages incorrect"); + } + + @When("Je recherche les livres avec le titre contenant {string}") + public void jeRechercheLesLivresAvecLeTitreContenant(String titrePartiel) { + filteredBooksResult = bookUseCase.findBooksByTitleContaining(titrePartiel); + } + + @Then("Je recupere uniquement les livres dont le titre contient {string}") + public void jeRecupereUniquementLesLivresDontLeTitreContient(String titrePartiel) { + assertFalse(filteredBooksResult.isEmpty(), "Aucun livre trouvé"); + for (BookDTO b : filteredBooksResult) { + assertTrue(b.getTitle().toLowerCase().contains(titrePartiel.toLowerCase()), + "Le titre ne contient pas la chaîne attendue"); + } + } + + @When("Je recherche les livres de l’auteur {string}") + public void jeRechercheLesLivresDeLAuteur(String auteur) { + filteredBooksResult = bookUseCase.findBooksByAuthor(auteur); + } + + @Then("Je recupere les livres suivants :") + public void jeRecupereLesLivresSuivants(DataTable dataTable) { + List> expectedBooks = dataTable.asMaps(String.class, String.class); + assertEquals(expectedBooks.size(), filteredBooksResult.size(), "Nombre de livres ne correspond pas"); + for (int i = 0; i < expectedBooks.size(); i++) { + assertBookEquals(expectedBooks.get(i), filteredBooksResult.get(i)); + } + } + + @When("Je recherche les livres publies par {string}") + public void jeRechercheLesLivresPubliesPar(String editeur) { + filteredBooksResult = bookUseCase.findBooksByPublisher(editeur); + } + + @When("Je recherche les livres de la categorie {string}") + public void jeRechercheLesLivresDeLaCategorie(String categorieStr) { + Category categorie = Category.valueOf(categorieStr.toUpperCase().replace(' ', '_')); + filteredBooksResult = bookUseCase.findBooksByCategory(categorie); + } + + @When("Je filtre les livres publies apres {string}") + public void jeFiltreLesLivresPubliesApres(String dateStr) { + LocalDate date = LocalDate.parse(dateStr); + filteredBooksResult = bookUseCase.findBooksPublishedAfter(date); + } + + @When("Je filtre les livres avec un prix minimum de {double} et maximum de {double}") + public void jeFiltreLesLivresAvecUnPrixMinimumEtMaximum(Double min, Double max) { + filteredBooksResult = bookUseCase.findBooksByPriceRange(min, max); + } + + @And("La réponse indique que c’est la dernière page") + public void laReponseIndiqueQueCestLaDernierePage() { + if (booksPageResult == null || booksPageResult.isEmpty()) { + assertEquals(0, totalPages, "Ce n'est pas la dernière page"); + } else { + assertTrue(true); // à adapter si besoin + } + } + + private static void assertBookEquals(Map expected, BookDTO actual) { + assertEquals(expected.get("ISBN"), actual.getIsbn()); + assertEquals(expected.get("Titre"), actual.getTitle()); + assertEquals(expected.get("Auteur"), actual.getAuthor()); + assertEquals(expected.get("Editeur"), actual.getPublisher()); + assertEquals(LocalDate.parse(expected.get("Date de publication")), actual.getPublicationDate()); + assertEquals(Double.parseDouble(expected.get("Prix")), actual.getPrice(), 0.01); + assertEquals(Integer.parseInt(expected.get("Stock initial")), actual.getStock()); + assertEquals(expected.get("Description"), actual.getDescription()); + assertEquals(expected.get("Langue"), actual.getLanguage()); + assertEquals( + Arrays.stream(expected.get("Categories").replaceAll("[\\[\\]]", "").split(",")) + .map(String::trim) + .map(Category::valueOf) + .collect(Collectors.toList()), + actual.getCategories() + ); + } + + @Then("Un nouveau livre est créé") + public void unNouveauLivreEstCree() { + assertNotNull(bookRegistration); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..5c92caa --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/order/OrderSteps.java @@ -0,0 +1,364 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.features.order; + +import static org.junit.jupiter.api.Assertions.*; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.BookInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.book.Category; +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.BookNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.book.repository.BookRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.book.usecase.BookUseCase; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.entity.Customer; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.CustomerNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.repository.CustomerRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.usecase.CustomerUseCase; +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.repository.OrderRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.order.usecase.OrderUseCase; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.OrderQuantityException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.WrongAddressException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.OrderNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.IllegalOrderPointException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.InvalidPaymentMethodException; +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.And; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + +public class OrderSteps { + + private final BookRepository bookRepository = new BookRepository(); + private final BookUseCase bookUseCase = new BookUseCase(bookRepository); + private final CustomerRepository customerRepository = new CustomerRepository(); + private final CustomerUseCase customerUseCase = new CustomerUseCase(customerRepository); + private final OrderRepository orderRepository = new OrderRepository(); + private final OrderUseCase orderUseCase = new OrderUseCase(orderRepository, customerRepository, bookRepository); + + private Exception lastException; + private Optional lastOrderDTO; + private List lastOrderList; + private OrderInfo currentOrderInfo; + private UUID lastOrderId; + + @Given("Le systeme contient les livres suivants :") + public void leSystemeContientLesLivresSuivants(DataTable dataTable) { + bookRepository.deleteAll(); + List> livres = dataTable.asMaps(String.class, String.class); + for (Map livre : livres) { + BookInfo info = new BookInfo( + livre.get("isbn"), + livre.get("titre"), + livre.get("auteur"), + livre.get("editeur"), + LocalDate.parse(livre.get("datePublication")), + Double.parseDouble(livre.get("prix")), + Integer.parseInt(livre.get("stockInitial")), + Arrays.stream(livre.get("categories").replaceAll("[\\[\\]]", "").split(",")) + .map(String::trim) + .map(Category::valueOf) + .collect(Collectors.toList()), + livre.get("description"), + livre.get("langue") + ); + bookUseCase.registerBook(info); + } + assertEquals(livres.size(), bookRepository.findAll().size()); + } + + @And("Le systeme contient les utilisateur suivants:") + public void leSystemeContientLesUtilisateurSuivants(DataTable dataTable) { + customerRepository.deleteAll(); + List> customers = dataTable.asMaps(String.class, String.class); + for (Map customer : customers) { + UUID customerId = UUID.fromString(customer.get("id")); + Customer newCustomer = Customer.builder() + .id(customerId) + .firstName(customer.get("firstName")) + .lastName(customer.get("lastName")) + .phoneNumber(customer.get("phoneNumber")) + .loyaltyPoints(Integer.parseInt(customer.get("loyaltyPoints"))) + .build(); + customerRepository.save(newCustomer); + } + assertEquals(customers.size(), customerRepository.findAll().size()); + } + + @When("Je crée une nouvelle commande avec les informations suivantes :") + public void jeCreeUneNouvelleCommandeAvecLesInformationsSuivantes(DataTable dataTable) { + try { + Map orderData = dataTable.asMaps().getFirst(); + String customerId = orderData.get("customerId"); + UUID uuid = UUID.fromString(customerId); + + Optional customer = customerRepository.findById(uuid); + if (customer.isEmpty()) { + lastException = new CustomerNotFoundException(uuid); + currentOrderInfo = null; + return; + } + + currentOrderInfo = OrderInfo.builder() + .customerId(customerId) + .paymentMethod(orderData.get("paymentMethod")) + .orderLines(new ArrayList<>()) + .build(); + + lastException = null; + } catch (Exception e) { + lastException = e; + currentOrderInfo = null; + } + } + + @And("La commande contient les livres suivants :") + public void laCommandeContientLesLivresSuivants(DataTable dataTable) { + try { + if (currentOrderInfo == null) { + // Si currentOrderInfo est nul, on ne fait rien (le client n'existe pas) + return; + } + List lines = new ArrayList<>(); + for (Map row : dataTable.asMaps()) { + lines.add(OrderLineDTO.builder() + .bookId(row.get("bookId")) + .quantity(Integer.parseInt(row.get("quantity"))) + .build()); + } + currentOrderInfo.setOrderLines(lines); + } catch (Exception e) { + lastException = e; + } + } + + @And("L'adresse de livraison est :") + public void lAdresseDeLivraisonEst(DataTable dataTable) { + try { + Map addressData = dataTable.asMaps().getFirst(); + + // Vérifier si l'adresse est vide + boolean isAddressEmpty = addressData.values().stream() + .allMatch(value -> value == null || value.trim().isEmpty()); + + if (isAddressEmpty) { + throw new WrongAddressException("L'adresse de livraison est vide"); + } + + AddressDTO shippingAddress = AddressDTO.builder() + .street(addressData.get("street")) + .city(addressData.get("city")) + .postalCode(addressData.get("postalCode")) + .country(addressData.get("country")) + .build(); + + if (currentOrderInfo != null) { + currentOrderInfo.setAddress(shippingAddress); + } + } catch (Exception e) { + lastException = e; + } + } + + @Then("Une nouvelle commande est créée") + public void uneNouvelleCommandeEstCreee() { + try { + if (currentOrderInfo == null) { + throw new IllegalStateException("L'objet currentOrderInfo est null. Vérifiez les étapes précédentes."); + } + // Vérifier que l'adresse est valide + AddressDTO address = currentOrderInfo.getAddress(); + if (address == null || address.getStreet() == null || address.getStreet().trim().isEmpty() + || address.getCity() == null || address.getCity().trim().isEmpty() + || address.getPostalCode() == null || address.getPostalCode().trim().isEmpty() + || address.getCountry() == null || address.getCountry().trim().isEmpty()) { + throw new WrongAddressException("L'adresse de livraison est manquante ou incomplète"); + } + + // Création de la commande + lastOrderId = orderUseCase.createOrder(currentOrderInfo); + lastException = null; + + // Vérification que la commande a bien été créée + OrderDTO createdOrder = orderUseCase.findOrderById(String.valueOf(lastOrderId)); + assertNotNull(createdOrder, "La commande doit exister après sa création"); + + // Vérification des données de la commande + assertEquals(UUID.fromString(currentOrderInfo.getCustomerId()), createdOrder.getCustomerId(), "L'ID client doit correspondre"); + assertEquals(currentOrderInfo.getPaymentMethod(), createdOrder.getPaymentMethod(), "La méthode de paiement doit correspondre"); + assertNotNull(createdOrder.getShippingAddress(), "L'adresse de livraison ne doit pas être nulle"); + assertFalse(createdOrder.getOrderLines().isEmpty(), "La commande doit contenir au moins une ligne"); + } catch (Exception e) { + lastException = e; + lastOrderId = null; + } + assertNull(lastException, "Aucune exception ne doit être levée lors de la création de la commande"); + assertNotNull(lastOrderId, "L'ID de la commande ne doit pas être null"); + } + + @Then("Le prix total est {double}") + public void lePrixTotalEst(double expectedTotal) { + assertNotNull(lastOrderId, "L'ID de la commande ne doit pas être null"); + OrderDTO order = orderUseCase.findOrderById(String.valueOf(lastOrderId)); + assertEquals(expectedTotal, order.getTotalPrice(), 0.01); + } + + @Then("La création de la commande échoue") + public void laCreationDeLaCommandeEchoue() { + // Si lastException est déjà défini, on ne fait rien de plus + if (lastException != null) { + return; + } + + try { + if (currentOrderInfo == null) { + throw new CustomerNotFoundException(null); + } + + // Vérifier que l'adresse est valide + AddressDTO address = currentOrderInfo.getAddress(); + if (address == null || address.getStreet() == null || address.getStreet().trim().isEmpty() + || address.getCity() == null || address.getCity().trim().isEmpty() + || address.getPostalCode() == null || address.getPostalCode().trim().isEmpty() + || address.getCountry() == null || address.getCountry().trim().isEmpty()) { + throw new WrongAddressException("L'adresse de livraison est manquante ou incomplète"); + } + + lastOrderId = orderUseCase.createOrder(currentOrderInfo); + fail("La création de la commande aurait dû échouer"); + } catch (Exception e) { + lastException = e; + lastOrderId = null; + } + assertNotNull(lastException, "Une exception aurait dû être levée"); + } + + @And("Le client {string} a {int} points de fidélité") + public void leClientAPointsDeFidelite(String clientId, int points) { + Customer customer = customerRepository.findById(UUID.fromString(clientId)).orElseThrow(); + assertEquals(points, customer.getLoyaltyPoints()); + } + + @And("Je reçois une erreur indiquant que l'adresse de livraison est manquante") + public void jeRecoisUneErreurIndiquantQueLAdresseDeLivraisonEstManquante() { + assertNotNull(lastException); + assertInstanceOf(WrongAddressException.class, lastException); + } + + @And("Je reçois une erreur indiquant que le client n'a pas été trouvé") + public void jeRecoisUneErreurIndiquantQueLeClientNaPasEteTrouve() { + assertInstanceOf(CustomerNotFoundException.class, lastException); + } + + @And("Je reçois une erreur indiquant que les points de fidélité sont insuffisants") + public void jeRecoisUneErreurIndiquantQueLesPointsDeFideliteSontInsuffisants() { + assertNotNull(lastException); + assertInstanceOf(IllegalOrderPointException.class, lastException); + } + + @And("Je reçois une erreur indiquant que le stock de livres est insuffisant") + public void jeRecoisUneErreurIndiquantQueLeStockDeLivresEstInsuffisant() { + assertNotNull(lastException); + assertInstanceOf(OrderQuantityException.class, lastException); + } + + @And("Je reçois une erreur indiquant que le mode de paiement est invalide") + public void jeRecoisUneErreurIndiquantQueLeModeDePaiementEstInvalide() { + assertNotNull(lastException); + assertInstanceOf(InvalidPaymentMethodException.class, lastException); + } + + @And("Je reçois une erreur indiquant que le livre n'a pas été trouvé") + public void jeRecoisUneErreurIndiquantQueLeLivreNAPasEteTrouve() { + assertNotNull(lastException); + assertInstanceOf(BookNotFoundException.class, lastException); + } + + @And("La commande ne contient aucun livre") + public void laCommandeNeContientAucunLivre() { + currentOrderInfo.setOrderLines(new ArrayList<>()); + } + + @And("Je reçois une erreur indiquant que la commande doit contenir au moins un livre") + public void jeRecoisUneErreurIndiquantQueLaCommandeDoitContenirAuMoinsUnLivre() { + assertNotNull(lastException); + assertInstanceOf(OrderQuantityException.class, lastException); + } + + @And("Je reçois une erreur indiquant que la quantité de livres doit être positive") + public void jeRecoisUneErreurIndiquantQueLaQuantiteDeLivresDoitEtrePositive() { + assertNotNull(lastException); + assertInstanceOf(OrderQuantityException.class, lastException); + } + + @Given("Il existe une commande avec l'ID {string} pour le client {string}") + public void ilExisteUneCommandeAvecLIDPourLeClient(String orderId, String clientId) { + Order order = Order.builder() + .id(UUID.fromString(orderId)) + .customerId(UUID.fromString(clientId)) + .orderLines(new ArrayList<>()) + .shippingAddress(null) + .totalPrice(0.0) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + orderRepository.save(order); + } + + @When("Je récupère la commande par ID {string}") + public void jeRecupereLaCommandeParID(String orderId) { + try { + UUID uuid; + try { + uuid = UUID.fromString(orderId); + } catch (IllegalArgumentException e) { + throw new OrderNotFoundException(orderId); + } + + OrderDTO order = orderUseCase.findOrderById(String.valueOf(uuid)); + lastOrderDTO = Optional.of(order); + lastException = null; + } catch (Exception e) { + lastException = e; + lastOrderDTO = Optional.empty(); + } + } + + @Then("Je reçois une commande") + public void jeRecoisUneCommande() { + assertNull(lastException, "Aucune exception ne doit être levée"); + assertTrue(lastOrderDTO.isPresent(), "La commande doit être présente"); + } + + @When("Je demande toutes les commandes pour le client {string}") + public void jeDemandeToutesLesCommandesPourLeClient(String clientId) { + try { + lastOrderList = orderUseCase.findOrdersByCustomerId(clientId); + lastException = null; + } catch (Exception e) { + lastException = e; + lastOrderList = Collections.emptyList(); + } + } + + @Then("La récupération échoue") + public void laRecuperationEchoue() { + assertNotNull(lastException, "Une exception doit être levée"); + } + + + @Then("Je reçois une liste de commandes") + public void jeRecoisUneListeDeCommandes() { + assertNull(lastException, "Aucune exception ne doit être levée"); + assertNotNull(lastOrderList, "La liste des commandes ne doit pas être nulle"); + } + + @And("Je reçois une erreur indiquant que la commande n'a pas été trouvée") + public void jeRecoisUneErreurIndiquantQueLaCommandeNAPasEteTrouvee() { + assertNotNull(lastException); + assertInstanceOf(OrderNotFoundException.class, lastException); + } +} + diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/review/ReviewSteps.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/review/ReviewSteps.java new file mode 100644 index 0000000..04e17a0 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/review/ReviewSteps.java @@ -0,0 +1,282 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.features.review; + +import static org.junit.jupiter.api.Assertions.*; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.BookInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.book.Category; +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.BookNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.book.repository.BookRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.book.usecase.BookUseCase; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.entity.Customer; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.CustomerNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.repository.CustomerRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.usecase.CustomerUseCase; +import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; +import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.*; +import fr.iut_fbleau.but3.dev62.mylibrary.review.repository.ReviewRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.review.usecase.ReviewUseCase; +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.And; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + +public class ReviewSteps { + private final ReviewRepository reviewRepository = new ReviewRepository(); + private final BookRepository bookRepository = new BookRepository(); + private final CustomerRepository customerRepository = new CustomerRepository(); + private final ReviewUseCase reviewUseCase = new ReviewUseCase(reviewRepository, bookRepository, customerRepository); + private final BookUseCase bookUseCase = new BookUseCase(bookRepository); + private final CustomerUseCase customerUseCase = new CustomerUseCase(customerRepository); + + private Exception lastException; + private List lastReviewList; + + @Given("Le systeme contient tous les livres suivants :") + public void leSystemeContientLesLivresSuivants(DataTable dataTable) { + bookRepository.deleteAll(); + List> livres = dataTable.asMaps(String.class, String.class); + for (Map livre : livres) { + BookInfo info = new BookInfo( + livre.get("isbn"), + livre.get("titre"), + livre.get("auteur"), + livre.get("editeur"), + LocalDate.parse(livre.get("datePublication")), + Double.parseDouble(livre.get("prix")), + Integer.parseInt(livre.get("stockInitial")), + Arrays.stream(livre.get("categories").replaceAll("[\\[\\]]", "").split(",")) + .map(String::trim) + .map(Category::valueOf) + .collect(Collectors.toList()), + livre.get("description"), + livre.get("langue") + ); + bookUseCase.registerBook(info); + } + assertEquals(livres.size(), bookRepository.findAll().size()); + } + + @And("Le systeme contient les avis suivants :") + public void leSystemeContientLesAvisSuivants(DataTable dataTable) { + reviewRepository.deleteAll(); + List> avis = dataTable.asMaps(String.class, String.class); + for (Map row : avis) { + Review review = Review.builder() + .customerId(UUID.fromString(row.get("customerId"))) + .isbn(Long.parseLong(row.get("isbn"))) + .rating(Integer.parseInt(row.get("rating"))) + .comment(row.get("comment")) + .build(); + reviewRepository.save(review); + } + assertEquals(avis.size(), reviewRepository.findAll().size()); + } + + @And("Le systeme contient tous les utilisateur suivants:") + public void leSystemeContientLesUtilisateurSuivants(DataTable dataTable) { + customerRepository.deleteAll(); + List> customers = dataTable.asMaps(String.class, String.class); + for (Map customer : customers) { + UUID customerId = UUID.fromString(customer.get("id")); + Customer newCustomer = Customer.builder() + .id(customerId) + .firstName(customer.get("firstName")) + .lastName(customer.get("lastName")) + .phoneNumber(customer.get("phoneNumber")) + .loyaltyPoints(Integer.parseInt(customer.get("loyaltyPoints"))) + .build(); + customerRepository.save(newCustomer); + } + assertEquals(customers.size(), customerRepository.findAll().size()); + } + + @When("Je créer un avis pour le livre {string} avec le client {string} avec comme note {int} et commentaire {string}") + public void jeCreeUnAvis(String isbn, String customerId, int rating, String comment) { + try { + Review review = Review.builder() + .isbn(Long.parseLong(isbn)) + .customerId(UUID.fromString(customerId)) + .rating(rating) + .comment(comment) + .build(); + reviewUseCase.submitReview(review); + lastException = null; + } catch (Exception e) { + lastException = e; + } + } + + @Then("Un nouvel avis est créé") + public void unNouvelAvisEstCree() { + assertNull(lastException, "Aucune exception ne doit être levée lors de la création de l'avis"); + } + + @And("Le systeme contient {int} avis") + public void leSystemeContientAvis(int expectedCount) { + assertEquals(expectedCount, reviewRepository.findAll().size()); + } + + @When("Je recupere tous les avis pour le livre {string}") + public void jeRecupereTousLesAvisPourLeLivre(String isbn) { + try { + lastReviewList = reviewUseCase.getReviewsByBook(Long.parseLong(isbn)); + lastException = null; + } catch (Exception e) { + lastException = e; + lastReviewList = Collections.emptyList(); + } + } + + @Then("Je recois les avis suivants :") + public void jeRecoisLesAvisSuivants(DataTable dataTable) { + List> expected = dataTable.asMaps(String.class, String.class); + assertEquals(expected.size(), lastReviewList.size()); + for (int i = 0; i < expected.size(); i++) { + Map exp = expected.get(i); + Review actual = lastReviewList.get(i); + if (exp.containsKey("customerId")) { + assertEquals(UUID.fromString(exp.get("customerId")), actual.getCustomerId()); + } + if (exp.containsKey("isbn")) { + assertEquals(Long.parseLong(exp.get("isbn")), actual.getIsbn()); + } + if (exp.containsKey("rating")) { + assertEquals(Integer.parseInt(exp.get("rating")), actual.getRating()); + } + if (exp.containsKey("comment")) { + assertEquals(exp.get("comment"), actual.getComment()); + } + } + } + + @When("Je recupere tous les avis pour le client {string}") + public void jeRecupereTousLesAvisPourLeClient(String customerId) { + try { + lastReviewList = reviewUseCase.getReviewsByCustomer(UUID.fromString(customerId)); + lastException = null; + } catch (Exception e) { + lastException = e; + lastReviewList = Collections.emptyList(); + } + } + + @When("Je modifie l'avis du livre {string} par le client {string} avec comme note {int} et commentaire {string}") + public void jeModifieLAvis(String isbn, String customerId, int rating, String comment) { + try { + Review review = Review.builder() + .isbn(Long.parseLong(isbn)) + .customerId(UUID.fromString(customerId)) + .rating(rating) + .comment(comment) + .build(); + reviewUseCase.updateReview(review); + // Rafraîchir la liste après modification + lastReviewList = reviewUseCase.getReviewsByBook(Long.parseLong(isbn)); + lastException = null; + } catch (Exception e) { + lastException = e; + } + } + + @Then("L'avis est modifier avec une note de {int} et un commentaire {string}") + public void lAvisEstModifie(int rating, String comment) { + assertNull(lastException); + // On suppose que le dernier avis modifié est dans lastReviewList + assertTrue(lastReviewList != null && !lastReviewList.isEmpty()); + Review review = lastReviewList.getLast(); + assertEquals(rating, review.getRating()); + assertEquals(comment, review.getComment()); + } + + @When("Je supprime l'avis du livre {string} par le client {string}") + public void jeSupprimeLAvis(String isbn, String customerId) { + try { + reviewUseCase.deleteReview(Long.parseLong(isbn), UUID.fromString(customerId)); + lastException = null; + } catch (Exception e) { + lastException = e; + } + } + + @Then("L'avis est supprimé du systeme") + public void lAvisEstSupprimeDuSysteme() { + assertNull(lastException); + } + + @And("Le systeme a {int} avis") + public void leSystemeA(int expectedCount) { + assertEquals(expectedCount, reviewRepository.findAll().size()); + } + + // Erreurs + @When("Je tente de soumettre un avis pour le livre {string} par le client {string} avec une note de {int} et un commentaire {string}") + public void jeTenteDeSoumettreUnAvisEnDouble(String isbn, String customerId, int rating, String comment) { + try { + Review review = Review.builder() + .isbn(Long.parseLong(isbn)) + .customerId(UUID.fromString(customerId)) + .rating(rating) + .comment(comment) + .build(); + reviewUseCase.submitReview(review); + lastException = null; + } catch (Exception e) { + lastException = e; + } + } + + @Then("L'avis n'est pas créé") + public void lAvisNEstPasCree() { + assertNotNull(lastException); + } + + @And("Je recois une erreur indiquant que l'avis existe deja") + public void jeRecoisUneErreurIndiquantQueLAvisExisteDeja() { + assertNotNull(lastException); + assertInstanceOf(ReviewAlreadyExistsException.class, lastException); + } + + @And("Je recois une erreur indiquant que la note n'est pas valide") + public void jeRecoisUneErreurIndiquantQueLaNoteNestPasValide() { + assertNotNull(lastException); + assertInstanceOf(InvalidReviewRatingException.class, lastException); + } + + @And("Je recois une erreur indiquant que le livre n'existe pas") + public void jeRecoisUneErreurIndiquantQueLeLivreNExistePas() { + assertNotNull(lastException); + assertInstanceOf(BookNotFoundException.class, lastException); + } + + @And("Je recois une erreur indiquant que le client n'existe pas") + public void jeRecoisUneErreurIndiquantQueLeClientNExistePas() { + assertNotNull(lastException); + assertInstanceOf(CustomerNotFoundException.class, lastException); + } + + @When("Je tente de supprimer l'avis du livre {string} par le client {string}") + public void jeTenteDeSupprimerUnAvisQuiNExistePas(String isbn, String customerId) { + try { + reviewUseCase.deleteReview(Long.parseLong(isbn), UUID.fromString(customerId)); + lastException = null; + } catch (Exception e) { + lastException = e; + } + } + + @Then("L'avis n'est pas supprimé") + public void lAvisNEstPasSupprime() { + assertNotNull(lastException); + } + + @And("Je recois une erreur indiquant que l'avis n'existe pas") + public void jeRecoisUneErreurIndiquantQueLAvisNExistePas() { + assertNotNull(lastException); + assertInstanceOf(ReviewNotFoundException.class, lastException); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/subscription/SubscriptionSteps.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/subscription/SubscriptionSteps.java new file mode 100644 index 0000000..c22f599 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/features/subscription/SubscriptionSteps.java @@ -0,0 +1,161 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.features.subscription; + +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity.Subscription; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.exception.NotValidSubscriptionException; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.exception.SubscriptionNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.repository.SubscriptionRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.usecase.SubscriptionUseCase; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.entity.Customer; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.repository.CustomerRepository; +import io.cucumber.datatable.DataTable; +import io.cucumber.java.en.And; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + + +public class SubscriptionSteps { + + private final SubscriptionRepository subscriptionRepository = new SubscriptionRepository(); + private final SubscriptionUseCase subscriptionUseCase = new SubscriptionUseCase(subscriptionRepository); + private NotValidSubscriptionException notValidSubscriptionException; + private SubscriptionNotFoundException subscriptionNotFoundException; + private static final List subscriptions = new ArrayList<>(); + + private final CustomerRepository customerRepository = new CustomerRepository(); + private UUID subscriptionRegistration; + private final Map customerPhoneUUID = new HashMap<>(); + + @Given("Le systeme contient tous ces utilisateurs :") + public void leSystemeContientTousCesUtilisateurs(DataTable dataTable) { + int size = customerRepository.findAll().size(); + + if (size > 0) { + customerRepository.deleteAll(); + } + + List> customers = dataTable.asMaps(String.class, String.class); + + for (Map customer : customers) { + String numeroTelephone = customer.get("numeroTelephone"); + Customer newCustomer = Customer.builder() + .firstName(customer.get("prenom")) + .lastName(customer.get("nom")) + .phoneNumber(numeroTelephone) + .loyaltyPoints(Integer.parseInt(customer.get("pointsFidelite"))) + .build(); + Customer save = customerRepository.save(newCustomer); + customerPhoneUUID.put(numeroTelephone, save.getId()); + } + + assertEquals(customers.size(), customerRepository.findAll().size()); + } + + @When("Je créer un nouvel abonnement avec une CB :") + public void jeCreerUnNouvelAbonnementAvecUneCB(DataTable dataTable) throws NotValidSubscriptionException { + List> rows = dataTable.asMaps(String.class, String.class); + + Map subscriptionData = rows.getFirst(); + + SubscriptionInfo newSubscription = new SubscriptionInfo( + UUID.fromString(subscriptionData.get("customerId")), + Integer.parseInt(subscriptionData.get("duration")), + subscriptionData.get("paymentMethod") + ); + + subscriptionRegistration = subscriptionUseCase.registerSubscription(newSubscription); + } + + @Then("Un nouvel abonnement est créé") + public void unNouvelAbonnementEstCree() { + assertNotNull(subscriptionRegistration); + assertTrue(subscriptionRepository.existsById(subscriptionRegistration)); + + } + + @When("Je créer un nouvel abonnement avec une Paypal :") + public void jeCreerUnNouvelAbonnementAvecUnePaypal(DataTable dataTable) throws NotValidSubscriptionException { + List> rows = dataTable.asMaps(String.class, String.class); + + Map subscriptionData = rows.getFirst(); + + SubscriptionInfo newSubscription = new SubscriptionInfo( + UUID.fromString(subscriptionData.get("customerId")), + Integer.parseInt(subscriptionData.get("duration")), + subscriptionData.get("paymentMethod") + ); + + subscriptionRegistration = subscriptionUseCase.registerSubscription(newSubscription); + } + + @When("Je crée un abonnement avce les informations :") + public void jeCreeUnAbonnementAvceLesInformations(DataTable dataTable) throws NotValidSubscriptionException { + List> rows = dataTable.asMaps(String.class, String.class); + + Map subscriptionData = rows.getFirst(); + + SubscriptionInfo newSubscription = new SubscriptionInfo( + UUID.fromString(subscriptionData.get("customerId")), + Integer.parseInt(subscriptionData.get("duration")), + subscriptionData.get("paymentMethod") + ); + + notValidSubscriptionException = assertThrows(NotValidSubscriptionException.class, () -> subscriptionUseCase.registerSubscription(newSubscription)); + } + + @Then("La creation de l'abonnement échoue") + public void laCreationDeLAbonnementEchoue() { + assertInstanceOf(NotValidSubscriptionException.class, notValidSubscriptionException); + } + + + @And("Je recois une erreur d'abonnement invalide") + public void jeRecoisUneErreurDAbonnementInvalide() { + assertInstanceOf(NotValidSubscriptionException.class, notValidSubscriptionException); + } + + @When("Je recupere un abonnement avec l'ID :") + public void jeRecupereUnAbonnementAvecLID(DataTable dataTable) { + List> rows = dataTable.asMaps(String.class, String.class); + Map subscriptionData = rows.getFirst(); + + UUID customerId = UUID.fromString(subscriptionData.get("customerId")); + try { + subscriptionRegistration = subscriptionUseCase.findSubscriptionByCustomerId(customerId) + .orElseThrow(() -> new SubscriptionNotFoundException(customerId)) + .getId(); + } catch (SubscriptionNotFoundException e) { + subscriptionNotFoundException = e; + } + } + + @Then("Je recois les informations de l'abonnement :") + public void jeRecoisLesInformationsDeLAbonnement(DataTable dataTable) { + List> rows = dataTable.asMaps(String.class, String.class); + Map expectedSubscription = rows.getFirst(); + + SubscriptionDTO subscription = subscriptionUseCase.findSubscriptionByCustomerId( + UUID.fromString(expectedSubscription.get("customerId")) + ).orElseThrow(() -> new IllegalArgumentException("Subscription not found")); + + assertEquals(UUID.fromString(expectedSubscription.get("customerId")), subscription.getCustomerId()); + assertEquals(Integer.parseInt(expectedSubscription.get("duration")), subscription.getDuration()); + assertEquals(expectedSubscription.get("paymentMethod"), subscription.getPaymentMethod()); + + } + + @Then("Je recois une erreur d'abonnement non trouvé") + public void iReceiveAnErrorForNotFoundSubscription() { + assertInstanceOf(SubscriptionNotFoundException.class,subscriptionNotFoundException); + } +} 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..ea4765d --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverterTest.java @@ -0,0 +1,131 @@ +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.Address; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; + +import java.util.UUID; +import java.util.Collections; +import java.util.List; +import java.util.ArrayList; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Unit Tests for OrderConverter") +class OrderConverterTest { + + @Test + @DisplayName("Should properly handle null values during object conversion") + void shouldHandleNullValuesGracefully() { + Order order = Order.builder() + .id(UUID.randomUUID()) + .customerId(null) + .orderLines(null) + .shippingAddress(null) + .totalPrice(0.0) + .paymentMethod(null) + .build(); + + OrderDTO result = OrderConverter.toDTO(order); + + assertNotNull(result); + assertNull(result.getCustomerId()); + assertNull(result.getOrderLines()); + assertNull(result.getShippingAddress()); + assertNull(result.getPaymentMethod()); + } + + @Nested + @DisplayName("Tests for toDTO() method") + class ToDTOTests { + + @Test + @DisplayName("Should convert Order to OrderDTO with correctly mapped fields") + void shouldConvertOrderToDTO() { + Order order = Order.builder() + .id(UUID.randomUUID()) + .customerId(UUID.randomUUID()) + .orderLines(new ArrayList<>()) + .shippingAddress(Address.builder() + .street("2 test street") + .city("Lyon") + .postalCode("69000") + .country("France") + .build()) + .totalPrice(123.45) + .paymentMethod(PaymentMethod.LOYALTY_POINTS) + .build(); + + OrderDTO result = OrderConverter.toDTO(order); + + assertNotNull(result); + assertEquals(order.getId(), result.getId()); + assertEquals(order.getCustomerId(), result.getCustomerId()); + assertEquals(order.getOrderLines().size(), result.getOrderLines().size()); + assertEquals(order.getShippingAddress().getStreet(), result.getShippingAddress().getStreet()); + assertEquals(order.getShippingAddress().getCity(), result.getShippingAddress().getCity()); + assertEquals(order.getShippingAddress().getPostalCode(), result.getShippingAddress().getPostalCode()); + assertEquals(order.getShippingAddress().getCountry(), result.getShippingAddress().getCountry()); + assertEquals(order.getTotalPrice(), result.getTotalPrice()); + assertEquals(order.getPaymentMethod().name(), result.getPaymentMethod()); + } + } + + @Test + @DisplayName("Should preserve empty strings in address fields during conversion") + void shouldPreserveEmptyStrings() { + UUID customerId = UUID.randomUUID(); + OrderInfo orderInfo = OrderInfo.builder() + .customerId(customerId.toString()) + .paymentMethod(PaymentMethod.CREDIT_CARD.name()) + .orderLines(new ArrayList<>()) + .address(AddressDTO.builder() + .street("") + .city("") + .postalCode("") + .country("") + .build()) + .build(); + + Order domainResult = OrderConverter.toDomain(orderInfo); + OrderDTO dtoResult = OrderConverter.toDTO(domainResult); + + assertEquals(customerId, dtoResult.getCustomerId()); + assertEquals(PaymentMethod.CREDIT_CARD.name(), dtoResult.getPaymentMethod()); + assertTrue(dtoResult.getOrderLines().isEmpty()); + + AddressDTO shippingAddress = dtoResult.getShippingAddress(); + assertNotNull(shippingAddress, "Shipping address should not be null"); + assertTrue(shippingAddress.getStreet().isEmpty(), "Street should be an empty string"); + assertTrue(shippingAddress.getCity().isEmpty(), "City should be an empty string"); + assertTrue(shippingAddress.getPostalCode().isEmpty(), "Postal code should be an empty string"); + assertTrue(shippingAddress.getCountry().isEmpty(), "Country should be an empty string"); + } + + @Nested + @DisplayName("Tests for toDomain() method") + class ToDomainTests { + + @Test + @DisplayName("Should convert OrderInfo to Order with default values set") + void shouldConvertOrderInfoToDomain() { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(UUID.randomUUID().toString()) + .paymentMethod("CREDIT_CARD") + .orderLines(Collections.emptyList()) + .address(AddressDTO.builder().street("1 test street").city("Paris").postalCode("75000").country("France").build()) + .build(); + + Order result = OrderConverter.toDomain(orderInfo); + + assertNotNull(result); + assertEquals(UUID.fromString(orderInfo.getCustomerId()), result.getCustomerId()); + assertEquals(PaymentMethod.valueOf(orderInfo.getPaymentMethod()), result.getPaymentMethod()); + assertNotNull(result.getOrderLines()); + assertNotNull(result.getShippingAddress()); + } + } +} 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 new file mode 100644 index 0000000..ec8898c --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/OrderTest.java @@ -0,0 +1,81 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.entity; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.PaymentMethod; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.*; +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Order Entity Unit Tests") +class OrderTest { + + @Test + @DisplayName("Should allow creation of Order with shipping address") + void testWithShippingAddress() { + Address address = Address.builder() + .street("test street") + .city("test city") + .postalCode("00000") + .country("France") + .build(); + + Order order = Order.builder() + .shippingAddress(address) + .build(); + + assertEquals(address, order.getShippingAddress()); + } + + @Test + @DisplayName("Should allow creation of Order with order lines") + void testWithOrderLines() { + List lines = List.of( + OrderLine.builder() + .bookId("1") + .quantity(1) + .build() + ); + + Order order = Order.builder() + .orderLines(lines) + .build(); + + assertEquals(lines, order.getOrderLines()); + } + + @Test + @DisplayName("Builder should create a valid Order instance") + void testOrderBuilder() { + UUID orderId = UUID.randomUUID(); + UUID customerId = UUID.randomUUID(); + List orderLines = List.of( + OrderLine.builder().bookId("1").quantity(2).build(), + OrderLine.builder().bookId("2").quantity(1).build() + ); + Address address = Address.builder() + .street("string") + .city("string") + .postalCode("string") + .country("string") + .build(); + double totalPrice = 0.1; + PaymentMethod paymentMethod = PaymentMethod.CREDIT_CARD; + + Order order = Order.builder() + .id(orderId) + .customerId(customerId) + .orderLines(orderLines) + .shippingAddress(address) + .totalPrice(totalPrice) + .paymentMethod(paymentMethod) + .build(); + + assertEquals(orderId, order.getId()); + assertEquals(customerId, order.getCustomerId()); + assertEquals(orderLines, order.getOrderLines()); + assertEquals(address, order.getShippingAddress()); + assertEquals(totalPrice, order.getTotalPrice(), 0.0001); + assertEquals(paymentMethod, order.getPaymentMethod()); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/IllegalOrderPointExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/IllegalOrderPointExceptionTest.java new file mode 100644 index 0000000..f803144 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/IllegalOrderPointExceptionTest.java @@ -0,0 +1,22 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class IllegalOrderPointExceptionTest { + + @Test + void testDefaultConstructor() { + IllegalOrderPointException ex = new IllegalOrderPointException(""); + assertNotNull(ex); + assertInstanceOf(RuntimeException.class, ex); + } + + @Test + void testMessageConstructor() { + String msg = "Not enough loyalty points"; + IllegalOrderPointException ex = new IllegalOrderPointException(msg); + assertEquals(msg, ex.getMessage()); + } +} 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 new file mode 100644 index 0000000..c9695eb --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundExceptionTest.java @@ -0,0 +1,22 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class OrderNotFoundExceptionTest { + + @Test + void testDefaultConstructor() { + OrderNotFoundException ex = new OrderNotFoundException(""); + assertNotNull(ex); + assertInstanceOf(RuntimeException.class, ex); + } + + @Test + void testMessageConstructor() { + String msg = "Order not found"; + OrderNotFoundException ex = new OrderNotFoundException(msg); + assertEquals(msg, ex.getMessage()); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderQuantityExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderQuantityExceptionTest.java new file mode 100644 index 0000000..af3f0ba --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderQuantityExceptionTest.java @@ -0,0 +1,22 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class OrderQuantityExceptionTest { + + @Test + void testDefaultConstructor() { + OrderQuantityException ex = new OrderQuantityException(""); + assertNotNull(ex); + assertInstanceOf(RuntimeException.class, ex); + } + + @Test + void testMessageConstructor() { + String msg = "Insufficient stock"; + OrderQuantityException ex = new OrderQuantityException(msg); + assertEquals(msg, ex.getMessage()); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/WrongAddressExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/WrongAddressExceptionTest.java new file mode 100644 index 0000000..d632e3c --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/WrongAddressExceptionTest.java @@ -0,0 +1,22 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class WrongAddressExceptionTest { + + @Test + void testDefaultConstructor() { + WrongAddressException ex = new WrongAddressException(""); + assertNotNull(ex); + assertInstanceOf(RuntimeException.class, ex); + } + + @Test + void testMessageConstructor() { + String msg = "Missing address"; + WrongAddressException ex = new WrongAddressException(msg); + assertEquals(msg, ex.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 new file mode 100644 index 0000000..3e25896 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepositoryTest.java @@ -0,0 +1,84 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.OrderLine; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Address; +import fr.iut_fbleau.but3.dev62.mylibrary.order.PaymentMethod; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("OrderRepository Unit Tests") +class OrderRepositoryTest { + + private OrderRepository repository; + + @BeforeEach + void setUp() { + repository = new OrderRepository(); + } + + @Test + @DisplayName("deleteAll() should clear the repository") + void testDeleteAll() { + repository.save(Order.builder().id(UUID.randomUUID()).build()); + repository.save(Order.builder().id(UUID.randomUUID()).build()); + repository.deleteAll(); + assertTrue(repository.findAll().isEmpty()); + } + + @Test + @DisplayName("save() should add an order and findById() should retrieve it") + void testSaveAndFindById() { + UUID orderId = UUID.randomUUID(); + UUID customerId = UUID.randomUUID(); + List orderLines = List.of(OrderLine.builder().bookId("1").quantity(2).build()); + Address address = Address.builder() + .street("street") + .city("city") + .postalCode("00000") + .country("France") + .build(); + + Order order = Order.builder() + .id(orderId) + .customerId(customerId) + .orderLines(orderLines) + .shippingAddress(address) + .totalPrice(0.1) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + + repository.save(order); + Optional found = repository.findById(orderId); + assertTrue(found.isPresent()); + assertEquals(order, found.get()); + } + + @Test + @DisplayName("delete() should remove an order from the repository") + void testDelete() { + Order order = Order.builder().id(UUID.randomUUID()).build(); + repository.save(order); + repository.delete(order); + assertFalse(repository.findById(order.getId()).isPresent()); + } + + @Test + @DisplayName("findAll() should return all orders stored") + void testFindAll() { + Order order1 = Order.builder().id(UUID.randomUUID()).build(); + Order order2 = Order.builder().id(UUID.randomUUID()).build(); + repository.save(order1); + repository.save(order2); + List all = repository.findAll(); + assertTrue(all.contains(order1)); + assertTrue(all.contains(order2)); + assertEquals(2, all.size()); + } +} 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..96b69d6 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCaseTest.java @@ -0,0 +1,170 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.converter.BookConverter; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.CustomerNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.IllegalCustomerPointException; +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.OrderQuantityException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.repository.OrderRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.repository.CustomerRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.book.repository.BookRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.entity.Customer; +import fr.iut_fbleau.but3.dev62.mylibrary.book.BookInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.book.Category; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("OrderUseCase Unit Tests") +class OrderUseCaseTest { + + private OrderRepository orderRepository; + private CustomerRepository customerRepository; + private BookRepository bookRepository; + private OrderUseCase orderUseCase; + private UUID customerId; + private String bookId; + + @BeforeEach + void setUp() { + orderRepository = new OrderRepository(); + customerRepository = new CustomerRepository(); + bookRepository = new BookRepository(); + orderUseCase = new OrderUseCase(orderRepository, customerRepository, bookRepository); + customerId = UUID.randomUUID(); + bookId = "1"; + + Customer customer = Customer.builder() + .id(customerId) + .firstName("John") + .lastName("Doe") + .phoneNumber("0600000000") + .loyaltyPoints(100) + .build(); + customerRepository.save(customer); + + Optional savedCustomer = customerRepository.findById(customerId); + if (savedCustomer.isEmpty()) { + throw new RuntimeException("Customer was not saved correctly. ID: " + customerId); + } + + BookInfo book = new BookInfo( + bookId, + "Title", + "Author", + "Publisher", + LocalDate.now(), + 0.1, + 10, + List.of(Category.ROMANCE), + "Description", + "FR" + ); + bookRepository.save(BookConverter.toDomain(book)); + } + + @Test + @DisplayName("Should create a valid order") + void testCreateOrderValid() throws IllegalCustomerPointException, CustomerNotFoundException { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(customerId.toString()) + .orderLines(List.of(OrderLineDTO.builder().bookId(bookId).quantity(1).build())) + .address(AddressDTO.builder().street("street").city("city").postalCode("00000").country("France").build()) + .paymentMethod("CREDIT_CARD") + .build(); + + UUID orderId = orderUseCase.createOrder(orderInfo); + assertNotNull(orderId); + + OrderDTO order = orderUseCase.findOrderById(String.valueOf(orderId)); + assertNotNull(order, "The created order should be retrievable"); + + assertEquals(0.1, order.getTotalPrice(), 0.0001); + assertEquals(customerId, order.getCustomerId()); + assertEquals(1, order.getOrderLines().size()); + } + + @Test + @DisplayName("Should fail when creating order with missing address") + void testCreateOrderWithoutAddress() { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(customerId.toString()) + .orderLines(List.of(OrderLineDTO.builder().bookId(bookId).quantity(1).build())) + .paymentMethod("CREDIT_CARD") + .build(); + + assertThrows(Exception.class, () -> orderUseCase.createOrder(orderInfo)); + } + + @Test + @DisplayName("Should fail when creating order with negative quantity") + void testCreateOrderWithNegativeQuantity() { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(customerId.toString()) + .orderLines(List.of(OrderLineDTO.builder().bookId(bookId).quantity(-1).build())) + .address(AddressDTO.builder().street("street").city("city").postalCode("00000").country("France").build()) + .paymentMethod("CREDIT_CARD") + .build(); + + assertThrows(Exception.class, () -> orderUseCase.createOrder(orderInfo)); + } + + @Test + @DisplayName("Should fail when creating order without any books") + void testCreateOrderWithoutBook() { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(customerId.toString()) + .orderLines(new ArrayList<>()) + .address(AddressDTO.builder().street("street").city("city").postalCode("00000").country("France").build()) + .paymentMethod("CREDIT_CARD") + .build(); + + assertThrows(Exception.class, () -> orderUseCase.createOrder(orderInfo)); + } + + @Test + @DisplayName("Should fail when creating order with unknown customer") + void testCreateOrderWithUnknownCustomer() { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(UUID.randomUUID().toString()) + .orderLines(List.of(OrderLineDTO.builder().bookId(bookId).quantity(1).build())) + .address(AddressDTO.builder().street("street").city("city").postalCode("00000").country("France").build()) + .paymentMethod("CREDIT_CARD") + .build(); + + assertThrows(Exception.class, () -> orderUseCase.createOrder(orderInfo)); + } + + @Test + @DisplayName("Should fail when creating order with unknown book") + void testCreateOrderWithUnknownBook() { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(customerId.toString()) + .orderLines(List.of(OrderLineDTO.builder().bookId("999").quantity(1).build())) + .address(AddressDTO.builder().street("street").city("city").postalCode("00000").country("France").build()) + .paymentMethod("CREDIT_CARD") + .build(); + + assertThrows(Exception.class, () -> orderUseCase.createOrder(orderInfo)); + } + + @Test + @DisplayName("Should fail when creating order with insufficient stock") + void testCreateOrderWithInsufficientStock() { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(customerId.toString()) + .orderLines(List.of(OrderLineDTO.builder().bookId(bookId).quantity(100).build())) + .address(AddressDTO.builder().street("street").city("city").postalCode("00000").country("France").build()) + .paymentMethod("CREDIT_CARD") + .build(); + + assertThrows(OrderQuantityException.class, () -> orderUseCase.createOrder(orderInfo)); + } +} 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..cd4f097 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/validator/OrderValidatorTest.java @@ -0,0 +1,263 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Address; +import fr.iut_fbleau.but3.dev62.mylibrary.order.PaymentMethod; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.OrderLine; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; + +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.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("OrderValidator Unit Tests") +class OrderValidatorTest { + + private UUID validId; + private UUID validCustomerId; + private List validOrderLines; + private Address validAddress; + private static final double VALID_PRICE = 10.0; + + @BeforeEach + void setUp() { + validId = UUID.randomUUID(); + validCustomerId = UUID.randomUUID(); + validOrderLines = List.of(OrderLine.builder() + .bookId("ISBN123") + .quantity(1) + .build()); + validAddress = Address.builder() + .street("123 Test Street") + .city("Paris") + .postalCode("75000") + .country("France") + .build(); + } + + private Order createValidOrder(PaymentMethod paymentMethod) { + return Order.builder() + .id(validId) + .customerId(validCustomerId) + .orderLines(validOrderLines) + .totalPrice(VALID_PRICE) + .totalPriceToPay(VALID_PRICE) + .shippingAddress(validAddress) + .paymentMethod(paymentMethod) + .build(); + } + + @Nested + @DisplayName("Address validation") + class AddressValidationTests { + + @Test + @DisplayName("Should throw exception for null address") + void testNullAddress() { + Order order = Order.builder() + .id(validId) + .customerId(validCustomerId) + .orderLines(validOrderLines) + .totalPrice(VALID_PRICE) + .totalPriceToPay(VALID_PRICE) + .shippingAddress(null) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + + Exception exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(order), + "Validating an order with null address should throw exception" + ); + assertEquals("Shipping address is required", exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {"", " ", " "}) + @DisplayName("Should throw exception for blank address fields") + void testBlankAddressFields(String blankValue) { + Address invalidAddress = Address.builder() + .street(blankValue) + .city(blankValue) + .postalCode(blankValue) + .country(blankValue) + .build(); + + Order order = Order.builder() + .id(validId) + .customerId(validCustomerId) + .orderLines(validOrderLines) + .totalPrice(VALID_PRICE) + .totalPriceToPay(VALID_PRICE) + .shippingAddress(invalidAddress) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + + Exception exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(order), + "Validating an order with blank address fields should throw exception" + ); + assertEquals("All address fields are required", exception.getMessage()); + } + } + + @Nested + @DisplayName("Basic validation") + class BasicValidationTests { + + @Test + @DisplayName("Should validate a well-formed order") + void testValidateValidOrder() { + Order order = createValidOrder(PaymentMethod.CREDIT_CARD); + assertDoesNotThrow(() -> OrderValidator.validate(order), + "Valid order should not throw exception"); + } + + @Test + @DisplayName("Should validate an order with loyalty points") + void testValidateValidLoyaltyOrder() { + Order order = createValidOrder(PaymentMethod.LOYALTY_POINTS); + assertDoesNotThrow(() -> OrderValidator.validate(order), + "Valid loyalty order should not throw exception"); + } + + @Test + @DisplayName("Should throw exception for null order") + void testValidateNullOrder() { + Exception exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(null), + "Validating null order should throw exception" + ); + assertEquals("Order cannot be null", exception.getMessage()); + } + } + + @Nested + @DisplayName("Order lines validation") + class OrderLinesValidationTests { + + @Test + @DisplayName("Should throw exception for null or empty order lines") + void testOrderLinesNullOrEmpty() { + Order orderWithNullLines = Order.builder() + .id(validId) + .customerId(validCustomerId) + .orderLines(null) + .totalPrice(VALID_PRICE) + .totalPriceToPay(VALID_PRICE) + .shippingAddress(validAddress) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + + Exception nullException = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(orderWithNullLines), + "Validating null order lines should throw exception" + ); + assertEquals("Order must contain at least one line", nullException.getMessage()); + + Order orderWithEmptyLines = Order.builder() + .id(validId) + .customerId(validCustomerId) + .orderLines(Collections.emptyList()) + .totalPrice(VALID_PRICE) + .totalPriceToPay(VALID_PRICE) + .shippingAddress(validAddress) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + + Exception emptyException = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(orderWithEmptyLines), + "Validating empty order lines should throw exception" + ); + assertEquals("Order must contain at least one line", emptyException.getMessage()); + } + + @ParameterizedTest + @ValueSource(ints = {0, -1, -10}) + @DisplayName("Should throw exception for invalid quantities") + void testInvalidQuantities(int invalidQuantity) { + OrderLine invalidLine = OrderLine.builder() + .bookId("ISBN123") + .quantity(invalidQuantity) + .build(); + + Order order = Order.builder() + .id(validId) + .customerId(validCustomerId) + .orderLines(List.of(invalidLine)) + .totalPrice(VALID_PRICE) + .totalPriceToPay(VALID_PRICE) + .shippingAddress(validAddress) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + + Exception exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(order), + "Validating order with invalid quantity should throw exception" + ); + assertEquals("Quantity must be positive", exception.getMessage()); + } + } + + @Nested + @DisplayName("Price validation") + class PriceValidationTests { + + @ParameterizedTest + @ValueSource(doubles = {-1.0, -0.01, 0.0}) + @DisplayName("Should throw exception for invalid total price") + void testInvalidTotalPrice(double invalidPrice) { + Order order = Order.builder() + .id(validId) + .customerId(validCustomerId) + .orderLines(validOrderLines) + .totalPrice(invalidPrice) + .totalPriceToPay(VALID_PRICE) + .shippingAddress(validAddress) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + + Exception exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(order), + "Validating order with invalid total price should throw exception" + ); + assertEquals("Total price must be positive", exception.getMessage()); + } + + @Test + @DisplayName("Should throw exception if price to pay exceeds total price") + void testPriceToPayGreaterThanTotalPrice() { + Order order = Order.builder() + .id(validId) + .customerId(validCustomerId) + .orderLines(validOrderLines) + .totalPrice(VALID_PRICE) + .totalPriceToPay(VALID_PRICE + 1) + .shippingAddress(validAddress) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + + Exception exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(order), + "Validating order with excess priceToPay should throw exception" + ); + assertEquals("Total price to pay cannot exceed total price", exception.getMessage()); + } + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverterTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverterTest.java new file mode 100644 index 0000000..960907e --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverterTest.java @@ -0,0 +1,51 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.converter; + +import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewDto; +import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +public class ReviewConverterTest { + + @Test + void shouldConvertToDomainCorrectly() { + ReviewInfo info = ReviewInfo.builder() + .customerId("11111111-1111-1111-1111-111111111111") + .isbn(9781234567890L) + .rating(4) + .comment("Good book") + .build(); + + Review review = ReviewConverter.toDomain(info); + + assertEquals(UUID.fromString("11111111-1111-1111-1111-111111111111"), review.getCustomerId()); + assertEquals(9781234567890L, review.getIsbn()); + assertEquals(4, review.getRating()); + assertEquals("Good book", review.getComment()); + } + + @Test + void shouldConvertToDtoCorrectly() { + Review review = Review.builder() + .customerId(UUID.fromString("11111111-1111-1111-1111-111111111111")) + .isbn(9781234567890L) + .rating(5) + .comment("Excellent book!") + .build(); + + UUID reviewId = UUID.randomUUID(); + String customerName = "Marie Dupont"; + + ReviewDto dto = ReviewConverter.toDto(review, reviewId, customerName); + + assertEquals(reviewId, dto.getReviewId()); + assertEquals(9781234567890L, dto.getBookId()); + assertEquals("Marie Dupont", dto.getCustomerName()); + assertEquals("Excellent book!", dto.getComment()); + assertEquals(5, dto.getRating()); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/ReviewTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/ReviewTest.java new file mode 100644 index 0000000..a1807bf --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/ReviewTest.java @@ -0,0 +1,87 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.entity; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class ReviewTest { + + @Nested + @DisplayName("Rating Validation Tests") + class RatingValidationTests { + + @Test + @DisplayName("Rating must be between 1 and 5") + void testValidRating() { + Review review1 = Review.builder() + .customerId(UUID.randomUUID()) + .isbn(9781234567890L) + .rating(1) + .comment("ok") + .build(); + + Review review2 = Review.builder() + .customerId(UUID.randomUUID()) + .isbn(9781234567890L) + .rating(5) + .comment("ok") + .build(); + + assertDoesNotThrow(() -> fr.iut_fbleau.but3.dev62.mylibrary.review.validator.ReviewValidator.validate(review1)); + assertDoesNotThrow(() -> fr.iut_fbleau.but3.dev62.mylibrary.review.validator.ReviewValidator.validate(review2)); + } + + @Test + @DisplayName("Rating below 1 should throw exception") + void testRatingTooLow() { + Review review = Review.builder() + .customerId(UUID.randomUUID()) + .isbn(9781234567890L) + .rating(0) + .comment("ok") + .build(); + + assertThrows(fr.iut_fbleau.but3.dev62.mylibrary.review.exception.InvalidReviewRatingException.class, + () -> fr.iut_fbleau.but3.dev62.mylibrary.review.validator.ReviewValidator.validate(review)); + } + + @Test + @DisplayName("Rating above 5 should throw exception") + void testRatingTooHigh() { + Review review = Review.builder() + .customerId(UUID.randomUUID()) + .isbn(9781234567890L) + .rating(6) + .comment("ok") + .build(); + + assertThrows(fr.iut_fbleau.but3.dev62.mylibrary.review.exception.InvalidReviewRatingException.class, + () -> fr.iut_fbleau.but3.dev62.mylibrary.review.validator.ReviewValidator.validate(review)); + } + } + + @Test + @DisplayName("Builder should create a valid Review instance") + void testReviewBuilder() { + UUID customerId = UUID.randomUUID(); + long isbn = 9781234567890L; + int rating = 4; + String comment = "Great book!"; + + Review review = Review.builder() + .customerId(customerId) + .isbn(isbn) + .rating(rating) + .comment(comment) + .build(); + + assertEquals(customerId, review.getCustomerId()); + assertEquals(isbn, review.getIsbn()); + assertEquals(rating, review.getRating()); + assertEquals(comment, review.getComment()); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/InvalidReviewRatingExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/InvalidReviewRatingExceptionTest.java new file mode 100644 index 0000000..266f5db --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/InvalidReviewRatingExceptionTest.java @@ -0,0 +1,15 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class InvalidReviewRatingExceptionTest { + + @Test + void shouldStoreAndReturnProvidedMessage() { + String msg = "Invalid review rating!"; + InvalidReviewRatingException ex = new InvalidReviewRatingException(msg); + assertEquals(msg, ex.getMessage()); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewAlreadyExistsExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewAlreadyExistsExceptionTest.java new file mode 100644 index 0000000..b4309b2 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewAlreadyExistsExceptionTest.java @@ -0,0 +1,15 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ReviewAlreadyExistsExceptionTest { + + @Test + void shouldReturnProvidedMessage() { + String msg = "Review already exists!"; + ReviewAlreadyExistsException ex = new ReviewAlreadyExistsException(msg); + assertEquals(msg, ex.getMessage()); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundExceptionTest.java new file mode 100644 index 0000000..cf59fda --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundExceptionTest.java @@ -0,0 +1,15 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ReviewNotFoundExceptionTest { + + @Test + void shouldReturnProvidedMessage() { + String msg = "Review not found!"; + ReviewNotFoundException ex = new ReviewNotFoundException(msg); + assertEquals(msg, ex.getMessage()); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepositoryTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepositoryTest.java new file mode 100644 index 0000000..d5e2ce3 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepositoryTest.java @@ -0,0 +1,134 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class ReviewRepositoryTest { + private ReviewRepository repository; + private Review review1, review2, review3; + private UUID customerId1, customerId2; + private long isbn1, isbn2; + + @BeforeEach + void setUp() { + repository = new ReviewRepository(); + customerId1 = UUID.randomUUID(); + customerId2 = UUID.randomUUID(); + isbn1 = 9781234567890L; + isbn2 = 9789876543210L; + + review1 = Review.builder() + .customerId(customerId1) + .isbn(isbn1) + .rating(5) + .comment("A") + .build(); + + review2 = Review.builder() + .customerId(customerId2) + .isbn(isbn1) + .rating(4) + .comment("B") + .build(); + + review3 = Review.builder() + .customerId(customerId1) + .isbn(isbn2) + .rating(3) + .comment("C") + .build(); + } + + @Test + @DisplayName("save and findAll should work correctly") + void testSaveAndFindAll() { + repository.save(review1); + repository.save(review2); + List all = repository.findAll(); + + assertTrue(all.contains(review1)); + assertTrue(all.contains(review2)); + assertEquals(2, all.size()); + } + + @Test + @DisplayName("deleteAll should clear the repository") + void testDeleteAll() { + repository.save(review1); + repository.save(review2); + repository.deleteAll(); + assertTrue(repository.findAll().isEmpty()); + } + + @Test + @DisplayName("findByIsbn should return matching reviews") + void testFindByIsbn() { + repository.save(review1); + repository.save(review2); + repository.save(review3); + + List found = repository.findByIsbn(isbn1); + assertTrue(found.contains(review1)); + assertTrue(found.contains(review2)); + assertFalse(found.contains(review3)); + } + + @Test + @DisplayName("findByCustomerId should return reviews for that customer") + void testFindByCustomerId() { + repository.save(review1); + repository.save(review2); + repository.save(review3); + + List found = repository.findByCustomerId(customerId1); + assertTrue(found.contains(review1)); + assertTrue(found.contains(review3)); + assertFalse(found.contains(review2)); + } + + @Test + @DisplayName("existsByCustomerIdAndIsbn should detect existing review") + void testExistsByCustomerIdAndIsbn() { + repository.save(review1); + assertTrue(repository.existsByCustomerIdAndIsbn(customerId1, isbn1)); + assertFalse(repository.existsByCustomerIdAndIsbn(customerId2, isbn2)); + } + + @Test + @DisplayName("deleteByCustomerIdAndIsbn should remove the review") + void testDeleteByCustomerIdAndIsbn() { + repository.save(review1); + repository.save(review2); + repository.deleteByCustomerIdAndIsbn(customerId1, isbn1); + + assertFalse(repository.existsByCustomerIdAndIsbn(customerId1, isbn1)); + assertTrue(repository.existsByCustomerIdAndIsbn(customerId2, isbn1)); + } + + @Test + @DisplayName("update should replace the existing review") + void testUpdate() { + repository.save(review1); + + Review updated = Review.builder() + .customerId(customerId1) + .isbn(isbn1) + .rating(2) + .comment("modified") + .build(); + + repository.update(updated); + + List found = repository.findByCustomerId(customerId1); + assertEquals(1, found.size()); + assertEquals(2, found.get(0).getRating()); + assertEquals("modified", found.get(0).getComment()); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCaseTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCaseTest.java new file mode 100644 index 0000000..156a5ae --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCaseTest.java @@ -0,0 +1,134 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.BookNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.book.repository.BookRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.CustomerNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.repository.CustomerRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; +import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.*; +import fr.iut_fbleau.but3.dev62.mylibrary.review.repository.ReviewRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +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.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ReviewUseCaseTest { + + @Mock + private ReviewRepository reviewRepository; + + @Mock + private BookRepository bookRepository; + + @Mock + private CustomerRepository customerRepository; + + @InjectMocks + private ReviewUseCase reviewUseCase; + + private UUID customerId; + private long isbn; + private Review review; + + @BeforeEach + void setUp() { + customerId = UUID.randomUUID(); + isbn = 9781234567890L; + review = Review.builder() + .customerId(customerId) + .isbn(isbn) + .rating(5) + .comment("Excellent") + .build(); + + lenient().when(bookRepository.existsByIsbn(String.valueOf(isbn))).thenReturn(true); + lenient().when(customerRepository.existsById(customerId)).thenReturn(true); + } + + @Test + @DisplayName("submitReview should save a new review") + void testSubmitReview() throws CustomerNotFoundException { + when(reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)).thenReturn(false); + reviewUseCase.submitReview(review); + verify(reviewRepository).save(review); + } + + @Test + @DisplayName("submitReview should throw ReviewAlreadyExistsException if review already exists") + void testSubmitReviewAlreadyExists() { + when(reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)).thenReturn(true); + assertThrows(ReviewAlreadyExistsException.class, () -> reviewUseCase.submitReview(review)); + } + + @Test + @DisplayName("submitReview should throw BookNotFoundException if book does not exist") + void testSubmitReviewBookNotFound() { + when(bookRepository.existsByIsbn(String.valueOf(isbn))).thenReturn(false); + assertThrows(BookNotFoundException.class, () -> reviewUseCase.submitReview(review)); + } + + @Test + @DisplayName("submitReview should throw CustomerNotFoundException if customer does not exist") + void testSubmitReviewCustomerNotFound() { + when(customerRepository.existsById(customerId)).thenReturn(false); + assertThrows(CustomerNotFoundException.class, () -> reviewUseCase.submitReview(review)); + } + + @Test + @DisplayName("getReviewsByBook should return the list of reviews for the given book") + void testGetReviewsByBook() { + List expected = List.of(review); + when(reviewRepository.findByIsbn(isbn)).thenReturn(expected); + List result = reviewUseCase.getReviewsByBook(isbn); + assertEquals(expected, result); + } + + @Test + @DisplayName("getReviewsByCustomer should return the list of reviews for the given customer") + void testGetReviewsByCustomer() { + List expected = List.of(review); + when(reviewRepository.findByCustomerId(customerId)).thenReturn(expected); + List result = reviewUseCase.getReviewsByCustomer(customerId); + assertEquals(expected, result); + } + + @Test + @DisplayName("updateReview should update an existing review") + void testUpdateReview() { + when(reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)).thenReturn(true); + reviewUseCase.updateReview(review); + verify(reviewRepository).update(review); + } + + @Test + @DisplayName("updateReview should throw ReviewNotFoundException if review does not exist") + void testUpdateReviewNotFound() { + when(reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)).thenReturn(false); + assertThrows(ReviewNotFoundException.class, () -> reviewUseCase.updateReview(review)); + } + + @Test + @DisplayName("deleteReview should delete an existing review") + void testDeleteReview() { + when(reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)).thenReturn(true); + reviewUseCase.deleteReview(isbn, customerId); + verify(reviewRepository).deleteByCustomerIdAndIsbn(customerId, isbn); + } + + @Test + @DisplayName("deleteReview should throw ReviewNotFoundException if review does not exist") + void testDeleteReviewNotFound() { + when(reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)).thenReturn(false); + assertThrows(ReviewNotFoundException.class, () -> reviewUseCase.deleteReview(isbn, customerId)); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidatorTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidatorTest.java new file mode 100644 index 0000000..1d9b9a3 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidatorTest.java @@ -0,0 +1,69 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +public class ReviewValidatorTest { + + @Test + @DisplayName("Should validate a correct review without throwing") + void testValidReview() { + Review review = Review.builder() + .customerId(UUID.randomUUID()) + .isbn(9781234567890L) + .rating(4) + .comment("Good book") + .build(); + assertDoesNotThrow(() -> ReviewValidator.validate(review)); + } + + @Test + @DisplayName("Should throw exception if rating is too low") + void testInvalidLowRating() { + Review review = Review.builder() + .customerId(UUID.randomUUID()) + .isbn(9781234567890L) + .rating(0) + .comment("Bad") + .build(); + assertThrows( + fr.iut_fbleau.but3.dev62.mylibrary.review.exception.InvalidReviewRatingException.class, + () -> ReviewValidator.validate(review) + ); + } + + @Test + @DisplayName("Should throw exception if rating is too high") + void testInvalidHighRating() { + Review review = Review.builder() + .customerId(UUID.randomUUID()) + .isbn(9781234567890L) + .rating(6) + .comment("Too good") + .build(); + assertThrows( + fr.iut_fbleau.but3.dev62.mylibrary.review.exception.InvalidReviewRatingException.class, + () -> ReviewValidator.validate(review) + ); + } + + @Test + @DisplayName("Should throw exception if comment is empty") + void testEmptyComment() { + Review review = Review.builder() + .customerId(UUID.randomUUID()) + .isbn(9781234567890L) + .rating(3) + .comment("") + .build(); + assertThrows( + IllegalArgumentException.class, + () -> ReviewValidator.validate(review) + ); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/converter/SubscriptionConverterTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/converter/SubscriptionConverterTest.java new file mode 100644 index 0000000..e5aa046 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/converter/SubscriptionConverterTest.java @@ -0,0 +1,98 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.converter; + +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity.Subscription; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Unit Tests for SubscriptionConverter") +public class SubscriptionConverterTest { + + @Nested + @DisplayName("Tests for toDTO method") + public class ToDTOTests { + + @Test + @DisplayName("Convert full Subscription to DTO with accurate mapping") + void convertSubscriptionToDto() { + Subscription subscription = Subscription.builder() + .id(UUID.randomUUID()) + .customerId(UUID.randomUUID()) + .duration(6) + .paymentMethod("CreditCard") + .debutDate("2025-06-08") + .build(); + + SubscriptionDTO dto = SubscriptionConverter.toDTO(subscription); + + assertNotNull(dto); + assertEquals(subscription.getId(), dto.getId()); + assertEquals(subscription.getCustomerId(), dto.getCustomerId()); + assertEquals(subscription.getDuration(), dto.getDuration()); + assertEquals(subscription.getPaymentMethod(), dto.getPaymentMethod()); + assertEquals(subscription.getDebutDate(), dto.getDebutDate()); + } + + @Test + @DisplayName("Conversion should allow null values without failure") + void convertWithNullsHandled() { + Subscription subscription = Subscription.builder() + .id(UUID.randomUUID()) + .customerId(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")) + .duration(null) + .paymentMethod("NullSafe") + .debutDate("2025-06-08") + .build(); + + SubscriptionDTO dto = SubscriptionConverter.toDTO(subscription); + + assertNotNull(dto); + assertNull(dto.getDuration()); + assertEquals("NullSafe", dto.getPaymentMethod()); + } + + @Test + @DisplayName("Conversion must retain empty payment method string") + void keepEmptyPaymentMethod() { + SubscriptionInfo info = new SubscriptionInfo( + UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), + 12, + "" + ); + + Subscription domain = SubscriptionConverter.toDomain(info); + SubscriptionDTO dto = SubscriptionConverter.toDTO(domain); + + assertEquals("", dto.getPaymentMethod()); + } + } + + @Nested + @DisplayName("Tests for toDomain method") + public class ToDomainTests { + + @Test + @DisplayName("Convert SubscriptionInfo into valid Subscription object") + void transformInfoToDomain() { + SubscriptionInfo info = new SubscriptionInfo( + UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), + 12, + "Visa" + ); + + Subscription result = SubscriptionConverter.toDomain(info); + + assertNotNull(result); + assertEquals(info.customerId(), result.getCustomerId()); + assertEquals(info.duration(), result.getDuration()); + assertEquals(info.paymentMethod(), result.getPaymentMethod()); + } + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/entity/SubscriptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/entity/SubscriptionTest.java new file mode 100644 index 0000000..9b1659f --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/entity/SubscriptionTest.java @@ -0,0 +1,47 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +public class SubscriptionTest { + + @Test + @DisplayName("Assigning a new random UUID should update the ID") + void shouldAssignNewRandomId() { + Subscription subscription = Subscription.builder().build(); + UUID previousId = subscription.getId(); + + subscription.setRandomUUID(); + + assertNotNull(subscription.getId()); + assertNotEquals(previousId, subscription.getId()); + } + + @Test + @DisplayName("Subscription created via builder should retain all values") + void shouldCreateValidSubscriptionFromBuilder() { + UUID generatedId = UUID.randomUUID(); + UUID clientId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + int period = 12; + String method = "CB"; + String date = "2025-06-08"; + + Subscription subscription = Subscription.builder() + .id(generatedId) + .customerId(clientId) + .duration(period) + .paymentMethod(method) + .debutDate(date) + .build(); + + assertEquals(generatedId, subscription.getId()); + assertEquals(clientId, subscription.getCustomerId()); + assertEquals(period, subscription.getDuration()); + assertEquals(method, subscription.getPaymentMethod()); + assertEquals(date, subscription.getDebutDate()); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/NotValidSubscriptionExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/NotValidSubscriptionExceptionTest.java new file mode 100644 index 0000000..5c66c9f --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/NotValidSubscriptionExceptionTest.java @@ -0,0 +1,53 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.exception; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; + +class NotValidSubscriptionExceptionTest { + + @ParameterizedTest + @ValueSource(strings = { + "Customer ID cannot be null", + "Duration is not valid", + "Payment Method cannot be blank" + }) + @DisplayName("Should store and return each provided error message") + void shouldPreserveVariousErrorMessages(String inputMessage) { + NotValidSubscriptionException ex = new NotValidSubscriptionException(inputMessage); + assertEquals(inputMessage, ex.getMessage()); + } + + @Test + @DisplayName("Should be correctly caught and identified as Exception") + void shouldBeCatchableAsGenericException() { + String message = "Invalid subscription data"; + try { + throw new NotValidSubscriptionException(message); + } catch (Exception e) { + assertEquals(NotValidSubscriptionException.class, e.getClass()); + assertEquals(message, e.getMessage()); + } + } + + @Test + @DisplayName("Should construct with provided message text") + void shouldConstructWithMessage() { + String message = "Subscription data is not valid"; + NotValidSubscriptionException exception = new NotValidSubscriptionException(message); + assertEquals(message, exception.getMessage()); + } + + @Test + @DisplayName("Should be thrown and verified by assertThrows") + void shouldThrowProperly() { + String message = "Required field is missing"; + Exception thrown = assertThrows(NotValidSubscriptionException.class, () -> { + throw new NotValidSubscriptionException(message); + }); + assertEquals(message, thrown.getMessage()); + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/SubscriptionNotFoundExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/SubscriptionNotFoundExceptionTest.java new file mode 100644 index 0000000..5fd2325 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/SubscriptionNotFoundExceptionTest.java @@ -0,0 +1,51 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.exception; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +public class SubscriptionNotFoundExceptionTest { + + @Test + @DisplayName("Exception message should contain the UUID provided") + void testExceptionMessageContainsUUID() { + UUID uuid = UUID.randomUUID(); + + SubscriptionNotFoundException exception = new SubscriptionNotFoundException(uuid); + + String expectedMessage = String.format("The subscription with id %s does not exist", uuid); + assertEquals(expectedMessage, exception.getMessage()); + } + + @Test + @DisplayName("Exception should use the correct constant message format") + void testExceptionUsesConstantMessageFormat() { + UUID uuid = UUID.randomUUID(); + + SubscriptionNotFoundException exception = new SubscriptionNotFoundException(uuid); + + String expectedFormatWithPlaceholder = "The subscription with id {0} does not exist"; + assertEquals(SubscriptionNotFoundException.THE_SUBSCRIPTION_WITH_ID_DOES_NOT_EXIST_MESSAGE, + expectedFormatWithPlaceholder); + assertTrue(exception.getMessage().contains(uuid.toString())); + } + + @Test + @DisplayName("Exception should be properly thrown and caught") + void testExceptionCanBeThrownAndCaught() { + UUID uuid = UUID.randomUUID(); + + try { + throw new SubscriptionNotFoundException(uuid); + } catch (SubscriptionNotFoundException e) { + String expectedMessage = String.format("The subscription with id %s does not exist", uuid); + assertEquals(expectedMessage, e.getMessage()); + } + } + +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/repository/SubscriptionRepositoryTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/repository/SubscriptionRepositoryTest.java new file mode 100644 index 0000000..983c676 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/repository/SubscriptionRepositoryTest.java @@ -0,0 +1,148 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity.Subscription; +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 java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +public class SubscriptionRepositoryTest { + + private SubscriptionRepository repository; + private Subscription firstSub; + private Subscription secondSub; + + @BeforeEach + void initRepository() { + repository = new SubscriptionRepository(); + + firstSub = Subscription.builder() + .customerId(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")) + .duration(12) + .paymentMethod("CB") + .build(); + firstSub.setRandomUUID(); + + secondSub = Subscription.builder() + .customerId(UUID.fromString("456e4567-e89b-12d3-a456-426614174000")) + .duration(24) + .paymentMethod("Paypal") + .build(); + secondSub.setRandomUUID(); + } + + @Test + @DisplayName("Repository should start empty") + void repositoryShouldBeInitiallyEmpty() { + List result = repository.findAll(); + + assertTrue(result.isEmpty()); + assertEquals(0, result.size()); + } + + @Nested + @DisplayName("Tests for saving subscriptions") + class SavingTests { + + @Test + @DisplayName("Saving one subscription should add it to the repository") + void saveSingleSubscription() { + Subscription saved = repository.save(firstSub); + + assertEquals(1, repository.findAll().size()); + assertEquals(firstSub.getId(), saved.getId()); + assertEquals(firstSub.getDebutDate(), saved.getDebutDate()); + } + + @Test + @DisplayName("Saving multiple entries should keep both") + void saveMultipleEntries() { + repository.save(firstSub); + repository.save(secondSub); + + List all = repository.findAll(); + + assertEquals(2, all.size()); + assertTrue(all.contains(firstSub)); + assertTrue(all.contains(secondSub)); + } + } + + @Nested + @DisplayName("Tests for fetching and checking subscriptions") + class RetrievalAndExistenceTests { + + @BeforeEach + void populateRepository() { + repository.save(firstSub); + repository.save(secondSub); + } + + @Test + @DisplayName("Should return all stored subscriptions") + void returnAllSubscriptions() { + List all = repository.findAll(); + + assertEquals(2, all.size()); + assertTrue(all.contains(firstSub)); + assertTrue(all.contains(secondSub)); + } + + @Test + @DisplayName("Should retrieve by ID if present") + void retrieveByIdSuccess() { + Optional result = repository.findById(firstSub.getId()); + + assertTrue(result.isPresent()); + assertEquals(firstSub.getId(), result.get().getId()); + assertEquals(firstSub.getCustomerId(), result.get().getCustomerId()); + } + + @Test + @DisplayName("Should return empty if ID not found") + void retrieveByIdFailure() { + UUID unknownId = UUID.randomUUID(); + Optional result = repository.findByCustomerId(unknownId); + + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("Should find subscription by customer UUID") + void retrieveByCustomerId() { + Optional result = repository.findByCustomerId(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")); + + assertTrue(result.isPresent()); + assertEquals(firstSub.getId(), result.get().getId()); + assertEquals(firstSub.getDebutDate(), result.get().getDebutDate()); + } + + @Test + @DisplayName("Should return empty Optional for unknown customer UUID") + void customerIdNotFound() { + UUID unknown = UUID.fromString("0000000-0000-0000-0000-000000000000"); + Optional result = repository.findByCustomerId(unknown); + + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("ExistsById should return true if present") + void existsByIdTrue() { + assertTrue(repository.existsById(firstSub.getId())); + } + + @Test + @DisplayName("ExistsById should return false for unknown ID") + void existsByIdFalse() { + UUID nonexistent = UUID.randomUUID(); + assertFalse(repository.existsById(nonexistent)); + } + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/usecase/SubscribeUseCaseTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/usecase/SubscribeUseCaseTest.java new file mode 100644 index 0000000..e820c8b --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/usecase/SubscribeUseCaseTest.java @@ -0,0 +1,112 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity.Subscription; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.exception.NotValidSubscriptionException; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.repository.SubscriptionRepository; + +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.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class SubscribeUseCaseTest { + + @Mock + private SubscriptionRepository subscriptionRepository; + + @InjectMocks + private SubscriptionUseCase subscriptionUseCase; + + private UUID generatedId; + private Subscription subscriptionMock; + private SubscriptionInfo validInfo; + + @BeforeEach + void initData() { + generatedId = UUID.randomUUID(); + + subscriptionMock = Subscription.builder() + .id(generatedId) + .customerId(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")) + .duration(12) + .paymentMethod("CB") + .build(); + + validInfo = new SubscriptionInfo(UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), 12, "CB"); + } + + @Nested + @DisplayName("Tests for subscription creation") + class CreationTests { + + @Test + @DisplayName("Should successfully register when input is valid") + void registerWithValidInput() throws NotValidSubscriptionException { + when(subscriptionRepository.save(any(Subscription.class))).thenReturn(subscriptionMock); + + UUID result = subscriptionUseCase.registerSubscription(validInfo); + + assertNotNull(result); + assertEquals(generatedId, result); + verify(subscriptionRepository, times(1)).save(any(Subscription.class)); + } + + @Test + @DisplayName("Should reject invalid input with exception") + void rejectInvalidSubscriptionInput() { + SubscriptionInfo brokenInfo = new SubscriptionInfo(null, null, ""); + + assertThrows(NotValidSubscriptionException.class, + () -> subscriptionUseCase.registerSubscription(brokenInfo)); + + verify(subscriptionRepository, never()).save(any(Subscription.class)); + } + } + + @Nested + @DisplayName("Tests for fetching subscription") + class FetchingTests { + + @Test + @DisplayName("Should find subscription using customer ID") + void findByCustomerIdSuccessfully() { + UUID targetId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + + when(subscriptionRepository.findByCustomerId(targetId)).thenReturn(Optional.of(subscriptionMock)); + + Optional result = subscriptionUseCase.findSubscriptionByCustomerId(targetId); + + assertTrue(result.isPresent()); + assertEquals(subscriptionMock.getId(), result.get().getId()); + assertEquals(subscriptionMock.getDebutDate(), result.get().getDebutDate()); + verify(subscriptionRepository, times(1)).findByCustomerId(targetId); + } + + @Test + @DisplayName("Should return empty when customer ID is not found") + void customerIdNotFoundReturnsEmpty() { + UUID unknown = UUID.fromString("0000000-0000-0000-0000-000000000000"); + + when(subscriptionRepository.findByCustomerId(unknown)).thenReturn(Optional.empty()); + + Optional result = subscriptionUseCase.findSubscriptionByCustomerId(unknown); + + assertTrue(result.isEmpty()); + verify(subscriptionRepository, times(1)).findByCustomerId(unknown); + } + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/validator/SubscriptionValidatorTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/validator/SubscriptionValidatorTest.java new file mode 100644 index 0000000..429069d --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/validator/SubscriptionValidatorTest.java @@ -0,0 +1,93 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.exception.NotValidSubscriptionException; + +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.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +public class SubscriptionValidatorTest { + + @Test + @DisplayName("Validation should pass for complete and correct data") + void validateCorrectSubscription() { + SubscriptionInfo valid = new SubscriptionInfo(UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), 12, "CB"); + + assertDoesNotThrow(() -> SubscriptionValidator.validate(valid)); + } + + @Nested + @DisplayName("Tests related to customer ID validation") + class CustomerIdTests { + + @Test + @DisplayName("Null customer ID should raise an exception") + void nullCustomerIdThrowsException() { + SubscriptionInfo info = new SubscriptionInfo(null, 12, "CB"); + + NotValidSubscriptionException exception = assertThrows( + NotValidSubscriptionException.class, + () -> SubscriptionValidator.validate(info) + ); + + assertEquals(SubscriptionValidator.CUSTOMER_ID_NULL_MESSAGE, exception.getMessage()); + } + } + + @Nested + @DisplayName("Tests focused on duration field") + class DurationTests { + + @Test + @DisplayName("Null duration should cause validation failure") + void durationIsNullShouldFail() { + SubscriptionInfo info = new SubscriptionInfo(UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), null, "CB"); + + NotValidSubscriptionException exception = assertThrows( + NotValidSubscriptionException.class, + () -> SubscriptionValidator.validate(info) + ); + + assertEquals(SubscriptionValidator.DURATION_NULL_MESSAGE, exception.getMessage()); + } + } + + @Nested + @DisplayName("Payment method validation scenarios") + class PaymentMethodTests { + + @Test + @DisplayName("Empty payment method string should not be accepted") + void emptyPaymentMethodShouldThrow() { + SubscriptionInfo info = new SubscriptionInfo(UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), 12, ""); + + NotValidSubscriptionException exception = assertThrows( + NotValidSubscriptionException.class, + () -> SubscriptionValidator.validate(info) + ); + + assertEquals(SubscriptionValidator.PAYMENT_METHOD_EMPTY_MESSAGE, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {" ", "\t", "\n", " "}) + @DisplayName("Whitespace-only payment methods should be rejected") + void whitespaceOnlyPaymentMethodFails(String input) { + SubscriptionInfo info = new SubscriptionInfo(UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), 12, input); + + NotValidSubscriptionException exception = assertThrows( + NotValidSubscriptionException.class, + () -> SubscriptionValidator.validate(info) + ); + + assertEquals(SubscriptionValidator.PAYMENT_METHOD_EMPTY_MESSAGE, exception.getMessage()); + } + } +} diff --git a/src/test/resources/features/book.feature b/src/test/resources/features/book.feature new file mode 100644 index 0000000..49b4d52 --- /dev/null +++ b/src/test/resources/features/book.feature @@ -0,0 +1,66 @@ +Feature: Manage books + + Background: + Given the system contains the following books: + | ISBN | Title | Author | Publisher | Publication Date | Price | Initial Stock | Categories | Description | Language | + | 9781234567890 | Les Mysteres de l'IA | Jean Dupont | TechEditions | 2024-01-15 | 29.99 | 10 | [SCIENCE] | Un livre fascinant sur l'IA. | Français | + | 9789876543210 | L'Art de la Guerre Moderne | Claire Martin | StrategeBooks | 2023-06-10 | 35.50 | 5 | [HISTORY] | Analyse des conflits modernes et tactiques. | Français | + | 9781112223334 | COOKING du Monde | Pierre Lemoine | GourmetEditions | 2022-09-20 | 24.90 | 20 | [COOKING, TRAVEL] | Un tour du monde gastronomique en recettes. | Français | + | 9785556667778 | Python pour Debutants | Alice Bernard | CodeMaster | 2021-04-05 | 19.99 | 15 | [SCIENCE] | Apprendre Python pas à pas avec des exercices. | Français | + | 9789998887776 | L'eveil Spirituel | Olivier Fontaine | ZenBooks | 2020-11-30 | 22.00 | 8 | [SELF_HELP] | Un guide vers la pleine conscience et la paix intérieure. | Français | + + Scenario: Create a new book + When I create a new book with the following data: + | ISBN | Title | Author | Publisher | Publication Date | Price | Initial Stock | Categories | Description | Language | + | 9791032719640 | My Hero Academia | Kohei Horikoshi | Ki-oon | 2025-02-06 | 6.95 | 900 | [FANTASY, SCIENCE_FICTION] | Retrouvez les super‑héros du manga phénomène My Hero Academia ! | Français | + Then a new book is created + + Scenario: Update a book's information + Given a book exists with ISBN "9791032719640" + When I update the book with ISBN "9791032719640" with the following data: + | ISBN | Title | Author | Publisher | Publication Date | Price | Initial Stock | Categories | Description | Language | + | 9791032719640 | My Hero Academia | Kohei Horikoshi | Ki-oon | 2025-02-06 | 6.95 | 999 | [FANTASY, SCIENCE_FICTION] | Retrouvez les super‑héros du manga phénomène My Hero Academia ! | Français | + Then the book "9791032719640" has the following data: + | ISBN | Title | Author | Publisher | Publication Date | Price | Initial Stock | Categories | Description | Language | + | 9791032719640 | My Hero Academia | Kohei Horikoshi | Ki-oon | 2025-02-06 | 6.95 | 999 | [FANTASY, SCIENCE_FICTION] | Retrouvez les super‑héros du manga phénomène My Hero Academia ! | Français | + + Scenario: Delete a book + Given a book exists with ISBN "9791032719640" + When I delete the book with ISBN "9791032719640" + Then the book no longer exists in the system + + Scenario: Retrieve book details + Given a book exists with ISBN "9789876543210" + When I request the details for the book with ISBN "9789876543210" + Then I receive the following data: + | ISBN | Title | Author | Publisher | Publication Date | Price | Initial Stock | Categories | Description | Language | + | 9789876543210 | L'Art de la Guerre Moderne | Claire Martin | StrategeBooks | 2023-06-10 | 35.50 | 5 | [HISTORY] | Analyse des conflits modernes et tactiques. | Français | + + Scenario: Fail to update a non-existent book + Given no book exists with ISBN "9999999999999" + When I update the book with ISBN "9999999999999" using the following data: + | ISBN | Title | Author | Publisher | Publication Date | Price | Initial Stock | Categories | Description | Language | + | 9999999999999 | Inconnu | Auteur X | Maison Y | 2025-01-01 | 10.00 | 1 | [FANTASY] | Ce livre n'existe pas | Français | + Then the update fails with an error indicating the book was not found + + Scenario: Fail to delete a non-existent book + Given no book exists with ISBN "9999999999999" + When I delete the book with ISBN "9999999999999" + Then the deletion fails with an error indicating the book was not found + + Scenario: Attempt to create an invalid book + When I create a new book with the following data: + | ISBN | Title | Author | Publisher | Publication Date | Price | Initial Stock | Categories | Description | Language | + | abcefg | L'Art de la Guerre Moderne | Claire Martin | StrategeBooks | 2023-06-10 | 35.50 | 5 | [HISTORY] | Analyse des conflits modernes et tactiques. | Français | + Then the creation fails + + Scenario: Attempt to create a book with an existing ISBN + Given a book exists with ISBN "9781234567890" + When I create a new book with the following data: + | ISBN | Title | Author | Publisher | Publication Date | Price | Initial Stock | Categories | Description | Language | + | 9781234567890 | L'Art de la Guerre Moderne | Claire Martin | StrategeBooks | 2023-06-10 | 35.50 | 5 | [HISTORY] | Analyse des conflits modernes et tactiques. | Français | + Then the creation fails + + Scenario: Retrieve a non-existent book + When I request the details for the book with ISBN "999999" + Then I receive an error indicating the book was not found diff --git a/src/test/resources/features/order.feature b/src/test/resources/features/order.feature new file mode 100644 index 0000000..853ab43 --- /dev/null +++ b/src/test/resources/features/order.feature @@ -0,0 +1,201 @@ +Feature: Manage customer orders + Manage the creation, validation, and retrieval of customer book orders. + + Background: + Given the system contains the following books: + | isbn | titre | auteur | editeur | datePublication | prix | stockInitial | categories | description | langue | + | 9791032719640 | My Hero Academia | Kohei Horikoshi | Ki-oon | 2025-02-06 | 6.95 | 900 | [FANTASY, SCIENCE_FICTION] | Retrouvez les super-héros du manga phénomène My Hero Academia ! | Francais | + | 1234567891011 | ORV | SingNsong | Munpia | 2020-02-02 | 666.66 | 69 | [FANTASY] | ORV c'est trop bien, allez lire | KR | + And the system contains the following users: + | id | firstName | lastName | phoneNumber | loyaltyPoints | + | 11111111-1111-1111-1111-111111111111 | Simon | Saye Babu | 0123456789 | 1000 | + | 22222222-2222-2222-2222-222222222222 | Hugo | Branco Gomez | 0987654321 | 10 | + + Scenario: Place an order using a credit card + When I create a new order with the following information: + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And the order includes the following books: + | bookId | quantity | + | 1234567891011 | 2 | + And the delivery address is: + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then a new order is created + And the total price is 1333.32 + And customer "11111111-1111-1111-1111-111111111111" has 1000 loyalty points + + Scenario: Place an order using loyalty points + When I create a new order with the following information: + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | LOYALTY_POINTS | + And the order includes the following books: + | bookId | quantity | + | 9791032719640 | 1 | + And the delivery address is: + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then a new order is created + And the total price is 6.95 + And customer "11111111-1111-1111-1111-111111111111" has 305 loyalty points + + Scenario: Place an order with minimum allowed quantity + When I create a new order with the following information: + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And the order includes the following books: + | bookId | quantity | + | 1234567891011 | 1 | + And the delivery address is: + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then a new order is created + And the total price is 666.66 + + Scenario: Place an order with maximum allowed quantity + When I create a new order with the following information: + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And the order includes the following books: + | bookId | quantity | + | 1234567891011 | 69 | + And the delivery address is: + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then a new order is created + And the total price is 45999.54 + + Scenario: Place an order with multiple different books + When I create a new order with the following information: + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And the order includes the following books: + | bookId | quantity | + | 1234567891011 | 1 | + | 9791032719640 | 2 | + And the delivery address is: + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then a new order is created + And the total price is 680.56 + + Scenario: Fail to place an order with missing delivery address + When I create a new order with the following information: + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And the order includes the following books: + | bookId | quantity | + | 1234567891011 | 1 | + And the delivery address is: + | street | city | postalCode | country | + | | | | | + Then the order creation fails + And I receive an error stating that the delivery address is missing + + Scenario: Fail to place an order with unknown customer + When I create a new order with the following information: + | customerId | paymentMethod | + | 22222222-2222-2222-2222-222222222223 | CREDIT_CARD | + And the order includes the following books: + | bookId | quantity | + | 1234567891011 | 1 | + And the delivery address is: + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then the order creation fails + And I receive an error stating that the customer was not found + + Scenario: Fail to place an order due to insufficient loyalty points + When I create a new order with the following information: + | customerId | paymentMethod | + | 22222222-2222-2222-2222-222222222222 | LOYALTY_POINTS | + And the order includes the following books: + | bookId | quantity | + | 1234567891011 | 1 | + And the delivery address is: + | street | city | postalCode | country | + | 3 rue de chez lui | SaVille | 77000 | France | + Then the order creation fails + And I receive an error stating that the loyalty points are insufficient + + Scenario: Fail to place an order due to insufficient book stock + When I create a new order with the following information: + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And the order includes the following books: + | bookId | quantity | + | 1234567891011 | 100 | + And the delivery address is: + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then the order creation fails + And I receive an error stating that the book stock is insufficient + + Scenario: Fail to place an order with an invalid payment method + When I create a new order with the following information: + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | UNKNOWN | + And the order includes the following books: + | bookId | quantity | + | 1234567891011 | 1 | + And the delivery address is: + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then the order creation fails + And I receive an error stating that the payment method is invalid + + Scenario: Fail to place an order with an unknown book + When I create a new order with the following information: + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And the order includes the following books: + | bookId | quantity | + | unknownBookId | 1 | + And the delivery address is: + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then the order creation fails + And I receive an error stating that the book was not found + + Scenario: Fail to place an order with no books + When I create a new order with the following information: + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And the order contains no books + And the delivery address is: + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then the order creation fails + And I receive an error stating that the order must include at least one book + + Scenario: Fail to place an order with a negative book quantity + When I create a new order with the following information: + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And the order includes the following books: + | bookId | quantity | + | 1234567891011 | -2 | + And the delivery address is: + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then the order creation fails + And I receive an error stating that the quantity must be positive + + Scenario: Retrieve an order by its ID + Given there is an order with ID "12345678-9101-1121-3141-516171819202" for customer "11111111-1111-1111-1111-111111111111" + When I retrieve the order by ID "12345678-9101-1121-3141-516171819202" + Then I receive an order + + Scenario: Retrieve all orders for a specific customer + When I request all orders for customer "11111111-1111-1111-1111-111111111111" + Then I receive a list of orders + + Scenario: Fail to retrieve an order with an unknown ID + When I retrieve the order by ID "nope" + Then retrieval fails + And I receive an error stating that the order was not found + + Scenario: Fail to retrieve orders for an unknown customer + When I request all orders for customer "00000000-0000-0000-0000-000000000000" + Then retrieval fails + And I receive an error stating that the customer was not found diff --git a/src/test/resources/features/review.feature b/src/test/resources/features/review.feature new file mode 100644 index 0000000..93d827b --- /dev/null +++ b/src/test/resources/features/review.feature @@ -0,0 +1,71 @@ +Feature: Manage book reviews + Handle the creation, update, retrieval, and deletion of book reviews by customers. + + Background: + Given the system contains the following books: + | isbn | titre | auteur | editeur | datePublication | prix | stockInitial | categories | description | langue | + | 9781234567890 | Livre Test 1 | Auteur 1 | Editeur 1 | 2020-01-01 | 10.0 | 10 | FICTION | Un super livre | FR | + | 9789876543210 | Livre Test 2 | Auteur 2 | Editeur 2 | 2021-05-10 | 15.0 | 5 | SCIENCE_FICTION | Un livre SF | FR | + And the system contains the following users: + | id | firstName | lastName | phoneNumber | loyaltyPoints | + | 11111111-1111-1111-1111-111111111111 | Marie | Dupont | 0612345678 | 100 | + | 22222222-2222-2222-2222-222222222222 | Jean | Martin | 0687654321 | 50 | + | 33333333-3333-3333-3333-333333333333 | Sophie | Dubois | 0698765432 | 0 | + | 44444444-4444-4444-4444-444444444444 | Pierre | Lambert | 0611223344 | 0 | + And the system contains the following reviews: + | customerId | isbn | rating | comment | + | 11111111-1111-1111-1111-111111111111 | 9781234567890 | 5 | Excellent book! | + | 22222222-2222-2222-2222-222222222222 | 9781234567890 | 3 | Average, but readable. | + | 33333333-3333-3333-3333-333333333333 | 9789876543210 | 4 | Very interesting. | + + Scenario: Create a review for a book + When I create a review for book "9781234567890" by customer "44444444-4444-4444-4444-444444444444" with rating 4 and comment "cool" + Then a new review is created + And the system contains 4 reviews + + Scenario: Retrieve all reviews for a specific book + When I retrieve all reviews for book "9781234567890" + Then I receive the following reviews: + | customerId | rating | comment | + | 11111111-1111-1111-1111-111111111111 | 5 | Excellent book! | + | 22222222-2222-2222-2222-222222222222 | 3 | Average, but readable. | + + Scenario: Retrieve all reviews from a specific customer + When I retrieve all reviews by customer "33333333-3333-3333-3333-333333333333" + Then I receive the following reviews: + | isbn | rating | comment | + | 9789876543210 | 4 | Very interesting. | + + Scenario: Update an existing review + When I update the review for book "9781234567890" by customer "22222222-2222-2222-2222-222222222222" with rating 4 and comment "cool2" + Then the review is updated with rating 4 and comment "cool2" + + Scenario: Delete a review + When I delete the review for book "9789876543210" by customer "33333333-3333-3333-3333-333333333333" + Then the review is removed from the system + And the system contains 2 reviews + + Scenario: Attempt to submit a duplicate review + When I try to submit a review for book "9781234567890" by customer "11111111-1111-1111-1111-111111111111" with rating 2 and comment "Changed my mind." + Then the review is not created + And I receive an error stating that the review already exists + + Scenario: Attempt to submit a review with an invalid rating + When I try to submit a review for book "9781234567890" by customer "11111111-1111-1111-1111-111111111111" with rating 6 and comment "Too good!" + Then the review is not created + And I receive an error stating that the rating is not valid + + Scenario: Attempt to review a non-existent book + When I try to submit a review for book "9780000000000" by customer "11111111-1111-1111-1111-111111111111" with rating 3 and comment "Not found" + Then the review is not created + And I receive an error stating that the book does not exist + + Scenario: Attempt to review with a non-existent customer + When I try to submit a review for book "9781234567890" by customer "99999999-9999-9999-9999-999999999999" with rating 3 and comment "Who am I?" + Then the review is not created + And I receive an error stating that the customer does not exist + + Scenario: Attempt to delete a non-existent review + When I try to delete the review for book "9781234567890" by customer "99999999-9999-9999-9999-999999999999" + Then the review is not deleted + And I receive an error stating that the review does not exist diff --git a/src/test/resources/features/subscription.feature b/src/test/resources/features/subscription.feature new file mode 100644 index 0000000..044d9f9 --- /dev/null +++ b/src/test/resources/features/subscription.feature @@ -0,0 +1,44 @@ +Feature: Manage subscriptions + + Background: + Given the system contains the following users: + | customerId | firstName | lastName | phoneNumer | pointsFidelite | + | 11111111-1111-1111-1111-111111111111 | John | Doe | 0612345678 | 100 | + | 22222222-2222-2222-2222-222222222222 | Bob | Dupond | 0687654321 | 50 | + | 33333333-3333-3333-3333-333333333333 | Alice | Untel | 0698765432 | 0 | + + Scenario: Create a new subscription using a credit card + When I create a new subscription using a CB: + | customerId | duration | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | 12 | CB | + Then a new subscription is created + + Scenario: Create a new subscription using PayPal + When I create a new subscription using PayPal: + | customerId | duration | paymentMethod | + | 22222222-2222-2222-2222-222222222222 | 24 | Paypal | + Then a new subscription is created + + Scenario: Retrieve a subscription by customer ID + Given I create a new subscription using a CB: + | customerId | duration | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | 12 | CB | + When I retrieve a subscription by customer ID: + | customerId | + | 11111111-1111-1111-1111-111111111111 | + Then I receive the following subscription details: + | subscriptionId | customerId | duration | paymentMethod | debutDate | + | 99999999-9999-9999-9999-999999999999 | 11111111-1111-1111-1111-111111111111 | 12 | CB | 2025-06-11 | + + Scenario: Fail to create a subscription with invalid duration + When I try to create a subscription with the following details: + | customerId | duration | paymentMethod | + | 33333333-3333-3333-3333-333333333333 | 0 | CB | + Then subscription creation fails + And I receive an error indicating the subscription is invalid + + Scenario: Fail to retrieve a subscription with unknown customer ID + When I retrieve a subscription by customer ID: + | customerId | + | 44444444-4444-4444-4444-444444444444 | + Then I receive an error indicating the subscription was not found