diff --git a/mylibrary/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCase.java b/mylibrary/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCase.java new file mode 100644 index 0000000..d5b88e7 --- /dev/null +++ b/mylibrary/src/main/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCase.java @@ -0,0 +1,46 @@ +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.BookAlreadyExistsException; +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.List; +import java.util.Optional; + +public final class BookUseCase { + + private final BookRepository bookRepository; + + public BookUseCase(BookRepository bookRepository) { + this.bookRepository = bookRepository; + } + + public long registerBook(BookInfo bookInfo) throws NotValidBookException, BookAlreadyExistsException { + BookValidator.validate(bookInfo); + if (bookRepository.existsByIsbn(bookInfo.isbn())) { + throw new BookAlreadyExistsException(bookInfo.isbn()); + } + Book toRegister = BookConverter.toDomain(bookInfo); + Book registered = bookRepository.save(toRegister); + return registered.getIsbn(); + } + + public BookDTO getBookByIsbn(long isbn) throws BookNotFoundException { + Optional optional = bookRepository.findByIsbn(isbn); + if (optional.isEmpty()) { + throw new BookNotFoundException(isbn); + } + return BookConverter.toDTO(optional.get()); + } + + public List getAllBooks() { + return bookRepository.findAll().stream() + .map(BookConverter::toDTO) + .toList(); + } +} diff --git a/mylibrary/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCaseTest.java b/mylibrary/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCaseTest.java new file mode 100644 index 0000000..eba95e6 --- /dev/null +++ b/mylibrary/src/test/java/fr/iut_fbleau/but3/dev62/mylibrary/book/usecase/BookUseCaseTest.java @@ -0,0 +1,152 @@ +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.entity.Category; +import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.BookAlreadyExistsException; +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 java.time.LocalDate; +import java.util.List; +import java.util.Optional; +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 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 BookInfo validInfo; + private Book validBook; + + @BeforeEach + void setUp() { + validInfo = new BookInfo( + 9780321125217L, + "DDD", + "Evans", + "Addison-Wesley", + LocalDate.of(2003, 8, 22), + 54.99, + 10, + List.of(Category.SCIENCE), + "desc", + "EN" + ); + validBook = Book.builder() + .isbn(validInfo.isbn()) + .title(validInfo.title()) + .author(validInfo.author()) + .publisher(validInfo.publisher()) + .publicationDate(validInfo.publicationDate()) + .price(validInfo.price()) + .quantity(validInfo.quantity()) + .categories(validInfo.categories()) + .description(validInfo.description()) + .language(validInfo.language()) + .build(); + } + + @Nested + @DisplayName("registerBook tests") + class RegisterTests { + + @Test + @DisplayName("Should register a new book and return its ISBN") + void testRegisterBook() throws NotValidBookException, BookAlreadyExistsException { + when(bookRepository.existsByIsbn(validInfo.isbn())).thenReturn(false); + when(bookRepository.save(any(Book.class))).thenReturn(validBook); + + long isbn = bookUseCase.registerBook(validInfo); + + assertEquals(validInfo.isbn(), isbn); + verify(bookRepository).existsByIsbn(validInfo.isbn()); + verify(bookRepository).save(any(Book.class)); + } + + @Test + @DisplayName("Should reject invalid book information without touching the repository") + void testRegisterInvalidBook() { + BookInfo invalid = new BookInfo(0L, "", "", "", null, -1, -1, null, "", ""); + assertThrows(NotValidBookException.class, () -> bookUseCase.registerBook(invalid)); + verifyNoInteractions(bookRepository); + } + + @Test + @DisplayName("Should reject duplicate ISBN") + void testRegisterDuplicate() { + when(bookRepository.existsByIsbn(validInfo.isbn())).thenReturn(true); + assertThrows(BookAlreadyExistsException.class, () -> bookUseCase.registerBook(validInfo)); + verify(bookRepository).existsByIsbn(validInfo.isbn()); + verify(bookRepository, never()).save(any(Book.class)); + } + } + + @Nested + @DisplayName("getBookByIsbn tests") + class GetByIdTests { + + @Test + @DisplayName("Should return the BookDTO when ISBN exists") + void testGetById() throws BookNotFoundException { + when(bookRepository.findByIsbn(validInfo.isbn())).thenReturn(Optional.of(validBook)); + + BookDTO dto = bookUseCase.getBookByIsbn(validInfo.isbn()); + + assertEquals(validInfo.isbn(), dto.getIsbn()); + assertEquals(validInfo.title(), dto.getTitle()); + } + + @Test + @DisplayName("Should throw when ISBN does not exist") + void testGetByIdNotFound() { + when(bookRepository.findByIsbn(99L)).thenReturn(Optional.empty()); + assertThrows(BookNotFoundException.class, () -> bookUseCase.getBookByIsbn(99L)); + } + } + + @Nested + @DisplayName("getAllBooks tests") + class GetAllBooksTests { + + @Test + @DisplayName("Should map every book returned by the repository to a DTO") + void testGetAllBooks() { + when(bookRepository.findAll()).thenReturn(List.of(validBook)); + + List result = bookUseCase.getAllBooks(); + + assertEquals(1, result.size()); + assertEquals(validBook.getIsbn(), result.getFirst().getIsbn()); + assertEquals(validBook.getTitle(), result.getFirst().getTitle()); + verify(bookRepository).findAll(); + } + + @Test + @DisplayName("Should return an empty list when no book is stored") + void testGetAllBooksWhenEmpty() { + when(bookRepository.findAll()).thenReturn(List.of()); + + List result = bookUseCase.getAllBooks(); + + assertTrue(result.isEmpty()); + } + } +}