diff --git a/pom.xml b/pom.xml index 27ec78e..cbe66e0 100644 --- a/pom.xml +++ b/pom.xml @@ -117,6 +117,12 @@ ${mockito.version} test + + junit + junit + RELEASE + test + diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/AvisDTO.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/AvisDTO.java new file mode 100644 index 0000000..09dad8f --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/AvisDTO.java @@ -0,0 +1,12 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis; + +import lombok.Builder; +import lombok.Getter; + +import java.util.UUID; + +@Builder +@Getter +public class AvisDTO { + private final UUID avisId; +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/AvisInfo.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/AvisInfo.java new file mode 100644 index 0000000..7175c38 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/AvisInfo.java @@ -0,0 +1,13 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis; + +import java.time.LocalDate; +import java.util.UUID; + +public record AvisInfo( + UUID clientId, + UUID livreId, + int note, + String commentaire, + LocalDate dateAchat +) { +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/converter/AvisConverter.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/converter/AvisConverter.java new file mode 100644 index 0000000..5c5d44a --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/converter/AvisConverter.java @@ -0,0 +1,27 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.converter; + +import fr.iut_fbleau.but3.dev62.mylibrary.avis.AvisDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.AvisInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.entity.Avis; + +public final class AvisConverter { + + private AvisConverter() { + } + + public static Avis toDomain(AvisInfo avisInfo) { + return Avis.builder() + .clientId(avisInfo.clientId()) + .livreId(avisInfo.livreId()) + .note(avisInfo.note()) + .commentaire(avisInfo.commentaire()) + .dateAchat(avisInfo.dateAchat()) + .build(); + } + + public static AvisDTO toDTO(Avis avis) { + return AvisDTO.builder() + .avisId(avis.getId()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/entity/Avis.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/entity/Avis.java new file mode 100644 index 0000000..715b5b7 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/entity/Avis.java @@ -0,0 +1,22 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.entity; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDate; +import java.util.UUID; + +@Builder +@Getter +public class Avis { + private UUID id; + private UUID clientId; + private UUID livreId; + private int note; + private String commentaire; + private LocalDate dateAchat; + + public void setRandomUUID() { + this.id = UUID.randomUUID(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/exception/AvisNotFoundException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/exception/AvisNotFoundException.java new file mode 100644 index 0000000..e8e6b00 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/exception/AvisNotFoundException.java @@ -0,0 +1,13 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.exception; + +import java.text.MessageFormat; +import java.util.UUID; + +public class AvisNotFoundException extends Exception { + + public static final String THE_AVIS_WITH_ID_DOES_NOT_EXIST_MESSAGE = "The avis with id {0} does not exist"; + + public AvisNotFoundException(UUID uuid) { + super(MessageFormat.format(THE_AVIS_WITH_ID_DOES_NOT_EXIST_MESSAGE, uuid)); + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/exception/NotValidAvisException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/exception/NotValidAvisException.java new file mode 100644 index 0000000..8bde194 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/exception/NotValidAvisException.java @@ -0,0 +1,8 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.exception; + +public class NotValidAvisException extends Exception { + + public NotValidAvisException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/repository/AvisRepository.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/repository/AvisRepository.java new file mode 100644 index 0000000..d26a933 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/repository/AvisRepository.java @@ -0,0 +1,57 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.avis.entity.Avis; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@NoArgsConstructor +public final class AvisRepository { + + private final List avisList = new ArrayList<>(); + + public List findAll() { + return avisList; + } + + public void deleteAll() { + avisList.clear(); + } + + public Avis save(Avis newAvis) { + Optional existing = this.findById(newAvis.getId()); + existing.ifPresentOrElse(avisList::remove, newAvis::setRandomUUID); + this.avisList.add(newAvis); + return newAvis; + } + + public Optional findById(UUID uuid) { + return this.avisList.stream() + .filter(avis -> avis.getId().equals(uuid)) + .findFirst(); + } + + public boolean existsById(UUID uuid) { + return this.avisList.stream() + .anyMatch(avis -> avis.getId().equals(uuid)); + } + + public List findByLivreId(UUID livreId) { + return this.avisList.stream() + .filter(avis -> avis.getLivreId().equals(livreId)) + .toList(); + } + + public List findByClientId(UUID clientId) { + return this.avisList.stream() + .filter(avis -> avis.getClientId().equals(clientId)) + .toList(); + } + + public void delete(Avis avis) { + avisList.remove(avis); + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/usecase/AvisUseCase.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/usecase/AvisUseCase.java new file mode 100644 index 0000000..0b81ede --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/usecase/AvisUseCase.java @@ -0,0 +1,41 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.avis.AvisDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.AvisInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.converter.AvisConverter; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.entity.Avis; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.exception.NotValidAvisException; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.repository.AvisRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.validator.AvisValidator; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.CustomerNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.repository.CustomerRepository; + +import java.util.Optional; +import java.util.UUID; + +public final class AvisUseCase { + + private final AvisRepository avisRepository; + private final CustomerRepository customerRepository; + + public AvisUseCase(AvisRepository avisRepository, CustomerRepository customerRepository) { + this.avisRepository = avisRepository; + this.customerRepository = customerRepository; + } + + public AvisDTO gererAvis(AvisInfo avisInfo) throws NotValidAvisException, CustomerNotFoundException { + AvisValidator.validate(avisInfo); + + customerRepository.findById(avisInfo.clientId()) + .orElseThrow(() -> new CustomerNotFoundException(avisInfo.clientId())); + + Avis avis = AvisConverter.toDomain(avisInfo); + Avis savedAvis = avisRepository.save(avis); + + return AvisConverter.toDTO(savedAvis); + } + + public Optional findAvisById(UUID uuid) { + return avisRepository.findById(uuid).map(AvisConverter::toDTO); + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/validator/AvisValidator.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/validator/AvisValidator.java new file mode 100644 index 0000000..0f4d0d8 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/validator/AvisValidator.java @@ -0,0 +1,54 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.avis.AvisInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.exception.NotValidAvisException; + +public final class AvisValidator { + + public static final String CLIENT_ID_CANNOT_BE_NULL = "Client id cannot be null"; + public static final String LIVRE_ID_CANNOT_BE_NULL = "Livre id cannot be null"; + public static final String NOTE_MUST_BE_BETWEEN_1_AND_5 = "Note must be between 1 and 5"; + public static final String COMMENTAIRE_CANNOT_BE_BLANK = "Commentaire cannot be blank"; + public static final String DATE_ACHAT_CANNOT_BE_NULL = "Date achat cannot be null"; + + private AvisValidator() { + } + + public static void validate(AvisInfo avisInfo) throws NotValidAvisException { + validateClientId(avisInfo); + validateLivreId(avisInfo); + validateNote(avisInfo); + validateCommentaire(avisInfo); + validateDateAchat(avisInfo); + } + + private static void validateClientId(AvisInfo avisInfo) throws NotValidAvisException { + if (avisInfo.clientId() == null) { + throw new NotValidAvisException(CLIENT_ID_CANNOT_BE_NULL); + } + } + + private static void validateLivreId(AvisInfo avisInfo) throws NotValidAvisException { + if (avisInfo.livreId() == null) { + throw new NotValidAvisException(LIVRE_ID_CANNOT_BE_NULL); + } + } + + private static void validateNote(AvisInfo avisInfo) throws NotValidAvisException { + if (avisInfo.note() < 1 || avisInfo.note() > 5) { + throw new NotValidAvisException(NOTE_MUST_BE_BETWEEN_1_AND_5); + } + } + + private static void validateCommentaire(AvisInfo avisInfo) throws NotValidAvisException { + if (avisInfo.commentaire() == null || avisInfo.commentaire().isBlank()) { + throw new NotValidAvisException(COMMENTAIRE_CANNOT_BE_BLANK); + } + } + + private static void validateDateAchat(AvisInfo avisInfo) throws NotValidAvisException { + if (avisInfo.dateAchat() == null) { + throw new NotValidAvisException(DATE_ACHAT_CANNOT_BE_NULL); + } + } +} \ 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..ea1a8d5 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/BookDTO.java @@ -0,0 +1,25 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book; + +import lombok.Builder; +import lombok.Getter; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +@Builder +@Getter +public class BookDTO { + private final UUID id; + private final String isbn; + private final String title; + private final String author; + private final String publisher; + private final LocalDate publicationDate; + private final BigDecimal price; + private final int stock; + private final List categories; + private final String description; + private final String language; +} 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..76201b1 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/BookInfo.java @@ -0,0 +1,19 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; + +public record BookInfo( + String isbn, + String title, + String author, + String publisher, + LocalDate publicationDate, + BigDecimal price, + int initialStock, + List categories, + String description, + String language +) { +} 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..24682fa --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/converter/BookConverter.java @@ -0,0 +1,42 @@ +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 bookInfo) { + return Book.builder() + .isbn(bookInfo.isbn()) + .title(bookInfo.title()) + .author(bookInfo.author()) + .publisher(bookInfo.publisher()) + .publicationDate(bookInfo.publicationDate()) + .price(bookInfo.price()) + .stock(bookInfo.initialStock()) + .categories(bookInfo.categories()) + .description(bookInfo.description()) + .language(bookInfo.language()) + .build(); + } + + public static BookDTO toDTO(Book book) { + return BookDTO.builder() + .id(book.getId()) + .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..a40dd1c --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/entity/Book.java @@ -0,0 +1,31 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.entity; + +import lombok.Builder; +import lombok.Getter; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +@Builder +@Getter +public class Book { + + private UUID id; + private String isbn; + private String title; + private String author; + private String publisher; + private LocalDate publicationDate; + private BigDecimal price; + private int stock; + private List categories; + private String description; + private String language; + + public void setRandomUUID() { + this.id = UUID.randomUUID(); + } +} + \ 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..091fde1 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/BookNotFoundException.java @@ -0,0 +1,14 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.exception; + +import java.text.MessageFormat; +import java.util.UUID; + +public class BookNotFoundException extends Exception { + + public static final String THE_BOOK_WITH_ID_DOES_NOT_EXIST_MESSAGE = "The book with id {0} does not exist"; + + public BookNotFoundException(UUID uuid) { + super(MessageFormat.format(THE_BOOK_WITH_ID_DOES_NOT_EXIST_MESSAGE, uuid)); + } +} + \ 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..017e965 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/NotValidBookException.java @@ -0,0 +1,9 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.exception; + +public class NotValidBookException extends Exception { + + public NotValidBookException(String message) { + super(message); + } +} + \ No newline at end of file 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..447c408 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/repository/BookRepository.java @@ -0,0 +1,52 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@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 optionalBookWithSameId = this.findById(newBook.getId()); + optionalBookWithSameId.ifPresentOrElse(books::remove, newBook::setRandomUUID); + this.books.add(newBook); + return newBook; + } + + public Optional findById(UUID uuid) { + return this.books.stream() + .filter(book -> book.getId().equals(uuid)) + .findFirst(); + } + + public boolean existsById(UUID uuid) { + return this.books.stream() + .anyMatch(book -> book.getId().equals(uuid)); + } + + public Optional findByIsbn(String isbn) { + return this.books.stream() + .filter(book -> book.getIsbn().equals(isbn)) + .findFirst(); + } + + public void delete(Book book) { + this.books.remove(book); + } +} + \ 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..1f2e03d --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCase.java @@ -0,0 +1,68 @@ +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.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.util.Optional; +import java.util.UUID; + +public final class BookUseCase { + + private final BookRepository bookRepository; + + public BookUseCase(BookRepository bookRepository) { + this.bookRepository = bookRepository; + } + + public String registerBook(BookInfo bookInfo) throws NotValidBookException { + BookValidator.validate(bookInfo); + Book bookToRegister = BookConverter.toDomain(bookInfo); + Book registeredBook = bookRepository.save(bookToRegister); + return registeredBook.getIsbn(); + } + + public Optional findBookByIsbn(String isbn) { + Optional optionalBook = bookRepository.findByIsbn(isbn); + return optionalBook.map(BookConverter::toDTO); + } + + public BookDTO updateBook(UUID uuid, BookInfo bookInfo) throws BookNotFoundException, NotValidBookException { + BookValidator.validate(bookInfo); + Book existingBook = getBookIfNotFoundThrowException(uuid); + Book updatedBook = Book.builder() + .id(uuid) + .isbn(bookInfo.isbn()) + .title(bookInfo.title()) + .author(bookInfo.author()) + .publisher(bookInfo.publisher()) + .publicationDate(bookInfo.publicationDate()) + .price(bookInfo.price()) + .stock(existingBook.getStock()) + .categories(bookInfo.categories()) + .description(bookInfo.description()) + .language(bookInfo.language()) + .build(); + Book saved = bookRepository.save(updatedBook); + return BookConverter.toDTO(saved); + } + + public void deleteBook(UUID uuid) throws BookNotFoundException { + Book bookToDelete = getBookIfNotFoundThrowException(uuid); + bookRepository.delete(bookToDelete); + } + + private Book getBookIfNotFoundThrowException(UUID uuid) throws BookNotFoundException { + Optional optionalBook = bookRepository.findById(uuid); + if (optionalBook.isEmpty()) { + throw new BookNotFoundException(uuid); + } + return optionalBook.get(); + } +} + \ 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..8313000 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/validator/BookValidator.java @@ -0,0 +1,60 @@ +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; + +import java.math.BigDecimal; + +public final class BookValidator { + + public static final String ISBN_IS_NOT_VALID = "ISBN is not valid"; + public static final String PRICE_MUST_BE_POSITIVE = "Price must be positive"; + public static final String TITLE_CANNOT_BE_BLANK = "Title cannot be blank"; + public static final String AUTHOR_CANNOT_BE_BLANK = "Author cannot be blank"; + public static final String PUBLISHER_CANNOT_BE_BLANK = "Publisher cannot be blank"; + public static final String ISBN_REGEX = "\\d{13}"; + + private BookValidator() { + } + + public static void validate(BookInfo bookInfo) throws NotValidBookException { + validateIsbn(bookInfo); + validateTitle(bookInfo); + validateAuthor(bookInfo); + validatePublisher(bookInfo); + validatePrice(bookInfo); + } + + private static void validateIsbn(BookInfo bookInfo) throws NotValidBookException { + if (bookInfo.isbn() == null || bookInfo.isbn().isBlank()) { + throw new NotValidBookException(ISBN_IS_NOT_VALID); + } + if (!bookInfo.isbn().matches(ISBN_REGEX)) { + 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 validatePublisher(BookInfo bookInfo) throws NotValidBookException { + if (bookInfo.publisher() == null || bookInfo.publisher().isBlank()) { + throw new NotValidBookException(PUBLISHER_CANNOT_BE_BLANK); + } + } + + private static void validatePrice(BookInfo bookInfo) throws NotValidBookException { + if (bookInfo.price() == null || bookInfo.price().compareTo(BigDecimal.ZERO) <= 0) { + throw new NotValidBookException(PRICE_MUST_BE_POSITIVE); + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/AdresseLivraison.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/AdresseLivraison.java new file mode 100644 index 0000000..1227699 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/AdresseLivraison.java @@ -0,0 +1,5 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order; + +public record AdresseLivraison(String rue, String ville, String codePostal, String pays) { +} + \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/LigneCommandeInfo.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/LigneCommandeInfo.java new file mode 100644 index 0000000..25bad52 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/LigneCommandeInfo.java @@ -0,0 +1,7 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order; + +import java.util.UUID; + +public record LigneCommandeInfo(UUID livreId, int quantite) { +} + \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/ModePaiement.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/ModePaiement.java new file mode 100644 index 0000000..ba49374 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/ModePaiement.java @@ -0,0 +1,8 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order; + +public enum ModePaiement { + CB, + PAYPAL, + POINTS_FIDELITE +} + \ No newline at end of file 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..7a2ef07 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderDTO.java @@ -0,0 +1,16 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order; + +import lombok.Builder; +import lombok.Getter; + +import java.math.BigDecimal; +import java.util.UUID; + +@Builder +@Getter +public class OrderDTO { + private final UUID commandeId; + private final BigDecimal montantTotal; + private final int pointsFideliteGagnes; +} + \ No newline at end of file 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..fa082e2 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/OrderInfo.java @@ -0,0 +1,13 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order; + +import java.util.List; +import java.util.UUID; + +public record OrderInfo( + UUID clientId, + List lignesCommande, + AdresseLivraison adresseLivraison, + ModePaiement modePaiement +) { +} + \ No newline at end of file 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..99680b2 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverter.java @@ -0,0 +1,35 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.converter; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.LigneCommande; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; + +import java.math.BigDecimal; +import java.util.List; + +public final class OrderConverter { + + private OrderConverter() { + } + + public static Order toDomain(OrderInfo orderInfo, List lignes, + BigDecimal montantTotal, int pointsFideliteGagnes) { + return Order.builder() + .clientId(orderInfo.clientId()) + .lignesCommande(lignes) + .adresseLivraison(orderInfo.adresseLivraison()) + .modePaiement(orderInfo.modePaiement()) + .montantTotal(montantTotal) + .pointsFideliteGagnes(pointsFideliteGagnes) + .build(); + } + + public static OrderDTO toDTO(Order order) { + return OrderDTO.builder() + .commandeId(order.getId()) + .montantTotal(order.getMontantTotal()) + .pointsFideliteGagnes(order.getPointsFideliteGagnes()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/LigneCommande.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/LigneCommande.java new file mode 100644 index 0000000..e70a249 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/LigneCommande.java @@ -0,0 +1,16 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.entity; + +import lombok.Builder; +import lombok.Getter; + +import java.math.BigDecimal; +import java.util.UUID; + +@Builder +@Getter +public class LigneCommande { + private UUID livreId; + private int quantite; + private BigDecimal prixUnitaire; +} + \ No newline at end of file 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..f38cea2 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/entity/Order.java @@ -0,0 +1,27 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.entity; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.AdresseLivraison; +import fr.iut_fbleau.but3.dev62.mylibrary.order.ModePaiement; +import lombok.Builder; +import lombok.Getter; + +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; + +@Builder +@Getter +public class Order { + private UUID id; + private UUID clientId; + private List lignesCommande; + private AdresseLivraison adresseLivraison; + private ModePaiement modePaiement; + private BigDecimal montantTotal; + private int pointsFideliteGagnes; + + public void setRandomUUID() { + this.id = UUID.randomUUID(); + } +} + \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/NotValidOrderException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/NotValidOrderException.java new file mode 100644 index 0000000..9d782c1 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/NotValidOrderException.java @@ -0,0 +1,9 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +public class NotValidOrderException extends Exception { + + public NotValidOrderException(String message) { + super(message); + } +} + \ No newline at end of file diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundException.java new file mode 100644 index 0000000..a8d62ca --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundException.java @@ -0,0 +1,14 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; + +import java.text.MessageFormat; +import java.util.UUID; + +public class OrderNotFoundException extends Exception { + + public static final String THE_ORDER_WITH_ID_DOES_NOT_EXIST_MESSAGE = "The order with id {0} does not exist"; + + public OrderNotFoundException(UUID uuid) { + super(MessageFormat.format(THE_ORDER_WITH_ID_DOES_NOT_EXIST_MESSAGE, uuid)); + } +} + \ No newline at end of file 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..c773123 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepository.java @@ -0,0 +1,51 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@NoArgsConstructor +public final class OrderRepository { + + private final List orders = new ArrayList<>(); + + public List findAll() { + return orders; + } + + public void deleteAll() { + orders.clear(); + } + + public Order save(Order newOrder) { + Optional existing = this.findById(newOrder.getId()); + existing.ifPresentOrElse(orders::remove, newOrder::setRandomUUID); + this.orders.add(newOrder); + return newOrder; + } + + public Optional findById(UUID uuid) { + return this.orders.stream() + .filter(order -> order.getId().equals(uuid)) + .findFirst(); + } + + public boolean existsById(UUID uuid) { + return this.orders.stream() + .anyMatch(order -> order.getId().equals(uuid)); + } + + public List findByClientId(UUID clientId) { + return this.orders.stream() + .filter(order -> order.getClientId().equals(clientId)) + .toList(); + } + + public void delete(Order order) { + this.orders.remove(order); + } +} \ No newline at end of file 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..5af112b --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCase.java @@ -0,0 +1,80 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; +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.customer.exception.CustomerNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.repository.CustomerRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.order.LigneCommandeInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.order.ModePaiement; +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.converter.OrderConverter; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.LigneCommande; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.repository.OrderRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.order.validator.OrderValidator; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +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 OrderDTO passerCommande(OrderInfo orderInfo) + throws NotValidOrderException, CustomerNotFoundException { + OrderValidator.validate(orderInfo); + + Customer customer = customerRepository.findById(orderInfo.clientId()) + .orElseThrow(() -> new CustomerNotFoundException(orderInfo.clientId())); + + List lignes = new ArrayList<>(); + BigDecimal montantTotal = BigDecimal.ZERO; + + for (LigneCommandeInfo ligneInfo : orderInfo.lignesCommande()) { + Book book = bookRepository.findById(ligneInfo.livreId()) + .orElseThrow(() -> new NotValidOrderException("Book not found: " + ligneInfo.livreId())); + + BigDecimal prixLigne = book.getPrice().multiply(BigDecimal.valueOf(ligneInfo.quantite())); + montantTotal = montantTotal.add(prixLigne); + + lignes.add(LigneCommande.builder() + .livreId(ligneInfo.livreId()) + .quantite(ligneInfo.quantite()) + .prixUnitaire(book.getPrice()) + .build()); + } + + int pointsGagnes = montantTotal.intValue(); + + if (orderInfo.modePaiement() == ModePaiement.POINTS_FIDELITE) { + customer.addLoyaltyPoints(-montantTotal.intValue()); + } else { + customer.addLoyaltyPoints(pointsGagnes); + } + customerRepository.save(customer); + + Order order = OrderConverter.toDomain(orderInfo, lignes, montantTotal, pointsGagnes); + Order savedOrder = orderRepository.save(order); + + return OrderConverter.toDTO(savedOrder); + } + + public Optional findOrderById(UUID uuid) { + return orderRepository.findById(uuid).map(OrderConverter::toDTO); + } +} \ 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..b7b1b04 --- /dev/null +++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/order/validator/OrderValidator.java @@ -0,0 +1,53 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.LigneCommandeInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; + +public final class OrderValidator { + + public static final String CLIENT_ID_CANNOT_BE_NULL = "Client id cannot be null"; + public static final String LIGNES_COMMANDE_CANNOT_BE_EMPTY = "Order must have at least one item"; + public static final String QUANTITE_MUST_BE_POSITIVE = "Quantity must be positive"; + public static final String MODE_PAIEMENT_CANNOT_BE_NULL = "Payment method cannot be null"; + public static final String ADRESSE_LIVRAISON_CANNOT_BE_NULL = "Delivery address cannot be null"; + + private OrderValidator() { + } + + public static void validate(OrderInfo orderInfo) throws NotValidOrderException { + validateClientId(orderInfo); + validateLignesCommande(orderInfo); + validateAdresseLivraison(orderInfo); + validateModePaiement(orderInfo); + } + + private static void validateClientId(OrderInfo orderInfo) throws NotValidOrderException { + if (orderInfo.clientId() == null) { + throw new NotValidOrderException(CLIENT_ID_CANNOT_BE_NULL); + } + } + + private static void validateLignesCommande(OrderInfo orderInfo) throws NotValidOrderException { + if (orderInfo.lignesCommande() == null || orderInfo.lignesCommande().isEmpty()) { + throw new NotValidOrderException(LIGNES_COMMANDE_CANNOT_BE_EMPTY); + } + for (LigneCommandeInfo ligne : orderInfo.lignesCommande()) { + if (ligne.quantite() <= 0) { + throw new NotValidOrderException(QUANTITE_MUST_BE_POSITIVE); + } + } + } + + private static void validateAdresseLivraison(OrderInfo orderInfo) throws NotValidOrderException { + if (orderInfo.adresseLivraison() == null) { + throw new NotValidOrderException(ADRESSE_LIVRAISON_CANNOT_BE_NULL); + } + } + + private static void validateModePaiement(OrderInfo orderInfo) throws NotValidOrderException { + if (orderInfo.modePaiement() == null) { + throw new NotValidOrderException(MODE_PAIEMENT_CANNOT_BE_NULL); + } + } +} \ No newline at end of file diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/converter/AvisConverterTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/converter/AvisConverterTest.java new file mode 100644 index 0000000..8bd0f71 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/converter/AvisConverterTest.java @@ -0,0 +1,77 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.converter; + +import fr.iut_fbleau.but3.dev62.mylibrary.avis.AvisDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.AvisInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.entity.Avis; +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.UUID; + + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("AvisConverter Unit Tests") +class AvisConverterTest { + + private final UUID clientId = UUID.randomUUID(); + private final UUID livreId = UUID.randomUUID(); + + @Nested + @DisplayName("toDomain() method tests") + class ToDomainTests { + + @Test + @DisplayName("Should convert AvisInfo to Avis domain object") + void shouldConvertAvisInfoToDomain() { + AvisInfo avisInfo = new AvisInfo( + clientId, livreId, 5, "Excellent livre !", LocalDate.of(2024, 1, 15) + ); + + Avis result = AvisConverter.toDomain(avisInfo); + + assertNotNull(result); + assertEquals(clientId, result.getClientId()); + assertEquals(livreId, result.getLivreId()); + assertEquals(5, result.getNote()); + assertEquals("Excellent livre !", result.getCommentaire()); + assertEquals(LocalDate.of(2024, 1, 15), result.getDateAchat()); + } + + @Test + @DisplayName("Should have null ID after toDomain (set by repository)") + void shouldHaveNullIdAfterToDomain() { + AvisInfo avisInfo = new AvisInfo(clientId, livreId, 3, "Commentaire", LocalDate.now()); + + Avis result = AvisConverter.toDomain(avisInfo); + + assertNull(result.getId()); + } + } + + @Nested + @DisplayName("toDTO() method tests") + class ToDTOTests { + + @Test + @DisplayName("Should convert Avis domain object to AvisDTO") + void shouldConvertAvisToDTO() { + UUID avisId = UUID.randomUUID(); + Avis avis = Avis.builder() + .id(avisId) + .clientId(clientId) + .livreId(livreId) + .note(5) + .commentaire("Excellent !") + .dateAchat(LocalDate.of(2024, 1, 15)) + .build(); + + AvisDTO result = AvisConverter.toDTO(avis); + + assertNotNull(result); + assertEquals(avisId, result.getAvisId()); + } + } +} \ No newline at end of file diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/entity/AvisTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/entity/AvisTest.java new file mode 100644 index 0000000..832fc99 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/entity/AvisTest.java @@ -0,0 +1,60 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.entity; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class AvisTest { + + @Test + @DisplayName("Builder should create a valid Avis instance") + void testAvisBuilder() { + UUID id = UUID.randomUUID(); + UUID clientId = UUID.randomUUID(); + UUID livreId = UUID.randomUUID(); + + Avis avis = Avis.builder() + .id(id) + .clientId(clientId) + .livreId(livreId) + .note(5) + .commentaire("Excellent livre !") + .dateAchat(LocalDate.of(2024, 1, 15)) + .build(); + + assertEquals(id, avis.getId()); + assertEquals(clientId, avis.getClientId()); + assertEquals(livreId, avis.getLivreId()); + assertEquals(5, avis.getNote()); + assertEquals("Excellent livre !", avis.getCommentaire()); + assertEquals(LocalDate.of(2024, 1, 15), avis.getDateAchat()); + } + + @Test + @DisplayName("setRandomUUID should set a new non-null UUID") + void testSetRandomUUID() { + Avis avis = Avis.builder().build(); + UUID originalId = avis.getId(); + + avis.setRandomUUID(); + + assertNotNull(avis.getId()); + assertNotEquals(originalId, avis.getId()); + } + + @Test + @DisplayName("Two setRandomUUID calls should produce different UUIDs") + void testSetRandomUUIDTwice() { + Avis avis = Avis.builder().build(); + avis.setRandomUUID(); + UUID firstId = avis.getId(); + + avis.setRandomUUID(); + + assertNotEquals(firstId, avis.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/exception/AvisNotFoundExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/exception/AvisNotFoundExceptionTest.java new file mode 100644 index 0000000..aa70cb3 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/exception/AvisNotFoundExceptionTest.java @@ -0,0 +1,47 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.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; + +class AvisNotFoundExceptionTest { + + @Test + @DisplayName("Exception message should contain the UUID provided") + void testExceptionMessageContainsUUID() { + UUID uuid = UUID.randomUUID(); + + AvisNotFoundException exception = new AvisNotFoundException(uuid); + + String expectedMessage = String.format("The avis 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(); + + AvisNotFoundException exception = new AvisNotFoundException(uuid); + + assertEquals("The avis with id {0} does not exist", + AvisNotFoundException.THE_AVIS_WITH_ID_DOES_NOT_EXIST_MESSAGE); + assertTrue(exception.getMessage().contains(uuid.toString())); + } + + @Test + @DisplayName("Exception should be properly thrown and caught") + void testExceptionCanBeThrownAndCaught() { + UUID uuid = UUID.randomUUID(); + + try { + throw new AvisNotFoundException(uuid); + } catch (AvisNotFoundException e) { + assertTrue(e.getMessage().contains(uuid.toString())); + } + } +} \ No newline at end of file diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/exception/NotValidAvisExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/exception/NotValidAvisExceptionTest.java new file mode 100644 index 0000000..768db03 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/exception/NotValidAvisExceptionTest.java @@ -0,0 +1,62 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.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 NotValidAvisExceptionTest { + + @Test + @DisplayName("Exception should be created with the provided message") + void testExceptionCreation() { + String errorMessage = "Note must be between 1 and 5"; + + NotValidAvisException exception = new NotValidAvisException(errorMessage); + + assertEquals(errorMessage, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = { + "Note must be between 1 and 5", + "Commentaire cannot be blank", + "Client id cannot be null", + "Livre id cannot be null", + "Date achat cannot be null" + }) + @DisplayName("Exception should handle different validation messages") + void testExceptionWithDifferentMessages(String errorMessage) { + NotValidAvisException exception = new NotValidAvisException(errorMessage); + + assertEquals(errorMessage, exception.getMessage()); + } + + @Test + @DisplayName("Exception should be properly thrown and caught") + void testExceptionCanBeThrownAndCaught() { + String errorMessage = "Note must be between 1 and 5"; + + Exception exception = assertThrows(NotValidAvisException.class, () -> { + throw new NotValidAvisException(errorMessage); + }); + + assertEquals(errorMessage, exception.getMessage()); + } + + @Test + @DisplayName("Exception should be catchable as a general Exception") + void testExceptionInheritance() { + String errorMessage = "Commentaire cannot be blank"; + + try { + throw new NotValidAvisException(errorMessage); + } catch (Exception e) { + assertEquals(NotValidAvisException.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/avis/repository/AvisRepositoryTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/repository/AvisRepositoryTest.java new file mode 100644 index 0000000..3cda2be --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/repository/AvisRepositoryTest.java @@ -0,0 +1,184 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.avis.entity.Avis; +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.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class AvisRepositoryTest { + + private AvisRepository repository; + private Avis avis1; + private Avis avis2; + private UUID clientId1; + private UUID livreId1; + + @BeforeEach + void setUp() { + repository = new AvisRepository(); + clientId1 = UUID.randomUUID(); + livreId1 = UUID.randomUUID(); + + avis1 = Avis.builder() + .clientId(clientId1) + .livreId(livreId1) + .note(5) + .commentaire("Excellent livre !") + .dateAchat(LocalDate.of(2024, 1, 15)) + .build(); + avis1.setRandomUUID(); + + avis2 = Avis.builder() + .clientId(UUID.randomUUID()) + .livreId(livreId1) + .note(3) + .commentaire("Pas mal") + .dateAchat(LocalDate.of(2024, 2, 10)) + .build(); + avis2.setRandomUUID(); + } + + @Test + @DisplayName("New repository should be empty") + void testNewRepositoryIsEmpty() { + assertTrue(repository.findAll().isEmpty()); + } + + @Nested + @DisplayName("Save operations") + class SaveOperations { + + @Test + @DisplayName("Save should add a new avis") + void testSaveNewAvis() { + Avis saved = repository.save(avis1); + + assertEquals(1, repository.findAll().size()); + assertEquals(avis1.getId(), saved.getId()); + } + + @Test + @DisplayName("Save should update existing avis with same ID") + void testSaveUpdatesExistingAvis() { + repository.save(avis1); + UUID id = avis1.getId(); + + Avis updated = Avis.builder() + .id(id) + .clientId(clientId1) + .livreId(livreId1) + .note(3) + .commentaire("Finalement moyen") + .dateAchat(LocalDate.of(2024, 1, 15)) + .build(); + + Avis saved = repository.save(updated); + + assertEquals(1, repository.findAll().size()); + assertEquals(id, saved.getId()); + assertEquals(3, saved.getNote()); + } + + @Test + @DisplayName("Save multiple avis should add all of them") + void testSaveMultipleAvis() { + repository.save(avis1); + repository.save(avis2); + + assertEquals(2, repository.findAll().size()); + } + } + + @Nested + @DisplayName("Find operations") + class FindOperations { + + @BeforeEach + void setUpAvis() { + repository.save(avis1); + repository.save(avis2); + } + + @Test + @DisplayName("FindById should return avis with matching ID") + void testFindById() { + Optional found = repository.findById(avis1.getId()); + + assertTrue(found.isPresent()); + assertEquals(avis1.getId(), found.get().getId()); + } + + @Test + @DisplayName("FindById should return empty Optional when ID doesn't exist") + void testFindByIdNotFound() { + Optional found = repository.findById(UUID.randomUUID()); + + assertTrue(found.isEmpty()); + } + + @Test + @DisplayName("FindByLivreId should return all avis for a book") + void testFindByLivreId() { + List found = repository.findByLivreId(livreId1); + + assertEquals(2, found.size()); + } + + @Test + @DisplayName("FindByClientId should return all avis for a client") + void testFindByClientId() { + List found = repository.findByClientId(clientId1); + + assertEquals(1, found.size()); + assertEquals(avis1.getId(), found.getFirst().getId()); + } + + @Test + @DisplayName("ExistsById should return true when ID exists") + void testExistsByIdExists() { + assertTrue(repository.existsById(avis1.getId())); + } + + @Test + @DisplayName("ExistsById should return false when ID doesn't exist") + void testExistsByIdNotExists() { + assertFalse(repository.existsById(UUID.randomUUID())); + } + } + + @Nested + @DisplayName("Delete operations") + class DeleteOperations { + + @BeforeEach + void setUpAvis() { + repository.save(avis1); + repository.save(avis2); + } + + @Test + @DisplayName("Delete should remove the specified avis") + void testDelete() { + repository.delete(avis1); + + assertEquals(1, repository.findAll().size()); + assertFalse(repository.findAll().contains(avis1)); + } + + @Test + @DisplayName("DeleteAll should remove all avis") + void testDeleteAll() { + repository.deleteAll(); + + assertTrue(repository.findAll().isEmpty()); + } + } +} \ No newline at end of file diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/usecase/AvisUseCaseTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/usecase/AvisUseCaseTest.java new file mode 100644 index 0000000..82d78c3 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/usecase/AvisUseCaseTest.java @@ -0,0 +1,169 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.avis.AvisDTO; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.AvisInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.entity.Avis; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.exception.NotValidAvisException; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.repository.AvisRepository; +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 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.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class AvisUseCaseTest { + + @Mock + private AvisRepository avisRepository; + + @Mock + private CustomerRepository customerRepository; + + @InjectMocks + private AvisUseCase avisUseCase; + + private UUID clientId; + private UUID livreId; + private Customer testCustomer; + private AvisInfo validAvisInfo; + + @BeforeEach + void setUp() { + clientId = UUID.randomUUID(); + livreId = UUID.randomUUID(); + + testCustomer = Customer.builder() + .id(clientId) + .firstName("Marie") + .lastName("Dupont") + .phoneNumber("0612345678") + .loyaltyPoints(100) + .build(); + + validAvisInfo = new AvisInfo(clientId, livreId, 5, "Excellent livre !", LocalDate.of(2024, 1, 15)); + } + + @Nested + @DisplayName("GererAvis tests") + class GererAvisTests { + + @Test + @DisplayName("Should create avis when valid data is provided") + void testGererAvisWithValidData() throws NotValidAvisException, CustomerNotFoundException { + when(customerRepository.findById(clientId)).thenReturn(Optional.of(testCustomer)); + + UUID avisId = UUID.randomUUID(); + Avis savedAvis = Avis.builder() + .id(avisId) + .clientId(clientId) + .livreId(livreId) + .note(5) + .commentaire("Excellent livre !") + .dateAchat(LocalDate.of(2024, 1, 15)) + .build(); + when(avisRepository.save(any(Avis.class))).thenReturn(savedAvis); + + AvisDTO result = avisUseCase.gererAvis(validAvisInfo); + + assertNotNull(result); + assertEquals(avisId, result.getAvisId()); + verify(avisRepository, times(1)).save(any(Avis.class)); + } + + @Test + @DisplayName("Should throw exception when customer does not exist") + void testGererAvisWithUnknownCustomer() { + when(customerRepository.findById(clientId)).thenReturn(Optional.empty()); + + assertThrows(CustomerNotFoundException.class, + () -> avisUseCase.gererAvis(validAvisInfo)); + + verify(avisRepository, never()).save(any(Avis.class)); + } + + @Test + @DisplayName("Should throw exception when note is invalid") + void testGererAvisWithInvalidNote() { + AvisInfo invalidAvis = new AvisInfo(clientId, livreId, 6, "Commentaire", LocalDate.now()); + + assertThrows(NotValidAvisException.class, + () -> avisUseCase.gererAvis(invalidAvis)); + + verify(avisRepository, never()).save(any(Avis.class)); + } + + @Test + @DisplayName("Should throw exception when commentaire is blank") + void testGererAvisWithBlankCommentaire() { + AvisInfo invalidAvis = new AvisInfo(clientId, livreId, 5, "", LocalDate.now()); + + assertThrows(NotValidAvisException.class, + () -> avisUseCase.gererAvis(invalidAvis)); + + verify(avisRepository, never()).save(any(Avis.class)); + } + + @Test + @DisplayName("Should throw exception when clientId is null") + void testGererAvisWithNullClientId() { + AvisInfo invalidAvis = new AvisInfo(null, livreId, 5, "Commentaire", LocalDate.now()); + + assertThrows(NotValidAvisException.class, + () -> avisUseCase.gererAvis(invalidAvis)); + + verify(avisRepository, never()).save(any(Avis.class)); + } + } + + @Nested + @DisplayName("FindAvis tests") + class FindAvisTests { + + @Test + @DisplayName("Should return avis when ID exists") + void testFindAvisById() { + UUID avisId = UUID.randomUUID(); + Avis avis = Avis.builder() + .id(avisId) + .clientId(clientId) + .livreId(livreId) + .note(5) + .commentaire("Excellent !") + .dateAchat(LocalDate.now()) + .build(); + + when(avisRepository.findById(avisId)).thenReturn(Optional.of(avis)); + + Optional result = avisUseCase.findAvisById(avisId); + + assertTrue(result.isPresent()); + assertEquals(avisId, result.get().getAvisId()); + } + + @Test + @DisplayName("Should return empty Optional when ID does not exist") + void testFindAvisByIdNotFound() { + UUID nonExistentId = UUID.randomUUID(); + when(avisRepository.findById(nonExistentId)).thenReturn(Optional.empty()); + + Optional result = avisUseCase.findAvisById(nonExistentId); + + assertTrue(result.isEmpty()); + } + } +} \ No newline at end of file diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/validator/AvisValidatorTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/validator/AvisValidatorTest.java new file mode 100644 index 0000000..3fee128 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/avis/validator/AvisValidatorTest.java @@ -0,0 +1,131 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.avis.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.avis.AvisInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.avis.exception.NotValidAvisException; +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.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class AvisValidatorTest { + + private final UUID clientId = UUID.randomUUID(); + private final UUID livreId = UUID.randomUUID(); + + private AvisInfo validAvis() { + return new AvisInfo(clientId, livreId, 5, "Excellent livre !", LocalDate.of(2024, 1, 15)); + } + + @Test + @DisplayName("Should validate avis with valid data") + void testValidateValidAvis() { + assertDoesNotThrow(() -> AvisValidator.validate(validAvis())); + } + + @Nested + @DisplayName("ClientId validation tests") + class ClientIdValidationTests { + + @Test + @DisplayName("Should throw exception when clientId is null") + void testValidateNullClientId() { + AvisInfo avis = new AvisInfo(null, livreId, 5, "Commentaire", LocalDate.now()); + + NotValidAvisException exception = assertThrows(NotValidAvisException.class, + () -> AvisValidator.validate(avis)); + + assertEquals(AvisValidator.CLIENT_ID_CANNOT_BE_NULL, exception.getMessage()); + } + } + + @Nested + @DisplayName("LivreId validation tests") + class LivreIdValidationTests { + + @Test + @DisplayName("Should throw exception when livreId is null") + void testValidateNullLivreId() { + AvisInfo avis = new AvisInfo(clientId, null, 5, "Commentaire", LocalDate.now()); + + NotValidAvisException exception = assertThrows(NotValidAvisException.class, + () -> AvisValidator.validate(avis)); + + assertEquals(AvisValidator.LIVRE_ID_CANNOT_BE_NULL, exception.getMessage()); + } + } + + @Nested + @DisplayName("Note validation tests") + class NoteValidationTests { + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3, 4, 5}) + @DisplayName("Should validate when note is between 1 and 5") + void testValidateValidNote(int validNote) { + AvisInfo avis = new AvisInfo(clientId, livreId, validNote, "Commentaire", LocalDate.now()); + assertDoesNotThrow(() -> AvisValidator.validate(avis)); + } + + @ParameterizedTest + @ValueSource(ints = {0, -1, 6, 10}) + @DisplayName("Should throw exception when note is out of range") + void testValidateInvalidNote(int invalidNote) { + AvisInfo avis = new AvisInfo(clientId, livreId, invalidNote, "Commentaire", LocalDate.now()); + + NotValidAvisException exception = assertThrows(NotValidAvisException.class, + () -> AvisValidator.validate(avis)); + + assertEquals(AvisValidator.NOTE_MUST_BE_BETWEEN_1_AND_5, exception.getMessage()); + } + } + + @Nested + @DisplayName("Commentaire validation tests") + class CommentaireValidationTests { + + @Test + @DisplayName("Should throw exception when commentaire is blank") + void testValidateBlankCommentaire() { + AvisInfo avis = new AvisInfo(clientId, livreId, 5, "", LocalDate.now()); + + NotValidAvisException exception = assertThrows(NotValidAvisException.class, + () -> AvisValidator.validate(avis)); + + assertEquals(AvisValidator.COMMENTAIRE_CANNOT_BE_BLANK, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {" ", " ", "\t", "\n"}) + @DisplayName("Should throw exception when commentaire contains only whitespace") + void testValidateWhitespaceCommentaire(String whitespace) { + AvisInfo avis = new AvisInfo(clientId, livreId, 5, whitespace, LocalDate.now()); + + NotValidAvisException exception = assertThrows(NotValidAvisException.class, + () -> AvisValidator.validate(avis)); + + assertEquals(AvisValidator.COMMENTAIRE_CANNOT_BE_BLANK, exception.getMessage()); + } + } + + @Nested + @DisplayName("DateAchat validation tests") + class DateAchatValidationTests { + + @Test + @DisplayName("Should throw exception when dateAchat is null") + void testValidateNullDateAchat() { + AvisInfo avis = new AvisInfo(clientId, livreId, 5, "Commentaire", null); + + NotValidAvisException exception = assertThrows(NotValidAvisException.class, + () -> AvisValidator.validate(avis)); + + assertEquals(AvisValidator.DATE_ACHAT_CANNOT_BE_NULL, exception.getMessage()); + } + } +} \ No newline at end of file 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..9f8bcdb --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/converter/BookConverterTest.java @@ -0,0 +1,108 @@ +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; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("BookConverter Unit Tests") +class BookConverterTest { + + @Nested + @DisplayName("toDomain() method tests") + class ToDomainTests { + + @Test + @DisplayName("Should convert BookInfo to Book domain object") + void shouldConvertBookInfoToDomain() { + BookInfo bookInfo = new BookInfo( + "9782016289308", + "Le Petit Prince", + "Antoine de Saint-Exupéry", + "Gallimard", + LocalDate.of(1943, 4, 6), + new BigDecimal("12.90"), + 10, + List.of("Roman", "Jeunesse"), + "Un classique", + "FR" + ); + + Book result = BookConverter.toDomain(bookInfo); + + assertNotNull(result); + assertEquals(bookInfo.isbn(), result.getIsbn()); + assertEquals(bookInfo.title(), result.getTitle()); + assertEquals(bookInfo.author(), result.getAuthor()); + assertEquals(bookInfo.publisher(), result.getPublisher()); + assertEquals(bookInfo.publicationDate(), result.getPublicationDate()); + assertEquals(bookInfo.price(), result.getPrice()); + assertEquals(bookInfo.initialStock(), result.getStock()); + assertEquals(bookInfo.categories(), result.getCategories()); + assertEquals(bookInfo.description(), result.getDescription()); + assertEquals(bookInfo.language(), result.getLanguage()); + } + + @Test + @DisplayName("Should have null ID after toDomain (set by repository)") + void shouldHaveNullIdAfterToDomain() { + BookInfo bookInfo = new BookInfo( + "9782016289308", "Titre", "Auteur", "Editeur", + LocalDate.now(), new BigDecimal("10.00"), 5, + List.of("Roman"), "Description", "FR" + ); + + Book result = BookConverter.toDomain(bookInfo); + + assertNull(result.getId()); + } + } + + @Nested + @DisplayName("toDTO() method tests") + class ToDTOTests { + + @Test + @DisplayName("Should convert Book domain object to BookDTO with all fields mapped correctly") + void shouldConvertBookToDTO() { + UUID id = UUID.randomUUID(); + Book book = Book.builder() + .id(id) + .isbn("9782016289308") + .title("Le Petit Prince") + .author("Antoine de Saint-Exupéry") + .publisher("Gallimard") + .publicationDate(LocalDate.of(1943, 4, 6)) + .price(new BigDecimal("12.90")) + .stock(10) + .categories(List.of("Roman", "Jeunesse")) + .description("Un classique") + .language("FR") + .build(); + + BookDTO result = BookConverter.toDTO(book); + + assertNotNull(result); + assertEquals(book.getId(), result.getId()); + 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()); + } + } +} 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..d3e5184 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/entity/BookTest.java @@ -0,0 +1,71 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.entity; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class BookTest { + + @Test + @DisplayName("Builder should create a valid Book instance") + void testBookBuilder() { + UUID id = UUID.randomUUID(); + + Book book = Book.builder() + .id(id) + .isbn("9782016289308") + .title("Le Petit Prince") + .author("Antoine de Saint-Exupéry") + .publisher("Gallimard") + .publicationDate(LocalDate.of(1943, 4, 6)) + .price(new BigDecimal("12.90")) + .stock(10) + .categories(List.of("Roman", "Jeunesse")) + .description("Un classique") + .language("FR") + .build(); + + assertEquals(id, book.getId()); + assertEquals("9782016289308", book.getIsbn()); + assertEquals("Le Petit Prince", book.getTitle()); + assertEquals("Antoine de Saint-Exupéry", book.getAuthor()); + assertEquals("Gallimard", book.getPublisher()); + assertEquals(LocalDate.of(1943, 4, 6), book.getPublicationDate()); + assertEquals(new BigDecimal("12.90"), book.getPrice()); + assertEquals(10, book.getStock()); + assertEquals(List.of("Roman", "Jeunesse"), book.getCategories()); + assertEquals("Un classique", book.getDescription()); + assertEquals("FR", book.getLanguage()); + } + + @Test + @DisplayName("setRandomUUID should set a new non-null UUID") + void testSetRandomUUID() { + Book book = Book.builder().build(); + UUID originalId = book.getId(); + + book.setRandomUUID(); + + assertNotNull(book.getId()); + assertNotEquals(originalId, book.getId()); + } + + @Test + @DisplayName("Two setRandomUUID calls should produce different UUIDs") + void testSetRandomUUIDTwice() { + Book book = Book.builder().build(); + book.setRandomUUID(); + UUID firstId = book.getId(); + + book.setRandomUUID(); + UUID secondId = book.getId(); + + assertNotEquals(firstId, secondId); + } +} 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..6b368aa --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/BookNotFoundExceptionTest.java @@ -0,0 +1,48 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.book.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; + +class BookNotFoundExceptionTest { + + @Test + @DisplayName("Exception message should contain the UUID provided") + void testExceptionMessageContainsUUID() { + UUID uuid = UUID.randomUUID(); + + BookNotFoundException exception = new BookNotFoundException(uuid); + + String expectedMessage = String.format("The book 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(); + + BookNotFoundException exception = new BookNotFoundException(uuid); + + assertEquals("The book with id {0} does not exist", + BookNotFoundException.THE_BOOK_WITH_ID_DOES_NOT_EXIST_MESSAGE); + assertTrue(exception.getMessage().contains(uuid.toString())); + } + + @Test + @DisplayName("Exception should be properly thrown and caught") + void testExceptionCanBeThrownAndCaught() { + UUID uuid = UUID.randomUUID(); + + try { + throw new BookNotFoundException(uuid); + } catch (BookNotFoundException e) { + String expectedMessage = String.format("The book with id %s does not exist", uuid); + assertEquals(expectedMessage, e.getMessage()); + } + } +} 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..cb86316 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/exception/NotValidBookExceptionTest.java @@ -0,0 +1,62 @@ +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("Exception should be created with the provided message") + void testExceptionCreation() { + String errorMessage = "Book data is not valid"; + + NotValidBookException exception = new NotValidBookException(errorMessage); + + assertEquals(errorMessage, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = { + "ISBN is not valid", + "Title cannot be blank", + "Author cannot be blank", + "Publisher cannot be blank", + "Price must be positive" + }) + @DisplayName("Exception should handle different validation messages") + void testExceptionWithDifferentMessages(String errorMessage) { + NotValidBookException exception = new NotValidBookException(errorMessage); + + assertEquals(errorMessage, exception.getMessage()); + } + + @Test + @DisplayName("Exception should be properly thrown and caught") + void testExceptionCanBeThrownAndCaught() { + String errorMessage = "ISBN is not valid"; + + Exception exception = assertThrows(NotValidBookException.class, () -> { + throw new NotValidBookException(errorMessage); + }); + + assertEquals(errorMessage, exception.getMessage()); + } + + @Test + @DisplayName("Exception should be catchable as a general Exception") + void testExceptionInheritance() { + String errorMessage = "Price must be positive"; + + try { + throw new NotValidBookException(errorMessage); + } catch (Exception e) { + assertEquals(NotValidBookException.class, e.getClass()); + assertEquals(errorMessage, e.getMessage()); + } + } +} 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..3024b91 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/repository/BookRepositoryTest.java @@ -0,0 +1,219 @@ +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.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +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("9782016289308") + .title("Le Petit Prince") + .author("Antoine de Saint-Exupéry") + .publisher("Gallimard") + .publicationDate(LocalDate.of(1943, 4, 6)) + .price(new BigDecimal("12.90")) + .stock(10) + .categories(List.of("Roman")) + .description("Un classique") + .language("FR") + .build(); + book1.setRandomUUID(); + + book2 = Book.builder() + .isbn("9782070409189") + .title("L'Étranger") + .author("Albert Camus") + .publisher("Gallimard") + .publicationDate(LocalDate.of(1942, 5, 19)) + .price(new BigDecimal("9.50")) + .stock(5) + .categories(List.of("Roman")) + .description("Roman philosophique") + .language("FR") + .build(); + book2.setRandomUUID(); + } + + @Test + @DisplayName("New repository should be empty") + void testNewRepositoryIsEmpty() { + assertTrue(repository.findAll().isEmpty()); + assertEquals(0, repository.findAll().size()); + } + + @Nested + @DisplayName("Save operations") + class SaveOperations { + + @Test + @DisplayName("Save should add a new book") + void testSaveNewBook() { + Book saved = repository.save(book1); + + assertEquals(1, repository.findAll().size()); + assertEquals(book1.getId(), saved.getId()); + assertEquals(book1.getIsbn(), saved.getIsbn()); + } + + @Test + @DisplayName("Save should update existing book with same ID") + void testSaveUpdatesExistingBook() { + repository.save(book1); + UUID id = book1.getId(); + + Book updatedBook = Book.builder() + .id(id) + .isbn("9782016289308") + .title("Le Petit Prince - Edition collector") + .author("Antoine de Saint-Exupéry") + .publisher("Gallimard") + .publicationDate(LocalDate.of(1943, 4, 6)) + .price(new BigDecimal("19.90")) + .stock(3) + .categories(List.of("Roman")) + .description("Edition collector") + .language("FR") + .build(); + + Book saved = repository.save(updatedBook); + + assertEquals(1, repository.findAll().size()); + assertEquals(id, saved.getId()); + assertEquals("Le Petit Prince - Edition collector", saved.getTitle()); + assertEquals(new BigDecimal("19.90"), saved.getPrice()); + } + + @Test + @DisplayName("Save multiple books should add all of them") + void testSaveMultipleBooks() { + repository.save(book1); + repository.save(book2); + + assertEquals(2, repository.findAll().size()); + assertTrue(repository.findAll().contains(book1)); + assertTrue(repository.findAll().contains(book2)); + } + } + + @Nested + @DisplayName("Find operations") + class FindOperations { + + @BeforeEach + void setUpBooks() { + repository.save(book1); + repository.save(book2); + } + + @Test + @DisplayName("FindAll should return all books") + void testFindAll() { + assertEquals(2, repository.findAll().size()); + } + + @Test + @DisplayName("FindById should return book with matching ID") + void testFindById() { + Optional found = repository.findById(book1.getId()); + + assertTrue(found.isPresent()); + assertEquals(book1.getIsbn(), found.get().getIsbn()); + assertEquals(book1.getTitle(), found.get().getTitle()); + } + + @Test + @DisplayName("FindById should return empty Optional when ID doesn't exist") + void testFindByIdNotFound() { + Optional found = repository.findById(UUID.randomUUID()); + + assertTrue(found.isEmpty()); + } + + @Test + @DisplayName("FindByIsbn should return book with matching ISBN") + void testFindByIsbn() { + Optional found = repository.findByIsbn("9782016289308"); + + assertTrue(found.isPresent()); + assertEquals(book1.getId(), found.get().getId()); + } + + @Test + @DisplayName("FindByIsbn should return empty Optional when ISBN doesn't exist") + void testFindByIsbnNotFound() { + Optional found = repository.findByIsbn("0000000000000"); + + assertTrue(found.isEmpty()); + } + + @Test + @DisplayName("ExistsById should return true when ID exists") + void testExistsByIdExists() { + assertTrue(repository.existsById(book1.getId())); + } + + @Test + @DisplayName("ExistsById should return false when ID doesn't exist") + void testExistsByIdNotExists() { + assertFalse(repository.existsById(UUID.randomUUID())); + } + } + + @Nested + @DisplayName("Delete operations") + class DeleteOperations { + + @BeforeEach + void setUpBooks() { + repository.save(book1); + repository.save(book2); + } + + @Test + @DisplayName("Delete should remove the specified book") + void testDelete() { + repository.delete(book1); + + assertEquals(1, repository.findAll().size()); + assertFalse(repository.findAll().contains(book1)); + assertTrue(repository.findAll().contains(book2)); + } + + @Test + @DisplayName("DeleteAll should remove all books") + void testDeleteAll() { + repository.deleteAll(); + + assertTrue(repository.findAll().isEmpty()); + } + + @Test + @DisplayName("Delete should not throw exception when book doesn't exist") + void testDeleteNonExistentBook() { + Book nonExistent = Book.builder().isbn("0000000000000").build(); + nonExistent.setRandomUUID(); + + assertDoesNotThrow(() -> repository.delete(nonExistent)); + assertEquals(2, repository.findAll().size()); + } + } +} 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..85c2dca --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCaseTest.java @@ -0,0 +1,231 @@ +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.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.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class BookUseCaseTest { + + @Mock + private BookRepository bookRepository; + + @InjectMocks + private BookUseCase bookUseCase; + + private UUID bookId; + private Book testBook; + private BookInfo validBookInfo; + + @BeforeEach + void setUp() { + bookId = UUID.randomUUID(); + testBook = Book.builder() + .id(bookId) + .isbn("9782016289308") + .title("Le Petit Prince") + .author("Antoine de Saint-Exupéry") + .publisher("Gallimard") + .publicationDate(LocalDate.of(1943, 4, 6)) + .price(new BigDecimal("12.90")) + .stock(10) + .categories(List.of("Roman", "Jeunesse")) + .description("Un classique") + .language("FR") + .build(); + + validBookInfo = new BookInfo( + "9782016289308", + "Le Petit Prince", + "Antoine de Saint-Exupéry", + "Gallimard", + LocalDate.of(1943, 4, 6), + new BigDecimal("12.90"), + 10, + List.of("Roman", "Jeunesse"), + "Un classique", + "FR" + ); + } + + @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("9782016289308", 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, + BigDecimal.ZERO, + -1, + null, + "", + "" + ); + + 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("9782016289308")).thenReturn(Optional.of(testBook)); + + Optional foundBook = bookUseCase.findBookByIsbn("9782016289308"); + + assertTrue(foundBook.isPresent()); + assertEquals(testBook.getId(), foundBook.get().getId()); + assertEquals(testBook.getTitle(), foundBook.get().getTitle()); + verify(bookRepository, times(1)).findByIsbn("9782016289308"); + } + + @Test + @DisplayName("Should return empty Optional when ISBN doesn't exist") + void testFindBookByIsbnNotFound() { + when(bookRepository.findByIsbn("9999999999999")).thenReturn(Optional.empty()); + + Optional foundBook = bookUseCase.findBookByIsbn("9999999999999"); + + assertTrue(foundBook.isEmpty()); + verify(bookRepository, times(1)).findByIsbn("9999999999999"); + } + } + + @Nested + @DisplayName("Update book tests") + class UpdateBookTests { + + @Test + @DisplayName("Should update book when valid data is provided") + void testUpdateBookWithValidData() throws BookNotFoundException, NotValidBookException { + when(bookRepository.findById(bookId)).thenReturn(Optional.of(testBook)); + + Book updatedBook = Book.builder() + .id(bookId) + .isbn("9782070409189") + .title("L'Étranger") + .author("Albert Camus") + .publisher("Gallimard") + .publicationDate(LocalDate.of(1942, 5, 19)) + .price(new BigDecimal("9.50")) + .stock(10) + .categories(List.of("Roman")) + .description("Roman philosophique") + .language("FR") + .build(); + + when(bookRepository.save(any(Book.class))).thenReturn(updatedBook); + + BookInfo updateInfo = new BookInfo( + "9782070409189", + "L'Étranger", + "Albert Camus", + "Gallimard", + LocalDate.of(1942, 5, 19), + new BigDecimal("9.50"), + 99, + List.of("Roman"), + "Roman philosophique", + "FR" + ); + + BookDTO result = bookUseCase.updateBook(bookId, updateInfo); + + assertNotNull(result); + assertEquals(bookId, result.getId()); + assertEquals("L'Étranger", result.getTitle()); + assertEquals("9782070409189", result.getIsbn()); + assertEquals(10, result.getStock()); + verify(bookRepository, times(1)).findById(bookId); + verify(bookRepository, times(1)).save(any(Book.class)); + } + + @Test + @DisplayName("Should throw exception when book ID doesn't exist") + void testUpdateBookNotFound() { + UUID nonExistentId = UUID.randomUUID(); + when(bookRepository.findById(nonExistentId)).thenReturn(Optional.empty()); + + assertThrows(BookNotFoundException.class, + () -> bookUseCase.updateBook(nonExistentId, validBookInfo)); + + verify(bookRepository, times(1)).findById(nonExistentId); + verify(bookRepository, never()).save(any(Book.class)); + } + } + + @Nested + @DisplayName("Delete book tests") + class DeleteBookTests { + + @Test + @DisplayName("Should delete book when ID exists") + void testDeleteBook() throws BookNotFoundException { + when(bookRepository.findById(bookId)).thenReturn(Optional.of(testBook)); + + assertDoesNotThrow(() -> bookUseCase.deleteBook(bookId)); + + verify(bookRepository, times(1)).findById(bookId); + verify(bookRepository, times(1)).delete(testBook); + } + + @Test + @DisplayName("Should throw exception when deleting unknown book") + void testDeleteBookNotFound() { + UUID nonExistentId = UUID.randomUUID(); + when(bookRepository.findById(nonExistentId)).thenReturn(Optional.empty()); + + assertThrows(BookNotFoundException.class, + () -> bookUseCase.deleteBook(nonExistentId)); + + verify(bookRepository, times(1)).findById(nonExistentId); + verify(bookRepository, never()).delete(any(Book.class)); + } + } +} 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..d4bed20 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/validator/BookValidatorTest.java @@ -0,0 +1,184 @@ +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; +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.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class BookValidatorTest { + + private BookInfo validBook() { + return new BookInfo( + "9782016289308", + "Le Petit Prince", + "Antoine de Saint-Exupéry", + "Gallimard", + LocalDate.of(1943, 4, 6), + new BigDecimal("12.90"), + 10, + List.of("Roman"), + "Un classique", + "FR" + ); + } + + @Test + @DisplayName("Should validate book with valid data") + void testValidateValidBook() { + assertDoesNotThrow(() -> BookValidator.validate(validBook())); + } + + @Nested + @DisplayName("ISBN validation tests") + class IsbnValidationTests { + + @Test + @DisplayName("Should throw exception when ISBN is blank") + void testValidateBlankIsbn() { + BookInfo book = new BookInfo("", "Titre", "Auteur", "Editeur", + LocalDate.now(), new BigDecimal("10.00"), 1, List.of("Roman"), "Desc", "FR"); + + NotValidBookException exception = assertThrows(NotValidBookException.class, + () -> BookValidator.validate(book)); + + assertEquals(BookValidator.ISBN_IS_NOT_VALID, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {"123456", "978201628930", "97820162893088", "abcdefghijklm", "978-016289308"}) + @DisplayName("Should throw exception when ISBN format is invalid") + void testValidateInvalidIsbnFormat(String invalidIsbn) { + BookInfo book = new BookInfo(invalidIsbn, "Titre", "Auteur", "Editeur", + LocalDate.now(), new BigDecimal("10.00"), 1, List.of("Roman"), "Desc", "FR"); + + NotValidBookException exception = assertThrows(NotValidBookException.class, + () -> BookValidator.validate(book)); + + assertEquals(BookValidator.ISBN_IS_NOT_VALID, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {"9782016289308", "9782070409189", "9782253006329"}) + @DisplayName("Should validate when ISBN has exactly 13 digits") + void testValidateValidIsbn(String validIsbn) { + BookInfo book = new BookInfo(validIsbn, "Titre", "Auteur", "Editeur", + LocalDate.now(), new BigDecimal("10.00"), 1, List.of("Roman"), "Desc", "FR"); + + assertDoesNotThrow(() -> BookValidator.validate(book)); + } + } + + @Nested + @DisplayName("Title validation tests") + class TitleValidationTests { + + @Test + @DisplayName("Should throw exception when title is blank") + void testValidateBlankTitle() { + BookInfo book = new BookInfo("9782016289308", "", "Auteur", "Editeur", + LocalDate.now(), new BigDecimal("10.00"), 1, List.of("Roman"), "Desc", "FR"); + + NotValidBookException exception = assertThrows(NotValidBookException.class, + () -> BookValidator.validate(book)); + + assertEquals(BookValidator.TITLE_CANNOT_BE_BLANK, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {" ", " ", "\t", "\n"}) + @DisplayName("Should throw exception when title contains only whitespace") + void testValidateWhitespaceTitle(String whitespace) { + BookInfo book = new BookInfo("9782016289308", whitespace, "Auteur", "Editeur", + LocalDate.now(), new BigDecimal("10.00"), 1, List.of("Roman"), "Desc", "FR"); + + NotValidBookException exception = assertThrows(NotValidBookException.class, + () -> BookValidator.validate(book)); + + assertEquals(BookValidator.TITLE_CANNOT_BE_BLANK, exception.getMessage()); + } + } + + @Nested + @DisplayName("Author validation tests") + class AuthorValidationTests { + + @Test + @DisplayName("Should throw exception when author is blank") + void testValidateBlankAuthor() { + BookInfo book = new BookInfo("9782016289308", "Titre", "", "Editeur", + LocalDate.now(), new BigDecimal("10.00"), 1, List.of("Roman"), "Desc", "FR"); + + NotValidBookException exception = assertThrows(NotValidBookException.class, + () -> BookValidator.validate(book)); + + assertEquals(BookValidator.AUTHOR_CANNOT_BE_BLANK, exception.getMessage()); + } + } + + @Nested + @DisplayName("Publisher validation tests") + class PublisherValidationTests { + + @Test + @DisplayName("Should throw exception when publisher is blank") + void testValidateBlankPublisher() { + BookInfo book = new BookInfo("9782016289308", "Titre", "Auteur", "", + LocalDate.now(), new BigDecimal("10.00"), 1, List.of("Roman"), "Desc", "FR"); + + NotValidBookException exception = assertThrows(NotValidBookException.class, + () -> BookValidator.validate(book)); + + assertEquals(BookValidator.PUBLISHER_CANNOT_BE_BLANK, exception.getMessage()); + } + } + + @Nested + @DisplayName("Price validation tests") + class PriceValidationTests { + + @Test + @DisplayName("Should throw exception when price is negative") + void testValidateNegativePrice() { + BookInfo book = new BookInfo("9782016289308", "Titre", "Auteur", "Editeur", + LocalDate.now(), new BigDecimal("-5.00"), 1, List.of("Roman"), "Desc", "FR"); + + NotValidBookException exception = assertThrows(NotValidBookException.class, + () -> BookValidator.validate(book)); + + assertEquals(BookValidator.PRICE_MUST_BE_POSITIVE, exception.getMessage()); + } + + @Test + @DisplayName("Should throw exception when price is zero") + void testValidateZeroPrice() { + BookInfo book = new BookInfo("9782016289308", "Titre", "Auteur", "Editeur", + LocalDate.now(), BigDecimal.ZERO, 1, List.of("Roman"), "Desc", "FR"); + + NotValidBookException exception = assertThrows(NotValidBookException.class, + () -> BookValidator.validate(book)); + + assertEquals(BookValidator.PRICE_MUST_BE_POSITIVE, exception.getMessage()); + } + + @Test + @DisplayName("Should throw exception when price is null") + void testValidateNullPrice() { + BookInfo book = new BookInfo("9782016289308", "Titre", "Auteur", "Editeur", + LocalDate.now(), null, 1, List.of("Roman"), "Desc", "FR"); + + NotValidBookException exception = assertThrows(NotValidBookException.class, + () -> BookValidator.validate(book)); + + assertEquals(BookValidator.PRICE_MUST_BE_POSITIVE, exception.getMessage()); + } + } +} 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..e527210 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/converter/OrderConverterTest.java @@ -0,0 +1,90 @@ +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.LigneCommande; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("OrderConverter Unit Tests") +class OrderConverterTest { + + private final UUID clientId = UUID.randomUUID(); + private final UUID livreId = UUID.randomUUID(); + private final AdresseLivraison adresse = new AdresseLivraison("1 rue de Paris", "Paris", "75001", "France"); + + @Nested + @DisplayName("toDomain() method tests") + class ToDomainTests { + + @Test + @DisplayName("Should convert OrderInfo to Order domain object") + void shouldConvertOrderInfoToDomain() { + OrderInfo orderInfo = new OrderInfo( + clientId, + List.of(new LigneCommandeInfo(livreId, 2)), + adresse, + ModePaiement.CB + ); + + List lignes = List.of( + LigneCommande.builder() + .livreId(livreId) + .quantite(2) + .prixUnitaire(new BigDecimal("12.90")) + .build() + ); + + Order result = OrderConverter.toDomain(orderInfo, lignes, new BigDecimal("25.80"), 25); + + assertNotNull(result); + assertEquals(clientId, result.getClientId()); + assertEquals(adresse, result.getAdresseLivraison()); + assertEquals(ModePaiement.CB, result.getModePaiement()); + assertEquals(new BigDecimal("25.80"), result.getMontantTotal()); + assertEquals(25, result.getPointsFideliteGagnes()); + assertEquals(1, result.getLignesCommande().size()); + } + + @Test + @DisplayName("Should have null ID after toDomain (set by repository)") + void shouldHaveNullIdAfterToDomain() { + OrderInfo orderInfo = new OrderInfo(clientId, List.of(new LigneCommandeInfo(livreId, 1)), adresse, ModePaiement.CB); + + Order result = OrderConverter.toDomain(orderInfo, List.of(), BigDecimal.ZERO, 0); + + assertNull(result.getId()); + } + } + + @Nested + @DisplayName("toDTO() method tests") + class ToDTOTests { + + @Test + @DisplayName("Should convert Order domain object to OrderDTO with all fields mapped correctly") + void shouldConvertOrderToDTO() { + UUID orderId = UUID.randomUUID(); + Order order = Order.builder() + .id(orderId) + .clientId(clientId) + .montantTotal(new BigDecimal("25.80")) + .pointsFideliteGagnes(25) + .build(); + + OrderDTO result = OrderConverter.toDTO(order); + + assertNotNull(result); + assertEquals(orderId, result.getCommandeId()); + assertEquals(new BigDecimal("25.80"), result.getMontantTotal()); + assertEquals(25, result.getPointsFideliteGagnes()); + } + } +} \ No newline at end of file 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..04f7411 --- /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.AdresseLivraison; +import fr.iut_fbleau.but3.dev62.mylibrary.order.ModePaiement; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class OrderTest { + + @Test + @DisplayName("Builder should create a valid Order instance") + void testOrderBuilder() { + UUID id = UUID.randomUUID(); + UUID clientId = UUID.randomUUID(); + AdresseLivraison adresse = new AdresseLivraison("1 rue de Paris", "Paris", "75001", "France"); + + Order order = Order.builder() + .id(id) + .clientId(clientId) + .adresseLivraison(adresse) + .modePaiement(ModePaiement.CB) + .montantTotal(new BigDecimal("25.80")) + .pointsFideliteGagnes(25) + .lignesCommande(List.of()) + .build(); + + assertEquals(id, order.getId()); + assertEquals(clientId, order.getClientId()); + assertEquals(adresse, order.getAdresseLivraison()); + assertEquals(ModePaiement.CB, order.getModePaiement()); + assertEquals(new BigDecimal("25.80"), order.getMontantTotal()); + assertEquals(25, order.getPointsFideliteGagnes()); + } + + @Test + @DisplayName("setRandomUUID should set a new non-null UUID") + void testSetRandomUUID() { + Order order = Order.builder().build(); + UUID originalId = order.getId(); + + order.setRandomUUID(); + + assertNotNull(order.getId()); + assertNotEquals(originalId, order.getId()); + } + + @Test + @DisplayName("Two setRandomUUID calls should produce different UUIDs") + void testSetRandomUUIDTwice() { + Order order = Order.builder().build(); + order.setRandomUUID(); + UUID firstId = order.getId(); + + order.setRandomUUID(); + UUID secondId = order.getId(); + + assertNotEquals(firstId, secondId); + } + + @Test + @DisplayName("Builder should create a valid LigneCommande instance") + void testLigneCommandeBuilder() { + UUID livreId = UUID.randomUUID(); + + LigneCommande ligne = LigneCommande.builder() + .livreId(livreId) + .quantite(3) + .prixUnitaire(new BigDecimal("12.90")) + .build(); + + assertEquals(livreId, ligne.getLivreId()); + assertEquals(3, ligne.getQuantite()); + assertEquals(new BigDecimal("12.90"), ligne.getPrixUnitaire()); + } +} \ No newline at end of file diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/NotValidOrderExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/NotValidOrderExceptionTest.java new file mode 100644 index 0000000..3c13c35 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/NotValidOrderExceptionTest.java @@ -0,0 +1,62 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.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 NotValidOrderExceptionTest { + + @Test + @DisplayName("Exception should be created with the provided message") + void testExceptionCreation() { + String errorMessage = "Order data is not valid"; + + NotValidOrderException exception = new NotValidOrderException(errorMessage); + + assertEquals(errorMessage, exception.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = { + "Client id cannot be null", + "Order must have at least one item", + "Quantity must be positive", + "Payment method cannot be null", + "Delivery address cannot be null" + }) + @DisplayName("Exception should handle different validation messages") + void testExceptionWithDifferentMessages(String errorMessage) { + NotValidOrderException exception = new NotValidOrderException(errorMessage); + + assertEquals(errorMessage, exception.getMessage()); + } + + @Test + @DisplayName("Exception should be properly thrown and caught") + void testExceptionCanBeThrownAndCaught() { + String errorMessage = "Order must have at least one item"; + + Exception exception = assertThrows(NotValidOrderException.class, () -> { + throw new NotValidOrderException(errorMessage); + }); + + assertEquals(errorMessage, exception.getMessage()); + } + + @Test + @DisplayName("Exception should be catchable as a general Exception") + void testExceptionInheritance() { + String errorMessage = "Quantity must be positive"; + + try { + throw new NotValidOrderException(errorMessage); + } catch (Exception e) { + assertEquals(NotValidOrderException.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/order/exception/OrderNotFoundExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundExceptionTest.java new file mode 100644 index 0000000..ce4937d --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/exception/OrderNotFoundExceptionTest.java @@ -0,0 +1,48 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.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; + +class OrderNotFoundExceptionTest { + + @Test + @DisplayName("Exception message should contain the UUID provided") + void testExceptionMessageContainsUUID() { + UUID uuid = UUID.randomUUID(); + + OrderNotFoundException exception = new OrderNotFoundException(uuid); + + String expectedMessage = String.format("The order 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(); + + OrderNotFoundException exception = new OrderNotFoundException(uuid); + + assertEquals("The order with id {0} does not exist", + OrderNotFoundException.THE_ORDER_WITH_ID_DOES_NOT_EXIST_MESSAGE); + assertTrue(exception.getMessage().contains(uuid.toString())); + } + + @Test + @DisplayName("Exception should be properly thrown and caught") + void testExceptionCanBeThrownAndCaught() { + UUID uuid = UUID.randomUUID(); + + try { + throw new OrderNotFoundException(uuid); + } catch (OrderNotFoundException e) { + String expectedMessage = String.format("The order with id %s does not exist", uuid); + assertEquals(expectedMessage, e.getMessage()); + } + } +} \ No newline at end of file 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..75bd662 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/repository/OrderRepositoryTest.java @@ -0,0 +1,202 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.AdresseLivraison; +import fr.iut_fbleau.but3.dev62.mylibrary.order.ModePaiement; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class OrderRepositoryTest { + + private OrderRepository repository; + private Order order1; + private Order order2; + private UUID clientId1; + private UUID clientId2; + + @BeforeEach + void setUp() { + repository = new OrderRepository(); + clientId1 = UUID.randomUUID(); + clientId2 = UUID.randomUUID(); + + AdresseLivraison adresse = new AdresseLivraison("1 rue de Paris", "Paris", "75001", "France"); + + order1 = Order.builder() + .clientId(clientId1) + .adresseLivraison(adresse) + .modePaiement(ModePaiement.CB) + .montantTotal(new BigDecimal("25.80")) + .pointsFideliteGagnes(25) + .lignesCommande(List.of()) + .build(); + order1.setRandomUUID(); + + order2 = Order.builder() + .clientId(clientId2) + .adresseLivraison(adresse) + .modePaiement(ModePaiement.PAYPAL) + .montantTotal(new BigDecimal("9.50")) + .pointsFideliteGagnes(9) + .lignesCommande(List.of()) + .build(); + order2.setRandomUUID(); + } + + @Test + @DisplayName("New repository should be empty") + void testNewRepositoryIsEmpty() { + assertTrue(repository.findAll().isEmpty()); + assertEquals(0, repository.findAll().size()); + } + + @Nested + @DisplayName("Save operations") + class SaveOperations { + + @Test + @DisplayName("Save should add a new order") + void testSaveNewOrder() { + Order saved = repository.save(order1); + + assertEquals(1, repository.findAll().size()); + assertEquals(order1.getId(), saved.getId()); + } + + @Test + @DisplayName("Save should update existing order with same ID") + void testSaveUpdatesExistingOrder() { + repository.save(order1); + UUID id = order1.getId(); + + Order updatedOrder = Order.builder() + .id(id) + .clientId(clientId1) + .adresseLivraison(new AdresseLivraison("2 rue de Lyon", "Lyon", "69001", "France")) + .modePaiement(ModePaiement.PAYPAL) + .montantTotal(new BigDecimal("50.00")) + .pointsFideliteGagnes(50) + .lignesCommande(List.of()) + .build(); + + Order saved = repository.save(updatedOrder); + + assertEquals(1, repository.findAll().size()); + assertEquals(id, saved.getId()); + assertEquals(new BigDecimal("50.00"), saved.getMontantTotal()); + } + + @Test + @DisplayName("Save multiple orders should add all of them") + void testSaveMultipleOrders() { + repository.save(order1); + repository.save(order2); + + assertEquals(2, repository.findAll().size()); + } + } + + @Nested + @DisplayName("Find operations") + class FindOperations { + + @BeforeEach + void setUpOrders() { + repository.save(order1); + repository.save(order2); + } + + @Test + @DisplayName("FindAll should return all orders") + void testFindAll() { + assertEquals(2, repository.findAll().size()); + } + + @Test + @DisplayName("FindById should return order with matching ID") + void testFindById() { + Optional found = repository.findById(order1.getId()); + + assertTrue(found.isPresent()); + assertEquals(order1.getId(), found.get().getId()); + } + + @Test + @DisplayName("FindById should return empty Optional when ID doesn't exist") + void testFindByIdNotFound() { + Optional found = repository.findById(UUID.randomUUID()); + + assertTrue(found.isEmpty()); + } + + @Test + @DisplayName("FindByClientId should return all orders for a client") + void testFindByClientId() { + List found = repository.findByClientId(clientId1); + + assertEquals(1, found.size()); + assertEquals(order1.getId(), found.getFirst().getId()); + } + + @Test + @DisplayName("ExistsById should return true when ID exists") + void testExistsByIdExists() { + assertTrue(repository.existsById(order1.getId())); + } + + @Test + @DisplayName("ExistsById should return false when ID doesn't exist") + void testExistsByIdNotExists() { + assertFalse(repository.existsById(UUID.randomUUID())); + } + } + + @Nested + @DisplayName("Delete operations") + class DeleteOperations { + + @BeforeEach + void setUpOrders() { + repository.save(order1); + repository.save(order2); + } + + @Test + @DisplayName("Delete should remove the specified order") + void testDelete() { + repository.delete(order1); + + assertEquals(1, repository.findAll().size()); + assertFalse(repository.findAll().contains(order1)); + assertTrue(repository.findAll().contains(order2)); + } + + @Test + @DisplayName("DeleteAll should remove all orders") + void testDeleteAll() { + repository.deleteAll(); + + assertTrue(repository.findAll().isEmpty()); + } + + @Test + @DisplayName("Delete should not throw exception when order doesn't exist") + void testDeleteNonExistentOrder() { + Order nonExistent = Order.builder().build(); + nonExistent.setRandomUUID(); + + assertDoesNotThrow(() -> repository.delete(nonExistent)); + assertEquals(2, repository.findAll().size()); + } + } +} + \ No newline at end of file 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..591ad3d --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/usecase/OrderUseCaseTest.java @@ -0,0 +1,223 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.usecase; + +import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; +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.customer.exception.CustomerNotFoundException; +import fr.iut_fbleau.but3.dev62.mylibrary.customer.repository.CustomerRepository; +import fr.iut_fbleau.but3.dev62.mylibrary.order.*; +import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; +import fr.iut_fbleau.but3.dev62.mylibrary.order.repository.OrderRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class OrderUseCaseTest { + + @Mock + private OrderRepository orderRepository; + + @Mock + private CustomerRepository customerRepository; + + @Mock + private BookRepository bookRepository; + + @InjectMocks + private OrderUseCase orderUseCase; + + private UUID customerId; + private UUID bookId; + private Customer testCustomer; + private Book testBook; + private AdresseLivraison adresse; + private OrderInfo validOrderInfo; + + @BeforeEach + void setUp() { + customerId = UUID.randomUUID(); + bookId = UUID.randomUUID(); + + testCustomer = Customer.builder() + .id(customerId) + .firstName("Marie") + .lastName("Dupont") + .phoneNumber("0612345678") + .loyaltyPoints(100) + .build(); + + testBook = Book.builder() + .id(bookId) + .isbn("9782016289308") + .title("Le Petit Prince") + .author("Antoine de Saint-Exupery") + .publisher("Gallimard") + .publicationDate(LocalDate.of(1943, 4, 6)) + .price(new BigDecimal("12.90")) + .stock(10) + .categories(List.of("Roman")) + .description("Un classique") + .language("FR") + .build(); + + adresse = new AdresseLivraison("1 rue de Paris", "Paris", "75001", "France"); + + validOrderInfo = new OrderInfo( + customerId, + List.of(new LigneCommandeInfo(bookId, 2)), + adresse, + ModePaiement.CB + ); + } + + @Nested + @DisplayName("PasserCommande tests") + class PasserCommandeTests { + + @Test + @DisplayName("Should place order when valid data is provided") + void testPasserCommandeWithValidData() throws NotValidOrderException, CustomerNotFoundException { + when(customerRepository.findById(customerId)).thenReturn(Optional.of(testCustomer)); + when(bookRepository.findById(bookId)).thenReturn(Optional.of(testBook)); + when(customerRepository.save(any(Customer.class))).thenReturn(testCustomer); + + UUID orderId = UUID.randomUUID(); + Order savedOrder = Order.builder() + .id(orderId) + .clientId(customerId) + .montantTotal(new BigDecimal("25.80")) + .pointsFideliteGagnes(25) + .build(); + when(orderRepository.save(any(Order.class))).thenReturn(savedOrder); + + OrderDTO result = orderUseCase.passerCommande(validOrderInfo); + + assertNotNull(result); + assertEquals(orderId, result.getCommandeId()); + verify(orderRepository, times(1)).save(any(Order.class)); + verify(customerRepository, times(1)).save(any(Customer.class)); + } + + @Test + @DisplayName("Should throw exception when customer does not exist") + void testPasserCommandeWithUnknownCustomer() { + when(customerRepository.findById(customerId)).thenReturn(Optional.empty()); + + assertThrows(CustomerNotFoundException.class, + () -> orderUseCase.passerCommande(validOrderInfo)); + + verify(orderRepository, never()).save(any(Order.class)); + } + + @Test + @DisplayName("Should throw exception when order has no items") + void testPasserCommandeWithNoItems() { + OrderInfo emptyOrder = new OrderInfo(customerId, List.of(), adresse, ModePaiement.CB); + + assertThrows(NotValidOrderException.class, + () -> orderUseCase.passerCommande(emptyOrder)); + + verify(orderRepository, never()).save(any(Order.class)); + } + + @Test + @DisplayName("Should throw exception when quantity is zero or negative") + void testPasserCommandeWithInvalidQuantity() { + OrderInfo invalidQty = new OrderInfo( + customerId, + List.of(new LigneCommandeInfo(bookId, 0)), + adresse, + ModePaiement.CB + ); + + assertThrows(NotValidOrderException.class, + () -> orderUseCase.passerCommande(invalidQty)); + + verify(orderRepository, never()).save(any(Order.class)); + } + + @Test + @DisplayName("Should throw exception when clientId is null") + void testPasserCommandeWithNullClientId() { + OrderInfo nullClient = new OrderInfo( + null, + List.of(new LigneCommandeInfo(bookId, 1)), + adresse, + ModePaiement.CB + ); + + assertThrows(NotValidOrderException.class, + () -> orderUseCase.passerCommande(nullClient)); + + verify(orderRepository, never()).save(any(Order.class)); + } + + @Test + @DisplayName("Should throw exception when payment method is null") + void testPasserCommandeWithNullModePaiement() { + OrderInfo nullPayment = new OrderInfo( + customerId, + List.of(new LigneCommandeInfo(bookId, 1)), + adresse, + null + ); + + assertThrows(NotValidOrderException.class, + () -> orderUseCase.passerCommande(nullPayment)); + + verify(orderRepository, never()).save(any(Order.class)); + } + } + + @Nested + @DisplayName("FindOrder tests") + class FindOrderTests { + + @Test + @DisplayName("Should return order when ID exists") + void testFindOrderById() { + UUID orderId = UUID.randomUUID(); + Order order = Order.builder() + .id(orderId) + .clientId(customerId) + .montantTotal(new BigDecimal("25.80")) + .pointsFideliteGagnes(25) + .build(); + + when(orderRepository.findById(orderId)).thenReturn(Optional.of(order)); + + Optional result = orderUseCase.findOrderById(orderId); + + assertTrue(result.isPresent()); + assertEquals(orderId, result.get().getCommandeId()); + } + + @Test + @DisplayName("Should return empty Optional when ID does not exist") + void testFindOrderByIdNotFound() { + UUID nonExistentId = UUID.randomUUID(); + when(orderRepository.findById(nonExistentId)).thenReturn(Optional.empty()); + + Optional result = orderUseCase.findOrderById(nonExistentId); + + assertTrue(result.isEmpty()); + } + } +} \ No newline at end of file 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..4401d7b --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/order/validator/OrderValidatorTest.java @@ -0,0 +1,155 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.order.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.order.AdresseLivraison; +import fr.iut_fbleau.but3.dev62.mylibrary.order.LigneCommandeInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.order.ModePaiement; +import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class OrderValidatorTest { + + private final UUID clientId = UUID.randomUUID(); + private final UUID livreId = UUID.randomUUID(); + private final AdresseLivraison adresse = new AdresseLivraison("1 rue de Paris", "Paris", "75001", "France"); + + private OrderInfo validOrder() { + return new OrderInfo( + clientId, + List.of(new LigneCommandeInfo(livreId, 1)), + adresse, + ModePaiement.CB + ); + } + + @Test + @DisplayName("Should validate order with valid data") + void testValidateValidOrder() { + assertDoesNotThrow(() -> OrderValidator.validate(validOrder())); + } + + @Nested + @DisplayName("ClientId validation tests") + class ClientIdValidationTests { + + @Test + @DisplayName("Should throw exception when clientId is null") + void testValidateNullClientId() { + OrderInfo order = new OrderInfo(null, List.of(new LigneCommandeInfo(livreId, 1)), adresse, ModePaiement.CB); + + NotValidOrderException exception = assertThrows(NotValidOrderException.class, + () -> OrderValidator.validate(order)); + + assertEquals(OrderValidator.CLIENT_ID_CANNOT_BE_NULL, exception.getMessage()); + } + } + + @Nested + @DisplayName("LignesCommande validation tests") + class LignesCommandeValidationTests { + + @Test + @DisplayName("Should throw exception when lignesCommande is empty") + void testValidateEmptyLignesCommande() { + OrderInfo order = new OrderInfo(clientId, List.of(), adresse, ModePaiement.CB); + + NotValidOrderException exception = assertThrows(NotValidOrderException.class, + () -> OrderValidator.validate(order)); + + assertEquals(OrderValidator.LIGNES_COMMANDE_CANNOT_BE_EMPTY, exception.getMessage()); + } + + @Test + @DisplayName("Should throw exception when lignesCommande is null") + void testValidateNullLignesCommande() { + OrderInfo order = new OrderInfo(clientId, null, adresse, ModePaiement.CB); + + NotValidOrderException exception = assertThrows(NotValidOrderException.class, + () -> OrderValidator.validate(order)); + + assertEquals(OrderValidator.LIGNES_COMMANDE_CANNOT_BE_EMPTY, exception.getMessage()); + } + + @Test + @DisplayName("Should throw exception when quantite is zero") + void testValidateZeroQuantite() { + OrderInfo order = new OrderInfo(clientId, List.of(new LigneCommandeInfo(livreId, 0)), adresse, ModePaiement.CB); + + NotValidOrderException exception = assertThrows(NotValidOrderException.class, + () -> OrderValidator.validate(order)); + + assertEquals(OrderValidator.QUANTITE_MUST_BE_POSITIVE, exception.getMessage()); + } + + @Test + @DisplayName("Should throw exception when quantite is negative") + void testValidateNegativeQuantite() { + OrderInfo order = new OrderInfo(clientId, List.of(new LigneCommandeInfo(livreId, -1)), adresse, ModePaiement.CB); + + NotValidOrderException exception = assertThrows(NotValidOrderException.class, + () -> OrderValidator.validate(order)); + + assertEquals(OrderValidator.QUANTITE_MUST_BE_POSITIVE, exception.getMessage()); + } + } + + @Nested + @DisplayName("AdresseLivraison validation tests") + class AdresseLivraisonValidationTests { + + @Test + @DisplayName("Should throw exception when adresseLivraison is null") + void testValidateNullAdresseLivraison() { + OrderInfo order = new OrderInfo(clientId, List.of(new LigneCommandeInfo(livreId, 1)), null, ModePaiement.CB); + + NotValidOrderException exception = assertThrows(NotValidOrderException.class, + () -> OrderValidator.validate(order)); + + assertEquals(OrderValidator.ADRESSE_LIVRAISON_CANNOT_BE_NULL, exception.getMessage()); + } + } + + @Nested + @DisplayName("ModePaiement validation tests") + class ModePaiementValidationTests { + + @Test + @DisplayName("Should throw exception when modePaiement is null") + void testValidateNullModePaiement() { + OrderInfo order = new OrderInfo(clientId, List.of(new LigneCommandeInfo(livreId, 1)), adresse, null); + + NotValidOrderException exception = assertThrows(NotValidOrderException.class, + () -> OrderValidator.validate(order)); + + assertEquals(OrderValidator.MODE_PAIEMENT_CANNOT_BE_NULL, exception.getMessage()); + } + + @Test + @DisplayName("Should validate with CB payment method") + void testValidateWithCB() { + OrderInfo order = new OrderInfo(clientId, List.of(new LigneCommandeInfo(livreId, 1)), adresse, ModePaiement.CB); + assertDoesNotThrow(() -> OrderValidator.validate(order)); + } + + @Test + @DisplayName("Should validate with PAYPAL payment method") + void testValidateWithPaypal() { + OrderInfo order = new OrderInfo(clientId, List.of(new LigneCommandeInfo(livreId, 1)), adresse, ModePaiement.PAYPAL); + assertDoesNotThrow(() -> OrderValidator.validate(order)); + } + + @Test + @DisplayName("Should validate with POINTS_FIDELITE payment method") + void testValidateWithPointsFidelite() { + OrderInfo order = new OrderInfo(clientId, List.of(new LigneCommandeInfo(livreId, 1)), adresse, ModePaiement.POINTS_FIDELITE); + assertDoesNotThrow(() -> OrderValidator.validate(order)); + } + } +} \ No newline at end of file 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..dc42b6c --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/converter/SubscriptionConverterTest.java @@ -0,0 +1,78 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.converter; + +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.*; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity.Subscription; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.Calendar; +import java.util.Date; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@DisplayName("SubscriptionConverter Unit Tests") +class SubscriptionConverterTest { + + @Nested + @DisplayName("toDomain() method tests") + class ToDomainTests { + + @Test + @DisplayName("Should convert SubscriptionInfo to Subscription domain object") + void shouldConvertSubscriptionInfoToDomain() { + SubscriptionInfo subscriptionInfo = new SubscriptionInfo( + UUID.randomUUID(), + SubscriptionDuree.M3, + ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build(), + new Date() + ); + + Subscription result = SubscriptionConverter.toDomain(subscriptionInfo); + + assertNotNull(result); + assertEquals(subscriptionInfo.clientId(), result.getClientId()); + assertEquals(subscriptionInfo.duree(), result.getDuree()); + assertEquals(subscriptionInfo.modePaiement().getType(), result.getModePaiement().getType()); + assertEquals(subscriptionInfo.modePaiement().getDetails(), result.getModePaiement().getDetails()); + assertEquals(subscriptionInfo.dateDebutSouhaitee(), result.getDateDebutSouhaitee()); + } + } + + @Nested + @DisplayName("toDTO() method tests") + class ToDTOTests { + + @Test + @DisplayName("Should convert Subscription domain object to SubscriptionDTO with all fields mapped correctly") + void shouldConvertSubscriptionToDTO() { + Subscription subscription = Subscription.builder() + .clientId(UUID.randomUUID()) + .duree(SubscriptionDuree.M3) + .modePaiement(ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build()) + .dateDebutSouhaitee(new Date()) + .build(); + subscription.setRandomUUID(); + + SubscriptionDTO result = SubscriptionConverter.toDTO(subscription); + + Calendar cal = Calendar.getInstance(); + cal.setTime(subscription.getDateDebutSouhaitee()); + cal.add(Calendar.MONTH, subscription.getDuree().getValue()); + + assertNotNull(result); + assertEquals(subscription.getAbonnementId(), result.getAbonnementId()); + assertEquals(subscription.getDateDebutSouhaitee(), result.getDateDebut()); + assertEquals(cal.getTime(), result.getDateFin()); + assertEquals(subscription.getDuree().getMonthlyPricing(), result.getMontantMensuel()); + } + } +} 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..eb50063 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/entity/SubscriptionTest.java @@ -0,0 +1,67 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity; + +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.ModePaiement; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionDuree; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.TypePaiement; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Date; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class SubscriptionTest { + + @Test + @DisplayName("Builder should create a valid Subscription instance") + void testSubscriptionBuilder() { + UUID id = UUID.randomUUID(); + UUID id2 = UUID.randomUUID(); + Date adj = new Date(); + + Subscription subscription = Subscription.builder() + .abonnementId(id) + .clientId(id2) + .duree(SubscriptionDuree.M3) + .modePaiement(ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build()) + .dateDebutSouhaitee(adj) + .build(); + + + assertEquals(id, subscription.getAbonnementId()); + assertEquals(id2, subscription.getClientId()); + assertEquals(SubscriptionDuree.M3, subscription.getDuree()); + assertEquals(TypePaiement.CB, subscription.getModePaiement().getType()); + assertEquals("yes", subscription.getModePaiement().getDetails()); + assertEquals(adj, subscription.getDateDebutSouhaitee()); + } + + @Test + @DisplayName("setRandomUUID should set a new non-null UUID") + void testSetRandomUUID() { + Subscription subscription = Subscription.builder().build(); + UUID originalId = subscription.getAbonnementId(); + + subscription.setRandomUUID(); + + assertNotNull(subscription.getAbonnementId()); + assertNotEquals(originalId, subscription.getAbonnementId()); + } + + @Test + @DisplayName("Two setRandomUUID calls should produce different UUIDs") + void testSetRandomUUIDTwice() { + Subscription subscription = Subscription.builder().build(); + subscription.setRandomUUID(); + UUID firstId = subscription.getAbonnementId(); + + subscription.setRandomUUID(); + UUID secondId = subscription.getAbonnementId(); + + assertNotEquals(firstId, secondId); + } +} 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..e857fbc --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/exception/SubscriptionNotFoundExceptionTest.java @@ -0,0 +1,48 @@ +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; + +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); + + assertEquals("The Subscription with id {0} does not exist", + SubscriptionNotFoundException.THE_SUBSCRIPTION_WITH_ID_DOES_NOT_EXIST_MESSAGE); + 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..07be69f --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/repository/SubscriptionRepositoryTest.java @@ -0,0 +1,194 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.repository; + +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.ModePaiement; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionDuree; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.TypePaiement; +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.Date; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class SubscriptionRepositoryTest { + + private SubscriptionRepository repository; + private Subscription subscription1; + private Subscription subscription2; + + @BeforeEach + void setUp() { + repository = new SubscriptionRepository(); + + subscription1 = Subscription.builder() + .clientId(UUID.randomUUID()) + .duree(SubscriptionDuree.M3) + .modePaiement(ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build()) + .dateDebutSouhaitee(new Date()) + .build(); + subscription1.setRandomUUID(); + + subscription2 = Subscription.builder() + .clientId(UUID.randomUUID()) + .duree(SubscriptionDuree.M6) + .modePaiement(ModePaiement.builder() + .type(TypePaiement.Paypal) + .details("No") + .build()) + .dateDebutSouhaitee(new Date(0)) + .build(); + subscription2.setRandomUUID(); + } + + @Test + @DisplayName("New repository should be empty") + void testNewRepositoryIsEmpty() { + assertTrue(repository.findAll().isEmpty()); + assertEquals(0, repository.findAll().size()); + } + + @Nested + @DisplayName("Save operations") + class SaveOperations { + + @Test + @DisplayName("Save should add a new subscription") + void testSaveNewSubscription() { + Subscription saved = repository.save(subscription1); + + assertEquals(1, repository.findAll().size()); + assertEquals(subscription1.getAbonnementId(), saved.getAbonnementId()); + assertEquals(subscription1.getClientId(), saved.getClientId()); + } + + @Test + @DisplayName("Save should update existing subscription with same ID") + void testSaveUpdatesExistingSubscription() { + repository.save(subscription1); + UUID uuid = subscription1.getAbonnementId(); + + Subscription updatedSubscription = Subscription.builder() + .abonnementId(uuid) + .clientId(UUID.randomUUID()) + .duree(SubscriptionDuree.M12) + .modePaiement(ModePaiement.builder() + .type(TypePaiement.CB) + .details("maybe") + .build()) + .dateDebutSouhaitee(new Date(500000)) + .build(); + + Subscription saved = repository.save(updatedSubscription); + + assertEquals(1, repository.findAll().size()); + assertEquals(uuid, saved.getAbonnementId()); + assertEquals(SubscriptionDuree.M12, saved.getDuree()); + assertEquals("maybe", saved.getModePaiement().getDetails()); + } + + @Test + @DisplayName("Save multiple subscriptions should add all of them") + void testSaveMultipleSubscriptions() { + repository.save(subscription1); + repository.save(subscription2); + + assertEquals(2, repository.findAll().size()); + assertTrue(repository.findAll().contains(subscription1)); + assertTrue(repository.findAll().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() { + assertEquals(2, repository.findAll().size()); + } + + @Test + @DisplayName("findByUuid should return subscription with matching ID") + void testfindByUuid() { + Optional found = repository.findByClientUuid(subscription1.getClientId()); + + assertTrue(found.isPresent()); + assertEquals(subscription1.getAbonnementId(), found.get().getAbonnementId()); + assertEquals(subscription1.getDuree(), found.get().getDuree()); + } + + @Test + @DisplayName("FindById should return empty Optional when ID doesn't exist") + void testFindByIdNotFound() { + Optional found = repository.findByUuid(UUID.randomUUID()); + + assertTrue(found.isEmpty()); + } + + @Test + @DisplayName("ExistsById should return true when ID exists") + void testExistsByIdExists() { + assertTrue(repository.existsById(subscription1.getAbonnementId())); + } + + @Test + @DisplayName("ExistsById should return false when ID doesn't exist") + void testExistsByIdNotExists() { + assertFalse(repository.existsById(UUID.randomUUID())); + } + } + + @Nested + @DisplayName("Delete operations") + class DeleteOperations { + + @BeforeEach + void setUpSubscriptions() { + repository.save(subscription1); + repository.save(subscription2); + } + + @Test + @DisplayName("Delete should remove the specified subscription") + void testDelete() { + repository.delete(subscription1); + + assertEquals(1, repository.findAll().size()); + assertFalse(repository.findAll().contains(subscription1)); + assertTrue(repository.findAll().contains(subscription2)); + } + + @Test + @DisplayName("DeleteAll should remove all subscriptions") + void testDeleteAll() { + repository.deleteAll(); + + assertTrue(repository.findAll().isEmpty()); + } + + @Test + @DisplayName("Delete should not throw exception when subscription doesn't exist") + void testDeleteNonExistentSubscription() { + Subscription nonExistent = Subscription.builder().clientId(UUID.randomUUID()).build(); + nonExistent.setRandomUUID(); + + assertDoesNotThrow(() -> repository.delete(nonExistent)); + assertEquals(2, repository.findAll().size()); + } + } +} diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/usecase/SubscriptionUseCaseTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/usecase/SubscriptionUseCaseTest.java new file mode 100644 index 0000000..d775ce7 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/usecase/SubscriptionUseCaseTest.java @@ -0,0 +1,218 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.usecase; + + +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.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.UUID; + +class SubscriptionUseCaseTest { + + @Mock + private SubscriptionRepository subscriptionRepository; + + @InjectMocks + private SubscriptionUseCase subscriptionUseCase; + + private UUID subscriptionId; + private UUID subscriptionClientId; + private Subscription testSubscription; + private SubscriptionInfo validSubscriptionInfo; + + @BeforeEach + void setUp() { + subscriptionId = UUID.randomUUID(); + subscriptionClientId = UUID.randomUUID(); + + Date adj = new Date(); + + testSubscription = Subscription.builder() + .abonnementId(subscriptionId) + .clientId(subscriptionClientId) + .duree(SubscriptionDuree.M3) + .modePaiement(ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build()) + .dateDebutSouhaitee(new Date()) + .build(); + + validSubscriptionInfo = new SubscriptionInfo( + subscriptionClientId, + SubscriptionDuree.M3, + ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build(), + adj + ); + } + + @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); + + SubscriptionDTO returnedSubscriptionDTO = subscriptionUseCase.registerSubscription(validSubscriptionInfo); + + assertNotNull(returnedSubscriptionDTO); + assertEquals(subscriptionId, returnedSubscriptionDTO.getAbonnementId()); + 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, + 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 client UUID exists") + void testFindSubscriptionByUuid() { + when(subscriptionRepository.findByClientUuid(subscriptionClientId)).thenReturn(Optional.of(testSubscription)); + + Optional foundSubscription = subscriptionUseCase.findSubscriptionByUuid(subscriptionClientId); + + assertTrue(foundSubscription.isPresent()); + assertEquals(testSubscription.getAbonnementId(), foundSubscription.get().getAbonnementId()); + verify(subscriptionRepository, times(1)).findByClientUuid(subscriptionClientId); + } + + @Test + @DisplayName("Should return empty Optional when UUID doesn't exist") + void testFindSubscriptionByUuidNotFound() { + UUID uuid = UUID.randomUUID(); + + Optional foundSubscription = subscriptionUseCase.findSubscriptionByUuid(uuid); + + assertTrue(foundSubscription.isEmpty()); + } + } + + + @Nested + @DisplayName("Update subscription tests") + class UpdateSubscriptionTests { + + @Test + @DisplayName("Should update subscription when valid data is provided") + void testUpdateSubscriptionWithValidData() throws SubscriptionNotFoundException, NotValidSubscriptionException { + when(subscriptionRepository.findByUuid(subscriptionId)).thenReturn(Optional.of(testSubscription)); + + Subscription updatedSubscription = Subscription.builder() + .abonnementId(subscriptionId) + .clientId(subscriptionClientId) + .duree(SubscriptionDuree.M6) + .modePaiement(ModePaiement.builder() + .type(TypePaiement.Paypal) + .details("no") + .build()) + .dateDebutSouhaitee(new Date(0)) + .build(); + + when(subscriptionRepository.save(any(Subscription.class))).thenReturn(updatedSubscription); + + SubscriptionInfo updateInfo = new SubscriptionInfo( + subscriptionClientId, + SubscriptionDuree.M6, + ModePaiement.builder() + .type(TypePaiement.Paypal) + .details("no") + .build(), + new Date(0) + ); + + SubscriptionDTO result = subscriptionUseCase.updateSubscription(subscriptionId, updateInfo); + + assertNotNull(result); + assertEquals(subscriptionId, result.getAbonnementId()); + assertEquals(new Date(0), result.getDateDebut()); + verify(subscriptionRepository, times(1)).findByUuid(subscriptionId); + verify(subscriptionRepository, times(1)).save(any(Subscription.class)); + } + + @Test + @DisplayName("Should throw exception when subscription ID doesn't exist") + void testUpdateSubscriptionNotFound() { + UUID nonExistentId = UUID.randomUUID(); + when(subscriptionRepository.findByUuid(nonExistentId)).thenReturn(Optional.empty()); + + assertThrows(SubscriptionNotFoundException.class, + () -> subscriptionUseCase.updateSubscription(nonExistentId, validSubscriptionInfo)); + + verify(subscriptionRepository, times(1)).findByUuid(nonExistentId); + verify(subscriptionRepository, never()).save(any(Subscription.class)); + } + } + + @Nested + @DisplayName("Delete subscription tests") + class DeleteSubscriptionTests { + + @Test + @DisplayName("Should delete subscription when ID exists") + void testDeleteSubscription() throws SubscriptionNotFoundException { + when(subscriptionRepository.findByUuid(subscriptionId)).thenReturn(Optional.of(testSubscription)); + + assertDoesNotThrow(() -> subscriptionUseCase.deleteSubscription(subscriptionId)); + + verify(subscriptionRepository, times(1)).findByUuid(subscriptionId); + verify(subscriptionRepository, times(1)).delete(testSubscription); + } + + @Test + @DisplayName("Should throw exception when deleting unknown subscription") + void testDeleteSubscriptionNotFound() { + UUID nonExistentId = UUID.randomUUID(); + when(subscriptionRepository.findByUuid(nonExistentId)).thenReturn(Optional.empty()); + + assertThrows(SubscriptionNotFoundException.class, + () -> subscriptionUseCase.deleteSubscription(nonExistentId)); + + verify(subscriptionRepository, times(1)).findByUuid(nonExistentId); + verify(subscriptionRepository, never()).delete(any(Subscription.class)); + } + } +} + + + + + + + + + + + + + + + + + + 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..da0d041 --- /dev/null +++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/subscription/validator/SubscriptionValidatorTest.java @@ -0,0 +1,200 @@ +package fr.iut_fbleau.but3.dev62.mylibrary.subscription.validator; + +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.ModePaiement; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionDuree; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionInfo; +import fr.iut_fbleau.but3.dev62.mylibrary.subscription.TypePaiement; +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 java.util.Date; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class SubscriptionValidatorTest { + + private SubscriptionInfo validSubscription() { + + UUID uuid = UUID.randomUUID(); + Date adj = new Date(); + + return new SubscriptionInfo( + uuid, + SubscriptionDuree.M3, + ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build(), + adj + ); + } + + @Test + @DisplayName("Should validate subscription with valid data") + void testValidateValidSubscription() { + assertDoesNotThrow(() -> SubscriptionValidator.validate(validSubscription())); + } + + @Nested + @DisplayName("UUID validation tests") + class UuidValidationTests { + + @Test + @DisplayName("Should Be valid") + void testValidatenormalUUID() { + SubscriptionInfo subscription = new SubscriptionInfo( + UUID.randomUUID(), + SubscriptionDuree.M3, + ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build(), + new Date(0) + ); + + assertDoesNotThrow(() -> SubscriptionValidator.validate(subscription)); + } + + @Test + @DisplayName("Should throw exception when UUID is blank") + void testValidateBlankUuid() { + SubscriptionInfo subscription = new SubscriptionInfo( + null, + SubscriptionDuree.M3, + ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build(), + new Date(0) + ); + + NotValidSubscriptionException exception = assertThrows(NotValidSubscriptionException.class, + () -> SubscriptionValidator.validate(subscription)); + + assertEquals(SubscriptionValidator.Client_ID_NOT_VALID, exception.getMessage()); + } + } + + @Nested + @DisplayName("Date validation tests") + class DateValidationTests { + + @Test + @DisplayName("Should be good") + void testValidateTodayDate() { + SubscriptionInfo subscription = new SubscriptionInfo( + UUID.randomUUID(), + SubscriptionDuree.M3, + ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build(), + new Date() + ); + + assertDoesNotThrow(() -> SubscriptionValidator.validate(subscription)); + } + + @Test + @DisplayName("Should throw exception when Date is null") + void testValidateNullDate() { + SubscriptionInfo subscription = new SubscriptionInfo( + UUID.randomUUID(), + SubscriptionDuree.M3, + ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build(), + null + ); + + NotValidSubscriptionException exception = assertThrows(NotValidSubscriptionException.class, + () -> SubscriptionValidator.validate(subscription)); + + assertEquals(SubscriptionValidator.LA_DATE_DE_DEBUT_N_A_PAS_ETE_RENSEIGNEE, exception.getMessage()); + } + } + + @Nested + @DisplayName("ModePaiement validation tests") + class ModePaiementValidationTests { + + @Test + @DisplayName("Should be good") + void testValidateGoodModePaiement() { + SubscriptionInfo subscription = new SubscriptionInfo( + UUID.randomUUID(), + SubscriptionDuree.M3, + ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build(), + new Date() + ); + + assertDoesNotThrow(() -> SubscriptionValidator.validate(subscription)); + } + + @Test + @DisplayName("Should throw exception when Date is null") + void testValidateNullModePaiement() { + SubscriptionInfo subscription = new SubscriptionInfo( + UUID.randomUUID(), + SubscriptionDuree.M3, + ModePaiement.builder() + .type(null) + .details("yes") + .build(), + new Date() + ); + + NotValidSubscriptionException exception = assertThrows(NotValidSubscriptionException.class, + () -> SubscriptionValidator.validate(subscription)); + + assertEquals(SubscriptionValidator.LE_TYPE_DE_PAYEMENT_N_EST_PAS_PRIS_EN_CHARGE, exception.getMessage()); + } + } + + @Nested + @DisplayName("Durée validation tests") + class DureeValidationTests { + + @Test + @DisplayName("Should be good") + void testValidateGoodDuree() { + SubscriptionInfo subscription = new SubscriptionInfo( + UUID.randomUUID(), + SubscriptionDuree.M3, + ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build(), + new Date() + ); + + assertDoesNotThrow(() -> SubscriptionValidator.validate(subscription)); + } + + @Test + @DisplayName("Should throw exception when Date is null") + void testValidateNullDuree() { + SubscriptionInfo subscription = new SubscriptionInfo( + UUID.randomUUID(), + null, + ModePaiement.builder() + .type(TypePaiement.CB) + .details("yes") + .build(), + new Date() + ); + + NotValidSubscriptionException exception = assertThrows(NotValidSubscriptionException.class, + () -> SubscriptionValidator.validate(subscription)); + + assertEquals(SubscriptionValidator.LA_DUREE_RENSEIGNEE_N_EST_PAS_CORRECTE, exception.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/resources/features/avis.feature b/src/test/resources/features/avis.feature new file mode 100644 index 0000000..24e6950 --- /dev/null +++ b/src/test/resources/features/avis.feature @@ -0,0 +1,54 @@ +# language: en + +Feature: Manage customer reviews + + Background: + Given the system has the following customers for avis: + | prenom | nom | numeroTelephone | pointsFidelite | + | Marie | Dupont | 0612345678 | 100 | + | Jean | Martin | 0687654321 | 50 | + And the catalog has the following books for avis: + | isbn | titre | auteur | editeur | datePublication | prix | stockInitial | categories | description | langue | + | 9782016289308 | Le Petit Prince | Antoine de Saint-Exupery | Gallimard | 1943-04-06 | 12.90 | 10 | Roman | Un classique | FR | + | 9782070409189 | L Etranger | Albert Camus | Gallimard | 1942-05-19 | 9.50 | 5 | Roman | Philosophique| FR | + + Scenario: Submit a valid review + When customer "0612345678" submits a review for book "9782016289308" with: + | note | commentaire | dateAchat | + | 5 | Excellent livre ! | 2024-01-15 | + Then a new review is created + And the system now has 1 reviews + + Scenario: Submit a review with minimum note + When customer "0687654321" submits a review for book "9782070409189" with: + | note | commentaire | dateAchat | + | 1 | Pas mon style | 2024-02-10 | + Then a new review is created + And the system now has 1 reviews + + Scenario: Attempt to submit a review with invalid note too high + When customer "0612345678" tries to submit a review for book "9782016289308" with: + | note | commentaire | dateAchat | + | 6 | Trop bien ! | 2024-01-15 | + Then the review creation fails + And I receive a validation avis error containing "Note must be between 1 and 5" + + Scenario: Attempt to submit a review with invalid note too low + When customer "0612345678" tries to submit a review for book "9782016289308" with: + | note | commentaire | dateAchat | + | 0 | Nul ! | 2024-01-15 | + Then the review creation fails + And I receive a validation avis error containing "Note must be between 1 and 5" + + Scenario: Attempt to submit a review with blank comment + When customer "0612345678" tries to submit a review for book "9782016289308" with: + | note | commentaire | dateAchat | + | 4 | | 2024-01-15 | + Then the review creation fails + And I receive a validation avis error containing "Commentaire cannot be blank" + + Scenario: Attempt to submit a review for unknown customer + When an unknown customer tries to submit a review for book "9782016289308" with: + | note | commentaire | dateAchat | + | 4 | Bon livre | 2024-01-15 | + Then the review creation fails with customer not found diff --git a/src/test/resources/features/book.feature b/src/test/resources/features/book.feature new file mode 100644 index 0000000..1643dc1 --- /dev/null +++ b/src/test/resources/features/book.feature @@ -0,0 +1,51 @@ +# language: en + +Feature: Manage books in the catalog + + Background: + Given the catalog has the following books: + | isbn | titre | auteur | editeur | datePublication | prix | stockInitial | categories | description | langue | + | 9782016289308 | Le Petit Prince | Antoine de Saint-Exupéry | Gallimard | 1943-04-06 | 12.90 | 10 | Roman;Jeunesse | Un classique | FR | + | 9782070409189 | L'Étranger | Albert Camus | Gallimard | 1942-05-19 | 9.50 | 5 | Roman | Roman philosophique | FR | + + Scenario: Register a new book + When I register a new book with the following information: + | isbn | titre | auteur | editeur | datePublication | prix | stockInitial | categories | description | langue | + | 9782253006329 | Harry Potter à l'école des sorciers | J.K. Rowling | Pocket | 1998-10-09 | 8.20 | 20 | Fantaisie;Jeunesse | Premier tome de la saga | FR | + Then a new book is created + And the catalog now has 3 books + And the book "9782253006329" has a stock of 20 + + Scenario: Retrieve a book by ISBN + When I request the book with ISBN "9782016289308" + Then I receive the following book information: + | isbn | titre | auteur | editeur | datePublication | prix | stockInitial | categories | description | langue | + | 9782016289308 | Le Petit Prince | Antoine de Saint-Exupéry | Gallimard | 1943-04-06 | 12.90 | 10 | Roman;Jeunesse | Un classique | FR | + + Scenario: Update a book information + When I update book "9782070409189" with the following information: + | isbn | titre | auteur | editeur | datePublication | prix | stockInitial | categories | description | langue | + | 9782070409189 | L'Étranger - Edition | Albert Camus | Gallimard | 1942-05-19 | 11.00 | 99 | Roman;Classique | Nouvelle édition commentée | FR | + Then the book "9782070409189" has the following updated information: + | isbn | titre | auteur | editeur | datePublication | prix | stockInitial | categories | description | langue | + | 9782070409189 | L'Étranger - Edition | Albert Camus | Gallimard | 1942-05-19 | 11.00 | 99 | Roman;Classique | Nouvelle édition commentée | FR | + And the stock remains unchanged at 5 + + Scenario: Delete a book + When I delete the book with ISBN "9782016289308" + Then the book "9782016289308" is removed from the catalog + And the catalog now has 1 books + + Scenario: Attempt to register a book with invalid ISBN + When I try to register a new book with the following information: + | isbn | titre | auteur | editeur | datePublication | prix | stockInitial | categories | description | langue | + | 123456 | Livre test | Test Auteur | TestEdit | 2024-01-01 | 5.00 | 1 | Test | ISBN incorrect | FR | + Then the book registration fails + And I receive a validation book error message containing "ISBN is not valid" + + Scenario: Attempt to register a book with invalid price + When I try to register a new book with the following information: + | isbn | titre | auteur | editeur | datePublication | prix | stockInitial | categories | description | langue | + | 9782016289308 | Livre test | Test Auteur | TestEdit | 2024-01-01 | -5.00 | 1 | Test | Prix incorrect | FR | + Then the book registration fails + And I receive a validation book error message containing "Price must be positive" diff --git a/src/test/resources/features/order.feature b/src/test/resources/features/order.feature new file mode 100644 index 0000000..8e5c7fb --- /dev/null +++ b/src/test/resources/features/order.feature @@ -0,0 +1,49 @@ +# language: en + +Feature: Place an order + + Background: + Given the system has the following customers for order: + | prenom | nom | numeroTelephone | pointsFidelite | + | Marie | Dupont | 0612345678 | 100 | + | Jean | Martin | 0687654321 | 50 | + And the catalog has the following books for order: + | isbn | titre | auteur | editeur | datePublication | prix | stockInitial | categories | description | langue | + | 9782016289308 | Le Petit Prince | Antoine de Saint-Exupery | Gallimard | 1943-04-06 | 12.90 | 10 | Roman | Un classique | FR | + | 9782070409189 | L Etranger | Albert Camus | Gallimard | 1942-05-19 | 9.50 | 5 | Roman | Philosophique| FR | + + Scenario: Place an order with CB payment + When I place an order for customer "0612345678" with: + | livreIsbn | quantite | rue | ville | codePostal | pays | modePaiement | + | 9782016289308 | 2 | 1 rue de Paris | Paris | 75001 | France | CB | + Then a new order is created + And the order total is 25.80 + And the customer "0612345678" now has 125 loyalty points + + Scenario: Place an order with PAYPAL payment + When I place an order for customer "0687654321" with: + | livreIsbn | quantite | rue | ville | codePostal | pays | modePaiement | + | 9782070409189 | 1 | 5 rue de Lyon | Lyon | 69001 | France | PAYPAL | + Then a new order is created + And the order total is 9.50 + And the customer "0687654321" now has 59 loyalty points + + Scenario: Attempt to place an order with no items + When I try to place an order for customer "0612345678" with no items: + | rue | ville | codePostal | pays | modePaiement | + | 1 rue de Paris | Paris | 75001 | France | CB | + Then the order placement fails + And I receive a validation order error containing "Order must have at least one item" + + Scenario: Attempt to place an order with invalid quantity + When I try to place an order for customer "0612345678" with invalid quantity: + | livreIsbn | quantite | rue | ville | codePostal | pays | modePaiement | + | 9782016289308 | 0 | 1 rue de Paris | Paris | 75001 | France | CB | + Then the order placement fails + And I receive a validation order error containing "Quantity must be positive" + + Scenario: Attempt to place an order for unknown customer + When I try to place an order for an unknown customer with: + | livreIsbn | quantite | rue | ville | codePostal | pays | modePaiement | + | 9782016289308 | 1 | 1 rue de Paris | Paris | 75001 | France | CB | + Then the order placement fails with customer not found