diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..a9dcb85
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1 @@
+Notre groupe est constitué de Marvin Aubert, Maxime Lebreton et de Patrick Felix Vimalaratnam
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 27ec78e..66c9447 100644
--- a/pom.xml
+++ b/pom.xml
@@ -117,6 +117,11 @@
${mockito.version}
test
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewDTO.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewDTO.java
new file mode 100644
index 0000000..92e40ca
--- /dev/null
+++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewDTO.java
@@ -0,0 +1,19 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review;
+
+import lombok.Builder;
+import lombok.Getter;
+
+import java.time.LocalDate;
+import java.util.UUID;
+
+@Getter
+@Builder
+
+public class ReviewDTO {
+ private UUID reviewId;
+ private UUID customerId;
+ private UUID bookId;
+ private Integer note;
+ private String comment;
+ private LocalDate purchaseDate;
+}
diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewInfo.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewInfo.java
new file mode 100644
index 0000000..5e73ff9
--- /dev/null
+++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/ReviewInfo.java
@@ -0,0 +1,7 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review;
+
+import java.time.LocalDate;
+import java.util.UUID;
+
+public record ReviewInfo(Integer note, String comment, LocalDate purchaseDate) {
+}
diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverter.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverter.java
new file mode 100644
index 0000000..d8ebead
--- /dev/null
+++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverter.java
@@ -0,0 +1,30 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.converter;
+
+import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewDTO;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewInfo;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review;
+
+public class ReviewConverter {
+ private ReviewConverter() {
+
+ }
+
+ public static Review toDomain(ReviewInfo newReview) {
+ return Review.builder()
+ .note(newReview.note())
+ .comment(newReview.comment())
+ .purchaseDate(newReview.purchaseDate())
+ .build();
+ }
+
+ public static ReviewDTO toDTO(Review review) {
+ return ReviewDTO.builder()
+ .reviewId(review.getReviewId())
+ .customerId(review.getCustomerId())
+ .bookId(review.getBookId())
+ .note(review.getNote())
+ .comment(review.getComment())
+ .purchaseDate(review.getPurchaseDate())
+ .build();
+ }
+}
diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/Review.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/Review.java
new file mode 100644
index 0000000..26b80ca
--- /dev/null
+++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/Review.java
@@ -0,0 +1,29 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.entity;
+
+import lombok.Builder;
+import lombok.Getter;
+
+import java.time.LocalDate;
+import java.util.UUID;
+
+@Getter
+@Builder
+
+public class Review {
+ private UUID reviewId;
+ private UUID customerId;
+ private UUID bookId;
+ private Integer note;
+ private String comment;
+ private LocalDate purchaseDate;
+
+ public void setRandomUUID() {
+ this.reviewId = UUID.randomUUID();
+ }
+
+ public void setRandomUUIDCustomerAndBook() {
+
+ this.customerId = UUID.randomUUID();
+ this.bookId = UUID.randomUUID();
+ }
+}
diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/NotValidReviewException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/NotValidReviewException.java
new file mode 100644
index 0000000..241184b
--- /dev/null
+++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/NotValidReviewException.java
@@ -0,0 +1,7 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.exception;
+
+public class NotValidReviewException extends RuntimeException {
+ public NotValidReviewException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundException.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundException.java
new file mode 100644
index 0000000..087454e
--- /dev/null
+++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundException.java
@@ -0,0 +1,25 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.exception;
+
+import java.text.MessageFormat;
+import java.util.Optional;
+import java.util.UUID;
+
+public class ReviewNotFoundException extends RuntimeException {
+
+ public static final String THE_REVIEWS_WITH_CUSTOMER_ID_DOES_NOT_EXIST_MESSAGE = "The reviews with the customer id {0} does not exists";
+ public static final String THE_REVIEWS_WITH_BOOK_ID_DOES_NOT_EXIST_MESSAGE = "The reviews with the book id {0} does not exists";
+ public static final String THE_REVIEWS_WITH_REVIEW_ID_DOES_NOT_EXIST_MESSAGE = "The review with review id {0} does not exists";
+
+ public ReviewNotFoundException(Optional customerUUID, Optional bookUUID, Optional reviewUUID) {
+ super(buildMessage(customerUUID, bookUUID, reviewUUID));
+ }
+
+ private static String buildMessage(Optional customerUUID, Optional bookUUID, Optional reviewUUID) {
+ if (customerUUID.isPresent()) {
+ return MessageFormat.format(THE_REVIEWS_WITH_CUSTOMER_ID_DOES_NOT_EXIST_MESSAGE, customerUUID.get());
+ }else if (bookUUID.isPresent()) {
+ return MessageFormat.format(THE_REVIEWS_WITH_BOOK_ID_DOES_NOT_EXIST_MESSAGE, bookUUID.get());
+ }
+ return MessageFormat.format(THE_REVIEWS_WITH_REVIEW_ID_DOES_NOT_EXIST_MESSAGE, reviewUUID.get());
+ }
+}
diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepository.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepository.java
new file mode 100644
index 0000000..378fceb
--- /dev/null
+++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepository.java
@@ -0,0 +1,79 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.repository;
+
+import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+public class ReviewRepository {
+
+ private final List reviews = new ArrayList<>();
+
+ public List findAll() {
+
+ return reviews;
+ }
+
+ public void deleteAll() {
+
+ reviews.clear();
+ }
+
+ public Review save(Review newReview) {
+ Optional optionalReviewWithSameReviewId = this.findByReviewId(newReview.getReviewId());
+ optionalReviewWithSameReviewId.ifPresent(reviews::remove);
+ this.reviews.add(newReview);
+ return newReview;
+ }
+
+ public ArrayList findByCustomerId(UUID customerUUID) {
+ return this.reviews.stream()
+ .filter(review -> review.getCustomerId().equals(customerUUID))
+ .collect(Collectors.toCollection(ArrayList::new));
+ }
+
+ public ArrayList findByBookId(UUID bookUUID) {
+ return this.reviews.stream()
+ .filter(review -> review.getBookId().equals(bookUUID))
+ .collect(Collectors.toCollection(ArrayList::new));
+ }
+
+ public Optional findByReviewId(UUID reviewUUID) {
+ return this.reviews.stream()
+ .filter(review -> review.getReviewId().equals(reviewUUID))
+ .findFirst();
+ }
+
+ public boolean existsByCustomerId(UUID customerUUID) {
+ return this.reviews.stream()
+ .anyMatch(review -> review.getCustomerId().equals(customerUUID));
+ }
+
+ public boolean existsByBookId(UUID bookUUID) {
+ return this.reviews.stream()
+ .anyMatch(review -> review.getBookId().equals(bookUUID));
+ }
+
+ public boolean existsByReviewId(UUID reviewUUID) {
+ return this.reviews.stream()
+ .anyMatch(review -> review.getReviewId().equals(reviewUUID));
+ }
+
+ public void deleteCustomerReviews(UUID customerUUID) {
+
+ this.reviews.removeIf(review -> review.getCustomerId().equals(customerUUID));
+ }
+
+ public void deleteBookReviews(UUID bookUUID) {
+
+ this.reviews.removeIf(review -> review.getBookId().equals(bookUUID));
+ }
+
+ public void delete(Review review) {
+
+ this.reviews.remove(review);
+ }
+}
diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCase.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCase.java
new file mode 100644
index 0000000..ddc80a5
--- /dev/null
+++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCase.java
@@ -0,0 +1,113 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.usecase;
+
+import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewDTO;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewInfo;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.converter.ReviewConverter;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.NotValidReviewException;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.ReviewNotFoundException;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.repository.ReviewRepository;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.validator.ReviewValidator;
+
+import java.util.ArrayList;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+public class ReviewUseCase {
+
+ private final ReviewRepository reviewRepository;
+
+ public ReviewUseCase(ReviewRepository reviewRepository) {
+ this.reviewRepository = reviewRepository;
+ }
+
+ public UUID registerReview(ReviewInfo newReview) throws NotValidReviewException {
+ ReviewValidator.validate(newReview);
+ Review reviewToRegister = ReviewConverter.toDomain(newReview);
+ Review reviewToRegistered = reviewRepository.save(reviewToRegister);
+ return reviewToRegistered.getReviewId();
+ }
+
+ public ArrayList findReviewByCustomerId(UUID customerId) {
+ ArrayList optionalReviews = reviewRepository.findByCustomerId(customerId);
+ return optionalReviews.stream()
+ .map(ReviewConverter::toDTO)
+ .collect(Collectors.toCollection(ArrayList::new));
+ }
+
+ public ArrayList findReviewByBookId(UUID bookId) {
+ ArrayList optionalReviews = reviewRepository.findByBookId(bookId);
+ return optionalReviews.stream()
+ .map(ReviewConverter::toDTO)
+ .collect(Collectors.toCollection(ArrayList::new));
+ }
+
+ public Optional findReviewByReviewId(UUID reviewId) {
+ Optional optionalReview = reviewRepository.findByReviewId(reviewId);
+ return optionalReview.map(ReviewConverter::toDTO);
+ }
+
+ public ReviewDTO updateReview(UUID reviewUUID, ReviewInfo reviewInfo)
+ throws ReviewNotFoundException, NotValidReviewException {
+ ReviewValidator.validate(reviewInfo);
+ Review reviewByReviewUUID = getReviewIfDoesNotExistThrowReviewNotFoundException(
+ reviewUUID);
+ Review review = Review.builder()
+ .reviewId(reviewUUID)
+ .customerId(reviewByReviewUUID.getCustomerId())
+ .bookId(reviewByReviewUUID.getBookId())
+ .note(reviewByReviewUUID.getNote())
+ .comment(reviewByReviewUUID.getComment())
+ .purchaseDate(reviewByReviewUUID.getPurchaseDate())
+ .build();
+ Review updatedReview = reviewRepository.save(review);
+ return ReviewConverter.toDTO(updatedReview);
+ }
+
+ public void deleteReview(UUID reviewUUID) throws ReviewNotFoundException {
+ Review reviewToDelete = getReviewIfDoesNotExistThrowReviewNotFoundException(reviewUUID);
+ this.reviewRepository.delete(reviewToDelete);
+ }
+
+ public void deleteCustomerReviews(UUID customerUUID) throws ReviewNotFoundException {
+ ArrayList reviewsToDelete = getReviewByCustomerIdIfDoesNotExistThrowReviewNotFoundException(customerUUID);
+ for (Review review : reviewsToDelete) {
+ reviewRepository.delete(review);
+ }
+ }
+
+ public void deleteBookReviews(UUID bookUUID) throws ReviewNotFoundException {
+ ArrayList reviewsToDelete = getReviewByBookIfDoesNotExistThrowReviewNotFoundException(bookUUID);
+ for (Review review : reviewsToDelete) {
+ reviewRepository.delete(review);
+ }
+ }
+
+ private Review getReviewIfDoesNotExistThrowReviewNotFoundException(UUID reviewUUID)
+ throws ReviewNotFoundException {
+ Optional optionalReviewByReviewId = reviewRepository.findByReviewId(reviewUUID);
+ if (optionalReviewByReviewId.isEmpty()) {
+ throw new ReviewNotFoundException(Optional.empty(), Optional.empty(),Optional.of(reviewUUID));
+ }
+ return optionalReviewByReviewId.get();
+ }
+
+ private ArrayList getReviewByCustomerIdIfDoesNotExistThrowReviewNotFoundException(UUID customerUUID)
+ throws ReviewNotFoundException {
+ ArrayList optionalReviewByReviewId = reviewRepository.findByCustomerId(customerUUID);
+ if (optionalReviewByReviewId.isEmpty()) {
+ throw new ReviewNotFoundException(Optional.of(customerUUID), Optional.empty(),Optional.empty());
+ }
+ return optionalReviewByReviewId;
+ }
+
+ private ArrayList getReviewByBookIfDoesNotExistThrowReviewNotFoundException(UUID bookUUID)
+ throws ReviewNotFoundException {
+ ArrayList optionalReviewByReviewId = reviewRepository.findByBookId(bookUUID);
+ if (optionalReviewByReviewId.isEmpty()) {
+ throw new ReviewNotFoundException(Optional.empty(), Optional.empty(), Optional.of(bookUUID));
+ }
+ return optionalReviewByReviewId;
+ }
+}
diff --git a/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidator.java b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidator.java
new file mode 100644
index 0000000..9609c5a
--- /dev/null
+++ b/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidator.java
@@ -0,0 +1,52 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.validator;
+
+import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.NotValidBookException;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewInfo;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.NotValidReviewException;
+
+import java.time.LocalDate;
+
+public class ReviewValidator {
+
+ public static final String NOTE_CANNOT_BE_LOWER_THAN_1 = "Note is greater than or equal to 1";
+ public static final String NOTE_CANNOT_BE_UPPER_THAN_5 = "Note is less than or equal to 5";
+ public static final String COMMENT_CANNOT_BE_BLANK = "Comment cannot be blank";
+ public static final String PURCHASE_DATE_IS_NOT_VALID = "Date is not valid";
+
+ public ReviewValidator() {
+
+ }
+
+ public static void validate(ReviewInfo newReview) throws NotValidReviewException {
+ validateNoteLower1(newReview);
+ validateNoteUpper5(newReview);
+ validateComment(newReview);
+ validatePurchaseDate(newReview);
+ }
+
+ private static void validateNoteLower1(ReviewInfo newReview)
+ throws NotValidReviewException {
+ if (newReview.note() <= 1) {
+ throw new NotValidReviewException(NOTE_CANNOT_BE_LOWER_THAN_1);
+ }
+ }
+
+ private static void validateNoteUpper5(ReviewInfo newReview)
+ throws NotValidReviewException {
+ if (newReview.note() >= 5) {
+ throw new NotValidReviewException("Note is less than or equal to 5");
+ }
+ }
+
+ private static void validateComment(ReviewInfo newReview) throws NotValidReviewException {
+ if (newReview.comment().isBlank()) {
+ throw new NotValidReviewException(COMMENT_CANNOT_BE_BLANK);
+ }
+ }
+
+ private static void validatePurchaseDate(ReviewInfo newReview) throws NotValidReviewException {
+ if (newReview.purchaseDate().isAfter(LocalDate.now())) {
+ throw new NotValidReviewException(PURCHASE_DATE_IS_NOT_VALID);
+ }
+ }
+}
diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverterTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverterTest.java
new file mode 100644
index 0000000..135ae7c
--- /dev/null
+++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/converter/ReviewConverterTest.java
@@ -0,0 +1,68 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.converter;
+
+import java.time.LocalDate;
+import java.util.UUID;
+
+import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewDTO;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewInfo;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@DisplayName("ReviewConverterTest Unit Tests")
+public class ReviewConverterTest {
+
+ @Nested
+ @DisplayName("toDomain() method tests")
+ class ToDomainTests {
+
+ @Test
+ @DisplayName("Should convert ReviewInfo to Review domain object")
+ void shouldConvertReviewInfoToDomain() {
+ // Given
+ LocalDate purchaseDate = LocalDate.of(2026, 3, 24);
+ ReviewInfo reviewInfo = new ReviewInfo(5, "tres bon livre", purchaseDate);
+
+ // When
+ Review result = ReviewConverter.toDomain(reviewInfo);
+
+ // Then
+ assertNotNull(result);
+ assertEquals(reviewInfo.note(), result.getNote());
+ assertEquals(reviewInfo.comment(), result.getComment());
+ assertEquals(reviewInfo.purchaseDate(), result.getPurchaseDate());
+ }
+ }
+
+ @Nested
+ @DisplayName("toDTO() method tests")
+ class ToDTOTests {
+
+ @Test
+ @DisplayName("Should convert Review domain object to ReviewDTO with all fields mapped correctly")
+ void shouldConvertReviewToDTO() {
+ LocalDate purchaseDate = LocalDate.of(2026, 3, 24);
+ Review review = Review.builder()
+ .reviewId(UUID.randomUUID())
+ .customerId(UUID.randomUUID())
+ .bookId(UUID.randomUUID())
+ .note(5)
+ .comment("très bon livre")
+ .purchaseDate(purchaseDate)
+ .build();
+
+ ReviewDTO result = ReviewConverter.toDTO(review);
+
+ assertNotNull(result);
+ assertEquals(review.getCustomerId(), result.getCustomerId());
+ assertEquals(review.getBookId(), result.getBookId());
+ assertEquals(review.getNote(), result.getNote());
+ assertEquals(review.getComment(), result.getComment());
+ assertEquals(review.getPurchaseDate(), result.getPurchaseDate());
+ }
+ }
+}
diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/ReviewTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/ReviewTest.java
new file mode 100644
index 0000000..44e78ea
--- /dev/null
+++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/entity/ReviewTest.java
@@ -0,0 +1,55 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.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.*;
+
+public class ReviewTest {
+
+ @Test
+ @DisplayName("Builder should create a valid review instance")
+ void testReviewBuilder() {
+ UUID customerId = UUID.randomUUID();
+ UUID bookId = UUID.randomUUID();
+ Integer note = 5;
+ String comment = "très bon livre";
+ LocalDate purchaseDate = LocalDate.of(2026, 3, 24);
+
+ Review review = Review.builder()
+ .customerId(customerId)
+ .bookId(bookId)
+ .note(note)
+ .comment(comment)
+ .purchaseDate(purchaseDate)
+ .build();
+
+ assertEquals(customerId, review.getCustomerId());
+ assertEquals(bookId, review.getBookId());
+ assertEquals(note, review.getNote());
+ assertEquals(comment, review.getComment());
+ assertEquals(purchaseDate, review.getPurchaseDate());
+ }
+
+ @Test
+ @DisplayName("setRandomUUID should change the ID to a new random UUID")
+ void testSetRandomUUID() {
+ Review review = Review.builder().build();
+ UUID originalReviewId = review.getReviewId();
+ UUID originalCustomerId = review.getCustomerId();
+ UUID originalBookId = review.getCustomerId();
+
+ review.setRandomUUID();
+ review.setRandomUUIDCustomerAndBook();
+
+ assertNotNull(review.getReviewId());
+ assertNotNull(review.getCustomerId());
+ assertNotNull(review.getBookId());
+ assertNotEquals(originalReviewId, review.getReviewId());
+ assertNotEquals(originalCustomerId, review.getCustomerId());
+ assertNotEquals(originalBookId, review.getBookId());
+ }
+}
diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/NotValidReviewExcpetionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/NotValidReviewExcpetionTest.java
new file mode 100644
index 0000000..f7f3712
--- /dev/null
+++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/NotValidReviewExcpetionTest.java
@@ -0,0 +1,60 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.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;
+
+public class NotValidReviewExcpetionTest {
+
+ @Test
+ @DisplayName("Exception should be created with the provided message")
+ void testExceptionCreation() {
+ String errorMessage = "Review data is not valid";
+
+ NotValidReviewException exception = new NotValidReviewException(errorMessage);
+
+ assertEquals(errorMessage, exception.getMessage());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "Note is greater than or equal to 1",
+ "Note is less than or equal to 1",
+ "Comment cannot be empty"
+ })
+ @DisplayName("Exception should handle different validation messages")
+ void testExceptionWithDifferentMessages(String errorMessage) {
+ NotValidReviewException exception = new NotValidReviewException(errorMessage);
+
+ assertEquals(errorMessage, exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("Exception should be properly thrown and caught")
+ void testExceptionCanBeThrownAndCaught() {
+ String errorMessage = "Comment field is empty";
+
+ Exception exception = assertThrows(NotValidReviewException.class, () -> {
+ throw new NotValidReviewException(errorMessage);
+ });
+
+ assertEquals(errorMessage, exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("Exception should be catchable as a general Exception")
+ void testExceptionInheritance() {
+ String errorMessage = "Invalid review data";
+
+ try {
+ throw new NotValidReviewException(errorMessage);
+ } catch (Exception e) {
+ assertEquals(NotValidReviewException.class, e.getClass());
+ assertEquals(errorMessage, e.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundExceptionTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundExceptionTest.java
new file mode 100644
index 0000000..9645ece
--- /dev/null
+++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/exception/ReviewNotFoundExceptionTest.java
@@ -0,0 +1,98 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.exception;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ReviewNotFoundExceptionTest {
+
+ @Test
+ @DisplayName("Exception message should contain the UUID provided for customer")
+ void testExceptionMessageContainsUUIDForCustomer() {
+ UUID customerUUID = UUID.randomUUID();
+
+ ReviewNotFoundException exception = new ReviewNotFoundException(Optional.of(customerUUID), Optional.empty(), Optional.empty());
+
+ String expectedMessage = String.format("The reviews with the customer id %s does not exists", customerUUID);
+ assertEquals(expectedMessage, exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("Exception message should contain the UUID provided for book")
+ void testExceptionMessageContainsUUIDForBook() {
+ UUID bookUUID = UUID.randomUUID();
+
+ ReviewNotFoundException exception = new ReviewNotFoundException(Optional.empty(), Optional.of(bookUUID), Optional.empty());
+
+ String expectedMessage = String.format("The reviews with the book id %s does not exists", bookUUID);
+ assertEquals(expectedMessage, exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("Exception message should contain the UUID provided for customer and book")
+ void testExceptionMessageContainsUUIDForCustomerAndBook() {
+ UUID reviewUUID = UUID.randomUUID();
+
+ ReviewNotFoundException exception = new ReviewNotFoundException(Optional.empty(), Optional.empty(), Optional.of(reviewUUID));
+
+ String expectedMessage = String.format("The review with review id %s does not exists", reviewUUID);
+ assertEquals(expectedMessage, exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("Exception should use the correct constant message format for customer")
+ void testExceptionUsesConstantMessageCustomerFormat() {
+ UUID customerUUID = UUID.randomUUID();
+
+ ReviewNotFoundException exception = new ReviewNotFoundException(Optional.of(customerUUID), Optional.empty(), Optional.empty());
+
+ String expectedFormatWithPlaceholder = "The reviews with the customer id {0} does not exists";
+ assertEquals(ReviewNotFoundException.THE_REVIEWS_WITH_CUSTOMER_ID_DOES_NOT_EXIST_MESSAGE,
+ expectedFormatWithPlaceholder);
+ assertTrue(exception.getMessage().contains(customerUUID.toString()));
+ }
+
+ @Test
+ @DisplayName("Exception should use the correct constant message format for book")
+ void testExceptionUsesConstantMessageBookFormat() {
+ UUID bookUUID = UUID.randomUUID();
+
+ ReviewNotFoundException exception = new ReviewNotFoundException(Optional.empty(), Optional.of(bookUUID), Optional.empty());
+
+ String expectedFormatWithPlaceholder = "The reviews with the book id {0} does not exists";
+ assertEquals(ReviewNotFoundException.THE_REVIEWS_WITH_BOOK_ID_DOES_NOT_EXIST_MESSAGE,
+ expectedFormatWithPlaceholder);
+ assertTrue(exception.getMessage().contains(bookUUID.toString()));
+ }
+
+ @Test
+ @DisplayName("Exception should use the correct constant message format for review")
+ void testExceptionUsesConstantMessageReviewFormat() {
+ UUID reviewUUID = UUID.randomUUID();
+
+ ReviewNotFoundException exception = new ReviewNotFoundException(Optional.empty(), Optional.empty(), Optional.of(reviewUUID));
+
+ String expectedFormatWithPlaceholder = "The review with review id {0} does not exists";
+ assertEquals(ReviewNotFoundException.THE_REVIEWS_WITH_REVIEW_ID_DOES_NOT_EXIST_MESSAGE,
+ expectedFormatWithPlaceholder);
+ assertTrue(exception.getMessage().contains(reviewUUID.toString()));
+ }
+
+ @Test
+ @DisplayName("Exception should be properly thrown and caught")
+ void testExceptionCanBeThrownAndCaught() {
+ UUID reviewUUID = UUID.randomUUID();
+
+ try {
+ throw new ReviewNotFoundException(Optional.empty(),Optional.empty(), Optional.of(reviewUUID));
+ } catch (ReviewNotFoundException e) {
+ String expectedMessage = String.format("The review with review id %s does not exists", reviewUUID);
+ assertEquals(expectedMessage, e.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepositoryTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepositoryTest.java
new file mode 100644
index 0000000..f4b5e4b
--- /dev/null
+++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/repository/ReviewRepositoryTest.java
@@ -0,0 +1,361 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.repository;
+
+import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ReviewRepositoryTest {
+
+ private ReviewRepository repository;
+ private Review review1;
+ private Review review2;
+ private Review review3;
+ private Review review4;
+
+ @BeforeEach
+ void setUp() {
+ repository = new ReviewRepository();
+
+ LocalDate purchaseDate = LocalDate.of(2026, 3, 24);
+ review1 = Review.builder()
+ .note(1)
+ .comment("nul")
+ .purchaseDate(purchaseDate)
+ .build();
+ review1.setRandomUUID();
+ review1.setRandomUUIDCustomerAndBook();
+
+ UUID customerId = UUID.randomUUID();
+ UUID bookId = UUID.randomUUID();
+ review2 = Review.builder()
+ .customerId(customerId)
+ .bookId(bookId)
+ .note(1)
+ .comment("nul")
+ .purchaseDate(purchaseDate)
+ .build();
+ review2.setRandomUUID();
+
+ UUID bookId3 = UUID.randomUUID();
+ review3 = Review.builder()
+ .customerId(customerId)
+ .bookId(bookId3)
+ .note(2)
+ .comment("ça passe")
+ .purchaseDate(purchaseDate)
+ .build();
+ review3.setRandomUUID();
+
+ UUID customerId4 = UUID.randomUUID();
+ review4 = Review.builder()
+ .customerId(customerId4)
+ .bookId(bookId)
+ .note(2)
+ .comment("ça passe")
+ .purchaseDate(purchaseDate)
+ .build();
+ review4.setRandomUUID();
+ }
+
+ @Test
+ @DisplayName("New repository should be empty")
+ void testNewRepositoryIsEmpty() {
+ List reviews = repository.findAll();
+
+ assertTrue(reviews.isEmpty());
+ assertEquals(0, reviews.size());
+ }
+
+ @Nested
+ @DisplayName("Save operations")
+ class SaveOperations {
+
+ @Test
+ @DisplayName("Save should add a new review")
+ void testSaveNewReview() {
+ Review savedReview = repository.save(review1);
+
+ assertEquals(1, repository.findAll().size());
+ assertEquals(review1.getCustomerId(), savedReview.getCustomerId());
+ assertEquals(review1.getBookId(), savedReview.getBookId());
+ }
+
+ @Test
+ @DisplayName("Save should update existing review with same customer and book ID")
+ void testSaveUpdatesExistingReview() {
+ repository.save(review1);
+
+ LocalDate purchaseDate = LocalDate.of(2026, 5, 24);
+ UUID reviewId = review1.getReviewId();
+ UUID customerId = UUID.randomUUID();
+ UUID bookId = UUID.randomUUID();
+ Review updatedReview = Review.builder()
+ .reviewId(reviewId)
+ .customerId(customerId)
+ .bookId(bookId)
+ .note(4)
+ .comment("pas mal")
+ .purchaseDate(purchaseDate)
+ .build();
+
+ Review savedReview = repository.save(updatedReview);
+
+ assertEquals(1, repository.findAll().size());
+ assertEquals(reviewId, savedReview.getReviewId());
+ assertEquals(customerId, savedReview.getCustomerId());
+ assertEquals(bookId, savedReview.getBookId());
+ assertEquals(4, savedReview.getNote());
+ assertEquals("pas mal", savedReview.getComment());
+ assertEquals(purchaseDate, savedReview.getPurchaseDate());
+ }
+
+ @Test
+ @DisplayName("Save multiple review should add all of them")
+ void testSaveMultipleReviews() {
+ repository.save(review1);
+ repository.save(review2);
+
+ List reviews = repository.findAll();
+
+ assertEquals(2, reviews.size());
+ assertTrue(reviews.contains(review1));
+ assertTrue(reviews.contains(review2));
+ }
+ }
+
+ @Nested
+ @DisplayName("Find operations")
+ class FindOperations {
+
+ @BeforeEach
+ void setUpReviews() {
+ repository.save(review1);
+ repository.save(review2);
+ repository.save(review3);
+ repository.save(review4);
+ }
+
+ @Test
+ @DisplayName("FindAll should return all reviews")
+ void testFindAll() {
+ List reviews = repository.findAll();
+
+ assertEquals(4, reviews.size());
+ assertTrue(reviews.contains(review1));
+ assertTrue(reviews.contains(review2));
+ assertTrue(reviews.contains(review3));
+ assertTrue(reviews.contains(review4));
+ }
+
+ @Test
+ @DisplayName("findByCustomerId should return review with matching customer ID")
+ void testFindByCustomerId() {
+ ArrayList foundreviews = repository.findByCustomerId(review2.getCustomerId());
+
+ assertTrue(!foundreviews.isEmpty());
+ boolean allSameCustomer = foundreviews.stream()
+ .allMatch(review -> review.getCustomerId().equals(review2.getCustomerId()));
+ assertTrue(allSameCustomer);
+ }
+
+ @Test
+ @DisplayName("findByCustomerId should return empty Optional when a review with customer ID doesn't exist")
+ void testFindByCustomerIdNotFound() {
+ UUID nonExistentCustomerId = UUID.randomUUID();
+
+ ArrayList foundreview = repository.findByCustomerId(nonExistentCustomerId);
+
+ assertTrue(foundreview.isEmpty());
+ }
+
+ @Test
+ @DisplayName("findByBookId should return review with matching book ID")
+ void testFindByBookId() {
+ ArrayList foundreviews = repository.findByBookId(review2.getBookId());
+
+ assertTrue(!foundreviews.isEmpty());
+ boolean allSameCustomer = foundreviews.stream()
+ .allMatch(review -> review.getBookId().equals(review2.getBookId()));
+ assertTrue(allSameCustomer);
+ }
+
+ @Test
+ @DisplayName("findByBookId should return empty Optional when a review with book ID doesn't exist")
+ void testFindByBookIdNotFound() {
+ UUID nonExistentBookId = UUID.randomUUID();
+
+ ArrayList foundreview = repository.findByBookId(nonExistentBookId);
+
+ assertTrue(foundreview.isEmpty());
+ }
+
+ @Test
+ @DisplayName("findByReviewId should return review with matching review ID")
+ void testFindByReviewId() {
+ Optional foundreview = repository.findByReviewId(review1.getReviewId());
+
+ assertTrue(foundreview.isPresent());
+ assertEquals(review1.getNote(), foundreview.get().getNote());
+ assertEquals(review1.getComment(), foundreview.get().getComment());
+ }
+
+ @Test
+ @DisplayName("findByReviewId should return empty Optional when a review with review ID doesn't exist")
+ void testFindByReviewIdNotFound() {
+ UUID nonExistentReviewId = UUID.randomUUID();
+
+ Optional foundreview = repository.findByReviewId(nonExistentReviewId);
+
+ assertTrue(foundreview.isEmpty());
+ }
+
+ @Test
+ @DisplayName("existsByCustomerId should return true when a review with customer ID exists")
+ void testExistsByCustomerIdExists() {
+ boolean exists = repository.existsByCustomerId(review1.getCustomerId());
+
+ assertTrue(exists);
+ }
+
+ @Test
+ @DisplayName("existsByCustomerId should return false when a review with customer ID doesn't exist")
+ void testExistsByCustomerIdNotExists() {
+ UUID nonExistentCustomerId = UUID.randomUUID();
+
+ boolean exists = repository.existsByCustomerId(nonExistentCustomerId);
+
+ assertFalse(exists);
+ }
+
+ @Test
+ @DisplayName("existsByBookId should return true when a review with book ID exists")
+ void testExistsByBookIdExists() {
+ boolean exists = repository.existsByBookId(review1.getBookId());
+
+ assertTrue(exists);
+ }
+
+ @Test
+ @DisplayName("existsByBookId should return false when a review with book ID doesn't exist")
+ void testExistsByBookIdNotExists() {
+ UUID nonExistentBookId = UUID.randomUUID();
+
+ boolean exists = repository.existsByBookId(nonExistentBookId);
+
+ assertFalse(exists);
+ }
+
+ @Test
+ @DisplayName("existsByReviewId should return true when a review with review ID exists")
+ void testExistsByReviewIdExists() {
+ boolean exists = repository.existsByReviewId(review1.getReviewId());
+
+ assertTrue(exists);
+ }
+
+ @Test
+ @DisplayName("existsByReviewId should return false when review ID doesn't exist")
+ void testExistsByReviewIdNotExists() {
+ UUID nonExistentReviewId = UUID.randomUUID();
+
+ boolean exists = repository.existsByReviewId(nonExistentReviewId);
+
+ assertFalse(exists);
+ }
+ }
+
+ @Nested
+ @DisplayName("Delete operations")
+ class DeleteOperations {
+
+ @BeforeEach
+ void setUpReviews() {
+ repository.save(review1);
+ repository.save(review2);
+ repository.save(review3);
+ repository.save(review4);
+ }
+
+ @Test
+ @DisplayName("Delete should remove all reviews of a customer")
+ void testDeleteCustomerReviews() {
+ repository.deleteCustomerReviews(review2.getCustomerId());
+
+ List reviews = repository.findAll();
+
+ assertEquals(2, reviews.size());
+ assertTrue(reviews.contains(review1));
+ assertFalse(reviews.contains(review2));
+ assertFalse(reviews.contains(review3));
+ assertTrue(reviews.contains(review4));
+ }
+
+ @Test
+ @DisplayName("Delete should remove all reviews of a book")
+ void testDeleteBookReviews() {
+ repository.deleteBookReviews(review2.getBookId());
+
+ List reviews = repository.findAll();
+
+ assertEquals(2, reviews.size());
+ assertTrue(reviews.contains(review1));
+ assertFalse(reviews.contains(review2));
+ assertTrue(reviews.contains(review3));
+ assertFalse(reviews.contains(review4));
+ }
+
+ @Test
+ @DisplayName("Delete should remove the specified review")
+ void testDelete() {
+ repository.delete(review1);
+
+ List reviews = repository.findAll();
+
+ assertEquals(3, reviews.size());
+ assertFalse(reviews.contains(review1));
+ assertTrue(reviews.contains(review2));
+ assertTrue(reviews.contains(review3));
+ assertTrue(reviews.contains(review4));
+ }
+
+ @Test
+ @DisplayName("DeleteAll should remove all reviews")
+ void testDeleteAll() {
+ repository.deleteAll();
+
+ List reviews = repository.findAll();
+
+ assertTrue(reviews.isEmpty());
+ assertEquals(0, reviews.size());
+ }
+
+ @Test
+ @DisplayName("Delete should not throw exception when review doesn't exist")
+ void testDeleteNonExistentReview() {
+ LocalDate purchaseDate = LocalDate.of(2026, 3, 24);
+ Review nonExistentReview = Review.builder()
+ .note(1)
+ .comment("nul")
+ .purchaseDate(purchaseDate)
+ .build();
+ nonExistentReview.setRandomUUID();
+
+ assertDoesNotThrow(() -> repository.delete(nonExistentReview));
+
+ assertEquals(4, repository.findAll().size());
+ }
+ }
+}
diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCaseTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCaseTest.java
new file mode 100644
index 0000000..61937b5
--- /dev/null
+++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/usecase/ReviewUseCaseTest.java
@@ -0,0 +1,318 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.usecase;
+
+import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewDTO;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewInfo;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.NotValidReviewException;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.ReviewNotFoundException;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.repository.ReviewRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.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.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.never;
+
+@ExtendWith(MockitoExtension.class)
+public class ReviewUseCaseTest {
+
+ @Mock
+ private ReviewRepository reviewRepository;
+
+ @InjectMocks
+ private ReviewUseCase reviewUseCase;
+
+ private UUID reviewId;
+ private UUID customerId;
+ private UUID bookId;
+ private LocalDate purchaseDate;
+ private Review testReview;
+ private ReviewInfo validReviewInfo;
+
+ @BeforeEach
+ void setUp() {
+ reviewId = UUID.randomUUID();
+ customerId = UUID.randomUUID();
+ bookId = UUID.randomUUID();
+ purchaseDate = LocalDate.of(2026, 5, 24);
+ testReview = Review.builder()
+ .reviewId(reviewId)
+ .customerId(customerId)
+ .bookId(bookId)
+ .note(2)
+ .comment("plutôt mauvais")
+ .purchaseDate(purchaseDate)
+ .build();
+
+ validReviewInfo = new ReviewInfo(2, "plutôt mauvais", purchaseDate);
+ }
+
+ @Nested
+ @DisplayName("Register review tests")
+ class RegisterReviewTests {
+
+ @Test
+ @DisplayName("Should register review when valid data is provided")
+ void testRegisterReviewWithValidData() throws NotValidReviewException {
+ when(reviewRepository.save(any(Review.class))).thenReturn(testReview);
+
+ UUID registeredId = reviewUseCase.registerReview(validReviewInfo);
+
+ assertNotNull(registeredId);
+ assertEquals(reviewId, registeredId);
+ verify(reviewRepository, times(1)).save(any(Review.class));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when review data is not valid")
+ void testRegisterReviewWithInvalidData() {
+ ReviewInfo invalidReviewInfo = new ReviewInfo(0, "plutôt mauvais", purchaseDate);
+
+ assertThrows(NotValidReviewException.class,
+ () -> reviewUseCase.registerReview(invalidReviewInfo));
+
+ verify(reviewRepository, never()).save(any(Review.class));
+ }
+ }
+
+ @Nested
+ @DisplayName("Find review tests")
+ class FindReviewTests {
+
+ @Test
+ @DisplayName("Should return reviews when customer ID exists")
+ void testFindReviewByCustomerId() {
+ when(reviewRepository.findByCustomerId(customerId)).thenReturn(new ArrayList(List.of(testReview)));
+
+ ArrayList foundReviews = reviewUseCase.findReviewByCustomerId(customerId);
+
+ assertTrue(!foundReviews.isEmpty());
+ boolean allSameCustomer = foundReviews.stream()
+ .allMatch(review -> review.getCustomerId().equals(customerId));
+ assertTrue(allSameCustomer);
+ verify(reviewRepository, times(1)).findByCustomerId(customerId);
+ }
+
+ @Test
+ @DisplayName("Should return empty Optional when customer ID doesn't exist")
+ void testFindReviewByCustomerIdNotFound() {
+ UUID nonExistentCustomerId = UUID.randomUUID();
+ when(reviewRepository.findByCustomerId(nonExistentCustomerId)).thenReturn(new ArrayList());
+
+ ArrayList foundReviews = reviewUseCase.findReviewByCustomerId(nonExistentCustomerId);
+
+ assertTrue(foundReviews.isEmpty());
+ verify(reviewRepository, times(1)).findByCustomerId(nonExistentCustomerId);
+ }
+
+ @Test
+ @DisplayName("Should return reviews when book ID exists")
+ void testFindReviewByBookId() {
+ when(reviewRepository.findByBookId(bookId)).thenReturn(new ArrayList(List.of(testReview)));
+
+ ArrayList foundReviews = reviewUseCase.findReviewByBookId(bookId);
+
+ assertTrue(!foundReviews.isEmpty());
+ boolean allSameCustomer = foundReviews.stream()
+ .allMatch(review -> review.getBookId().equals(bookId));
+ assertTrue(allSameCustomer);
+ verify(reviewRepository, times(1)).findByBookId(bookId);
+ }
+
+ @Test
+ @DisplayName("Should return empty Optional when book ID doesn't exist")
+ void testFindReviewByBookIdNotFound() {
+ UUID nonExistentBookId = UUID.randomUUID();
+ when(reviewRepository.findByBookId(nonExistentBookId)).thenReturn(new ArrayList());
+
+ ArrayList foundReviews = reviewUseCase.findReviewByBookId(nonExistentBookId);
+
+ assertTrue(foundReviews.isEmpty());
+ verify(reviewRepository, times(1)).findByBookId(nonExistentBookId);
+ }
+
+ @Test
+ @DisplayName("Should return review when Review ID exists")
+ void testFindReviewByReviewId() {
+ when(reviewRepository.findByReviewId(reviewId)).thenReturn(Optional.of(testReview));
+
+ Optional foundReview = reviewUseCase.findReviewByReviewId(reviewId);
+
+ assertTrue(foundReview.isPresent());
+ assertEquals(testReview.getBookId(), foundReview.get().getBookId());
+ assertEquals(testReview.getNote(), foundReview.get().getNote());
+ verify(reviewRepository, times(1)).findByReviewId(reviewId);
+ }
+
+ @Test
+ @DisplayName("Should return empty Optional when review ID doesn't exist")
+ void testFindReviewByReviewIdNotFound() {
+ UUID nonExistentReviewId = UUID.randomUUID();
+ when(reviewRepository.findByReviewId(nonExistentReviewId)).thenReturn(Optional.empty());
+
+ Optional foundReview = reviewUseCase.findReviewByReviewId(nonExistentReviewId);
+
+ assertTrue(foundReview.isEmpty());
+ verify(reviewRepository, times(1)).findByReviewId(nonExistentReviewId);
+ }
+ }
+
+ @Nested
+ @DisplayName("Update review tests")
+ class UpdateReviewTests {
+
+ @Test
+ @DisplayName("Should update review when valid data is provided")
+ void testUpdateReviewWithValidData() throws ReviewNotFoundException, NotValidReviewException {
+ when(reviewRepository.findByReviewId(reviewId)).thenReturn(Optional.of(testReview));
+
+ LocalDate updatePurchaseDate = LocalDate.of(2026, 5, 30);
+ Review updatedReview = Review.builder()
+ .reviewId(reviewId)
+ .customerId(customerId)
+ .bookId(bookId)
+ .note(4)
+ .comment("en fait c'est bien")
+ .purchaseDate(updatePurchaseDate)
+ .build();
+
+ when(reviewRepository.save(any(Review.class))).thenReturn(updatedReview);
+
+ ReviewInfo updateInfo = new ReviewInfo(4, "en fait c'est bien", updatePurchaseDate);
+
+ ReviewDTO result = reviewUseCase.updateReview(reviewId, updateInfo);
+
+ assertNotNull(result);
+ assertEquals(customerId, result.getCustomerId());
+ assertEquals(4, result.getNote());
+ assertEquals("en fait c'est bien", result.getComment());
+ verify(reviewRepository, times(1)).findByReviewId(reviewId);
+ verify(reviewRepository, times(1)).save(any(Review.class));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when review ID doesn't exist")
+ void testUpdateReviewNotFound() {
+ UUID nonExistentReviewId = UUID.randomUUID();
+ when(reviewRepository.findByReviewId(nonExistentReviewId)).thenReturn(Optional.empty());
+
+ LocalDate updatePurchaseDate = LocalDate.of(2026, 5, 24);
+ ReviewInfo updateInfo = new ReviewInfo(3, "moyen", updatePurchaseDate);
+
+ assertThrows(ReviewNotFoundException.class,
+ () -> reviewUseCase.updateReview(nonExistentReviewId, updateInfo));
+
+ verify(reviewRepository, times(1)).findByReviewId(nonExistentReviewId);
+ verify(reviewRepository, never()).save(any(Review.class));
+ }
+
+ @Test
+ @DisplayName("Should throw exception when update data is not valid")
+ void testUpdateReviewWithInvalidData() {
+ LocalDate updatePurchaseDate = LocalDate.of(2026, 5, 24);
+ ReviewInfo invalidUpdateInfo = new ReviewInfo(0, "éclaté au sol", updatePurchaseDate);
+
+ assertThrows(NotValidReviewException.class,
+ () -> reviewUseCase.updateReview(reviewId, invalidUpdateInfo));
+
+ verify(reviewRepository, never()).findByReviewId(any(UUID.class));
+ verify(reviewRepository, never()).save(any(Review.class));
+ }
+ }
+
+ @Nested
+ @DisplayName("Delete review tests")
+ class DeleteReviewTests {
+
+ @Test
+ @DisplayName("Should delete reviews when customer ID exists")
+ void testDeleteCustomerReviews() throws ReviewNotFoundException {
+ when(reviewRepository.findByCustomerId(customerId)).thenReturn(new ArrayList(List.of(testReview)));
+ doNothing().when(reviewRepository).delete(testReview);
+
+ reviewUseCase.deleteCustomerReviews(customerId);
+
+ verify(reviewRepository, times(1)).findByCustomerId(customerId);
+ verify(reviewRepository, times(1)).delete(testReview);
+ }
+
+ @Test
+ @DisplayName("Should throw exception when customer ID doesn't exist")
+ void testDeleteCustomerReviewsNotFound() {
+ UUID nonExistentCustomerId = UUID.randomUUID();
+ when(reviewRepository.findByCustomerId(nonExistentCustomerId)).thenReturn(new ArrayList());
+
+ assertThrows(ReviewNotFoundException.class,
+ () -> reviewUseCase.deleteCustomerReviews(nonExistentCustomerId));
+
+ verify(reviewRepository, times(1)).findByCustomerId(nonExistentCustomerId);
+ verify(reviewRepository, never()).delete(any(Review.class));
+ }
+
+ @Test
+ @DisplayName("Should delete reviews when book ID exists")
+ void testDeleteBookReviews() throws ReviewNotFoundException {
+ when(reviewRepository.findByBookId(bookId)).thenReturn(new ArrayList(List.of(testReview)));
+ doNothing().when(reviewRepository).delete(testReview);
+
+ reviewUseCase.deleteBookReviews(bookId);
+
+ verify(reviewRepository, times(1)).findByBookId(bookId);
+ verify(reviewRepository, times(1)).delete(testReview);
+ }
+
+ @Test
+ @DisplayName("Should throw exception when book ID doesn't exist")
+ void testDeleteBookReviewsNotFound() {
+ UUID nonExistentBookId = UUID.randomUUID();
+ when(reviewRepository.findByBookId(nonExistentBookId)).thenReturn(new ArrayList());
+
+ assertThrows(ReviewNotFoundException.class,
+ () -> reviewUseCase.deleteBookReviews(nonExistentBookId));
+
+ verify(reviewRepository, times(1)).findByBookId(nonExistentBookId);
+ verify(reviewRepository, never()).delete(any(Review.class));
+ }
+
+ @Test
+ @DisplayName("Should delete review when review ID exists")
+ void testDeleteReview() throws ReviewNotFoundException {
+ when(reviewRepository.findByReviewId(reviewId)).thenReturn(Optional.of(testReview));
+ doNothing().when(reviewRepository).delete(testReview);
+
+ reviewUseCase.deleteReview(reviewId);
+
+ verify(reviewRepository, times(1)).findByReviewId(reviewId);
+ verify(reviewRepository, times(1)).delete(testReview);
+ }
+
+ @Test
+ @DisplayName("Should throw exception when review ID doesn't exist")
+ void testDeleteReviewNotFound() {
+ UUID nonExistentReviewId = UUID.randomUUID();
+ when(reviewRepository.findByReviewId(nonExistentReviewId)).thenReturn(Optional.empty());
+
+ assertThrows(ReviewNotFoundException.class,
+ () -> reviewUseCase.deleteReview(nonExistentReviewId));
+
+ verify(reviewRepository, times(1)).findByReviewId(nonExistentReviewId);
+ verify(reviewRepository, never()).delete(any(Review.class));
+ }
+ }
+}
diff --git a/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidatorTest.java b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidatorTest.java
new file mode 100644
index 0000000..dacdee3
--- /dev/null
+++ b/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/review/validator/ReviewValidatorTest.java
@@ -0,0 +1,113 @@
+package fr.iut_fbleau.but3.dev62.mylibrary.review.validator;
+
+import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewInfo;
+import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.NotValidReviewException;
+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 static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class ReviewValidatorTest {
+
+ @Test
+ @DisplayName("Should validate review with valid data")
+ void testValidateValidReview() {
+ LocalDate purchaseDate = LocalDate.of(2026, 3, 24);
+ ReviewInfo validReview = new ReviewInfo(3, "Bof", purchaseDate);
+
+ assertDoesNotThrow(() -> ReviewValidator.validate(validReview));
+ }
+
+ @Nested
+ @DisplayName("Note validation tests")
+ class NoteValidationTests {
+
+ @Test
+ @DisplayName("Should throw exception when note is lower than 1")
+ void testValidateNoteLower1() {
+ LocalDate purchaseDate = LocalDate.of(2026, 3, 24);
+ ReviewInfo reviewWithNoteLower1 = new ReviewInfo(0, "Bof", purchaseDate);
+
+ NotValidReviewException exception = assertThrows(
+ NotValidReviewException.class,
+ () -> ReviewValidator.validate(reviewWithNoteLower1)
+ );
+
+ assertEquals(ReviewValidator.NOTE_CANNOT_BE_LOWER_THAN_1, exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("Should throw exception when note is upper than 5")
+ void testValidateNoteUpper5() {
+ LocalDate purchaseDate = LocalDate.of(2026, 3, 24);
+ ReviewInfo reviewWithNoteUpper5 = new ReviewInfo(6, "Bof", purchaseDate);
+
+ NotValidReviewException exception = assertThrows(
+ NotValidReviewException.class,
+ () -> ReviewValidator.validate(reviewWithNoteUpper5)
+ );
+
+ assertEquals(ReviewValidator.NOTE_CANNOT_BE_UPPER_THAN_5, exception.getMessage());
+ }
+ }
+
+ @Nested
+ @DisplayName("Comment validation tests")
+ class CommentValidationTests {
+
+ @Test
+ @DisplayName("Should throw exception when comment is blank")
+ void testValidateBlankComment() {
+ LocalDate purchaseDate = LocalDate.of(2026, 3, 24);
+ ReviewInfo reviewWithBlankComment = new ReviewInfo(3, "", purchaseDate);
+
+ NotValidReviewException exception = assertThrows(
+ NotValidReviewException.class,
+ () -> ReviewValidator.validate(reviewWithBlankComment)
+ );
+
+ assertEquals(ReviewValidator.COMMENT_CANNOT_BE_BLANK, exception.getMessage());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {" ", " ", "\t", "\n"})
+ @DisplayName("Should throw exception when last name contains only whitespace")
+ void testValidateWhitespaceLastName(String whitespace) {
+ LocalDate purchaseDate = LocalDate.of(2026, 3, 24);
+ ReviewInfo reviewWithBlankComment = new ReviewInfo(3, whitespace, purchaseDate);
+
+ NotValidReviewException exception = assertThrows(
+ NotValidReviewException.class,
+ () -> ReviewValidator.validate(reviewWithBlankComment)
+ );
+
+ assertEquals(ReviewValidator.COMMENT_CANNOT_BE_BLANK, exception.getMessage());
+ }
+ }
+
+ @Nested
+ @DisplayName("Purchase date validation tests")
+ class PurchaseDateValidationTests {
+
+ @Test
+ @DisplayName("Should throw exception when purchase date is after the actual date")
+ void testValidateFuturPurchaseDate() {
+ LocalDate futurepurchaseDate = LocalDate.of(2026, 6, 24);
+ ReviewInfo reviewWithFuturPurchaseDate = new ReviewInfo(2, "Bof", futurepurchaseDate);
+
+ NotValidReviewException exception = assertThrows(
+ NotValidReviewException.class,
+ () -> ReviewValidator.validate(reviewWithFuturPurchaseDate)
+ );
+
+ assertEquals(ReviewValidator.PURCHASE_DATE_IS_NOT_VALID, exception.getMessage());
+ }
+ }
+}