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