diff --git a/pom.xml b/pom.xml
index 7d4ba60..ad87c94 100644
--- a/pom.xml
+++ b/pom.xml
@@ -113,6 +113,10 @@
org.springframework.boot
spring-boot-docker-compose
+
+ org.springframework.security
+ spring-security-test
+
org.junit.jupiter
junit-jupiter
diff --git a/src/main/java/mate/academy/bookstore/controller/BookController.java b/src/main/java/mate/academy/bookstore/controller/BookController.java
index 74033ba..6e56c30 100644
--- a/src/main/java/mate/academy/bookstore/controller/BookController.java
+++ b/src/main/java/mate/academy/bookstore/controller/BookController.java
@@ -9,7 +9,9 @@
import mate.academy.bookstore.dto.book.BookSearchParametersDto;
import mate.academy.bookstore.dto.book.CreateBookRequestDto;
import mate.academy.bookstore.service.BookService;
+import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -33,7 +35,9 @@ public class BookController {
@GetMapping
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Get all books", description = "Get a list of all available books")
- public List getAll(Pageable pageable) {
+ public List getAll(@ParameterObject
+ @PageableDefault(sort = {"price", "title"}, value = 5)
+ Pageable pageable) {
return bookService.findAll(pageable);
}
diff --git a/src/main/java/mate/academy/bookstore/repository/book/BookRepository.java b/src/main/java/mate/academy/bookstore/repository/book/BookRepository.java
index ea2900e..4d15ccd 100644
--- a/src/main/java/mate/academy/bookstore/repository/book/BookRepository.java
+++ b/src/main/java/mate/academy/bookstore/repository/book/BookRepository.java
@@ -3,7 +3,9 @@
import java.util.List;
import java.util.Optional;
import mate.academy.bookstore.model.Book;
+import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -15,9 +17,15 @@ public interface BookRepository extends JpaRepository, JpaSpecificat
@Query("FROM Book b JOIN FETCH b.categories")
List findAllBooks(Pageable pageable);
+ @EntityGraph(attributePaths = {"categories"})
+ Page findAll(Specification spec, Pageable pageable);
+
@EntityGraph(attributePaths = {"categories"})
Optional findBookByIsbn(String isbn);
+ @Query("FROM Book b JOIN FETCH b.categories WHERE b.id = :id")
+ Optional findBookById(Long id);
+
@Query("SELECT b FROM Book b LEFT JOIN FETCH b.categories c WHERE c.id = :categoryId")
List findAllBooksByCategoryId(@Param("categoryId") Long categoryId, Pageable pageable);
}
diff --git a/src/main/java/mate/academy/bookstore/repository/book/specification/PriceSpecificationProvider.java b/src/main/java/mate/academy/bookstore/repository/book/specification/PriceSpecificationProvider.java
index f29fb3e..b9d5650 100644
--- a/src/main/java/mate/academy/bookstore/repository/book/specification/PriceSpecificationProvider.java
+++ b/src/main/java/mate/academy/bookstore/repository/book/specification/PriceSpecificationProvider.java
@@ -20,8 +20,11 @@ public String getKey() {
@Override
public Specification getSpecification(BookSearchParametersDto params) {
- int minPrice = Optional.ofNullable(params.minPrice()).orElse(DEFAULT_MIN_PRICE);
- int maxPrice = Optional.ofNullable(params.maxPrice()).orElse(DEFAULT_MAX_PRICE);
+ int paramsMinPrice = Optional.ofNullable(params.minPrice()).orElse(DEFAULT_MIN_PRICE);
+ int paramsMaxPrice = Optional.ofNullable(params.maxPrice()).orElse(DEFAULT_MAX_PRICE);
+
+ int minPrice = Math.max(paramsMinPrice, DEFAULT_MIN_PRICE);
+ int maxPrice = (paramsMaxPrice > paramsMinPrice) ? paramsMaxPrice : DEFAULT_MAX_PRICE;
return ((root, query, criteriaBuilder) ->
criteriaBuilder.between(root.get(getKey()), minPrice, maxPrice));
diff --git a/src/main/java/mate/academy/bookstore/service/impl/BookServiceImpl.java b/src/main/java/mate/academy/bookstore/service/impl/BookServiceImpl.java
index ba3972d..1761233 100644
--- a/src/main/java/mate/academy/bookstore/service/impl/BookServiceImpl.java
+++ b/src/main/java/mate/academy/bookstore/service/impl/BookServiceImpl.java
@@ -52,7 +52,7 @@ public List findAll(Pageable pageable) {
}
public BookDto findById(Long id) {
- Book book = bookRepository.findById(id).orElseThrow(
+ Book book = bookRepository.findBookById(id).orElseThrow(
() -> new EntityNotFoundException("Book not found by id " + id));
return bookMapper.toDto(book);
}
diff --git a/src/test/java/mate/academy/bookstore/controller/BookControllerTest.java b/src/test/java/mate/academy/bookstore/controller/BookControllerTest.java
index c13dbca..82e3c1c 100644
--- a/src/test/java/mate/academy/bookstore/controller/BookControllerTest.java
+++ b/src/test/java/mate/academy/bookstore/controller/BookControllerTest.java
@@ -1,74 +1,169 @@
package mate.academy.bookstore.controller;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.when;
+import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.Set;
+import mate.academy.bookstore.dto.book.BookDto;
+import mate.academy.bookstore.dto.book.BookSearchParametersDto;
+import mate.academy.bookstore.dto.book.CreateBookRequestDto;
+import mate.academy.bookstore.service.BookService;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+public class BookControllerTest {
+ private static final String ID = "/{id}";
+ private static final String TITLE_PARAM_NAME = "title";
+ private static final String AUTHOR_PARAM_NAME = "author";
+ private static final String ISBN_PARAM_NAME = "isbn";
+ private static final String PRICE_PARAM_NAME = "price";
+ private static final String BASE_URL = "/books";
+ private static final String SEARCH_URL = BASE_URL + "/search";
+ private static final String ADMIN_ROLE = "ADMIN";
+ private static final String USER_ROLE = "USER";
+ private static final String VALID_BOOK_TITLE = "Kobzar";
+ private static final String VALID_BOOK_AUTHOR = "Taras Shevchenko";
+ private static final String VALID_BOOK_ISBN = "978-0-7847-5628-7";
+ private static final BigDecimal VALID_BOOK_PRICE = BigDecimal.valueOf(14.99);
+ private static final Set VALID_CATEGORY_ID = Set.of(1L);
+ private static final Long VALID_BOOK_ID = 1L;
+ private static final String TITLE_0_EXPRESSION = "$[0].title";
+ private static final String TITLE_EXPRESSION = "$.title";
-class BookControllerTest {
+ private static MockMvc mockMvc;
- @Test
- void getAll_ValidRequest_True() {
- }
-
- @Test
- void getAll_InvalidRequest_False() {
- }
-
- @Test
- void getBookById_ValidId_True() {
- }
-
- @Test
- void getBookById_InvalidId_False() {
- }
-
- @Test
- void getBookById_NullId_False() {
- }
-
- @Test
- void createBook_ValidBook_True() {
- }
-
- @Test
- void createBook_InvalidBook_False() {
- }
-
- @Test
- void createBook_NullBook_False() {
- }
+ @MockBean
+ private BookService bookService;
- @Test
- void updateBook_ValidBook_True() {
- }
-
- @Test
- void updateBook_InvalidBook_False() {
- }
+ @Autowired
+ private ObjectMapper objectMapper;
- @Test
- void updateBook_NullBook_False() {
- }
+ private BookDto bookDto;
+ private CreateBookRequestDto createBookDto;
+
+ @BeforeAll
+ static void beforeAll(@Autowired WebApplicationContext context) {
+ mockMvc = MockMvcBuilders
+ .webAppContextSetup(context)
+ .apply(springSecurity())
+ .build();
+ }
+
+ @BeforeEach
+ void setup() {
+ bookDto = new BookDto();
+ bookDto.setId(VALID_BOOK_ID);
+ bookDto.setTitle(VALID_BOOK_TITLE);
+ bookDto.setAuthor(VALID_BOOK_AUTHOR);
+ bookDto.setIsbn(VALID_BOOK_ISBN);
+ bookDto.setPrice(VALID_BOOK_PRICE);
+
+ createBookDto = new CreateBookRequestDto();
+ createBookDto.setTitle(VALID_BOOK_TITLE);
+ createBookDto.setAuthor(VALID_BOOK_AUTHOR);
+ createBookDto.setIsbn(VALID_BOOK_ISBN);
+ createBookDto.setCategoryIds(VALID_CATEGORY_ID);
+ createBookDto.setPrice(VALID_BOOK_PRICE);
+ }
+
+ @Test
+ @WithMockUser(roles = {USER_ROLE, ADMIN_ROLE})
+ @DisplayName("Get all books (valid request)")
+ void getAllBooks_ValidRequest_ReturnsListOfBooks() throws Exception {
+ when(bookService.findAll(any(Pageable.class))).thenReturn(
+ Collections.singletonList(bookDto));
+
+ mockMvc.perform(get(BASE_URL))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath(TITLE_0_EXPRESSION).value(VALID_BOOK_TITLE));
+ }
+
+ @Test
+ @WithMockUser(roles = {USER_ROLE, ADMIN_ROLE})
+ @DisplayName("Get book by id (valid request)")
+ void getBookById_ValidRequest_ReturnsBookDto() throws Exception {
+ when(bookService.findById(anyLong())).thenReturn(bookDto);
+
+ mockMvc.perform(get(BASE_URL + ID, VALID_BOOK_ID))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath(TITLE_EXPRESSION).value(VALID_BOOK_TITLE));
+ }
+
+ @Test
+ @WithMockUser(roles = ADMIN_ROLE)
+ @DisplayName("Create book (valid request)")
+ void createBook_ValidRequest_ReturnsCreatedBookDto() throws Exception {
+ when(bookService.save(any(CreateBookRequestDto.class))).thenReturn(bookDto);
+
+ String requestContent = objectMapper.writeValueAsString(createBookDto);
+
+ mockMvc.perform(post(BASE_URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestContent))
+ .andExpect(status().isCreated())
+ .andExpect(jsonPath(TITLE_EXPRESSION).value(VALID_BOOK_TITLE));
+ }
@Test
- void deleteBook_ValidId_True() {
- }
+ @WithMockUser(roles = ADMIN_ROLE)
+ @DisplayName("Update book (valid request)")
+ void updateBook_ValidRequest_ReturnsUpdatedBookDto() throws Exception {
+ when(bookService.updateById(anyLong(), any(CreateBookRequestDto.class))).thenReturn(
+ bookDto);
- @Test
- void deleteBook_InvalidId_False() {
- }
+ String requestContent = objectMapper.writeValueAsString(createBookDto);
- @Test
- void deleteBook_NullId_False() {
+ mockMvc.perform(put(BASE_URL + ID, VALID_BOOK_ID)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(requestContent))
+ .andExpect(status().isAccepted())
+ .andExpect(jsonPath(TITLE_EXPRESSION).value(VALID_BOOK_TITLE));
}
@Test
- void searchBook_ValidCriteria_True() {
+ @WithMockUser(roles = ADMIN_ROLE)
+ @DisplayName("Delete book (valid request)")
+ void deleteBook_ValidRequest_ReturnsNoContent() throws Exception {
+ mockMvc.perform(delete(BASE_URL + ID, VALID_BOOK_ID))
+ .andExpect(status().isNoContent());
}
@Test
- void searchBook_InvalidCriteria_False() {
- }
+ @WithMockUser(roles = {USER_ROLE, ADMIN_ROLE})
+ @DisplayName("Search books (valid request)")
+ void searchBooks_ValidRequest_ReturnsListOfBooks() throws Exception {
+ when(bookService.search(any(BookSearchParametersDto.class), any(Pageable.class)))
+ .thenReturn(Collections.singletonList(bookDto));
- @Test
- void searchBook_EmptyCriteria_False() {
+ mockMvc.perform(get(SEARCH_URL)
+ .param(TITLE_PARAM_NAME, VALID_BOOK_TITLE)
+ .param(AUTHOR_PARAM_NAME, VALID_BOOK_AUTHOR)
+ .param(ISBN_PARAM_NAME, VALID_BOOK_ISBN)
+ .param(PRICE_PARAM_NAME, VALID_BOOK_PRICE.toString()))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath(TITLE_0_EXPRESSION).value(VALID_BOOK_TITLE));
}
}
diff --git a/src/test/java/mate/academy/bookstore/controller/CategoryControllerTest.java b/src/test/java/mate/academy/bookstore/controller/CategoryControllerTest.java
index 77e7cc8..0fad4e0 100644
--- a/src/test/java/mate/academy/bookstore/controller/CategoryControllerTest.java
+++ b/src/test/java/mate/academy/bookstore/controller/CategoryControllerTest.java
@@ -1,74 +1,110 @@
package mate.academy.bookstore.controller;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
+import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.List;
+import mate.academy.bookstore.dto.book.BookDtoWithoutCategoryIds;
+import mate.academy.bookstore.dto.category.CategoryDto;
+import mate.academy.bookstore.service.BookService;
+import mate.academy.bookstore.service.CategoryService;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-
+import org.mockito.Mock;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class CategoryControllerTest {
+ private static final Long VALID_CATEGORY_ID = 1L;
+ private static final String VALID_CATEGORY_NAME = "Fiction";
+ private static final String CATEGORIES_URL = "/categories";
+ private static final String CATEGORIES_ID_URL = "/categories/{id}";
+ private static final String CATEGORIES_ID_BOOKS_URL = "/categories/{id}/books";
+ private static final String EXPRESSION = "$";
+ private static final String NAME_EXPRESSION = "$.name";
+ private static final String ADMIN_ROLE = "ADMIN";
+ private static final String USER_ROLE = "USER";
+ private static MockMvc mockMvc;
- @Test
- void createCategory_ValidCategory_True() {
- }
-
- @Test
- void createCategory_InvalidCategory_False() {
- }
-
- @Test
- void createCategory_NullCategory_False() {
- }
-
- @Test
- void getAll_ValidRequest_True() {
- }
-
- @Test
- void getAll_InvalidRequest_False() {
- }
+ @Mock
+ private CategoryService categoryService;
- @Test
- void getCategoryById_ValidId_True() {
- }
+ @Mock
+ private BookService bookService;
- @Test
- void getCategoryById_InvalidId_False() {
+ @BeforeAll
+ static void beforeAll(@Autowired WebApplicationContext context) {
+ mockMvc = MockMvcBuilders
+ .webAppContextSetup(context)
+ .apply(springSecurity())
+ .build();
}
@Test
- void getCategoryById_NullId_False() {
- }
+ @WithMockUser(roles = ADMIN_ROLE)
+ @DisplayName("Create new category")
+ void create_ValidRequest_ReturnsCreatedCategory() throws Exception {
+ CategoryDto categoryDto = new CategoryDto();
+ categoryDto.setName(VALID_CATEGORY_NAME);
- @Test
- void updateCategory_ValidCategory_True() {
- }
+ when(categoryService.save(any(CategoryDto.class))).thenReturn(categoryDto);
- @Test
- void updateCategory_InvalidCategory_False() {
+ mockMvc.perform(post(CATEGORIES_URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(new ObjectMapper().writeValueAsString(categoryDto)))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath(NAME_EXPRESSION).value(VALID_CATEGORY_NAME));
}
@Test
- void updateCategory_NullCategory_False() {
- }
+ @WithMockUser(roles = USER_ROLE)
+ @DisplayName("Get all categories")
+ void getAll_ValidRequest_ReturnsListOfCategories() throws Exception {
+ List categories = List.of(new CategoryDto());
- @Test
- void deleteCategory_ValidId_True() {
- }
+ when(categoryService.findAll(any())).thenReturn(categories);
- @Test
- void deleteCategory_InvalidId_False() {
+ mockMvc.perform(get(CATEGORIES_URL))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath(EXPRESSION).exists());
}
@Test
- void deleteCategory_NullId_False() {
+ @WithMockUser(roles = USER_ROLE)
+ @DisplayName("Delete category by id with invalid role")
+ void deleteCategory_WIthInValidRoleUser_Forbidden() throws Exception {
+ mockMvc.perform(delete(CATEGORIES_ID_URL, VALID_CATEGORY_ID).with(csrf()))
+ .andExpect(status().isForbidden());
}
@Test
- void getBooksByCategoryId_ValidId_True() {
- }
+ @WithMockUser(roles = {USER_ROLE, ADMIN_ROLE})
+ @DisplayName("Get all books by category id")
+ void getBooksByCategoryId_ValidRequest_ReturnsListOfBooks() throws Exception {
+ List books = List.of(new BookDtoWithoutCategoryIds());
- @Test
- void getBooksByCategoryId_InvalidId_False() {
- }
+ when(bookService.getAllBookByCategoryId(eq(VALID_CATEGORY_ID),
+ any(Pageable.class))).thenReturn(books);
- @Test
- void getBooksByCategoryId_NullId_False() {
+ mockMvc.perform(get(CATEGORIES_ID_BOOKS_URL, VALID_CATEGORY_ID))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath(EXPRESSION).exists());
}
}
diff --git a/src/test/java/mate/academy/bookstore/repository/book/BookRepositoryTest.java b/src/test/java/mate/academy/bookstore/repository/book/BookRepositoryTest.java
index 09dedf3..afbfca3 100644
--- a/src/test/java/mate/academy/bookstore/repository/book/BookRepositoryTest.java
+++ b/src/test/java/mate/academy/bookstore/repository/book/BookRepositoryTest.java
@@ -1,64 +1,167 @@
package mate.academy.bookstore.repository.book;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.Optional;
+import mate.academy.bookstore.model.Book;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.test.context.jdbc.Sql;
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@DataJpaTest
class BookRepositoryTest {
+ private static final String DELETE_DATA_FROM_DB = "classpath:database/delete-data-from-db.sql";
+ private static final String INSERT_DATA_INTO_DB = "classpath:database/insert-data-into-db.sql";
+ private static final Long VALID_BOOK_ID_KOBZAR = 1L;
+ private static final String VALID_BOOK_TITLE_KOBZAR = "Kobzar";
+ private static final String VALID_BOOK_ISBN_KOBZAR = "978-1-1516-4732-0";
+ private static final String INVALID_BOOK_ISBN = "978-11-1516-4732-02";
+ private static final long INVALID_ID = -1L;
+ private static final long VALID_CATEGORY_ID = 1L;
+ private static final long EMPTY_CATEGORY_ID = 4L;
+ private static final int PAGE_NUMBER = 0;
+ private static final int PAGE_SIZE = 5;
+ private static final String EMPTY_STRING = "";
+ private static final String VALID_BOOK_ISBN_NOT_IN_DB = "978-1-2345-6789-0";
@Autowired
private BookRepository bookRepository;
@Test
- void findAllBooks_ValidRequest_True() {
+ @DisplayName("Find all books (valid request)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findAllBooks_ValidRequest_ReturnsNonEmptyListOfBooks() {
+ List books = bookRepository.findAllBooks(PageRequest.of(PAGE_NUMBER, PAGE_SIZE));
+ assertNotNull(books);
+ assertFalse(books.isEmpty());
}
@Test
- void findAllBooks_InvalidRequest_False() {
+ @DisplayName("Find a book by isbn - (valid isbn)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findBookByIsbn_ValidIsbn_ReturnsBookWithMatchingIsbn() {
+ Optional bookOptional = bookRepository.findBookByIsbn(VALID_BOOK_ISBN_KOBZAR);
+ assertTrue(bookOptional.isPresent());
+ assertEquals(VALID_BOOK_ID_KOBZAR, bookOptional.get().getId());
+ assertEquals(VALID_BOOK_ISBN_KOBZAR, bookOptional.get().getIsbn());
}
@Test
- void findBookByIsbn_GivenValidIsbn_True() {
+ @DisplayName("Find a book by isbn - (invalid isbn)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findBookByIsbn_InvalidIsbn_ReturnsEmptyOptional() {
+ Optional bookOptional = bookRepository.findBookByIsbn(INVALID_BOOK_ISBN);
+ assertFalse(bookOptional.isPresent());
}
@Test
- void findBookByIsbn_GivenInvalidIsbn_False() {
+ @DisplayName("Find a book by isbn - (empty isbn)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findBookByIsbn_EmptyIsbn_ReturnsEmptyOptional() {
+ Optional bookOptional = bookRepository.findBookByIsbn(EMPTY_STRING);
+ assertFalse(bookOptional.isPresent());
}
@Test
- void findBookByIsbn_EmptyIsbn_False() {
+ @DisplayName("Find a book by isbn - (null isbn)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findBookByIsbn_NullIsbn_ReturnsEmptyOptional() {
+ Optional bookOptional = bookRepository.findBookByIsbn(null);
+ assertFalse(bookOptional.isPresent());
}
@Test
- void findBookByIsbn_NullIsbn_False() {
+ @DisplayName("Find book by id (existing book)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findBookById_ValidId_ReturnsBookWithMatchingId() {
+ Book book = bookRepository.findBookById(VALID_BOOK_ID_KOBZAR).orElse(null);
+ assertNotNull(book);
+ assertEquals(VALID_BOOK_TITLE_KOBZAR, book.getTitle());
}
@Test
- void findAllBooksByCategoryId_ValidCategoryId_True() {
+ @DisplayName("Find a book by isbn - (valid isbn, not in DB)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findBookByIsbn_ValidIsbnBookNotInRepository_ReturnsEmptyOptional() {
+ Optional bookOptional = bookRepository.findBookByIsbn(VALID_BOOK_ISBN_NOT_IN_DB);
+ assertFalse(bookOptional.isPresent());
}
@Test
- void findAllBooksByCategoryId_InvalidCategoryId_False() {
+ @DisplayName("Find book by id (non existing book)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findBookById_InvalidId_ReturnsEmptyOptional() {
+ Optional bookOptional = bookRepository.findBookById(INVALID_ID);
+ assertFalse(bookOptional.isPresent());
}
@Test
- void findAllBooksByCategoryId_EmptyCategoryId_False() {
+ @DisplayName("Find book by id (null id)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findBookById_NullId_ReturnsEmptyOptional() {
+ Optional bookOptional = bookRepository.findBookById(null);
+ assertFalse(bookOptional.isPresent());
}
@Test
- void findAllBooksByCategoryId_NullCategoryId_False() {
+ @DisplayName("Find all book by category id (valid id)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findAllBooksByCategoryId_ValidCategoryId_ReturnsNonEmptyListOfBooks() {
+ List books = bookRepository.findAllBooksByCategoryId(
+ VALID_CATEGORY_ID, PageRequest.of(PAGE_NUMBER, PAGE_SIZE));
+ assertNotNull(books);
+ assertFalse(books.isEmpty());
}
@Test
- void findAllBooks_NoBooksInRepository_False() {
+ @DisplayName("Find all book by category id (invalid id")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findAllBooksByCategoryId_InvalidCategoryId_ReturnsEmptyList() {
+ List books = bookRepository.findAllBooksByCategoryId(
+ INVALID_ID, PageRequest.of(PAGE_NUMBER, PAGE_SIZE));
+ assertNotNull(books);
+ assertTrue(books.isEmpty());
}
@Test
- void findAllBooksByCategoryId_CategoryHasNoBooks_False() {
+ @DisplayName("Find all book by category id (empty id")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findAllBooksByCategoryId_NullCategoryId_ReturnsEmptyList() {
+ List books = bookRepository.findAllBooksByCategoryId(
+ null, PageRequest.of(PAGE_NUMBER, PAGE_SIZE));
+ assertNotNull(books);
+ assertTrue(books.isEmpty());
}
@Test
- void findBookByIsbn_ValidIsbnBookNotInRepository_False() {
+ @DisplayName("Find all book by category id (no books in category")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findAllBooksByCategoryId_CategoryHasNoBooks_ReturnsEmptyList() {
+ List books = bookRepository.findAllBooksByCategoryId(
+ EMPTY_CATEGORY_ID, PageRequest.of(PAGE_NUMBER, PAGE_SIZE));
+ assertNotNull(books);
+ assertTrue(books.isEmpty());
}
}
diff --git a/src/test/java/mate/academy/bookstore/repository/category/CategoryRepositoryTest.java b/src/test/java/mate/academy/bookstore/repository/category/CategoryRepositoryTest.java
index 765762b..c53932a 100644
--- a/src/test/java/mate/academy/bookstore/repository/category/CategoryRepositoryTest.java
+++ b/src/test/java/mate/academy/bookstore/repository/category/CategoryRepositoryTest.java
@@ -1,5 +1,10 @@
package mate.academy.bookstore.repository.category;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import mate.academy.bookstore.model.Category;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -12,38 +17,68 @@
class CategoryRepositoryTest {
private static final String DELETE_DATA_FROM_DB = "classpath:database/delete-data-from-db.sql";
private static final String INSERT_DATA_INTO_DB = "classpath:database/insert-data-into-db.sql";
+ private static final String VALID_CATEGORY_NAME = "Poetry";
+ private static final String INVALID_CATEGORY_NAME = "Coco Jamboo";
+ private static final String VALID_CATEGORY_NAME_DIFFERENT_CASES = "pOetRy";
+ private static final String CATEGORY_NAME_SPECIAL_CHARACTERS = "Po*try";
+ private static final String EMPTY_STRING = "";
@Autowired
private CategoryRepository categoryRepository;
@Test
- @DisplayName("Find a book by name")
+ @DisplayName("Find a category by name - (valid name)")
@Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
- void findByName_ValidName_True() {
- }
-
- @Test
- void findByName_InvalidName_False() {
+ void findByName_ValidName_ReturnsCategory() {
+ Category category = categoryRepository.findByName(VALID_CATEGORY_NAME);
+ assertNotNull(category);
+ assertEquals(VALID_CATEGORY_NAME, category.getName());
}
@Test
- void findByName_EmptyName_False() {
+ @DisplayName("Find a category by name - (invalid name)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findByName_InvalidName_ReturnsNull() {
+ Category category = categoryRepository.findByName(INVALID_CATEGORY_NAME);
+ assertNull(category);
}
@Test
- void findByName_NullName_False() {
+ @DisplayName("Find a category by name - (empty name)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findByName_EmptyName_ReturnsNull() {
+ Category category = categoryRepository.findByName(EMPTY_STRING);
+ assertNull(category);
}
@Test
- void findByName_NameWithDifferentCase_False() {
+ @DisplayName("Find a category by name - (null name)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findByName_NullName_ReturnsNull() {
+ Category category = categoryRepository.findByName(null);
+ assertNull(category);
}
@Test
- void findByName_SpecialCharactersInName_False() {
+ @DisplayName("Find a category by name - (name with different case)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findByName_NameWithDifferentCase_ReturnsNull() {
+ Category expected = categoryRepository.findByName(VALID_CATEGORY_NAME);
+ Category actual = categoryRepository.findByName(VALID_CATEGORY_NAME_DIFFERENT_CASES);
+ assertEquals(expected, actual);
}
@Test
- void findByName_MultipleCategoriesWithSameName_True() {
+ @DisplayName("Find a category by name - (name with special characters)")
+ @Sql(scripts = {DELETE_DATA_FROM_DB, INSERT_DATA_INTO_DB},
+ executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
+ void findByName_SpecialCharactersInName_ReturnsNull() {
+ Category category = categoryRepository.findByName(CATEGORY_NAME_SPECIAL_CHARACTERS);
+ assertNull(category);
}
}
diff --git a/src/test/java/mate/academy/bookstore/service/BookServiceTest.java b/src/test/java/mate/academy/bookstore/service/BookServiceTest.java
deleted file mode 100644
index 495dfc9..0000000
--- a/src/test/java/mate/academy/bookstore/service/BookServiceTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package mate.academy.bookstore.service;
-
-import org.junit.jupiter.api.Test;
-
-class BookServiceTest {
-
- @Test
- void save_ValidBook_True() {
- }
-
- @Test
- void save_InvalidBook_False() {
- }
-
- @Test
- void save_NullBook_False() {
- }
-
- @Test
- void findAll_ValidRequest_True() {
- }
-
- @Test
- void findAll_InvalidRequest_False() {
- }
-
- @Test
- void findById_ValidId_True() {
- }
-
- @Test
- void findById_InvalidId_False() {
- }
-
- @Test
- void findById_NullId_False() {
- }
-
- @Test
- void search_ValidCriteria_True() {
- }
-
- @Test
- void search_InvalidCriteria_False() {
- }
-
- @Test
- void search_EmptyCriteria_False() {
- }
-
- @Test
- void search_NullCriteria_False() {
- }
-
- @Test
- void updateById_ValidBook_True() {
- }
-
- @Test
- void updateById_InvalidBook_False() {
- }
-
- @Test
- void updateById_NullBook_False() {
- }
-
- @Test
- void delete_ValidId_True() {
- }
-
- @Test
- void delete_InvalidId_False() {
- }
-
- @Test
- void delete_NullId_False() {
- }
-
- @Test
- void getAllBookByCategoryId_ValidCategoryId_True() {
- }
-
- @Test
- void getAllBookByCategoryId_InvalidCategoryId_False() {
- }
-
- @Test
- void getAllBookByCategoryId_NullCategoryId_False() {
- }
-}
diff --git a/src/test/java/mate/academy/bookstore/service/CategoryServiceTest.java b/src/test/java/mate/academy/bookstore/service/CategoryServiceTest.java
deleted file mode 100644
index a3250f4..0000000
--- a/src/test/java/mate/academy/bookstore/service/CategoryServiceTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package mate.academy.bookstore.service;
-
-import org.junit.jupiter.api.Test;
-
-class CategoryServiceTest {
-
- @Test
- void findAll_ValidRequest_True() {
- }
-
- @Test
- void findAll_InvalidRequest_False() {
- }
-
- @Test
- void getById_ValidId_True() {
- }
-
- @Test
- void getById_InvalidId_False() {
- }
-
- @Test
- void getById_NullId_False() {
- }
-
- @Test
- void save_ValidCategory_True() {
- }
-
- @Test
- void save_InvalidCategory_False() {
- }
-
- @Test
- void save_NullCategory_False() {
- }
-
- @Test
- void update_ValidCategory_True() {
- }
-
- @Test
- void update_InvalidCategory_False() {
- }
-
- @Test
- void update_NullCategory_False() {
- }
-
- @Test
- void delete_ValidId_True() {
- }
-
- @Test
- void delete_InvalidId_False() {
- }
-
- @Test
- void delete_NullId_False() {
- }
-}
diff --git a/src/test/java/mate/academy/bookstore/service/impl/BookServiceImplTest.java b/src/test/java/mate/academy/bookstore/service/impl/BookServiceImplTest.java
new file mode 100644
index 0000000..baf0e52
--- /dev/null
+++ b/src/test/java/mate/academy/bookstore/service/impl/BookServiceImplTest.java
@@ -0,0 +1,281 @@
+package mate.academy.bookstore.service.impl;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import mate.academy.bookstore.dto.book.BookDto;
+import mate.academy.bookstore.dto.book.BookDtoWithoutCategoryIds;
+import mate.academy.bookstore.dto.book.BookSearchParametersDto;
+import mate.academy.bookstore.dto.book.CreateBookRequestDto;
+import mate.academy.bookstore.exception.DuplicateIsbnException;
+import mate.academy.bookstore.exception.EntityNotFoundException;
+import mate.academy.bookstore.mapper.BookMapper;
+import mate.academy.bookstore.model.Book;
+import mate.academy.bookstore.repository.book.BookRepository;
+import mate.academy.bookstore.repository.book.BookSpecificationBuilder;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.domain.Specification;
+
+@ExtendWith(MockitoExtension.class)
+class BookServiceImplTest {
+ private static final String VALID_BOOK_TITLE = "Kobzar";
+ private static final String VALID_BOOK_AUTHOR = "Taras Shevchenko";
+ private static final String VALID_BOOK_ISBN = "978-0-7847-5628-7";
+ private static final BigDecimal VALID_BOOK_PRICE = BigDecimal.valueOf(14.99);
+ private static final Set VALID_CATEGORY_ID = Set.of(1L);
+ private static final String VALID_BOOK_DESCRIPTION = "Awesome description...";
+ private static final String VALID_BOOK_COVER_IMAGE = "kobzar.png";
+ private static final Long VALID_BOOK_ID = 1L;
+ private static final int EXPECTED_LIST_SIZE = 1;
+ private static final int PAGE_NUMBER = 0;
+ private static final int PAGE_SIZE = 5;
+ private static final int VALID_MIN_PRICE = 10;
+ private static final int VALID_MAX_PRICE = 20;
+ private static final int NEGATIVE_PRICE = -10;
+ private static final int ZERO_MAX_PRICE = 0;
+
+ @Mock
+ private BookRepository bookRepository;
+
+ @InjectMocks
+ private BookServiceImpl bookService;
+
+ @Mock
+ private BookMapper bookMapper;
+
+ @Mock
+ private BookSpecificationBuilder specificationBuilder;
+
+ @Test
+ @DisplayName("Save a valid book (return BookDto)")
+ void save_ValidBook_ReturnBookDto() {
+ CreateBookRequestDto createBookDto = getCreateBookRequestDto();
+ BookDto bookDto = getBookDto(createBookDto);
+ Book newBook = new Book();
+
+ when(bookMapper.toEntity(createBookDto)).thenReturn(newBook);
+ when(bookRepository.save(newBook)).thenReturn(newBook);
+ when(bookMapper.toDto(newBook)).thenReturn(bookDto);
+
+ BookDto savedBook = bookService.save(createBookDto);
+
+ assertEquals(VALID_BOOK_TITLE, savedBook.getTitle());
+ assertEquals(VALID_BOOK_AUTHOR, savedBook.getAuthor());
+ }
+
+ @Test
+ @DisplayName("Save a book with duplicate isbn (throws exception)")
+ void save_BookWithDuplicate_Isbn_ThrowException() {
+ CreateBookRequestDto createBookDto = getCreateBookRequestDto();
+ String expectedErrorMessage = "Book with ISBN " + VALID_BOOK_ISBN + " already exists";
+
+ when(bookRepository.findBookByIsbn(VALID_BOOK_ISBN)).thenReturn(Optional.of(new Book()));
+
+ DuplicateIsbnException exception =
+ assertThrows(DuplicateIsbnException.class, () -> bookService.save(createBookDto));
+ assertEquals(expectedErrorMessage, exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("Find all books (return list of BookDto)")
+ void findAll_ValidRequest_ReturnListOfBookDto() {
+ Pageable pageable = PageRequest.of(PAGE_NUMBER, PAGE_SIZE);
+ Book book = new Book();
+ book.setId(VALID_BOOK_ID);
+ BookDto bookDto = getBookDto(getCreateBookRequestDto());
+
+ when(bookRepository.findAllBooks(pageable)).thenReturn(List.of(book));
+ when(bookMapper.toDto(book)).thenReturn(bookDto);
+
+ List books = bookService.findAll(pageable);
+
+ assertEquals(EXPECTED_LIST_SIZE, books.size());
+ assertEquals(VALID_BOOK_TITLE, books.getFirst().getTitle());
+ }
+
+ @Test
+ @DisplayName("Find book by id (return BookDto)")
+ void findById_ValidRequest_ReturnBookDto() {
+ Book book = new Book();
+ book.setId(VALID_BOOK_ID);
+ BookDto bookDto = getBookDto(getCreateBookRequestDto());
+
+ when(bookRepository.findBookById(VALID_BOOK_ID)).thenReturn(Optional.of(book));
+ when(bookMapper.toDto(book)).thenReturn(bookDto);
+
+ BookDto foundBook = bookService.findById(VALID_BOOK_ID);
+
+ assertEquals(VALID_BOOK_TITLE, foundBook.getTitle());
+ }
+
+ @Test
+ @DisplayName("Find book by id (throws EntityNotFoundException)")
+ void findById_NonExistentBook_ThrowException() {
+ when(bookRepository.findBookById(VALID_BOOK_ID)).thenReturn(Optional.empty());
+ String expectedErrorMessage = "Book not found by id " + VALID_BOOK_ID;
+
+ EntityNotFoundException exception = assertThrows(EntityNotFoundException.class, () ->
+ bookService.findById(VALID_BOOK_ID));
+
+ assertEquals(expectedErrorMessage, exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("Update book by id (return updated BookDto)")
+ void updateById_ValidRequest_ReturnBookDto() {
+ Book existingBook = new Book();
+ existingBook.setId(VALID_BOOK_ID);
+ existingBook.setIsbn(VALID_BOOK_ISBN);
+
+ Book updatedBook = new Book();
+ updatedBook.setId(VALID_BOOK_ID);
+ CreateBookRequestDto updateBookDto = getCreateBookRequestDto();
+ BookDto updatedBookDto = getBookDto(updateBookDto);
+
+ when(bookRepository.findById(VALID_BOOK_ID)).thenReturn(Optional.of(existingBook));
+ when(bookMapper.toEntity(updateBookDto)).thenReturn(updatedBook);
+ when(bookRepository.save(updatedBook)).thenReturn(updatedBook);
+ when(bookMapper.toDto(updatedBook)).thenReturn(updatedBookDto);
+
+ BookDto result = bookService.updateById(VALID_BOOK_ID, updateBookDto);
+
+ assertEquals(VALID_BOOK_TITLE, result.getTitle());
+ assertEquals(VALID_BOOK_AUTHOR, result.getAuthor());
+ }
+
+ @Test
+ @DisplayName("Delete book by id")
+ void delete_ValidRequest_DeleteBook() {
+ bookService.delete(VALID_BOOK_ID);
+ verify(bookRepository, times(1)).deleteById(VALID_BOOK_ID);
+ }
+
+ @Test
+ @DisplayName("Search books with parameters (return list of BookDto)")
+ void search_ValidParameters_ReturnListOfBookDto() {
+ BookSearchParametersDto params = getSearchParams(VALID_MIN_PRICE, VALID_MAX_PRICE);
+ Book book = getMockBook();
+ Pageable pageable = PageRequest.of(PAGE_NUMBER, PAGE_SIZE);
+ BookDto bookDto = getBookDto(getCreateBookRequestDto());
+ Specification spec = (root, query, criteriaBuilder) -> criteriaBuilder.conjunction();
+
+ when(specificationBuilder.build(params)).thenReturn(spec);
+ when(bookRepository.findAll(spec, pageable)).thenReturn(new PageImpl<>(List.of(book)));
+ when(bookMapper.toDto(book)).thenReturn(bookDto);
+
+ List books = bookService.search(params, pageable);
+
+ assertEquals(EXPECTED_LIST_SIZE, books.size());
+ assertEquals(VALID_BOOK_TITLE, books.getFirst().getTitle());
+ }
+
+ @Test
+ @DisplayName("Search books with negative price (ignore negative min price)")
+ void search_NegativeMinPrice_IgnoreNegativeMinPrice() {
+ BookSearchParametersDto params = getSearchParams(NEGATIVE_PRICE, VALID_MAX_PRICE);
+ Book book = getMockBook();
+ Pageable pageable = PageRequest.of(PAGE_NUMBER, PAGE_SIZE);
+ BookDto bookDto = getBookDto(getCreateBookRequestDto());
+ Specification spec = (root, query, criteriaBuilder) -> criteriaBuilder.conjunction();
+
+ when(specificationBuilder.build(params)).thenReturn(spec);
+ when(bookRepository.findAll(spec, pageable)).thenReturn(new PageImpl<>(List.of(book)));
+ when(bookMapper.toDto(book)).thenReturn(bookDto);
+
+ List books = assertDoesNotThrow(() -> bookService.search(params, pageable));
+ assertEquals(EXPECTED_LIST_SIZE, books.size());
+ }
+
+ @Test
+ @DisplayName("Search books with negative price (ignore smaller max price)")
+ void search_ZeroMaxPrice_IgnoreSmallerMaxPrice() {
+ BookSearchParametersDto params = getSearchParams(NEGATIVE_PRICE, ZERO_MAX_PRICE);
+ Book book = getMockBook();
+ Pageable pageable = PageRequest.of(PAGE_NUMBER, PAGE_SIZE);
+ BookDto bookDto = getBookDto(getCreateBookRequestDto());
+ Specification spec = (root, query, criteriaBuilder) -> criteriaBuilder.conjunction();
+
+ when(specificationBuilder.build(params)).thenReturn(spec);
+ when(bookRepository.findAll(spec, pageable)).thenReturn(new PageImpl<>(List.of(book)));
+ when(bookMapper.toDto(book)).thenReturn(bookDto);
+
+ List books = assertDoesNotThrow(() -> bookService.search(params, pageable));
+ assertEquals(EXPECTED_LIST_SIZE, books.size());
+ }
+
+ @Test
+ @DisplayName("Get all books by category id (return list of BookDtoWithoutCategoryIds)")
+ void getAllBookByCategoryId() {
+ Pageable pageable = PageRequest.of(PAGE_NUMBER, PAGE_SIZE);
+ Book book = new Book();
+ BookDtoWithoutCategoryIds bookDtoWithoutCategoryIds = new BookDtoWithoutCategoryIds();
+ bookDtoWithoutCategoryIds.setTitle(VALID_BOOK_TITLE);
+ bookDtoWithoutCategoryIds.setAuthor(VALID_BOOK_AUTHOR);
+
+ when(bookRepository.findAllBooksByCategoryId(VALID_BOOK_ID, pageable))
+ .thenReturn(List.of(book));
+ when(bookMapper.toDtoWithoutCategories(book)).thenReturn(bookDtoWithoutCategoryIds);
+
+ List books =
+ bookService.getAllBookByCategoryId(VALID_BOOK_ID, pageable);
+
+ assertEquals(EXPECTED_LIST_SIZE, books.size());
+ assertEquals(VALID_BOOK_TITLE, books.getFirst().getTitle());
+ }
+
+ private CreateBookRequestDto getCreateBookRequestDto() {
+ CreateBookRequestDto createBookDto = new CreateBookRequestDto();
+ createBookDto.setTitle(VALID_BOOK_TITLE);
+ createBookDto.setAuthor(VALID_BOOK_AUTHOR);
+ createBookDto.setIsbn(VALID_BOOK_ISBN);
+ createBookDto.setPrice(VALID_BOOK_PRICE);
+ createBookDto.setCategoryIds(VALID_CATEGORY_ID);
+ createBookDto.setDescription(VALID_BOOK_DESCRIPTION);
+ createBookDto.setCoverImage(VALID_BOOK_COVER_IMAGE);
+ return createBookDto;
+ }
+
+ private BookDto getBookDto(CreateBookRequestDto dto) {
+ BookDto bookDto = new BookDto();
+ bookDto.setTitle(dto.getTitle());
+ bookDto.setAuthor(dto.getAuthor());
+ bookDto.setIsbn(dto.getIsbn());
+ bookDto.setPrice(dto.getPrice());
+ bookDto.setDescription(dto.getDescription());
+ bookDto.setCoverImage(dto.getCoverImage());
+ return bookDto;
+ }
+
+ private Book getMockBook() {
+ Book book = new Book();
+ book.setTitle(VALID_BOOK_TITLE);
+ book.setAuthor(VALID_BOOK_AUTHOR);
+ return book;
+ }
+
+ private static BookSearchParametersDto getSearchParams(int minPrice, int maxPrice) {
+ return new BookSearchParametersDto(
+ new String[]{VALID_BOOK_AUTHOR},
+ VALID_BOOK_TITLE,
+ VALID_BOOK_ISBN,
+ minPrice,
+ maxPrice
+ );
+ }
+}
diff --git a/src/test/java/mate/academy/bookstore/service/impl/CategoryServiceImplTest.java b/src/test/java/mate/academy/bookstore/service/impl/CategoryServiceImplTest.java
new file mode 100644
index 0000000..677c8d9
--- /dev/null
+++ b/src/test/java/mate/academy/bookstore/service/impl/CategoryServiceImplTest.java
@@ -0,0 +1,162 @@
+package mate.academy.bookstore.service.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.Optional;
+import mate.academy.bookstore.dto.category.CategoryDto;
+import mate.academy.bookstore.exception.DuplicateEntityException;
+import mate.academy.bookstore.exception.EntityNotFoundException;
+import mate.academy.bookstore.mapper.CategoryMapper;
+import mate.academy.bookstore.model.Category;
+import mate.academy.bookstore.repository.category.CategoryRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
+
+class CategoryServiceImplTest {
+ private static final Long VALID_CATEGORY_ID = 1L;
+ private static final String VALID_CATEGORY_NAME = "Fiction";
+ private static final int PAGE_SIZE = 5;
+ private static final String NEW_VALID_CATEGORY_NAME = "New Fiction";
+ private static final int EXPECTED_LIST_SIZE = 1;
+ private static final int NUMBER_OF_INVOCATIONS = 1;
+
+ @Mock
+ private CategoryRepository categoryRepository;
+
+ @Mock
+ private CategoryMapper categoryMapper;
+
+ @InjectMocks
+ private CategoryServiceImpl categoryService;
+
+ @BeforeEach
+ void setup() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ @DisplayName("Save a valid category (return CategoryDto)")
+ void save_ValidCategory_ReturnCategoryDto() {
+ CategoryDto categoryDto = getCategoryDto();
+ Category category = getCategory();
+
+ when(categoryMapper.toEntity(categoryDto)).thenReturn(category);
+ when(categoryRepository.findByName(VALID_CATEGORY_NAME)).thenReturn(null);
+ when(categoryRepository.save(category)).thenReturn(category);
+ when(categoryMapper.toDto(category)).thenReturn(categoryDto);
+
+ CategoryDto savedCategory = categoryService.save(categoryDto);
+
+ assertEquals(VALID_CATEGORY_NAME, savedCategory.getName());
+ }
+
+ @Test
+ @DisplayName("Save a category with duplicate name (throws exception)")
+ void save_CategoryWithDuplicateName_ThrowException() {
+ CategoryDto categoryDto = getCategoryDto();
+ String expectedErrorMessage =
+ "Category with name " + VALID_CATEGORY_NAME + " already exists";
+
+ when(categoryRepository.findByName(VALID_CATEGORY_NAME)).thenReturn(new Category());
+
+ DuplicateEntityException exception = assertThrows(DuplicateEntityException.class, () ->
+ categoryService.save(categoryDto));
+ assertEquals(expectedErrorMessage, exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("Find all categories (return list of CategoryDto)")
+ void findAll_ValidRequest_ReturnListOfCategoryDto() {
+ Pageable pageable = Pageable.ofSize(PAGE_SIZE);
+ Category category = getCategory();
+ CategoryDto categoryDto = getCategoryDto();
+
+ when(categoryRepository.findAll(pageable)).thenReturn(new PageImpl<>(List.of(category)));
+ when(categoryMapper.toDto(category)).thenReturn(categoryDto);
+
+ List categories = categoryService.findAll(pageable);
+
+ assertEquals(EXPECTED_LIST_SIZE, categories.size());
+ assertEquals(VALID_CATEGORY_NAME, categories.getFirst().getName());
+ }
+
+ @Test
+ @DisplayName("Find category by id (return CategoryDto)")
+ void getById_ValidRequest_ReturnCategoryDto() {
+ Category category = getCategory();
+ CategoryDto categoryDto = getCategoryDto();
+
+ when(categoryRepository.findById(VALID_CATEGORY_ID)).thenReturn(Optional.of(category));
+ when(categoryMapper.toDto(category)).thenReturn(categoryDto);
+
+ CategoryDto foundCategory = categoryService.getById(VALID_CATEGORY_ID);
+
+ assertEquals(VALID_CATEGORY_NAME, foundCategory.getName());
+ }
+
+ @Test
+ @DisplayName("Find category by id (throws exception)")
+ void getById_NonExistentCategory_ThrowException() {
+ when(categoryRepository.findById(VALID_CATEGORY_ID)).thenReturn(Optional.empty());
+ String expectedErrorMessage = "Category not found by id " + VALID_CATEGORY_ID;
+
+ EntityNotFoundException exception = assertThrows(EntityNotFoundException.class, () ->
+ categoryService.getById(VALID_CATEGORY_ID));
+
+ assertEquals(expectedErrorMessage, exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("Update category (return updated CategoryDto)")
+ void update_ValidRequest_ReturnCategoryDto() {
+ Category category = getCategory();
+ CategoryDto updateCategoryDto = new CategoryDto();
+ updateCategoryDto.setName(NEW_VALID_CATEGORY_NAME);
+
+ when(categoryRepository.findById(VALID_CATEGORY_ID)).thenReturn(Optional.of(category));
+ when(categoryRepository.findByName(updateCategoryDto.getName())).thenReturn(null);
+ when(categoryMapper.toEntity(updateCategoryDto)).thenReturn(category);
+ when(categoryRepository.save(category)).thenReturn(category);
+ when(categoryMapper.toDto(category)).thenReturn(updateCategoryDto);
+
+ CategoryDto result = categoryService.update(VALID_CATEGORY_ID, updateCategoryDto);
+ assertEquals(updateCategoryDto.getName(), result.getName());
+ }
+
+ @Test
+ @DisplayName("Delete category by id")
+ void delete_ValidRequest_DeleteCategory() {
+ Category category = getCategory();
+ category.setId(VALID_CATEGORY_ID);
+
+ when(categoryRepository.findById(VALID_CATEGORY_ID)).thenReturn(Optional.of(category));
+
+ categoryService.delete(VALID_CATEGORY_ID);
+ verify(categoryRepository, times(NUMBER_OF_INVOCATIONS)).deleteById(VALID_CATEGORY_ID);
+ }
+
+ private static Category getCategory() {
+ Category category = new Category();
+ category.setId(VALID_CATEGORY_ID);
+ category.setName(VALID_CATEGORY_NAME);
+ return category;
+ }
+
+ private static CategoryDto getCategoryDto() {
+ CategoryDto categoryDto = new CategoryDto();
+ categoryDto.setId(VALID_CATEGORY_ID);
+ categoryDto.setName(VALID_CATEGORY_NAME);
+ return categoryDto;
+ }
+}
diff --git a/src/test/resources/database/insert-data-into-db.sql b/src/test/resources/database/insert-data-into-db.sql
index a42708e..3551351 100644
--- a/src/test/resources/database/insert-data-into-db.sql
+++ b/src/test/resources/database/insert-data-into-db.sql
@@ -1,20 +1,18 @@
--- Inserting data into the books table
-INSERT INTO books (title, author, isbn, price, description, cover_image)
+INSERT INTO books (id, title, author, isbn, price, description)
VALUES
- ('Kobzar', 'Taras Shevchenko', '978-1-1516-4732-0', 34.99, 'Kobzar, Taras Shevchenko.', 'kobzar.jpg'),
- ('Earth', 'Olha Kobylianska', '978-7-3664-5711-2', 14.99, 'Earth, Olha Kobylianska', 'earth.jpg'),
- ('Tiger hunters', 'Ivan Bahriany', '978-8-9176-0894-6', 16.99, 'Tiger hunters, Ivan Bahriany', 'tiger-hunters.jpg'),
- ('Marusja Churai', 'Lesya Ukrainka', '978-0-8386-9622-4', 9.99, 'Marusja Churai, Lesya Ukrainka', 'marusja-churai.jpg'),
- ('The Tale of Igor\'s Campaign', 'Anonymous', '978-6-3292-3392-3', 25.99, 'The Tale of Igor\'s Campaign, Anonymous', 'igor-campaign.jpg');
+ (1, 'Kobzar', 'Taras Shevchenko', '978-1-1516-4732-0', 34.99, 'Awesome description...'),
+ (2, 'Earth', 'Olha Kobylianska', '978-7-3664-5711-2', 14.99, 'Awesome description...'),
+ (3, 'Tiger hunters', 'Ivan Bahriany', '978-8-9176-0894-6', 16.99, 'Awesome description...'),
+ (4, 'Marusja Churai', 'Lesya Ukrainka', '978-0-8386-9622-4', 9.99, 'Awesome description...'),
+ (5, 'Haidamaky', 'Taras Shevchenko', '978-6-3292-3392-3', 25.99, 'Awesome description...');
--- Inserting data into the categories table
-INSERT INTO categories (name, description)
+INSERT INTO categories (id, name, description)
VALUES
- ('Poetry', 'Literary genre that uses language for its poetic...'),
- ('Novel', 'Prose literary genre that describes various situations...'),
- ('Epic', 'Literary genre that describes great events, historical episodes...');
+ (1, 'Poetry', 'Awesome description...'),
+ (2, 'Novel', 'Awesome description...'),
+ (3, 'Epic', 'Awesome description...'),
+ (4, 'Drama', 'Awesome description...');
--- Inserting data into the books_categories table
INSERT INTO books_categories (book_id, category_id)
VALUES
(1, 1),