diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.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/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..146ab09 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6546f14 --- /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..9521ef3 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCase.java @@ -0,0 +1,155 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.usecase; + +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.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 bookRepository; + + public BookUseCase(BookRepository bookRepository) { + this.bookRepository = bookRepository; + } + + public String registerBook(BookInfo newBook) throws NotValidBookException { + BookValidator.validate(newBook); + + if (bookRepository.existsByIsbn(newBook.isbn())) { + throw new NotValidBookException("Un livre avec cet ISBN existe déjà"); + } + + Book bookToRegister = BookConverter.toDomain(newBook); + Book registeredBook = bookRepository.save(bookToRegister); + return registeredBook.getIsbn(); + } + + public BookDTO findBookByIsbn(String isbn) throws BookNotFoundException { + return bookRepository.findByIsbn(isbn) + .map(BookConverter::toDTO) + .orElseThrow(() -> new BookNotFoundException(isbn)); + } + + public BookDTO updateBook(String isbn, BookInfo bookInfo) + throws BookNotFoundException, NotValidBookException { + BookValidator.validate(bookInfo); + getBookIfDoesNotExistThrowBookNotFoundException(isbn); + Book updated = Book.builder() + .isbn(isbn) + .title(bookInfo.title()) + .author(bookInfo.author()) + .publisher(bookInfo.publisher()) + .publicationDate(bookInfo.publicationDate()) + .price(bookInfo.price()) + .stock(bookInfo.stock()) + .categories(bookInfo.categories()) + .description(bookInfo.description()) + .language(bookInfo.language()) + .build(); + Book saved = bookRepository.save(updated); + return BookConverter.toDTO(saved); + } + + public void deleteBook(String isbn) throws BookNotFoundException { + Book bookToDelete = getBookIfDoesNotExistThrowBookNotFoundException(isbn); + this.bookRepository.delete(bookToDelete); + } + + public int addStock(String isbn, int quantity) throws BookNotFoundException { + Book book = getBookIfDoesNotExistThrowBookNotFoundException(isbn); + book.addStock(quantity); + bookRepository.save(book); + return book.getStock(); + } + + public int removeStock(String isbn, int quantity) throws BookNotFoundException { + Book book = getBookIfDoesNotExistThrowBookNotFoundException(isbn); + book.removeStock(quantity); + bookRepository.save(book); + return book.getStock(); + } + + public Map findBooksPageSorted(int page, int size, String sortBy) { + List allBooks = bookRepository.findAll(); + + allBooks.sort(Comparator.comparing( + book -> { + switch (sortBy.toLowerCase()) { + case "titre": + case "title": + return book.getTitle() == null ? "" : book.getTitle().toLowerCase(); + case "auteur": + case "author": + return book.getAuthor() == null ? "" : book.getAuthor().toLowerCase(); + case "editeur": + case "publisher": + return book.getPublisher() == null ? "" : book.getPublisher().toLowerCase(); + default: + return book.getTitle() == null ? "" : book.getTitle().toLowerCase(); + } + }, + Comparator.naturalOrder() + )); + + int totalElements = allBooks.size(); + int totalPages = (int) Math.ceil((double) totalElements / size); + int fromIndex = Math.min(page * size, totalElements); + int toIndex = Math.min(fromIndex + size, totalElements); + + List content = allBooks.subList(fromIndex, toIndex) + .stream() + .map(BookConverter::toDTO) + .collect(Collectors.toList()); + + Map result = new HashMap<>(); + result.put("content", content); + result.put("totalElements", totalElements); + result.put("totalPages", totalPages); + return result; + } + + public List findBooksByTitleContaining(String title) { + List books = bookRepository.findByTitleContaining(title); + return books.stream().map(BookConverter::toDTO).collect(Collectors.toList()); + } + + public List findBooksByAuthor(String author) { + List books = bookRepository.findByAuthor(author); + return books.stream().map(BookConverter::toDTO).collect(Collectors.toList()); + } + + public List findBooksByPublisher(String publisher) { + List books = bookRepository.findByPublisher(publisher); + return books.stream().map(BookConverter::toDTO).collect(Collectors.toList()); + } + + public List findBooksByCategory(Category category) { + List books = bookRepository.findByCategory(category); + return books.stream().map(BookConverter::toDTO).collect(Collectors.toList()); + } + + public List findBooksPublishedAfter(LocalDate date) { + List books = bookRepository.findPublishedAfter(date); + return books.stream().map(BookConverter::toDTO).collect(Collectors.toList()); + } + + public List findBooksByPriceRange(double min, double max) { + List books = bookRepository.findByPriceRange(min, max); + return books.stream().map(BookConverter::toDTO).collect(Collectors.toList()); + } + + private Book getBookIfDoesNotExistThrowBookNotFoundException(String isbn) { + return bookRepository.findByIsbn(isbn) + .orElseThrow(() -> new BookNotFoundException(isbn)); + } +} \ No newline at end of file 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..925ca9b --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/validator/BookValidator.java @@ -0,0 +1,62 @@ +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 ISBN_CANNOT_BE_NULL = "L'ISBN ne peut pas être nul"; + public static final String TITLE_CANNOT_BE_BLANK = "Le titre ne peut pas être vide"; + public static final String AUTHOR_CANNOT_BE_BLANK = "L'auteur ne peut pas être vide"; + public static final String PRICE_MUST_BE_POSITIVE = "Le prix doit être positif"; + public static final String STOCK_CANNOT_BE_NEGATIVE = "Le stock ne peut pas être négatif"; + public static final String ISBN_IS_NOT_VALID = "L'ISBN n'est pas valide"; + public static final String PUBLISHER_CANNOT_BE_BLANK = "L'éditeur ne peut pas être vide"; + public static final String PUBLICATION_DATE_CANNOT_BE_NULL = "La date de publication ne peut pas être nulle"; + public static final String CATEGORIES_CANNOT_BE_EMPTY = "La liste des catégories ne peut pas être vide"; + public static final String DESCRIPTION_CANNOT_BE_BLANK = "La description ne peut pas être vide"; + public static final String LANGUAGE_CANNOT_BE_BLANK = "La langue ne peut pas être vide"; + + private BookValidator() {} + + public static void validate(BookInfo bookInfo) throws NotValidBookException { + validateIsbn(bookInfo); + validateTitle(bookInfo); + validateAuthor(bookInfo); + validatePrice(bookInfo); + validateStock(bookInfo); + } + + private static void validateIsbn(BookInfo bookInfo) throws NotValidBookException { + if (bookInfo.isbn() == null) { + throw new NotValidBookException(ISBN_CANNOT_BE_NULL); + } + if (!bookInfo.isbn().matches("\\d{13}")) { + throw new NotValidBookException(ISBN_IS_NOT_VALID); + } + } + + private static void validateTitle(BookInfo bookInfo) throws NotValidBookException { + if (bookInfo.title() == null || bookInfo.title().isBlank()) { + throw new NotValidBookException(TITLE_CANNOT_BE_BLANK); + } + } + + private static void validateAuthor(BookInfo bookInfo) throws NotValidBookException { + if (bookInfo.author() == null || bookInfo.author().isBlank()) { + throw new NotValidBookException(AUTHOR_CANNOT_BE_BLANK); + } + } + + private static void validatePrice(BookInfo bookInfo) throws NotValidBookException { + if (bookInfo.price() < 0) { + throw new NotValidBookException(PRICE_MUST_BE_POSITIVE); + } + } + + private static void validateStock(BookInfo bookInfo) throws NotValidBookException { + if (bookInfo.stock() < 0) { + throw new NotValidBookException(STOCK_CANNOT_BE_NEGATIVE); + } + } +} \ No newline at end of file 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/repository/CustomerRepository.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/repository/CustomerRepository.java index 7d79f32..8ad8ab3 100644 --- a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/repository/CustomerRepository.java +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/customer/repository/CustomerRepository.java @@ -20,27 +20,35 @@ public final class CustomerRepository { } public Customer save(Customer newCustomer) { - Optional optionalCustomerWithSameId = this.findById(newCustomer.getId()); - optionalCustomerWithSameId.ifPresentOrElse(customers::remove, newCustomer::setRandomUUID); + + if (newCustomer.getId() == null) { + newCustomer.setRandomUUID(); + } + + this.findById(newCustomer.getId()).ifPresent(customers::remove); + this.customers.add(newCustomer); return newCustomer; } public Optional findById(UUID uuid) { + if (uuid == null) { + return Optional.empty(); + } return this.customers.stream() - .filter(customer -> customer.getId().equals(uuid)) - .findFirst(); + .filter(c -> uuid.equals(c.getId())) + .findFirst(); } public boolean existsById(UUID uuid) { - return this.customers.stream() - .anyMatch(customer -> customer.getId().equals(uuid)); + return uuid != null && this.customers.stream() + .anyMatch(c -> uuid.equals(c.getId())); } public Optional findByPhoneNumber(String phoneNumber) { return this.customers.stream() - .filter(customer -> customer.getPhoneNumber().equals(phoneNumber)) - .findFirst(); + .filter(c -> c.getPhoneNumber().equals(phoneNumber)) + .findFirst(); } public void delete(Customer customer) { 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..347902e --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverter.java @@ -0,0 +1,96 @@ +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("Cette classe utilitaire ne doit pas être instanciée"); + } + + 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 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<>()); + } + + 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 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); + } +} 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..78dda69 --- /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); + } +} 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..ab59e54 --- /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); + } +} 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..2609b42 --- /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); + } +} 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..6f0a48f --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundException.java @@ -0,0 +1,7 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +public class OrderNotFoundException extends RuntimeException { + public OrderNotFoundException(String message) { + super(message); + } +} 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..fed6f4a --- /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 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 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 List findByCustomerId(UUID customerId) { + return this.orders.stream() + .filter(order -> Objects.equals(order.getCustomerId(), customerId)) + .toList(); + } + + public List findAll() { + return orders; + } + + public void deleteAll() { + orders.clear(); + } + + public void delete(Order order) { + this.orders.remove(order); + } +} 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..71eca46 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCase.java @@ -0,0 +1,156 @@ +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.order.OrderDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.order.PaymentMethod; +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 fr.iut_fbleau.but3.dev62.mylibrary.customer.entity.Customer; + +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)); + + // Pré-chargement des livres pour éviter les appels multiples + Map books = orderInfo.getOrderLines().stream() + .map(line -> bookRepository.findByIsbn(line.getBookId()) + .orElseThrow(() -> new BookNotFoundException("Le livre avec l'ISBN " + line.getBookId() + " n'a pas été trouvé"))) + .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(); + } + + private void validateOrderInfo(OrderInfo orderInfo) throws NotValidOrderException { + if (orderInfo == null) { + throw new NotValidOrderException("La commande ne peut pas être nulle"); + } + if (orderInfo.getOrderLines() == null || orderInfo.getOrderLines().isEmpty()) { + throw new OrderQuantityException("La commande doit contenir au moins un livre"); + } + if (orderInfo.getAddress() == null) { + throw new NotValidOrderException("L'adresse de livraison est obligatoire"); + } + } + + private PaymentMethod parsePaymentMethod(String paymentMethod) { + try { + return PaymentMethod.valueOf(paymentMethod); + } catch (IllegalArgumentException | NullPointerException e) { + throw new InvalidPaymentMethodException("Le mode de paiement " + paymentMethod + " n'est pas valide"); + } + } + + private void validateOrderLines(OrderInfo orderInfo, Map books) { + orderInfo.getOrderLines().forEach(line -> { + if (line.getQuantity() <= 0) { + throw new OrderQuantityException("La quantité doit être supérieure à 0 pour le livre " + line.getBookId()); + } + var book = books.get(line.getBookId()); + if (book.getStock() < line.getQuantity()) { + throw new OrderQuantityException("Stock insuffisant pour le livre " + line.getBookId()); + } + }); + } + + private double calculateTotalPrice(OrderInfo orderInfo, Map books) { + return orderInfo.getOrderLines().stream() + .mapToDouble(line -> books.get(line.getBookId()).getPrice() * line.getQuantity()) + .sum(); + } + + private double calculateTotalPriceToPay(OrderInfo orderInfo, Map books) { + // Appliquer ici les éventuelles remises + return calculateTotalPrice(orderInfo, books); + } + + private UUID parseOrderId(String orderId) { + try { + return UUID.fromString(orderId); + } catch (IllegalArgumentException e) { + throw new OrderNotFoundException("La commande avec l'ID " + orderId + " n'a pas été trouvée"); + } + } + + public OrderDTO findOrderById(String orderId) { + UUID uuid = parseOrderId(orderId); + return orderRepository.findById(uuid) + .map(OrderConverter::toDTO) + .orElseThrow(() -> new OrderNotFoundException("La commande n'a pas été trouvée")); + } + + 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 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 void handleLoyaltyPointsPayment(Customer customer, double totalPrice) throws IllegalCustomerPointException { + int requiredPoints = (int) (totalPrice * 100); + if (customer.getLoyaltyPoints() < requiredPoints) { + throw new IllegalOrderPointException("Points de fidélité insuffisants pour payer la commande"); + } + customer.removeLoyaltyPoints(requiredPoints); + customerRepository.save(customer); + } +} \ No newline at end of file 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..eb7506c --- /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("La commande ne peut pas être nulle"); + } + validateAddress(order); + validateOrderLines(order); + validatePrices(order); + validatePaymentMethod(order); + } + + private static void validateAddress(Order order) { + Address address = order.getShippingAddress(); + if (address == null) { + throw new NotValidOrderException("L'adresse de livraison est obligatoire"); + } + + if (isNullOrBlank(address.getStreet()) || + isNullOrBlank(address.getCity()) || + isNullOrBlank(address.getPostalCode()) || + isNullOrBlank(address.getCountry())) { + throw new NotValidOrderException("Tous les champs de l'adresse sont obligatoires"); + } + } + + private static void validateOrderLines(Order order) { + if (order.getOrderLines() == null || order.getOrderLines().isEmpty()) { + throw new NotValidOrderException("La commande doit contenir au moins une ligne"); + } + + order.getOrderLines().forEach(line -> { + if (line.getQuantity() <= 0) { + throw new NotValidOrderException("La quantité doit être positive"); + } + }); + } + + private static void validatePrices(Order order) { + if (order.getTotalPrice() <= 0) { + throw new NotValidOrderException("Le prix total doit être positif"); + } + if (order.getTotalPriceToPay() <= 0) { + throw new NotValidOrderException("Le prix total à payer doit être positif"); + } + if (order.getTotalPriceToPay() > order.getTotalPrice()) { + throw new NotValidOrderException("Le prix à payer ne peut pas être supérieur au prix total"); + } + } + + private static void validatePaymentMethod(Order order) { + if (order.getPaymentMethod() == null) { + throw new NotValidOrderException("Le mode de paiement ne peut pas être nul"); + } + } + + 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..0225291 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCase.java @@ -0,0 +1,68 @@ +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 submitReview(Review review) throws CustomerNotFoundException { + ReviewValidator.validate(review); + + if (!bookRepository.existsByIsbn(String.valueOf(review.getIsbn()))) { + throw new BookNotFoundException("Book not found: " + review.getIsbn()); + } + + if (!customerRepository.existsById(review.getCustomerId())) { + throw new CustomerNotFoundException(review.getCustomerId()); + } + + if (reviewRepository.existsByCustomerIdAndIsbn(review.getCustomerId(), review.getIsbn())) { + throw new ReviewAlreadyExistsException("Review already exists for this customer and book."); + } + + reviewRepository.save(review); + } + + public List getReviewsByBook(long isbn) { + return reviewRepository.findByIsbn(isbn); + } + + 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("Review not found for this customer and book."); + } + reviewRepository.update(review); + } + + public void deleteReview(long isbn, UUID customerId) { + if (!reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)) { + throw new ReviewNotFoundException("Review not found for this customer and book."); + } + reviewRepository.deleteByCustomerIdAndIsbn(customerId, 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..ace3c79 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidator.java @@ -0,0 +1,23 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.review.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; + +public class ReviewValidator { + public static void validate(Review review) { + if (review == null) { + throw new IllegalArgumentException("Review cannot be null"); + } + if (review.getRating() < 1 || review.getRating() > 5) { + throw new fr.iut_fbleau.but3.dev62.mylibrary.review.exception.InvalidReviewRatingException("Rating must be between 1 and 5"); + } + if (review.getComment() == null || review.getComment().trim().isEmpty()) { + throw new IllegalArgumentException("Comment cannot be empty"); + } + if (review.getCustomerId() == null) { + throw new IllegalArgumentException("CustomerId cannot be null"); + } + if (review.getIsbn() <= 0) { + throw new IllegalArgumentException("ISBN must be positive"); + } + } +} 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..d7fef2b --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/repository/SubscriptionRepository.java @@ -0,0 +1,42 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.customer.entity.Customer; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity.Subscription; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import lombok.NoArgsConstructor; + +public class SubscriptionRepository { + private final List subscriptions = new ArrayList<>(); + + public List findAll() { + return subscriptions; + } + + public Subscription save(Subscription newSubscription) { + Optional optionalSubscriptionWithSameId = this.findByCustomerId(newSubscription.getCustomerId()); + optionalSubscriptionWithSameId.ifPresentOrElse(subscriptions::remove, newSubscription::setRandomUUID); + this.subscriptions.add(newSubscription); + return newSubscription; + } + + public Optional findByCustomerId(UUID uuid) { + return this.subscriptions.stream() + .filter(subscription -> subscription.getCustomerId().equals(uuid)) + .findFirst(); + } + + public Optional findById(UUID id) { + return this.subscriptions.stream() + .filter(subscription -> subscription.getId().equals(id)) + .findFirst(); + } + + public boolean existsById(UUID uuid) { + return this.subscriptions.stream() + .anyMatch(subscription -> subscription.getId().equals(uuid)); + } + +} 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..a8c5e24 --- /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.SubscriptionNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.exception.NotValidSubscriptionException; +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 { + + private final SubscriptionRepository subscriptionRepository; + + public SubscriptionUseCase(SubscriptionRepository subscriptionRepository) { + this.subscriptionRepository = subscriptionRepository; + } + + public UUID registerSubscription(SubscriptionInfo newSubscription) throws NotValidSubscriptionException { + SubscriptionValidator.validate(newSubscription); + Subscription subscriptionToRegister = SubscriptionConverter.toDomain(newSubscription); + Subscription subscriptionToRegistered = subscriptionRepository.save(subscriptionToRegister); + if (subscriptionToRegistered.getDuration() <= 0) { + throw new NotValidSubscriptionException("Duration must be positive"); + } + return subscriptionToRegistered.getId(); + } + + public Optional findSubscriptionByCustomerId(UUID customerId) { + Optional optionalSubscription = subscriptionRepository.findByCustomerId(customerId); + return optionalSubscription.map(SubscriptionConverter::toDTO); + } + + private boolean getSubscriptionIfDoesNotExistThrowSubscriptionNotFoundException(UUID uuid) + throws SubscriptionNotFoundException { + if (subscriptionRepository.existsById(uuid)) { + throw new SubscriptionNotFoundException(uuid); + } + return subscriptionRepository.existsById(uuid); + } + +} 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..07de674 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/validator/SubscriptionValidator.java @@ -0,0 +1,44 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.customer.CustomerInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.NotValidCustomerException; +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.exception.NotValidSubscriptionException; + +public class SubscriptionValidator { + + public static final String CUSTOMER_ID_CANNOT_BE_NULL = "Customer ID cannot be null"; + public static final String DURATION_CANNOT_BE_NULL = "Duration is not valid"; + public static final String PAYMENT_METHOD_CANNOT_BE_BLANK = "Payment Method cannot be blank"; + + + private SubscriptionValidator() { + + } + + public static void validate(SubscriptionInfo newSubscription) throws NotValidSubscriptionException { + validateCustomerId(newSubscription); + validateDuration(newSubscription); + validatePaymentMethod(newSubscription); + } + + private static void validateCustomerId(SubscriptionInfo newSubscription) throws NotValidSubscriptionException { + if (newSubscription.customerId() == null) { + throw new NotValidSubscriptionException(CUSTOMER_ID_CANNOT_BE_NULL); + } + } + + private static void validateDuration(SubscriptionInfo newSubscription) throws NotValidSubscriptionException { + if (newSubscription.duration() == null) { + throw new NotValidSubscriptionException(DURATION_CANNOT_BE_NULL); + } + } + + private static void validatePaymentMethod(SubscriptionInfo newSubscription) throws NotValidSubscriptionException { + if (newSubscription.paymentMethod().isBlank()) { + throw new NotValidSubscriptionException(PAYMENT_METHOD_CANNOT_BE_BLANK); + } + } + +} 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..ef43cb8 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/converter/BookConverterTest.java @@ -0,0 +1,149 @@ +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.Category; +import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Tests unitaires du BookConverter") +class BookConverterTest { + + @Nested + @DisplayName("Tests de la méthode toDomain()") + class ToDomainTests { + + @Test + @DisplayName("Doit convertir BookInfo en Book (objet domaine) avec les valeurs par défaut") + void shouldConvertBookInfoToDomain() { + // Given + BookInfo info = new BookInfo( + "1234567890123", + "Titre", + "Auteur", + "Editeur", + LocalDate.of(2020, 1, 1), + 19.99, + 10, + List.of(Category.FICTION), + "Description", + "Francais" + ); + + // When + Book result = BookConverter.toDomain(info); + + // Then + 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()); + } + } + + @Nested + @DisplayName("Tests de la méthode toDTO()") + class ToDTOTests { + + @Test + @DisplayName("Doit convertir Book (domaine) en BookDTO avec tous les champs correctement mappés") + void shouldConvertBookToDTO() { + Book book = Book.builder() + .isbn("9876543210123") + .title("Titre2") + .author("Auteur2") + .publisher("Editeur2") + .publicationDate(LocalDate.of(2021, 5, 10)) + .price(25.5) + .stock(5) + .categories(List.of(Category.FANTASY)) + .description("Desc2") + .language("Anglais") + .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("Doit gérer correctement les valeurs null lors de la 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()); + } + + @Test + @DisplayName("Doit préserver les chaînes vides lors de la conversion") + void shouldPreserveEmptyStrings() { + BookInfo info = new BookInfo( + "1111111111111", + "", + "", + "", + LocalDate.of(2022, 2, 2), + 0.0, + 0, + Collections.emptyList(), + "", + "" + ); + + Book domainResult = BookConverter.toDomain(info); + BookDTO dtoResult = BookConverter.toDTO(domainResult); + + assertEquals("", dtoResult.getTitle()); + assertEquals("", dtoResult.getAuthor()); + assertEquals("", dtoResult.getPublisher()); + assertEquals("", dtoResult.getDescription()); + assertEquals("", dtoResult.getLanguage()); + } +} \ No newline at end of file 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..257a3ef --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/entity/BookTest.java @@ -0,0 +1,128 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.entity; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.Category; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("Tests unitaires de la classe Book") +class BookTest { + + @Test + @DisplayName("Le builder doit créer une instance Book valide") + void testBookBuilder() { + String isbn = "9781234567890"; + String title = "Les Mysteres de l'IA"; + String author = "Jean Dupont"; + String publisher = "TechEditions"; + LocalDate publicationDate = LocalDate.of(2024, 1, 15); + double price = 29.99; + int stock = 10; + List categories = List.of(Category.SCIENCE); + String description = "Un livre fascinant sur l'IA."; + String language = "Francais"; + + 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("Tests sur la gestion du stock") + class StockTests { + + @Test + @DisplayName("addStock doit correctement incrémenter le stock") + void testAddStock() { + Book book = Book.builder().stock(10).build(); + int toAdd = 5; + int expected = 15; + + book.addStock(toAdd); + + assertEquals(expected, book.getStock()); + } + + @Test + @DisplayName("addStock doit gérer l'ajout de zéro correctement") + void testAddZeroStock() { + Book book = Book.builder().stock(10).build(); + + book.addStock(0); + + assertEquals(10, book.getStock()); + } + + @Test + @DisplayName("removeStock doit correctement décrémenter le stock") + void testRemoveStock() { + Book book = Book.builder().stock(10).build(); + int toRemove = 4; + int expected = 6; + + book.removeStock(toRemove); + + assertEquals(expected, book.getStock()); + } + + @Test + @DisplayName("removeStock doit permettre de retirer tout le stock") + void testRemoveAllStock() { + Book book = Book.builder().stock(10).build(); + + book.removeStock(10); + + assertEquals(0, book.getStock()); + } + + @Test + @DisplayName("removeStock doit lever une exception si on retire plus que le stock disponible") + void testRemoveTooMuchStock() { + Book book = Book.builder().stock(10).build(); + int toRemove = 15; + + Exception exception = assertThrows( + IllegalArgumentException.class, + () -> book.removeStock(toRemove) + ); + + assertEquals(10, book.getStock()); + assertTrue(exception.getMessage().contains(String.valueOf(toRemove))); + } + + @Test + @DisplayName("removeStock doit gérer le retrait de zéro correctement") + void testRemoveZeroStock() { + Book book = Book.builder().stock(10).build(); + + book.removeStock(0); + + assertEquals(10, book.getStock()); + } + } +} \ No newline at end of file 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..5d6bc99 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/BookNotFoundExceptionTest.java @@ -0,0 +1,45 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.exception; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class BookNotFoundExceptionTest { + + @Test + @DisplayName("Le message de l'exception doit contenir l'ISBN fourni") + void testExceptionMessageContainsIsbn() { + String isbn = "9781234567890"; + + BookNotFoundException exception = new BookNotFoundException(isbn); + + String expectedMessage = String.format("The book with isbn %s does not exist", isbn); + assertEquals(expectedMessage, exception.getMessage()); + } + + @Test + @DisplayName("L'exception doit utiliser le bon format de message constant") + void testExceptionUsesConstantMessageFormat() { + String isbn = "9781234567890"; + + BookNotFoundException exception = new BookNotFoundException(isbn); + + String expectedFormatWithPlaceholder = "The book with isbn {0} does not exist"; + assertEquals(BookNotFoundException.THE_BOOK_WITH_ISBN_DOES_NOT_EXIST_MESSAGE, expectedFormatWithPlaceholder); + assertTrue(exception.getMessage().contains(isbn)); + } + + @Test + @DisplayName("L'exception doit pouvoir être levée et attrapée correctement") + void testExceptionCanBeThrownAndCaught() { + String isbn = "9781234567890"; + + try { + throw new BookNotFoundException(isbn); + } catch (BookNotFoundException e) { + String expectedMessage = String.format("The book with isbn %s does not exist", isbn); + assertEquals(expectedMessage, e.getMessage()); + } + } +} \ No newline at end of file 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..4c312fa --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/NotValidBookExceptionTest.java @@ -0,0 +1,61 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.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.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class NotValidBookExceptionTest { + + @Test + @DisplayName("L'exception doit être créée avec le message fourni") + void testExceptionCreation() { + String errorMessage = "Les données du livre ne sont pas valides"; + + NotValidBookException exception = new NotValidBookException(errorMessage); + + assertEquals(errorMessage, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = { + "Le titre est obligatoire", + "L'ISBN est invalide", + "Le prix doit être positif", + "La quantité ne peut pas être négative" + }) + @DisplayName("L'exception doit gérer différents messages de validation") + void testExceptionWithDifferentMessages(String errorMessage) { + NotValidBookException exception = new NotValidBookException(errorMessage); + + assertEquals(errorMessage, exception.getMessage()); + } + + @Test + @DisplayName("L'exception doit pouvoir être levée et attrapée correctement") + void testExceptionCanBeThrownAndCaught() { + String errorMessage = "Champ requis manquant"; + + Exception exception = assertThrows(NotValidBookException.class, () -> { + throw new NotValidBookException(errorMessage); + }); + + assertEquals(errorMessage, exception.getMessage()); + } + + @Test + @DisplayName("L'exception doit être attrapable comme une Exception générale") + void testExceptionInheritance() { + String errorMessage = "Livre invalide"; + + try { + throw new NotValidBookException(errorMessage); + } catch (Exception e) { + assertEquals(NotValidBookException.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/book/repository/BookRepositoryTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/repository/BookRepositoryTest.java new file mode 100644 index 0000000..73aa8c0 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/repository/BookRepositoryTest.java @@ -0,0 +1,205 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; +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 static org.junit.jupiter.api.Assertions.*; + +class BookRepositoryTest { + + private BookRepository repository; + private Book book1; + private Book book2; + + @BeforeEach + void setUp() { + repository = new BookRepository(); + + book1 = Book.builder() + .isbn("9780132350884") + .title("Clean Code") + .author("Robert C. Martin") + .stock(10) + .price(35.0) + .build(); + + book2 = Book.builder() + .isbn("9780134685991") + .title("Effective Java") + .author("Joshua Bloch") + .stock(5) + .price(40.0) + .build(); + } + + @Test + @DisplayName("Le repository nouvellement créé doit être vide") + void testNewRepositoryIsEmpty() { + List books = repository.findAll(); + + assertTrue(books.isEmpty()); + assertEquals(0, books.size()); + } + + @Nested + @DisplayName("Opérations de sauvegarde") + class SaveOperations { + + @Test + @DisplayName("Save doit ajouter un nouveau livre") + void testSaveNewBook() { + Book savedBook = repository.save(book1); + + assertEquals(1, repository.findAll().size()); + assertEquals(book1.getIsbn(), savedBook.getIsbn()); + assertEquals(book1.getTitle(), savedBook.getTitle()); + } + + @Test + @DisplayName("Save doit mettre à jour un livre existant avec le même ISBN") + void testSaveUpdatesExistingBook() { + repository.save(book1); + + String isbn = book1.getIsbn(); + Book updatedBook = Book.builder() + .isbn(isbn) + .title("Clean Code (2nd Edition)") + .author("Robert C. Martin") + .stock(15) + .price(38.0) + .build(); + + Book savedBook = repository.save(updatedBook); + + assertEquals(1, repository.findAll().size()); + assertEquals(isbn, savedBook.getIsbn()); + assertEquals("Clean Code (2nd Edition)", savedBook.getTitle()); + assertEquals(15, savedBook.getStock()); + assertEquals(38.0, savedBook.getPrice()); + } + + @Test + @DisplayName("Save de plusieurs livres doit tous les ajouter") + void testSaveMultipleBooks() { + repository.save(book1); + repository.save(book2); + + List books = repository.findAll(); + + assertEquals(2, books.size()); + assertTrue(books.contains(book1)); + assertTrue(books.contains(book2)); + } + } + + @Nested + @DisplayName("Opérations de recherche") + class FindOperations { + + @BeforeEach + void setUpBooks() { + repository.save(book1); + repository.save(book2); + } + + @Test + @DisplayName("FindAll doit retourner tous les livres") + void testFindAll() { + List books = repository.findAll(); + + assertEquals(2, books.size()); + assertTrue(books.contains(book1)); + assertTrue(books.contains(book2)); + } + + @Test + @DisplayName("FindByIsbn doit retourner le livre correspondant à l'ISBN") + void testFindByIsbn() { + Optional foundBook = repository.findByIsbn(book1.getIsbn()); + + assertTrue(foundBook.isPresent()); + assertEquals(book1.getTitle(), foundBook.get().getTitle()); + assertEquals(book1.getAuthor(), foundBook.get().getAuthor()); + } + + @Test + @DisplayName("FindByIsbn doit retourner un Optional vide si l'ISBN n'existe pas") + void testFindByIsbnNotFound() { + Optional foundBook = repository.findByIsbn("0"); + + assertTrue(foundBook.isEmpty()); + } + + @Test + @DisplayName("ExistsByIsbn doit retourner true si l'ISBN existe") + void testExistsByIsbnExists() { + boolean exists = repository.existsByIsbn(book1.getIsbn()); + + assertTrue(exists); + } + + @Test + @DisplayName("ExistsByIsbn doit retourner false si l'ISBN n'existe pas") + void testExistsByIsbnNotExists() { + boolean exists = repository.existsByIsbn("0"); + + assertFalse(exists); + } + } + + @Nested + @DisplayName("Opérations de suppression") + class DeleteOperations { + + @BeforeEach + void setUpBooks() { + repository.save(book1); + repository.save(book2); + } + + @Test + @DisplayName("Delete doit supprimer le livre spécifié") + void testDelete() { + repository.delete(book1); + + List books = repository.findAll(); + + assertEquals(1, books.size()); + assertFalse(books.contains(book1)); + assertTrue(books.contains(book2)); + } + + @Test + @DisplayName("DeleteAll doit supprimer tous les livres") + void testDeleteAll() { + repository.deleteAll(); + + List books = repository.findAll(); + + assertTrue(books.isEmpty()); + assertEquals(0, books.size()); + } + + @Test + @DisplayName("Delete ne doit pas lever d'exception si le livre n'existe pas") + void testDeleteNonExistentBook() { + Book nonExistentBook = Book.builder() + .isbn("0") + .title("Non Existent") + .author("Nobody") + .stock(0) + .price(0.0) + .build(); + + assertDoesNotThrow(() -> repository.delete(nonExistentBook)); + + assertEquals(2, repository.findAll().size()); + } + } +} \ No newline at end of file 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..82bdb19 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCaseTest.java @@ -0,0 +1,279 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.usecase; + +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 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.time.LocalDate; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class BookUseCaseTest { + + @Mock + private BookRepository bookRepository; + + @InjectMocks + private BookUseCase bookUseCase; + + private String isbn; + private Book testBook; + private BookInfo validBookInfo; + + @BeforeEach + void setUp() { + isbn = "9780132350884"; + testBook = Book.builder() + .isbn(isbn) + .title("Clean Code") + .author("Robert C. Martin") + .publisher("Prentice Hall") + .publicationDate(LocalDate.of(2008, 8, 1)) + .price(35.0) + .stock(10) + .categories(List.of(Category.SCIENCE)) + .description("A book about writing clean code.") + .language("Francais") + .build(); + + validBookInfo = new BookInfo( + isbn, + "Clean Code", + "Robert C. Martin", + "Prentice Hall", + LocalDate.of(2008, 8, 1), + 35.0, + 10, + List.of(Category.SCIENCE), + "A book about writing clean code.", + "Francais" + ); + } + + @Nested + @DisplayName("Register book tests") + class RegisterBookTests { + + @Test + @DisplayName("Should register book when valid data is provided") + void testRegisterBookWithValidData() throws NotValidBookException { + when(bookRepository.save(any(Book.class))).thenReturn(testBook); + + String registeredIsbn = bookUseCase.registerBook(validBookInfo); + + assertNotNull(registeredIsbn); + assertEquals(isbn, registeredIsbn); + verify(bookRepository, times(1)).save(any(Book.class)); + } + + @Test + @DisplayName("Should throw exception when book data is not valid") + void testRegisterBookWithInvalidData() { + BookInfo invalidBookInfo = new BookInfo( + null, "", "", "", null, 0.0, 0, Collections.emptyList(), "", "" + ); + + assertThrows(NotValidBookException.class, + () -> bookUseCase.registerBook(invalidBookInfo)); + + verify(bookRepository, never()).save(any(Book.class)); + } + } + + @Nested + @DisplayName("Find book tests") + class FindBookTests { + + @Test + @DisplayName("Should return book when ISBN exists") + void testFindBookByIsbn() { + when(bookRepository.findByIsbn(isbn)).thenReturn(Optional.of(testBook)); + + BookDTO foundBook = bookUseCase.findBookByIsbn(isbn); + + assertNotNull(foundBook); + assertEquals(isbn, foundBook.getIsbn()); + verify(bookRepository, times(1)).findByIsbn(isbn); + } + + @Test + @DisplayName("Should throw exception when ISBN doesn't exist") + void testFindBookByIsbnNotFound() { + when(bookRepository.findByIsbn("0")).thenReturn(Optional.empty()); + + assertThrows(BookNotFoundException.class, + () -> bookUseCase.findBookByIsbn("0")); + + verify(bookRepository, times(1)).findByIsbn("0"); + } + } + + @Nested + @DisplayName("Update book tests") + class UpdateBookTests { + + @Test + @DisplayName("Should update book when valid data is provided") + void testUpdateBookWithValidData() throws BookNotFoundException, NotValidBookException { + when(bookRepository.findByIsbn(isbn)).thenReturn(Optional.of(testBook)); + + Book updatedBook = Book.builder() + .isbn(isbn) + .title("Clean Code (2nd Edition)") + .author("Robert C. Martin") + .publisher("Prentice Hall") + .publicationDate(LocalDate.of(2008, 8, 1)) + .price(38.0) + .stock(15) + .categories(List.of(Category.SCIENCE)) + .description("Updated description") + .language("Francais") + .build(); + + when(bookRepository.save(any(Book.class))).thenReturn(updatedBook); + + BookInfo updateInfo = new BookInfo( + isbn, + "Clean Code (2nd Edition)", + "Robert C. Martin", + "Prentice Hall", + LocalDate.of(2008, 8, 1), + 38.0, + 15, + List.of(Category.SCIENCE), + "Updated description", + "Francais" + ); + + BookDTO result = bookUseCase.updateBook(isbn, updateInfo); + + assertNotNull(result); + assertEquals("Clean Code (2nd Edition)", result.getTitle()); + assertEquals(38.0, result.getPrice()); + verify(bookRepository, times(1)).findByIsbn(isbn); + verify(bookRepository, times(1)).save(any(Book.class)); + } + + @Test + @DisplayName("Should throw exception when book ISBN doesn't exist") + void testUpdateBookNotFound() { + when(bookRepository.findByIsbn("0")).thenReturn(Optional.empty()); + + BookInfo updateInfo = validBookInfo; + + assertThrows(BookNotFoundException.class, + () -> bookUseCase.updateBook("0", updateInfo)); + + verify(bookRepository, times(1)).findByIsbn("0"); + verify(bookRepository, never()).save(any(Book.class)); + } + + @Test + @DisplayName("Should throw exception when update data is not valid") + void testUpdateBookWithInvalidData() { + BookInfo invalidUpdateInfo = new BookInfo( + null, "", "", "", null, 0.0, 0, Collections.emptyList(), "", "" + ); + + assertThrows(NotValidBookException.class, + () -> bookUseCase.updateBook(isbn, invalidUpdateInfo)); + + verify(bookRepository, never()).findByIsbn(any(String.class)); + verify(bookRepository, never()).save(any(Book.class)); + } + } + + @Nested + @DisplayName("Delete book tests") + class DeleteBookTests { + + @Test + @DisplayName("Should delete book when ISBN exists") + void testDeleteBook() throws BookNotFoundException { + when(bookRepository.findByIsbn(isbn)).thenReturn(Optional.of(testBook)); + doNothing().when(bookRepository).delete(testBook); + + bookUseCase.deleteBook(isbn); + + verify(bookRepository, times(1)).findByIsbn(isbn); + verify(bookRepository, times(1)).delete(testBook); + } + + @Test + @DisplayName("Should throw exception when book ISBN doesn't exist") + void testDeleteBookNotFound() { + when(bookRepository.findByIsbn("0")).thenReturn(Optional.empty()); + + assertThrows(BookNotFoundException.class, + () -> bookUseCase.deleteBook("0")); + + verify(bookRepository, times(1)).findByIsbn("0"); + verify(bookRepository, never()).delete(any(Book.class)); + } + } + + @Nested + @DisplayName("Stock management tests") + class StockManagementTests { + + @Test + @DisplayName("Should add stock correctly") + void testAddStock() { + when(bookRepository.findByIsbn(isbn)).thenReturn(Optional.of(testBook)); + when(bookRepository.save(any(Book.class))).thenReturn(testBook); + + int result = bookUseCase.addStock(isbn, 5); + + assertEquals(15, result); + verify(bookRepository, times(1)).findByIsbn(isbn); + verify(bookRepository, times(1)).save(testBook); + } + + @Test + @DisplayName("Should remove stock correctly") + void testRemoveStock() { + when(bookRepository.findByIsbn(isbn)).thenReturn(Optional.of(testBook)); + when(bookRepository.save(any(Book.class))).thenReturn(testBook); + + int result = bookUseCase.removeStock(isbn, 5); + + assertEquals(5, result); + verify(bookRepository, times(1)).findByIsbn(isbn); + verify(bookRepository, times(1)).save(testBook); + } + } + + @Nested + @DisplayName("Search tests") + class SearchTests { + + @Test + @DisplayName("Should find books by title") + void testFindBooksByTitle() { + List books = List.of(testBook); + when(bookRepository.findByTitleContaining("Clean")).thenReturn(books); + + List result = bookUseCase.findBooksByTitleContaining("Clean"); + + assertEquals(1, result.size()); + assertEquals("Clean Code", result.get(0).getTitle()); + verify(bookRepository, times(1)).findByTitleContaining("Clean"); + } + } +} \ No newline at end of file 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..7f81bc4 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/validator/BookValidatorTest.java @@ -0,0 +1,259 @@ +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.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.time.LocalDate; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class BookValidatorTest { + + @Test + @DisplayName("Valide un livre avec des données correctes") + void testValidateValidBook() { + BookInfo validBook = new BookInfo( + "9780132350884", + "Clean Code", + "Robert C. Martin", + "Prentice Hall", + LocalDate.of(2008, 8, 1), + 35.0, + 10, + List.of(Category.SCIENCE), + "Un livre sur le clean code.", + "Francais" + ); + assertDoesNotThrow(() -> BookValidator.validate(validBook)); + } + + @Nested + @DisplayName("Validation de l'ISBN") + class IsbnValidationTests { + + @Test + @DisplayName("Doit lever une exception si l'ISBN est null") + void testIsbnNull() { + BookInfo book = new BookInfo( + null, "Titre", "Auteur", "Editeur", LocalDate.now(), 10.0, 1, + List.of(Category.FICTION), "Desc", "Francais" + ); + NotValidBookException ex = assertThrows( + NotValidBookException.class, + () -> BookValidator.validate(book) + ); + assertEquals(BookValidator.ISBN_CANNOT_BE_NULL, ex.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {"", "0", "-1", "123"}) + @DisplayName("Doit lever une exception si l'ISBN est invalide") + void testIsbnInvalid(String isbn) { + BookInfo book = new BookInfo( + isbn, "Titre", "Auteur", "Editeur", LocalDate.now(), 10.0, 1, + List.of(Category.FICTION), "Desc", "Francais" + ); + NotValidBookException ex = assertThrows( + NotValidBookException.class, + () -> BookValidator.validate(book) + ); + assertEquals(BookValidator.ISBN_IS_NOT_VALID, ex.getMessage()); + } + } + + @Nested + @DisplayName("Validation du titre") + class TitleValidationTests { + + @Test + @DisplayName("Doit lever une exception si le titre est vide") + void testTitleBlank() { + BookInfo book = new BookInfo( + "9780132350884", "", "Auteur", "Editeur", LocalDate.now(), 10.0, 1, + List.of(Category.FICTION), "Desc", "Francais" + ); + NotValidBookException ex = assertThrows( + NotValidBookException.class, + () -> BookValidator.validate(book) + ); + assertEquals(BookValidator.TITLE_CANNOT_BE_BLANK, ex.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {" ", "\t", "\n"}) + @DisplayName("Doit lever une exception si le titre ne contient que des espaces") + void testTitleWhitespace(String whitespace) { + BookInfo book = new BookInfo( + "9780132350884", whitespace, "Auteur", "Editeur", LocalDate.now(), 10.0, 1, + List.of(Category.FICTION), "Desc", "Francais" + ); + NotValidBookException ex = assertThrows( + NotValidBookException.class, + () -> BookValidator.validate(book) + ); + assertEquals(BookValidator.TITLE_CANNOT_BE_BLANK, ex.getMessage()); + } + } + + @Nested + @DisplayName("Validation de l'auteur") + class AuthorValidationTests { + + @Test + @DisplayName("Doit lever une exception si l'auteur est vide") + void testAuthorBlank() { + BookInfo book = new BookInfo( + "9780132350884", "Titre", "", "Editeur", LocalDate.now(), 10.0, 1, + List.of(Category.FICTION), "Desc", "Francais" + ); + NotValidBookException ex = assertThrows( + NotValidBookException.class, + () -> BookValidator.validate(book) + ); + assertEquals(BookValidator.AUTHOR_CANNOT_BE_BLANK, ex.getMessage()); + } + } + + @Nested + @DisplayName("Validation de l'éditeur") + class PublisherValidationTests { + + @Test + @DisplayName("Doit lever une exception si l'éditeur est vide") + void testPublisherBlank() { + BookInfo book = new BookInfo( + "9780132350884", "Titre", "Auteur", "", LocalDate.now(), 10.0, 1, + List.of(Category.FICTION), "Desc", "Francais" + ); + NotValidBookException ex = assertThrows( + NotValidBookException.class, + () -> BookValidator.validate(book) + ); + assertEquals(BookValidator.PUBLISHER_CANNOT_BE_BLANK, ex.getMessage()); + } + } + + @Nested + @DisplayName("Validation de la date de publication") + class PublicationDateValidationTests { + + @Test + @DisplayName("Doit lever une exception si la date de publication est null") + void testPublicationDateNull() { + BookInfo book = new BookInfo( + "9780132350884", "Titre", "Auteur", "Editeur", null, 10.0, 1, + List.of(Category.FICTION), "Desc", "Francais" + ); + NotValidBookException ex = assertThrows( + NotValidBookException.class, + () -> BookValidator.validate(book) + ); + assertEquals(BookValidator.PUBLICATION_DATE_CANNOT_BE_NULL, ex.getMessage()); + } + } + + @Nested + @DisplayName("Validation du prix") + class PriceValidationTests { + + @ParameterizedTest + @ValueSource(doubles = {0.0, -1.0, -10.5}) + @DisplayName("Doit lever une exception si le prix est négatif ou nul") + void testInvalidPrice(double price) { + BookInfo book = new BookInfo( + "9780132350884", "Titre", "Auteur", "Editeur", LocalDate.now(), price, 1, + List.of(Category.FICTION), "Desc", "Francais" + ); + NotValidBookException ex = assertThrows( + NotValidBookException.class, + () -> BookValidator.validate(book) + ); + assertEquals(BookValidator.PRICE_MUST_BE_POSITIVE, ex.getMessage()); + } + } + + @Nested + @DisplayName("Validation du stock") + class StockValidationTests { + + @ParameterizedTest + @ValueSource(ints = {-1, -10}) + @DisplayName("Doit lever une exception si le stock est négatif") + void testNegativeStock(int stock) { + BookInfo book = new BookInfo( + "9780132350884", "Titre", "Auteur", "Editeur", LocalDate.now(), 10.0, stock, + List.of(Category.FICTION), "Desc", "Francais" + ); + NotValidBookException ex = assertThrows( + NotValidBookException.class, + () -> BookValidator.validate(book) + ); + assertEquals(BookValidator.STOCK_CANNOT_BE_NEGATIVE, ex.getMessage()); + } + } + + @Nested + @DisplayName("Validation des catégories") + class CategoriesValidationTests { + + @Test + @DisplayName("Doit lever une exception si la liste des catégories est vide") + void testEmptyCategories() { + BookInfo book = new BookInfo( + "9780132350884", "Titre", "Auteur", "Editeur", LocalDate.now(), 10.0, 1, + Collections.emptyList(), "Desc", "Francais" + ); + NotValidBookException ex = assertThrows( + NotValidBookException.class, + () -> BookValidator.validate(book) + ); + assertEquals(BookValidator.CATEGORIES_CANNOT_BE_EMPTY, ex.getMessage()); + } + } + + @Nested + @DisplayName("Validation de la description") + class DescriptionValidationTests { + + @Test + @DisplayName("Doit lever une exception si la description est vide") + void testDescriptionBlank() { + BookInfo book = new BookInfo( + "9780132350884", "Titre", "Auteur", "Editeur", LocalDate.now(), 10.0, 1, + List.of(Category.FICTION), "", "Francais" + ); + NotValidBookException ex = assertThrows( + NotValidBookException.class, + () -> BookValidator.validate(book) + ); + assertEquals(BookValidator.DESCRIPTION_CANNOT_BE_BLANK, ex.getMessage()); + } + } + + @Nested + @DisplayName("Validation de la langue") + class LanguageValidationTests { + + @Test + @DisplayName("Doit lever une exception si la langue est vide") + void testLanguageBlank() { + BookInfo book = new BookInfo( + "9780132350884", "Titre", "Auteur", "Editeur", LocalDate.now(), 10.0, 1, + List.of(Category.FICTION), "Desc", "" + ); + NotValidBookException ex = assertThrows( + NotValidBookException.class, + () -> BookValidator.validate(book) + ); + assertEquals(BookValidator.LANGUAGE_CANNOT_BE_BLANK, ex.getMessage()); + } + } +} \ 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..60e37c0 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverterTest.java @@ -0,0 +1,134 @@ +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("OrderConverter Unit Tests") +class OrderConverterTest { + + @Nested + @DisplayName("toDomain() method tests") + class ToDomainTests { + + @Test + @DisplayName("Should convert OrderInfo to Order domain object with default values") + void shouldConvertOrderInfoToDomain() { + // Given + OrderInfo orderInfo = OrderInfo.builder() + .customerId(UUID.randomUUID().toString()) + .paymentMethod("CREDIT_CARD") + .orderLines(Collections.emptyList()) + .address(AddressDTO.builder().street("1 rue test").city("Paris").postalCode("75000").country("France").build()) + .build(); + + // When + Order result = OrderConverter.toDomain(orderInfo); + + // Then + assertNotNull(result); + assertEquals(UUID.fromString(orderInfo.getCustomerId()), result.getCustomerId()); + assertEquals(PaymentMethod.valueOf(orderInfo.getPaymentMethod()), result.getPaymentMethod()); + assertNotNull(result.getOrderLines()); + assertNotNull(result.getShippingAddress()); + } + } + + @Nested + @DisplayName("toDTO() method tests") + class ToDTOTests { + + @Test + @DisplayName("Should convert Order domain object to OrderDTO with all fields mapped correctly") + void shouldConvertOrderToDTO() { + Order order = Order.builder() + .id(UUID.randomUUID()) + .customerId(UUID.randomUUID()) + .orderLines(new ArrayList<>()) + .shippingAddress(Address.builder() + .street("2 rue test") + .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 handle null values properly when converting between objects") + 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()); + } + + @Test + @DisplayName("Should preserve empty string values for optional 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, "L'adresse de livraison ne devrait pas être nulle"); + assertTrue(shippingAddress.getStreet().isEmpty(), "La rue devrait être une chaîne vide"); + assertTrue(shippingAddress.getCity().isEmpty(), "La ville devrait être une chaîne vide"); + assertTrue(shippingAddress.getPostalCode().isEmpty(), "Le code postal devrait être une chaîne vide"); + assertTrue(shippingAddress.getCountry().isEmpty(), "Le pays devrait être une chaîne vide"); + } +} 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..e96ab74 --- /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.Nested; +import org.junit.jupiter.api.Test; +import java.util.*; +import static org.junit.jupiter.api.Assertions.*; + +class OrderTest { + + @Test + @DisplayName("Builder doit créer une instance Order valide") + 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()); + } + + @Test + @DisplayName("On peut créer une commande avec une adresse de livraison") + void testWithShippingAddress() { + Address address = Address.builder() + .street("rue test") + .city("ville test") + .postalCode("00000") + .country("France") + .build(); + + Order order = Order.builder() + .shippingAddress(address) + .build(); + + assertEquals(address, order.getShippingAddress()); + } + + @Test + @DisplayName("On peut créer une commande avec des lignes de commande") + void testWithOrderLines() { + List lines = List.of( + OrderLine.builder() + .bookId("1") + .quantity(1) + .build() + ); + + Order order = Order.builder() + .orderLines(lines) + .build(); + + assertEquals(lines, order.getOrderLines()); + } + +} 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..6707e9b --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/IllegalOrderPointExceptionTest.java @@ -0,0 +1,21 @@ +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 = "Points de fidélité insuffisants"; + 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..4fda9b4 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundExceptionTest.java @@ -0,0 +1,21 @@ +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 = "Commande non trouvée"; + 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..ddbbffb --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderQuantityExceptionTest.java @@ -0,0 +1,21 @@ +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 = "Stock insuffisant"; + 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..c1f116d --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/WrongAddressExceptionTest.java @@ -0,0 +1,21 @@ +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 = "Adresse manquante"; + 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..eaf77ec --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepositoryTest.java @@ -0,0 +1,73 @@ +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.Nested; +import org.junit.jupiter.api.Test; +import java.util.*; +import static org.junit.jupiter.api.Assertions.*; + +class OrderRepositoryTest { + private OrderRepository repository; + + @BeforeEach + void setUp() { + repository = new OrderRepository(); + } + + @Test + @DisplayName("save() doit ajouter une commande et findById doit la retrouver") + 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("rue").city("ville").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("findAll() doit retourner toutes les commandes") + 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()); + } + + @Test + @DisplayName("delete() doit supprimer la commande") + void testDelete() { + Order order = Order.builder().id(UUID.randomUUID()).build(); + repository.save(order); + repository.delete(order); + assertFalse(repository.findById(order.getId()).isPresent()); + } + + @Test + @DisplayName("deleteAll() doit vider le 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()); + } +} 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..fd9d5d8 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCaseTest.java @@ -0,0 +1,165 @@ +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.OrderDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderLineDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.order.AddressDTO; +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.Nested; +import org.junit.jupiter.api.Test; +import java.time.LocalDate; +import java.util.*; +import static org.junit.jupiter.api.Assertions.*; + +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"; + // Ajout d'un client + Customer customer = Customer.builder() + .id(customerId) + .firstName("John") + .lastName("Doe") + .phoneNumber("0600000000") + .loyaltyPoints(100) + .build(); + customerRepository.save(customer); + + // Vérification que le client est bien sauvegardé + Optional savedCustomer = customerRepository.findById(customerId); + if (!savedCustomer.isPresent()) { + throw new RuntimeException("Le client n'a pas été correctement sauvegardé. ID: " + customerId); + } + + // Ajout d'un livre + BookInfo book = new BookInfo( + bookId, + "Titre", + "Auteur", + "Editeur", + LocalDate.now(), + 0.1, + 10, + List.of(Category.ROMANCE), + "Description", + "FR" + ); + bookRepository.save(BookConverter.toDomain(book)); + } + + @Test + @DisplayName("Créer une commande valide") + 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("string").city("string").postalCode("string").country("string").build()) + .paymentMethod("CREDIT_CARD") + .build(); + + UUID orderId = orderUseCase.createOrder(orderInfo); + assertNotNull(orderId); + + OrderDTO order = orderUseCase.findOrderById(String.valueOf(orderId)); + assertNotNull(order, "La commande créée devrait être trouvée"); + + assertEquals(0.1, order.getTotalPrice(), 0.0001); + assertEquals(customerId, order.getCustomerId()); + assertEquals(1, order.getOrderLines().size()); + } + + @Test + @DisplayName("Créer une commande sans adresse doit échouer") + 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("Créer une commande avec quantité négative doit échouer") + void testCreateOrderWithNegativeQuantity() { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(customerId.toString()) + .orderLines(List.of(OrderLineDTO.builder().bookId(bookId).quantity(-1).build())) + .address(AddressDTO.builder().street("string").city("string").postalCode("string").country("string").build()) + .paymentMethod("CREDIT_CARD") + .build(); + assertThrows(Exception.class, () -> orderUseCase.createOrder(orderInfo)); + } + + @Test + @DisplayName("Créer une commande sans livre doit échouer") + void testCreateOrderWithoutBook() { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(customerId.toString()) + .orderLines(new ArrayList<>()) + .address(AddressDTO.builder().street("string").city("string").postalCode("string").country("string").build()) + .paymentMethod("CREDIT_CARD") + .build(); + assertThrows(Exception.class, () -> orderUseCase.createOrder(orderInfo)); + } + + @Test + @DisplayName("Créer une commande avec un client inexistant doit échouer") + void testCreateOrderWithUnknownCustomer() { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(UUID.randomUUID().toString()) + .orderLines(List.of(OrderLineDTO.builder().bookId(bookId).quantity(1).build())) + .address(AddressDTO.builder().street("string").city("string").postalCode("string").country("string").build()) + .paymentMethod("CREDIT_CARD") + .build(); + assertThrows(Exception.class, () -> orderUseCase.createOrder(orderInfo)); + } + + @Test + @DisplayName("Créer une commande avec un livre inexistant doit échouer") + void testCreateOrderWithUnknownBook() { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(customerId.toString()) + .orderLines(List.of(OrderLineDTO.builder().bookId("999").quantity(1).build())) + .address(AddressDTO.builder().street("string").city("string").postalCode("string").country("string").build()) + .paymentMethod("CREDIT_CARD") + .build(); + assertThrows(Exception.class, () -> orderUseCase.createOrder(orderInfo)); + } + + @Test + @DisplayName("Créer une commande avec stock insuffisant doit échouer") + void testCreateOrderWithInsufficientStock() { + OrderInfo orderInfo = OrderInfo.builder() + .customerId(customerId.toString()) + .orderLines(List.of(OrderLineDTO.builder().bookId(bookId).quantity(100).build())) + .address(AddressDTO.builder().street("string").city("string").postalCode("string").country("string").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..236c32d --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/validator/OrderValidatorTest.java @@ -0,0 +1,264 @@ +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 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 Rue Test") + .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("Basic validation tests") + class BasicValidationTests { + @Test + @DisplayName("Should validate order with valid data") + void testValidateValidOrder() { + Order validOrder = createValidOrder(PaymentMethod.CREDIT_CARD); + assertDoesNotThrow( + () -> OrderValidator.validate(validOrder), + "La validation d'une commande valide ne devrait pas lever d'exception" + ); + } + + @Test + @DisplayName("Should validate order with loyalty points payment") + void testValidateValidLoyaltyOrder() { + Order validOrder = createValidOrder(PaymentMethod.LOYALTY_POINTS); + assertDoesNotThrow( + () -> OrderValidator.validate(validOrder), + "La validation d'une commande avec points de fidélité ne devrait pas lever d'exception" + ); + } + + @Test + @DisplayName("Should throw exception for null order") + void testValidateNullOrder() { + Exception exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(null), + "La validation d'une commande null devrait lever une exception" + ); + assertEquals("La commande ne peut pas être nulle", exception.getMessage()); + } + } + + @Nested + @DisplayName("Order lines validation") + class OrderLinesValidationTests { + @Test + @DisplayName("Should throw exception for null or empty order lines") + void testOrderLinesNullOrEmpty() { + Order orderWithNullLines = createValidOrder(PaymentMethod.CREDIT_CARD); + orderWithNullLines = Order.builder() + .id(validId) + .customerId(validCustomerId) + .orderLines(null) + .totalPrice(VALID_PRICE) + .totalPriceToPay(VALID_PRICE) + .shippingAddress(validAddress) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + + Order finalOrderWithNullLines = orderWithNullLines; + Exception nullException = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(finalOrderWithNullLines), + "La validation d'une commande avec des lignes null devrait lever une exception" + ); + assertEquals("La commande doit contenir au moins une ligne", nullException.getMessage()); + + Order orderWithEmptyLines = createValidOrder(PaymentMethod.CREDIT_CARD); + orderWithEmptyLines = Order.builder() + .id(validId) + .customerId(validCustomerId) + .orderLines(Collections.emptyList()) + .totalPrice(VALID_PRICE) + .totalPriceToPay(VALID_PRICE) + .shippingAddress(validAddress) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + + Order finalOrderWithEmptyLines = orderWithEmptyLines; + Exception emptyException = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(finalOrderWithEmptyLines), + "La validation d'une commande avec une liste vide devrait lever une exception" + ); + assertEquals("La commande doit contenir au moins une ligne", emptyException.getMessage()); + } + + @ParameterizedTest + @ValueSource(ints = {0, -1, -10}) + @DisplayName("Should throw exception for invalid quantities") + void testInvalidQuantities(int invalidQuantity) { + Order orderWithInvalidQuantity = Order.builder() + .id(validId) + .customerId(validCustomerId) + .orderLines(List.of(OrderLine.builder() + .bookId("ISBN123") + .quantity(invalidQuantity) + .build())) + .totalPrice(VALID_PRICE) + .totalPriceToPay(VALID_PRICE) + .shippingAddress(validAddress) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + + Exception exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(orderWithInvalidQuantity), + "La validation d'une commande avec une quantité invalide devrait lever une exception" + ); + assertEquals("La quantité doit être positive", exception.getMessage()); + } + } + + @Nested + @DisplayName("Address validation") + class AddressValidationTests { + @Test + @DisplayName("Should throw exception for null address") + void testNullAddress() { + Order orderWithNullAddress = createValidOrder(PaymentMethod.CREDIT_CARD); + orderWithNullAddress = Order.builder() + .id(validId) + .customerId(validCustomerId) + .orderLines(validOrderLines) + .totalPrice(VALID_PRICE) + .totalPriceToPay(VALID_PRICE) + .shippingAddress(null) + .paymentMethod(PaymentMethod.CREDIT_CARD) + .build(); + + Order finalOrderWithNullAddress = orderWithNullAddress; + Exception exception = assertThrows( + NotValidOrderException.class, + () -> OrderValidator.validate(finalOrderWithNullAddress), + "La validation d'une commande avec une adresse nulle devrait lever une exception" + ); + assertEquals("L'adresse de livraison est obligatoire", 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 orderWithInvalidAddress = 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(orderWithInvalidAddress), + "La validation d'une commande avec une adresse vide devrait lever une exception" + ); + assertEquals("Tous les champs de l'adresse sont obligatoires", 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 orderWithInvalidPrice = 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(orderWithInvalidPrice), + "La validation d'une commande avec un prix total invalide devrait lever une exception" + ); + assertEquals("Le prix total doit être positif", exception.getMessage()); + } + + @Test + @DisplayName("Should throw exception when price to pay is greater than total price") + void testPriceToPayGreaterThanTotalPrice() { + Order orderWithInvalidPriceToPay = 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(orderWithInvalidPriceToPay), + "La validation d'une commande avec un prix à payer supérieur au prix total devrait lever une exception" + ); + assertEquals("Le prix à payer ne peut pas être supérieur au prix total", 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..f1467ed --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverterTest.java @@ -0,0 +1,45 @@ +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 testToDto() { + 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()); + } + + @Test + void testToDomain() { + ReviewInfo info = ReviewInfo.builder() + .customerId("11111111-1111-1111-1111-111111111111") + .isbn(9781234567890L) + .rating(4) + .comment("Bon livre") + .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("Bon livre", review.getComment()); + } +} 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..a5deb46 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/ReviewTest.java @@ -0,0 +1,80 @@ +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 { + + @Test + @DisplayName("Builder doit créer une instance Review valide") + void testReviewBuilder() { + UUID customerId = UUID.randomUUID(); + long isbn = 9781234567890L; + int rating = 4; + String comment = "Super livre !"; + + 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()); + } + + @Nested + @DisplayName("Tests de validation de la note") + class RatingValidationTests { + @Test + @DisplayName("La note doit être comprise entre 1 et 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("La note inférieure à 1 doit lever une 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("La note supérieure à 5 doit lever une 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)); + } + } +} 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..be4752a --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/InvalidReviewRatingExceptionTest.java @@ -0,0 +1,14 @@ +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 testMessage() { + 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..bc7f804 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewAlreadyExistsExceptionTest.java @@ -0,0 +1,14 @@ +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 testMessage() { + 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..ded7356 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundExceptionTest.java @@ -0,0 +1,14 @@ +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 testMessage() { + 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..ffe2bf3 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepositoryTest.java @@ -0,0 +1,106 @@ +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.Nested; +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 et findAll doivent fonctionner") + 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("findByIsbn doit retourner les bons avis") + 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 doit retourner les bons avis") + 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 doit détecter la présence d'un avis") + void testExistsByCustomerIdAndIsbn() { + repository.save(review1); + assertTrue(repository.existsByCustomerIdAndIsbn(customerId1, isbn1)); + assertFalse(repository.existsByCustomerIdAndIsbn(customerId2, isbn2)); + } + + @Test + @DisplayName("deleteByCustomerIdAndIsbn doit supprimer l'avis") + 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 doit modifier l'avis") + void testUpdate() { + repository.save(review1); + Review updated = Review.builder().customerId(customerId1).isbn(isbn1).rating(2).comment("modif").build(); + repository.update(updated); + List found = repository.findByCustomerId(customerId1); + assertEquals(1, found.size()); + assertEquals(2, found.get(0).getRating()); + assertEquals("modif", found.get(0).getComment()); + } + + @Test + @DisplayName("deleteAll doit vider le repository") + void testDeleteAll() { + repository.save(review1); + repository.save(review2); + repository.deleteAll(); + assertTrue(repository.findAll().isEmpty()); + } +} + 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..be4861f --- /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 doit sauvegarder un nouvel avis") + void testSubmitReview() throws CustomerNotFoundException { + when(reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)).thenReturn(false); + reviewUseCase.submitReview(review); + verify(reviewRepository).save(review); + } + + @Test + @DisplayName("submitReview doit lever ReviewAlreadyExistsException si avis déjà existant") + void testSubmitReviewAlreadyExists() { + when(reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)).thenReturn(true); + assertThrows(ReviewAlreadyExistsException.class, () -> reviewUseCase.submitReview(review)); + } + + @Test + @DisplayName("submitReview doit lever BookNotFoundException si livre inexistant") + void testSubmitReviewBookNotFound() { + when(bookRepository.existsByIsbn(String.valueOf(isbn))).thenReturn(false); + assertThrows(BookNotFoundException.class, () -> reviewUseCase.submitReview(review)); + } + + @Test + @DisplayName("submitReview doit lever CustomerNotFoundException si client inexistant") + void testSubmitReviewCustomerNotFound() { + when(customerRepository.existsById(customerId)).thenReturn(false); + assertThrows(CustomerNotFoundException.class, () -> reviewUseCase.submitReview(review)); + } + + @Test + @DisplayName("getReviewsByBook doit retourner la liste des avis pour un livre") + void testGetReviewsByBook() { + List expected = List.of(review); + when(reviewRepository.findByIsbn(isbn)).thenReturn(expected); + List result = reviewUseCase.getReviewsByBook(isbn); + assertEquals(expected, result); + } + + @Test + @DisplayName("getReviewsByCustomer doit retourner la liste des avis pour un client") + void testGetReviewsByCustomer() { + List expected = List.of(review); + when(reviewRepository.findByCustomerId(customerId)).thenReturn(expected); + List result = reviewUseCase.getReviewsByCustomer(customerId); + assertEquals(expected, result); + } + + @Test + @DisplayName("updateReview doit modifier un avis existant") + void testUpdateReview() { + when(reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)).thenReturn(true); + reviewUseCase.updateReview(review); + verify(reviewRepository).update(review); + } + + @Test + @DisplayName("updateReview doit lever ReviewNotFoundException si avis inexistant") + void testUpdateReviewNotFound() { + when(reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)).thenReturn(false); + assertThrows(ReviewNotFoundException.class, () -> reviewUseCase.updateReview(review)); + } + + @Test + @DisplayName("deleteReview doit supprimer un avis existant") + void testDeleteReview() { + when(reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)).thenReturn(true); + reviewUseCase.deleteReview(isbn, customerId); + verify(reviewRepository).deleteByCustomerIdAndIsbn(customerId, isbn); + } + + @Test + @DisplayName("deleteReview doit lever ReviewNotFoundException si avis inexistant") + 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..e3e968c --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidatorTest.java @@ -0,0 +1,59 @@ +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("Valide un avis correct") + void testValidReview() { + Review review = Review.builder() + .customerId(UUID.randomUUID()) + .isbn(9781234567890L) + .rating(4) + .comment("Bon livre") + .build(); + assertDoesNotThrow(() -> ReviewValidator.validate(review)); + } + + @Test + @DisplayName("Invalide si la note est trop basse") + void testInvalidLowRating() { + Review review = Review.builder() + .customerId(UUID.randomUUID()) + .isbn(9781234567890L) + .rating(0) + .comment("Mauvais") + .build(); + assertThrows(fr.iut_fbleau.but3.dev62.mylibrary.review.exception.InvalidReviewRatingException.class, () -> ReviewValidator.validate(review)); + } + + @Test + @DisplayName("Invalide si la note est trop haute") + void testInvalidHighRating() { + Review review = Review.builder() + .customerId(UUID.randomUUID()) + .isbn(9781234567890L) + .rating(6) + .comment("Trop bien") + .build(); + assertThrows(fr.iut_fbleau.but3.dev62.mylibrary.review.exception.InvalidReviewRatingException.class, () -> ReviewValidator.validate(review)); + } + + @Test + @DisplayName("Invalide si le commentaire est vide") + 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..a50967a --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/converter/SubscriptionConverterTest.java @@ -0,0 +1,96 @@ +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 java.util.UUID; + +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; + +@DisplayName("SubscriptionConverter Unit Tests") +public class SubscriptionConverterTest { + + @Nested + @DisplayName("toDomain() method tests") + public class toDomainTests { + + @Test + @DisplayName("Should convert SubscriptionInfo to Subscription domain object") + void shouldConvertSubscriptionInfoToDomain() { + SubscriptionInfo subscriptionInfo = new SubscriptionInfo(UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), 12, "CB"); + + Subscription result = SubscriptionConverter.toDomain(subscriptionInfo); + + assertNotNull(result); + assertEquals(subscriptionInfo.customerId(), result.getCustomerId()); + assertEquals(subscriptionInfo.duration(), result.getDuration()); + assertEquals(subscriptionInfo.paymentMethod(), result.getPaymentMethod()); + } + } + + @Nested + @DisplayName("toDTO() method tests") + public class toDTOTests { + + @Test + @DisplayName("Should convert Subscriber domain object to SubscriberDTO with all fields mapped correctly") + void shouldConvertSubscriptionToDTO() { + UUID id = UUID.randomUUID(); + + Subscription subscription = Subscription.builder() + .id(UUID.randomUUID()) + .customerId(id) + .duration(12) + .paymentMethod("CB") + .debutDate("2025-06-08") + .build(); + + SubscriptionDTO result = SubscriptionConverter.toDTO(subscription); + + assertNotNull(result); + assertEquals(subscription.getId(), result.getId()); + assertEquals(subscription.getCustomerId(), result.getCustomerId()); + assertEquals(subscription.getDuration(), result.getDuration()); + assertEquals(subscription.getPaymentMethod(), result.getPaymentMethod()); + assertEquals(subscription.getDebutDate(), result.getDebutDate()); + } + + @Test + @DisplayName("Should handle null values properly when converting between objects") + void shouldHandleNullValuesGracefully() { + Subscription subscription = Subscription.builder() + .id(UUID.randomUUID()) + .customerId(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")) + .duration(null) + .paymentMethod("NullTest") + .debutDate("2025-06-08") + .build(); + + SubscriptionDTO result = SubscriptionConverter.toDTO(subscription); + + assertNotNull(result); + assertNull(result.getDuration()); + assertEquals("NullTest", result.getPaymentMethod()); + } + + @Test + @DisplayName("Should preserve empty string values during conversion") + void shouldPreserveEmptyStrings() { + SubscriptionInfo subscriptionInfo = new SubscriptionInfo(UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), 12, ""); + + Subscription domainResult = SubscriptionConverter.toDomain(subscriptionInfo); + SubscriptionDTO dtoResult = SubscriptionConverter.toDTO(domainResult); + + assertEquals("", dtoResult.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..4003114 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/entity/SubscriptionTest.java @@ -0,0 +1,48 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.UUID; + + +public class SubscriptionTest { + + @Test + @DisplayName("Builder should create a valid Subscription instance") + void testSubscriptionBuilder() { + UUID id = UUID.randomUUID(); + UUID customerId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + int duration = 12; + String paymentMethod = "CB"; + String debutDate = "2025-06-08"; + + Subscription subscription = Subscription.builder() + .id(id) + .customerId(customerId) + .duration(duration) + .paymentMethod(paymentMethod) + .debutDate(debutDate) + .build(); + + assertEquals(id, subscription.getId()); + assertEquals(customerId, subscription.getCustomerId()); + assertEquals(duration, subscription.getDuration()); + assertEquals(paymentMethod, subscription.getPaymentMethod()); + assertEquals(debutDate, subscription.getDebutDate()); + } + + @Test + @DisplayName("setRandomUUID should change the ID to a new random UUID") + void testSetRandomUUID() { + Subscription subscription = Subscription.builder().build(); + UUID originalId = subscription.getId(); + + subscription.setRandomUUID(); + + assertNotNull(subscription.getId()); + assertNotEquals(originalId, subscription.getId()); + } + +} 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..086ac8e --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/NotValidSubscriptionExceptionTest.java @@ -0,0 +1,60 @@ +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.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class NotValidSubscriptionExceptionTest { + + @Test + @DisplayName("Exception should be created with the provided message") + void testExceptionCreation() { + String errorMessage = "Subscription data is not valid"; + + NotValidSubscriptionException exception = new NotValidSubscriptionException(errorMessage); + + assertEquals(errorMessage, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = { + "Customer ID cannot be null", + "Duration is not valid", + "Payment Method cannot be blank" + }) + @DisplayName("Exception should handle different validation messages") + void testExceptionWithDifferentMessages(String errorMessage) { + NotValidSubscriptionException exception = new NotValidSubscriptionException(errorMessage); + + assertEquals(errorMessage, exception.getMessage()); + } + + @Test + @DisplayName("Exception should be properly thrown and caught") + void testExceptionCanBeThrownAndCaught() { + String errorMessage = "Required field is missing"; + + Exception exception = assertThrows(NotValidSubscriptionException.class, () -> { + throw new NotValidSubscriptionException(errorMessage); + }); + + assertEquals(errorMessage, exception.getMessage()); + } + + @Test + @DisplayName("Exception should be catchable as a general Exception") + void testExceptionInheritance() { + String errorMessage = "Invalid subscription data"; + + try { + throw new NotValidSubscriptionException(errorMessage); + } catch (Exception e) { + assertEquals(NotValidSubscriptionException.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/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..ed0ead0 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/repository/SubscriptionRepositoryTest.java @@ -0,0 +1,154 @@ +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 Subscription1; + private Subscription Subscription2; + + @BeforeEach + void setUp() { + repository = new SubscriptionRepository(); + + Subscription1 = Subscription.builder() + .customerId(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")) + .duration(12) + .paymentMethod("CB") + .build(); + Subscription1.setRandomUUID(); + + Subscription2 = Subscription.builder() + .customerId(UUID.fromString("456e4567-e89b-12d3-a456-426614174000")) + .duration(24) + .paymentMethod("Paypal") + .build(); + Subscription2.setRandomUUID(); + } + + @Test + @DisplayName("New repository should be empty") + void testNewRepositoryIsEmpty() { + List subscriptions = repository.findAll(); + + assertTrue(subscriptions.isEmpty()); + assertEquals(0, subscriptions.size()); + } + + @Nested + @DisplayName("Save operations") + class SaveOperations { + + @Test + @DisplayName("Save should add a new subsciption") + void testSaveNewSubscription() { + Subscription savedSubscription = repository.save(Subscription1); + + assertEquals(1, repository.findAll().size()); + assertEquals(Subscription1.getId(), savedSubscription.getId()); + assertEquals(Subscription1.getDebutDate(), savedSubscription.getDebutDate()); + } + + @Test + @DisplayName("Save multiple Subscriptions should add all of them") + void testSaveMultipleSubscriptions() { + repository.save(Subscription1); + repository.save(Subscription2); + + List subscriptions = repository.findAll(); + + assertEquals(2, subscriptions.size()); + assertTrue(subscriptions.contains(Subscription1)); + assertTrue(subscriptions.contains(Subscription2)); + } + } + + @Nested + @DisplayName("Find operations") + class FindOperations { + + @BeforeEach + void setUpSubscriptions() { + repository.save(Subscription1); + repository.save(Subscription2); + } + + @Test + @DisplayName("FindAll should return all subscriptions") + void testFindAll() { + List subscriptions = repository.findAll(); + + assertEquals(2, subscriptions.size()); + assertTrue(subscriptions.contains(Subscription1)); + assertTrue(subscriptions.contains(Subscription2)); + } + + @Test + @DisplayName("FindById should return subscriptions with matching ID") + void testFindById() { + Optional foundSubscription = repository.findById(Subscription1.getId()); + + assertTrue(foundSubscription.isPresent()); + assertEquals(Subscription1.getId(), foundSubscription.get().getId()); + assertEquals(Subscription1.getCustomerId(), foundSubscription.get().getCustomerId()); + } + + @Test + @DisplayName("FindById should return empty Optional when ID doesn't exist") + void testFindByIdNotFound() { + UUID nonExistentId = UUID.randomUUID(); + + Optional foundSubscription = repository.findByCustomerId(nonExistentId); + + assertTrue(foundSubscription.isEmpty()); + } + + @Test + @DisplayName("FindByCustomerId should return customer with matching customer id") + void testFindByCustomerId() { + Optional foundSubscription = repository.findByCustomerId(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")); + + assertTrue(foundSubscription.isPresent()); + assertEquals(Subscription1.getId(), foundSubscription.get().getId()); + assertEquals(Subscription1.getDebutDate(), foundSubscription.get().getDebutDate()); + } + + @Test + @DisplayName("FindByCustomerId should return empty Optional when phone number doesn't exist") + void testFindByPhoneNumberNotFound() { + Optional foundSubscription = repository.findByCustomerId(UUID.fromString("0000000-0000-0000-0000-000000000000")); + + assertTrue(foundSubscription.isEmpty()); + } + + @Test + @DisplayName("ExistsById should return true when ID exists") + void testExistsByIdExists() { + boolean exists = repository.existsById(Subscription1.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); + } + } +} 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..25dac33 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/usecase/SubscribeUseCaseTest.java @@ -0,0 +1,107 @@ +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.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class SubscribeUseCaseTest { + + @Mock + private SubscriptionRepository subscriptionRepository; + + @InjectMocks + private SubscriptionUseCase subscriptionUseCase; + + private UUID subscriptionId; + private Subscription testSubscription; + private SubscriptionInfo validSubscriptionInfo; + + @BeforeEach + void setUp() { + subscriptionId = UUID.randomUUID(); + testSubscription = Subscription.builder() + .id(subscriptionId) + .customerId(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")) + .duration(12) + .paymentMethod("CB") + .build(); + + validSubscriptionInfo = new SubscriptionInfo(UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), 12, "CB"); + } + + @Nested + @DisplayName("Register subscription tests") + class RegisterSubscriptionTests { + + @Test + @DisplayName("Should register subscription when valid data is provided") + void testRegisterSubscriptionWithValidData() throws NotValidSubscriptionException { + when(subscriptionRepository.save(any(Subscription.class))).thenReturn(testSubscription); + + UUID registeredId = subscriptionUseCase.registerSubscription(validSubscriptionInfo); + + assertNotNull(registeredId); + assertEquals(subscriptionId, registeredId); + verify(subscriptionRepository, times(1)).save(any(Subscription.class)); + } + + @Test + @DisplayName("Should throw exception when subscription data is not valid") + void testRegisterSubscriptionWithInvalidData() { + SubscriptionInfo invalidSubscriptionInfo = new SubscriptionInfo(null, null, ""); + + assertThrows(NotValidSubscriptionException.class, + () -> subscriptionUseCase.registerSubscription(invalidSubscriptionInfo)); + + verify(subscriptionRepository, never()).save(any(Subscription.class)); + } + } + + @Nested + @DisplayName("Find subscription tests") + class FindSubscriptionTests { + + @Test + @DisplayName("Should return subscription when phone number exists") + void testFindSubscriptionByCustomerId() { + when(subscriptionRepository.findByCustomerId(UUID.fromString("123e4567-e89b-12d3-a456-426614174000"))).thenReturn(Optional.of(testSubscription)); + + Optional foundSubscription = subscriptionUseCase.findSubscriptionByCustomerId(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")); + + assertTrue(foundSubscription.isPresent()); + assertEquals(testSubscription.getId(), foundSubscription.get().getId()); + assertEquals(testSubscription.getDebutDate(), foundSubscription.get().getDebutDate()); + verify(subscriptionRepository, times(1)).findByCustomerId(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")); + } + + @Test + @DisplayName("Should return empty Optional when phone number doesn't exist") + void testFindSubscriptionByPhoneNumberNotFound() { + when(subscriptionRepository.findByCustomerId(UUID.fromString("0000000-0000-0000-0000-000000000000"))).thenReturn(Optional.empty()); + + Optional foundSubscription = subscriptionUseCase.findSubscriptionByCustomerId(UUID.fromString("0000000-0000-0000-0000-000000000000")); + + assertTrue(foundSubscription.isEmpty()); + verify(subscriptionRepository, times(1)).findByCustomerId(UUID.fromString("0000000-0000-0000-0000-000000000000")); + } + } + +} 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..1f92f0e --- /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("Should validate subscription with valid data") + void testValidateValidSubscription() { + SubscriptionInfo validSubscription = new SubscriptionInfo(UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), 12, "CB"); + + assertDoesNotThrow(() -> SubscriptionValidator.validate(validSubscription)); + } + + @Nested + @DisplayName("customer id validation tests") + class CustomerIdValidationTests { + + @Test + @DisplayName("Should throw exception when customer id is null") + void testValidateBlankCustomerId() { + SubscriptionInfo subscriptionWithNullCustomerId = new SubscriptionInfo(null, 12, "CB"); + + NotValidSubscriptionException exception = assertThrows( + NotValidSubscriptionException.class, + () -> SubscriptionValidator.validate(subscriptionWithNullCustomerId) + ); + + assertEquals(SubscriptionValidator.CUSTOMER_ID_CANNOT_BE_NULL, + exception.getMessage()); + } + } + + @Nested + @DisplayName("Duration validation tests") + class DurationValidationTests { + + @Test + @DisplayName("Should throw exception when duration is blank") + void testValidateBlankDuration() { + SubscriptionInfo subscriptionWithBlankDuration = new SubscriptionInfo(UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), null, "CB"); + + NotValidSubscriptionException exception = assertThrows( + NotValidSubscriptionException.class, + () -> SubscriptionValidator.validate(subscriptionWithBlankDuration) + ); + + assertEquals(SubscriptionValidator.DURATION_CANNOT_BE_NULL, exception.getMessage()); + } + } + + @Nested + @DisplayName("Payment method validation tests") + class PaymentMethodValidationTests { + + @Test + @DisplayName("Should throw exception when payment method is blank") + void testValidateBlankPaymentMethod() { + SubscriptionInfo subscriptionWithBlankPaymentMethod = new SubscriptionInfo(UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), 12, ""); + + NotValidSubscriptionException exception = assertThrows( + NotValidSubscriptionException.class, + () -> SubscriptionValidator.validate(subscriptionWithBlankPaymentMethod) + ); + + assertEquals(SubscriptionValidator.PAYMENT_METHOD_CANNOT_BE_BLANK, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {" ", " ", "\t", "\n"}) + @DisplayName("Should throw exception when payment method contains only whitespace") + void testValidateWhitespacePhoneNumber(String whitespace) { + SubscriptionInfo subscriptionWithWhitespacePaymentMethod = new SubscriptionInfo(UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), 12, whitespace); + + NotValidSubscriptionException exception = assertThrows( + NotValidSubscriptionException.class, + () -> SubscriptionValidator.validate(subscriptionWithWhitespacePaymentMethod) + ); + + assertEquals(SubscriptionValidator.PAYMENT_METHOD_CANNOT_BE_BLANK, exception.getMessage()); + } + } +} diff --git a/src/test/resources/features/book.feature b/src/test/resources/features/book.feature new file mode 100644 index 0000000..9ba73ff --- /dev/null +++ b/src/test/resources/features/book.feature @@ -0,0 +1,138 @@ +Feature: Gerez les livres + + Background: + Given Le systeme possedent les livres suivants : + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9781234567890 | Les Mysteres de l'IA | Jean Dupont | TechEditions | 2024-01-15 | 29.99 | 10 | [SCIENCE] | Un livre fascinant sur l'IA. | Francais | + | 9789876543210 | L'Art de la Guerre Moderne | Claire Martin | StrategeBooks | 2023-06-10 | 35.50 | 5 | [HISTORY] | Analyse des conflits modernes et tactiques. | Francais | + | 9781112223334 | COOKING du Monde | Pierre Lemoine | GourmetEditions | 2022-09-20 | 24.90 | 20 | [COOKING, TRAVEL] | Un tour du monde gastronomique en recettes. | Francais | + | 9785556667778 | Python pour Debutants | Alice Bernard | CodeMaster | 2021-04-05 | 19.99 | 15 | [SCIENCE] | Apprendre Python pas a pas avec des exercices. | Francais | + | 9789998887776 | L'eveil Spirituel | Olivier Fontaine | ZenBooks | 2020-11-30 | 22.00 | 8 | [SELF_HELP] | Un guide vers la pleine conscience et la paix interieure. | Francais | + + Scenario: Creer un nouveau livre + When Je cree un nouveau livre avec les informations suivantes : + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9791032719640 | My Hero Academia | Kohei Horikoshi | Ki-oon | 2025-02-06 | 6.95 | 900 | [FANTASY, SCIENCE_FICTION] | Retrouvez les super-heros du manga phenomene My Hero Academia ! | Francais | + Then Un nouveau livre est créé + + Scenario: Modifier les informations d'un livre + Given Il existe un livre avec l'ISBN "9791032719640" + When Je modifie le livre avec l'ISBN "9791032719640" avec les informations suivantes: + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9791032719640 | My Hero Academia | Kohei Horikoshi | Ki-oon | 2025-02-06 | 6.95 | 999 | [FANTASY, SCIENCE_FICTION] | Retrouvez les super-heros du manga phenomene My Hero Academia ! | Francais | + Then Le livre "9791032719640" a les informations suivantes: + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9791032719640 | My Hero Academia | Kohei Horikoshi | Ki-oon | 2025-02-06 | 6.95 | 999 | [FANTASY, SCIENCE_FICTION] | Retrouvez les super-heros du manga phenomene My Hero Academia ! | Francais | + + Scenario: Supprimer un livre + Given Il existe un livre avec l'ISBN "9791032719640" + When Je supprime le livre avec l'ISBN "9791032719640" + Then Le livre n'existe plus dans le systeme + + Scenario: Recuperer les informations d'un livre + Given Il existe un livre avec l'ISBN "9789876543210" + When Je demande les informations du livre avec l'ISBN "9789876543210" + Then Je recupere les informations suivantes: + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9789876543210 | L'Art de la Guerre Moderne | Claire Martin | StrategeBooks | 2023-06-10 | 35.50 | 5 | [HISTORY] | Analyse des conflits modernes et tactiques. | Francais | + + Scenario: Échec de modification d'un livre inexistant + Given Il n'existe pas un livre avec l'ISBN "9999999999999" + When Je modifie le livre avec l'ISBN "9999999999999" avec les informations suivantes: + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9999999999999 | Inconnu | Auteur X | Maison Y | 2025-01-01 | 10.00 | 1 | [FANTASY] | Ce livre n'existe pas | Francais | + Then La modification échoue avec une erreur indiquant que le livre est introuvable + + Scenario: Échec de suppression d'un livre inexistant + Given Il n'existe pas un livre avec l'ISBN "9999999999999" + When Je supprime le livre avec l'ISBN "9999999999999" + Then La suppression échoue avec une erreur indiquant que le livre est introuvable + + Scenario: Tentative de creation d'un livre incorrect + When Je cree un nouveau livre avec les informations suivantes : + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | abcefg | L'Art de la Guerre Moderne | Claire Martin | StrategeBooks | 2023-06-10 | 35.50 | 5 | [HISTORY] | Analyse des conflits modernes et tactiques. | Francais | + Then La creation echoue + + Scenario: Tentative de création d'un livre avec un ISBN existant + Given Il existe un livre avec l'ISBN "9781234567890" + When Je cree un nouveau livre avec les informations suivantes : + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9781234567890 | L'Art de la Guerre Moderne | Claire Martin | StrategeBooks | 2023-06-10 | 35.50 | 5 | [HISTORY] | Analyse des conflits modernes et tactiques. | Francais | + Then La creation echoue + + Scenario: Récupération d'un livre inexistant + When Je demande les informations du livre avec l'ISBN "999999" + Then Je recois une erreur indiquant que le livre est introuvable + + #pages + + Scenario: Récupérer la liste des livres triée par titre (page 0, taille 3) + Given Le systeme possedent les livres suivants : + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9781234567890 | Les Mysteres de l'IA | Jean Dupont | TechEditions | 2024-01-15 | 29.99 | 10 | [SCIENCE] | Un livre fascinant sur l'IA. | Francais | + | 9789876543210 | L'Art de la Guerre Moderne | Claire Martin | StrategeBooks | 2023-06-10 | 35.50 | 5 | [HISTORY] | Analyse des conflits modernes et tactiques. | Francais | + | 9781112223334 | COOKING du Monde | Pierre Lemoine | GourmetEditions | 2022-09-20 | 24.90 | 20 | [COOKING, TRAVEL] | Un tour du monde gastronomique en recettes. | Francais | + | 9785556667778 | Python pour Debutants | Alice Bernard | CodeMaster | 2021-04-05 | 19.99 | 15 | [SCIENCE] | Apprendre Python pas a pas avec des exercices. | Francais | + | 9789998887776 | L'eveil Spirituel | Olivier Fontaine | ZenBooks | 2020-11-30 | 22.00 | 8 | [SELF_HELP] | Un guide vers la pleine conscience et la paix interieure. | Francais | + When Je demande la page 0 de taille 3 triée par "titre" + Then La réponse contient les livres suivants : + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9781112223334 | COOKING du Monde | Pierre Lemoine | GourmetEditions | 2022-09-20 | 24.90 | 20 | [COOKING, TRAVEL] | Un tour du monde gastronomique en recettes. | Francais | + | 9789876543210 | L'Art de la Guerre Moderne | Claire Martin | StrategeBooks | 2023-06-10 | 35.50 | 5 | [HISTORY] | Analyse des conflits modernes et tactiques. | Francais | + | 9789998887776 | L'eveil Spirituel | Olivier Fontaine | ZenBooks | 2020-11-30 | 22.00 | 8 | [SELF_HELP] | Un guide vers la pleine conscience et la paix interieure. | Francais | + And La réponse indique qu’il y a 5 livres au total et 2 pages au total + + Scenario: Récupérer la deuxième page des livres triée par titre (page 1, taille 3) + Given Le systeme possedent les livres suivants : + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9781234567890 | Les Mysteres de l'IA | Jean Dupont | TechEditions | 2024-01-15 | 29.99 | 10 | [SCIENCE] | Un livre fascinant sur l'IA. | Francais | + | 9789876543210 | L'Art de la Guerre Moderne | Claire Martin | StrategeBooks | 2023-06-10 | 35.50 | 5 | [HISTORY] | Analyse des conflits modernes et tactiques. | Francais | + | 9781112223334 | COOKING du Monde | Pierre Lemoine | GourmetEditions | 2022-09-20 | 24.90 | 20 | [COOKING, TRAVEL] | Un tour du monde gastronomique en recettes. | Francais | + | 9785556667778 | Python pour Debutants | Alice Bernard | CodeMaster | 2021-04-05 | 19.99 | 15 | [SCIENCE] | Apprendre Python pas a pas avec des exercices. | Francais | + | 9789998887776 | L'eveil Spirituel | Olivier Fontaine | ZenBooks | 2020-11-30 | 22.00 | 8 | [SELF_HELP] | Un guide vers la pleine conscience et la paix interieure. | Francais | + When Je demande la page 1 de taille 3 triée par "titre" + Then La réponse contient les livres suivants : + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9781234567890 | Les Mysteres de l'IA | Jean Dupont | TechEditions | 2024-01-15 | 29.99 | 10 | [SCIENCE] | Un livre fascinant sur l'IA. | Francais | + | 9785556667778 | Python pour Debutants | Alice Bernard | CodeMaster | 2021-04-05 | 19.99 | 15 | [SCIENCE] | Apprendre Python pas a pas avec des exercices. | Francais | + And La réponse indique que c’est la dernière page + + Scenario: Filtrer les livres par titre contenant "Python" + When Je recherche les livres avec le titre contenant "Python" + Then Je recupere uniquement les livres dont le titre contient "Python" + + Scenario: Filtrer les livres par auteur "Claire Martin" + When Je recherche les livres de l’auteur "Claire Martin" + Then Je recupere les livres suivants : + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9789876543210 | L'Art de la Guerre Moderne | Claire Martin | StrategeBooks | 2023-06-10 | 35.50 | 5 | [HISTORY] | Analyse des conflits modernes et tactiques. | Francais | + + Scenario: Filtrer les livres par editeur "TechEditions" + When Je recherche les livres publies par "TechEditions" + Then Je recupere les livres suivants : + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9781234567890 | Les Mysteres de l'IA | Jean Dupont | TechEditions | 2024-01-15 | 29.99 | 10 | [SCIENCE] | Un livre fascinant sur l'IA. | Francais | + + Scenario: Filtrer les livres dans la categorie "Science" + When Je recherche les livres de la categorie "Science" + Then Je recupere les livres suivants : + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9781234567890 | Les Mysteres de l'IA | Jean Dupont | TechEditions | 2024-01-15 | 29.99 | 10 | [SCIENCE] | Un livre fascinant sur l'IA. | Francais | + | 9785556667778 | Python pour Debutants | Alice Bernard | CodeMaster | 2021-04-05 | 19.99 | 15 | [SCIENCE] | Apprendre Python pas a pas avec des exercices. | Francais | + + Scenario: Filtrer les livres publies apres le 2022-01-01 + When Je filtre les livres publies apres "2022-01-01" + Then Je recupere les livres suivants : + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9781234567890 | Les Mysteres de l'IA | Jean Dupont | TechEditions | 2024-01-15 | 29.99 | 10 | [SCIENCE] | Un livre fascinant sur l'IA. | Francais | + | 9789876543210 | L'Art de la Guerre Moderne | Claire Martin | StrategeBooks | 2023-06-10 | 35.50 | 5 | [HISTORY] | Analyse des conflits modernes et tactiques. | Francais | + | 9781112223334 | COOKING du Monde | Pierre Lemoine | GourmetEditions | 2022-09-20 | 24.90 | 20 | [COOKING, TRAVEL] | Un tour du monde gastronomique en recettes. | Francais | + + Scenario: Filtrer les livres avec un prix entre 20 et 30 euros + When Je filtre les livres avec un prix minimum de 20 et maximum de 30 + Then Je recupere les livres suivants : + | ISBN | Titre | Auteur | Editeur | Date de publication | Prix | Stock initial | Categories | Description | Langue | + | 9781234567890 | Les Mysteres de l'IA | Jean Dupont | TechEditions | 2024-01-15 | 29.99 | 10 | [SCIENCE] | Un livre fascinant sur l'IA. | Francais | + | 9781112223334 | COOKING du Monde | Pierre Lemoine | GourmetEditions | 2022-09-20 | 24.90 | 20 | [COOKING, TRAVEL] | Un tour du monde gastronomique en recettes. | Francais | + | 9789998887776 | L'eveil Spirituel | Olivier Fontaine | ZenBooks | 2020-11-30 | 22.00 | 8 | [SELF_HELP] | Un guide vers la pleine conscience et la paix interieure. | Francais | \ No newline at end of file diff --git a/src/test/resources/features/order.feature b/src/test/resources/features/order.feature new file mode 100644 index 0000000..60f982d --- /dev/null +++ b/src/test/resources/features/order.feature @@ -0,0 +1,205 @@ +Feature: Gérez les commandes clients + Background: + Given Le systeme contient les livres suivants : + | 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-heros du manga phenomene My Hero Academia ! | Francais | + | 1234567891011 | ORV | SingNsong | Munpia | 2020-02-02 | 666.66 | 69 | [FANTASY] | ORV c'est trop bien, allez lire | KR | + And Le systeme contient les utilisateur suivants: + | 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 | + +# Create + Scenario: Créer une commande avec une carte de crédit + When Je crée une nouvelle commande avec les informations suivantes : + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And La commande contient les livres suivants : + | bookId | quantity | + | 1234567891011 | 2 | + And L'adresse de livraison est : + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then Une nouvelle commande est créée + And Le prix total est 1333.32 + And Le client "11111111-1111-1111-1111-111111111111" a 1000 points de fidélité + + Scenario: Créer une commande avec des points de fidélité + When Je crée une nouvelle commande avec les informations suivantes : + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | LOYALTY_POINTS | + And La commande contient les livres suivants : + | bookId | quantity | + | 9791032719640 | 1 | + And L'adresse de livraison est : + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then Une nouvelle commande est créée + And Le prix total est 6.95 + And Le client "11111111-1111-1111-1111-111111111111" a 305 points de fidélité + + Scenario: Créer une commande avec la quantité minimale autorisée + When Je crée une nouvelle commande avec les informations suivantes : + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And La commande contient les livres suivants : + | bookId | quantity | + | 1234567891011 | 1 | + And L'adresse de livraison est : + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then Une nouvelle commande est créée + And Le prix total est 666.66 + + Scenario: Créer une commande avec la quantité maximale autorisée + When Je crée une nouvelle commande avec les informations suivantes : + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And La commande contient les livres suivants : + | bookId | quantity | + | 1234567891011 | 69 | + And L'adresse de livraison est : + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then Une nouvelle commande est créée + And Le prix total est 45999.54 + + Scenario: Créer une commande avec plusieurs livres différents + When Je crée une nouvelle commande avec les informations suivantes : + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And La commande contient les livres suivants : + | bookId | quantity | + | 1234567891011 | 1 | + | 9791032719640 | 2 | + And L'adresse de livraison est : + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then Une nouvelle commande est créée + And Le prix total est 680.56 + +# Erreur create + Scenario: Essayer de créer une commande sans adresse de livraison + When Je crée une nouvelle commande avec les informations suivantes : + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And La commande contient les livres suivants : + | bookId | quantity | + | 1234567891011 | 1 | + And L'adresse de livraison est : + | street | city | postalCode | country | + | | | | | + Then La création de la commande échoue + And Je reçois une erreur indiquant que l'adresse de livraison est manquante + + Scenario: Essayer de créer une commande avec un client inconnu + When Je crée une nouvelle commande avec les informations suivantes : + | customerId | paymentMethod | + | 22222222-2222-2222-2222-222222222223 | CREDIT_CARD | + And La commande contient les livres suivants : + | bookId | quantity | + | 1234567891011 | 1 | + And L'adresse de livraison est : + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then La création de la commande échoue + And Je reçois une erreur indiquant que le client n'a pas été trouvé + + Scenario: Essayer de créer une commande avec des points de fidélité insuffisants + When Je crée une nouvelle commande avec les informations suivantes : + | customerId | paymentMethod | + | 22222222-2222-2222-2222-222222222222 | LOYALTY_POINTS | + And La commande contient les livres suivants : + | bookId | quantity | + | 1234567891011 | 1 | + And L'adresse de livraison est : + | street | city | postalCode | country | + | 3 rue de chez lui | SaVille | 77000 | France | + Then La création de la commande échoue + And Je reçois une erreur indiquant que les points de fidélité sont insuffisants + + Scenario: Essayer de créer une commande avec un stock de livres insuffisant + When Je crée une nouvelle commande avec les informations suivantes : + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And La commande contient les livres suivants : + | bookId | quantity | + | 1234567891011 | 100 | + And L'adresse de livraison est : + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then La création de la commande échoue + And Je reçois une erreur indiquant que le stock de livres est insuffisant + + Scenario: Essayer de créer une commande avec un mode de paiement invalide + When Je crée une nouvelle commande avec les informations suivantes : + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | UNKNOWN | + And La commande contient les livres suivants : + | bookId | quantity | + | 1234567891011 | 1 | + And L'adresse de livraison est : + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then La création de la commande échoue + And Je reçois une erreur indiquant que le mode de paiement est invalide + + Scenario: Essayer de créer une commande avec un livre inconnu + When Je crée une nouvelle commande avec les informations suivantes : + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And La commande contient les livres suivants : + | bookId | quantity | + | unknownBookId | 1 | + And L'adresse de livraison est : + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then La création de la commande échoue + And Je reçois une erreur indiquant que le livre n'a pas été trouvé + + Scenario: Essayer de créer une commande sans livres + When Je crée une nouvelle commande avec les informations suivantes : + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And La commande ne contient aucun livre + And L'adresse de livraison est : + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then La création de la commande échoue + And Je reçois une erreur indiquant que la commande doit contenir au moins un livre + + Scenario: Essayer de créer une commande avec une quantité de livre négative + When Je crée une nouvelle commande avec les informations suivantes : + | customerId | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | CREDIT_CARD | + And La commande contient les livres suivants : + | bookId | quantity | + | 1234567891011 | -2 | + And L'adresse de livraison est : + | street | city | postalCode | country | + | 3 rue de chez moi | MaVille | 77000 | France | + Then La création de la commande échoue + And Je reçois une erreur indiquant que la quantité de livres doit être positive + +# Get + + Scenario: Récupérer une commande par ID + Given Il existe une commande avec l'ID "12345678-9101-1121-3141-516171819202" pour le client "11111111-1111-1111-1111-111111111111" + When Je récupère la commande par ID "12345678-9101-1121-3141-516171819202" + Then Je reçois une commande + + Scenario: Récupérer toutes les commandes pour un client + When Je demande toutes les commandes pour le client "11111111-1111-1111-1111-111111111111" + Then Je reçois une liste de commandes + +# Erreur Get + + Scenario: Essayer de récupérer une commande avec un ID inconnu + When Je récupère la commande par ID "nope" + Then La récupération échoue + And Je reçois une erreur indiquant que la commande n'a pas été trouvée + + Scenario: Essayer de récupérer les commandes pour un client inconnu + When Je demande toutes les commandes pour le client "00000000-0000-0000-0000-000000000000" + Then La récupération échoue + And Je reçois une erreur indiquant que le client n'a pas été trouvé \ No newline at end of file diff --git a/src/test/resources/features/review.feature b/src/test/resources/features/review.feature new file mode 100644 index 0000000..94dde34 --- /dev/null +++ b/src/test/resources/features/review.feature @@ -0,0 +1,73 @@ +Feature: Gérer les avis + + Background: + Given Le systeme contient tous les livres suivants : + | 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 Le systeme contient tous les utilisateur suivants: + | 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 Le systeme contient les avis suivants : + | 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. | + +#CRUD + + Scenario: Créer un avis pour un livre + When Je créer un avis pour le livre "9781234567890" avec le client "44444444-4444-4444-4444-444444444444" avec comme note 4 et commentaire "cool" + Then Un nouvel avis est créé + And Le systeme contient 4 avis + + Scenario: Recuperer tous les avis pour un livre + When Je recupere tous les avis pour le livre "9781234567890" + Then Je recois les avis suivants : + | customerId | rating | comment | + | 11111111-1111-1111-1111-111111111111 | 5 | Excellent book! | + | 22222222-2222-2222-2222-222222222222 | 3 | Average, but readable. | + + Scenario: Recuperer tous les avis pour un client + When Je recupere tous les avis pour le client "33333333-3333-3333-3333-333333333333" + Then Je recois les avis suivants : + | isbn | rating | comment | + | 9789876543210 | 4 | Very interesting. | + + Scenario: Modifier un avis + When Je modifie l'avis du livre "9781234567890" par le client "22222222-2222-2222-2222-222222222222" avec comme note 4 et commentaire "cool2" + Then L'avis est modifier avec une note de 4 et un commentaire "cool2" + + Scenario: Supprimer un avis + When Je supprime l'avis du livre "9789876543210" par le client "33333333-3333-3333-3333-333333333333" + Then L'avis est supprimé du systeme + And Le systeme a 2 avis + +# erreur + Scenario: Essayer de soumettre un avis en double + When Je tente de soumettre un avis pour le livre "9781234567890" par le client "11111111-1111-1111-1111-111111111111" avec une note de 2 et un commentaire "Changed my mind." + Then L'avis n'est pas créé + And Je recois une erreur indiquant que l'avis existe deja + + Scenario: Essayer de soumettre un avis avec une note invalide + When Je tente de soumettre un avis pour le livre "9781234567890" par le client "11111111-1111-1111-1111-111111111111" avec une note de 6 et un commentaire "Too good!" + Then L'avis n'est pas créé + And Je recois une erreur indiquant que la note n'est pas valide + + Scenario: Essayer de soumettre un avis pour un livre non existant + When Je tente de soumettre un avis pour le livre "9780000000000" par le client "11111111-1111-1111-1111-111111111111" avec une note de 3 et un commentaire "Not found" + Then L'avis n'est pas créé + And Je recois une erreur indiquant que le livre n'existe pas + + Scenario: Essayer de soumettre un avis pour un client non existant + When Je tente de soumettre un avis pour le livre "9781234567890" par le client "99999999-9999-9999-9999-999999999999" avec une note de 3 et un commentaire "Who am I?" + Then L'avis n'est pas créé + And Je recois une erreur indiquant que le client n'existe pas + + Scenario: Essayer de supprimer un avis qui n'existe pas + When Je tente de supprimer l'avis du livre "9781234567890" par le client "99999999-9999-9999-9999-999999999999" + Then L'avis n'est pas supprimé + And Je recois une erreur indiquant que l'avis n'existe pas diff --git a/src/test/resources/features/subscription.feature b/src/test/resources/features/subscription.feature new file mode 100644 index 0000000..7bffb47 --- /dev/null +++ b/src/test/resources/features/subscription.feature @@ -0,0 +1,47 @@ +Feature: Gérer les abbonements + Background: + Given Le systeme contient tous ces utilisateurs : + | 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 | + +#CRUD + + Scenario: Créer un nouvel abonnement avec une CB + When Je créer un nouvel abonnement avec une CB : + | customerId | duration | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | 12 | CB | + Then Un nouvel abonnement est créé + + Scenario: Créer un nouvel abonnement avec Paypal + When Je créer un nouvel abonnement avec une Paypal : + | customerId | duration | paymentMethod | + | 22222222-2222-2222-2222-222222222222 | 24 | Paypal | + Then Un nouvel abonnement est créé + + Scenario: Recupérer un abonnement avec son ID + Given Je créer un nouvel abonnement avec une CB : + | customerId | duration | paymentMethod | + | 11111111-1111-1111-1111-111111111111 | 12 | CB | + When Je recupere un abonnement avec l'ID : + | customerId | + | 11111111-1111-1111-1111-111111111111 | + Then Je recois les informations de l'abonnement : + | subscriptionId | customerId | duration | paymentMethod | debutDate | + | 99999999-9999-9999-9999-999999999999 | 11111111-1111-1111-1111-111111111111 | 12 | CB | 2025-06-11 | + +#Erreur + + Scenario: Créer un nouvel abonnement avec une durée incorrecte + When Je crée un abonnement avce les informations : + | customerId | duration | paymentMethod | + | 33333333-3333-3333-3333-333333333333 | 0 | CB | + Then La creation de l'abonnement échoue + And Je recois une erreur d'abonnement invalide + + Scenario: Recuperer un abonnement avec un ID non existant + When Je recupere un abonnement avec l'ID : + | customerId | + | 44444444-4444-4444-4444-444444444444 | + Then Je recois une erreur d'abonnement non trouvé