diff --git a/src/main/java/com/origin/bookstore/dto/order/OrderResponseDto.java b/src/main/java/com/origin/bookstore/dto/order/OrderResponseDto.java index 8af5eb4..8898c63 100644 --- a/src/main/java/com/origin/bookstore/dto/order/OrderResponseDto.java +++ b/src/main/java/com/origin/bookstore/dto/order/OrderResponseDto.java @@ -4,11 +4,13 @@ import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.Set; +import lombok.Builder; import lombok.Getter; import lombok.Setter; @Getter @Setter +@Builder public class OrderResponseDto { private Long id; private Long userId; diff --git a/src/main/java/com/origin/bookstore/dto/user/UserRegistrationRequestDto.java b/src/main/java/com/origin/bookstore/dto/user/UserRegistrationRequestDto.java index 6b21e7f..695f5eb 100644 --- a/src/main/java/com/origin/bookstore/dto/user/UserRegistrationRequestDto.java +++ b/src/main/java/com/origin/bookstore/dto/user/UserRegistrationRequestDto.java @@ -3,11 +3,13 @@ import com.origin.bookstore.validation.FieldMatch; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; +import lombok.Builder; import lombok.Getter; import lombok.Setter; @Getter @Setter +@Builder @FieldMatch( field = "password", fieldToMatch = "repeatPassword" diff --git a/src/main/java/com/origin/bookstore/dto/user/UserResponseDto.java b/src/main/java/com/origin/bookstore/dto/user/UserResponseDto.java index aa7a8e9..5658c21 100644 --- a/src/main/java/com/origin/bookstore/dto/user/UserResponseDto.java +++ b/src/main/java/com/origin/bookstore/dto/user/UserResponseDto.java @@ -1,10 +1,12 @@ package com.origin.bookstore.dto.user; +import lombok.Builder; import lombok.Getter; import lombok.Setter; @Getter @Setter +@Builder public class UserResponseDto { private Long id; private String email; diff --git a/src/main/java/com/origin/bookstore/model/CartItem.java b/src/main/java/com/origin/bookstore/model/CartItem.java index 4aa7e78..8c5bc98 100644 --- a/src/main/java/com/origin/bookstore/model/CartItem.java +++ b/src/main/java/com/origin/bookstore/model/CartItem.java @@ -9,13 +9,19 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; @Entity @Table(name = "cart_items") +@Builder @Getter @Setter +@NoArgsConstructor +@AllArgsConstructor public class CartItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/origin/bookstore/model/Order.java b/src/main/java/com/origin/bookstore/model/Order.java index 25789ad..7f69c64 100644 --- a/src/main/java/com/origin/bookstore/model/Order.java +++ b/src/main/java/com/origin/bookstore/model/Order.java @@ -17,8 +17,11 @@ import java.time.LocalDateTime; import java.util.HashSet; import java.util.Set; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import org.hibernate.annotations.SQLDelete; @@ -29,7 +32,10 @@ @SQLRestriction("is_deleted=false") @Getter @Setter +@Builder @Table(name = "orders") +@NoArgsConstructor +@AllArgsConstructor public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/origin/bookstore/model/OrderItem.java b/src/main/java/com/origin/bookstore/model/OrderItem.java index 64f65c9..1b725cc 100644 --- a/src/main/java/com/origin/bookstore/model/OrderItem.java +++ b/src/main/java/com/origin/bookstore/model/OrderItem.java @@ -10,7 +10,10 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import java.math.BigDecimal; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.SQLRestriction; @@ -20,7 +23,10 @@ @SQLRestriction("is_deleted=false") @Getter @Setter +@Builder @Table(name = "order_items") +@NoArgsConstructor +@AllArgsConstructor public class OrderItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/origin/bookstore/model/ShoppingCart.java b/src/main/java/com/origin/bookstore/model/ShoppingCart.java index ebbe908..39f6997 100644 --- a/src/main/java/com/origin/bookstore/model/ShoppingCart.java +++ b/src/main/java/com/origin/bookstore/model/ShoppingCart.java @@ -12,8 +12,11 @@ import jakarta.persistence.Table; import java.util.HashSet; import java.util.Set; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import org.hibernate.annotations.SQLDelete; @@ -23,8 +26,11 @@ @SQLDelete(sql = "UPDATE shopping_carts SET is_deleted = true WHERE id = ?") @SQLRestriction("is_deleted=false") @Getter +@Builder @Setter @Table(name = "shopping_carts") +@NoArgsConstructor +@AllArgsConstructor public class ShoppingCart { @Id private Long id; diff --git a/src/main/java/com/origin/bookstore/model/User.java b/src/main/java/com/origin/bookstore/model/User.java index db38bfe..93e312f 100644 --- a/src/main/java/com/origin/bookstore/model/User.java +++ b/src/main/java/com/origin/bookstore/model/User.java @@ -13,7 +13,10 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.SQLRestriction; @@ -23,9 +26,12 @@ @Entity @Getter @Setter +@Builder @SQLDelete(sql = "UPDATE users SET is_deleted = true WHERE id = ?") @SQLRestriction("is_deleted=false") @Table(name = "users") +@NoArgsConstructor +@AllArgsConstructor public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/origin/bookstore/repository/order/OrderRepository.java b/src/main/java/com/origin/bookstore/repository/order/OrderRepository.java index 91a6840..11a6288 100644 --- a/src/main/java/com/origin/bookstore/repository/order/OrderRepository.java +++ b/src/main/java/com/origin/bookstore/repository/order/OrderRepository.java @@ -2,6 +2,7 @@ import com.origin.bookstore.model.Order; import com.origin.bookstore.model.User; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; @@ -10,4 +11,7 @@ public interface OrderRepository extends JpaRepository { @EntityGraph(attributePaths = "orderItems") Page getOrdersByUser(User user, Pageable pageable); + + @EntityGraph(attributePaths = "orderItems") + Optional findByIdAndUser(Long id, User user); } diff --git a/src/main/java/com/origin/bookstore/service/impl/OrderServiceImpl.java b/src/main/java/com/origin/bookstore/service/impl/OrderServiceImpl.java index dfec5d8..0a5d471 100644 --- a/src/main/java/com/origin/bookstore/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/origin/bookstore/service/impl/OrderServiceImpl.java @@ -19,6 +19,7 @@ import jakarta.transaction.Transactional; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.HashSet; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -50,6 +51,7 @@ public OrderResponseDto save(User user, OrderRequestDto orderRequestDto) { order.setStatus(Order.Status.PENDING); order.setUser(user); order.setTotal(BigDecimal.ZERO); + order.setOrderItems(new HashSet<>()); order.getOrderItems() .addAll(shoppingCart .getCartItems().stream() @@ -77,6 +79,11 @@ public Page getAllOrders(User user, Pageable pageable) { @Override public List getAllOrderItems(User user, Long orderId) { + orderRepository.findByIdAndUser(orderId, user) + .orElseThrow(() -> new EntityNotFoundException( + "Can't find order by id: " + orderId + " for user: " + user.getId() + )); + return orderItemRepository .findAllByOrderIdAndOrderUser(orderId, user).stream() .map(orderItemMapper::toDto) diff --git a/src/main/resources/db/changelog/changes/09-create-orders-table.yaml b/src/main/resources/db/changelog/changes/09-create-orders-table.yaml index 62b5d30..049a774 100644 --- a/src/main/resources/db/changelog/changes/09-create-orders-table.yaml +++ b/src/main/resources/db/changelog/changes/09-create-orders-table.yaml @@ -13,6 +13,11 @@ databaseChangeLog: constraints: nullable: false primaryKey: true + - column: + name: user_id + type: bigint + constraints: + nullable: false - column: name: status type: varchar(255) diff --git a/src/test/java/com/origin/bookstore/controller/AuthenticationControllerTest.java b/src/test/java/com/origin/bookstore/controller/AuthenticationControllerTest.java new file mode 100644 index 0000000..3b5b7a5 --- /dev/null +++ b/src/test/java/com/origin/bookstore/controller/AuthenticationControllerTest.java @@ -0,0 +1,97 @@ +package com.origin.bookstore.controller; + +import com.origin.bookstore.dto.user.UserLoginRequestDto; +import com.origin.bookstore.dto.user.UserRegistrationRequestDto; +import com.origin.bookstore.util.TestUtil; +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.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.web.servlet.MockMvc; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.http.MediaType; +import static org.hamcrest.Matchers.notNullValue; +import static com.origin.bookstore.util.TestConstants.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@SpringBootTest +@AutoConfigureMockMvc +class AuthenticationControllerTest { + private static final String EMAIL_JSON_PATH = + "$.email"; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + @DisplayName("Register a new user successfully") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void register_ValidRequest_ReturnsUserResponse() throws Exception { + UserRegistrationRequestDto request = TestUtil.createUserRegistrationRequestDto(); + + mockMvc.perform(post(REGISTRATION_PATH) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath(EMAIL_JSON_PATH).value(request.getEmail()) + ); + } + + @Test + @DisplayName("Should login user successfully and return token") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_USER_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void login_ValidRequest_ReturnsToken() throws Exception { + UserLoginRequestDto request = new UserLoginRequestDto("rudycooper@gmail.com", "example"); + + mockMvc.perform(post(LOGIN_PATH) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.token").value(notNullValue()) + ); + } + + @Test + @DisplayName("Register with invalid data should return bad request") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void register_InvalidRequest_ReturnsBadRequest() throws Exception { + UserRegistrationRequestDto invalidRequest = TestUtil.createUserRegistrationRequestDto(); + invalidRequest.setEmail("not an email"); + invalidRequest.setPassword(""); + + mockMvc.perform(post(REGISTRATION_PATH) + .content(objectMapper.writeValueAsString(invalidRequest)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + } + + @Test + @DisplayName("Register with existing email should return conflict") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_USER_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void register_DuplicateEmail_ReturnsConflict() throws Exception { + UserRegistrationRequestDto request = TestUtil.createUserRegistrationRequestDto(); + request.setEmail("rudycooper@gmail.com"); + + mockMvc.perform(post(REGISTRATION_PATH) + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isConflict()); + } +} \ No newline at end of file diff --git a/src/test/java/com/origin/bookstore/controller/BookControllerTest.java b/src/test/java/com/origin/bookstore/controller/BookControllerTest.java index ebb2b43..adfd25c 100644 --- a/src/test/java/com/origin/bookstore/controller/BookControllerTest.java +++ b/src/test/java/com/origin/bookstore/controller/BookControllerTest.java @@ -13,6 +13,8 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.web.servlet.MockMvc; import java.util.Set; + +import static com.origin.bookstore.util.TestConstants.*; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; @@ -22,26 +24,10 @@ @SpringBootTest @AutoConfigureMockMvc public class BookControllerTest { - private static final String API_PATH = - "/books"; - private static final String API_PATH_ID = - "/books/{id}"; - private static final String ADD_BOOKS_PATH = - "/database/books/add-books-with-categories.sql"; - private static final String REMOVE_BOOKS_PATH = - "/database/books/remove-books-with-categories.sql"; - private static final String API_SEARCH_PATH = - "/books/search"; - private static final String ID_JSON_PATH = - "$.id"; private static final String TITLE_JSON_PATH = "$.title"; private static final String AUTHOR_JSON_PATH = "$.author"; - private static final String CONTENT_JSON_PATH = - "$.content"; - private static final String ADMIN_ROLE = "ADMIN"; - private static final String USER_ROLE = "USER"; private static final Integer BOOK_ID = 7; private static final Integer INVALID_BOOK_ID = 999; private static final String BOOK_AUTHOR = "Sam Sapiol"; @@ -56,16 +42,16 @@ public class BookControllerTest { @Test @DisplayName("Should successfully create book") @WithMockUser(roles = ADMIN_ROLE) - @Sql(scripts = ADD_BOOKS_PATH, + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOKS_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void createBook_Request_ReturnsBookDto() throws Exception { CreateBookRequestDto requestDto = TestUtil.createBookRequestDto(); requestDto.setCategoryIds(Set.of(4L)); String json = objectMapper.writeValueAsString(requestDto); - mockMvc.perform(post(API_PATH) + mockMvc.perform(post(API_BOOKS_PATH) .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isOk()) @@ -76,12 +62,14 @@ void createBook_Request_ReturnsBookDto() throws Exception { @Test @DisplayName("Should return bad request if book doesn't have a title") @WithMockUser(roles = ADMIN_ROLE) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) void createBookWithoutTitle_Request_ReturnsBadRequest() throws Exception { CreateBookRequestDto requestDto = TestUtil.createBookRequestDto(); requestDto.setTitle(null); String json = objectMapper.writeValueAsString(requestDto); - mockMvc.perform(post(API_PATH) + mockMvc.perform(post(API_BOOKS_PATH) .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isBadRequest() @@ -91,12 +79,14 @@ void createBookWithoutTitle_Request_ReturnsBadRequest() throws Exception { @Test @DisplayName("Should return forbidden if user tries to create a book") @WithMockUser(roles = USER_ROLE) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) void createBookByUser_Request_ReturnsForbidden() throws Exception { CreateBookRequestDto requestDto = TestUtil.createBookRequestDto(); requestDto.setCategoryIds(Set.of(5L)); String json = objectMapper.writeValueAsString(requestDto); - mockMvc.perform(post(API_PATH) + mockMvc.perform(post(API_BOOKS_PATH) .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isForbidden() @@ -106,31 +96,31 @@ void createBookByUser_Request_ReturnsForbidden() throws Exception { @Test @DisplayName("Should successfully get all books") @WithMockUser(roles = USER_ROLE) - @Sql(scripts = ADD_BOOKS_PATH, + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOKS_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void findAllBooks_Request_ReturnsBookDtos() throws Exception { - mockMvc.perform(get(API_PATH)) + mockMvc.perform(get(API_BOOKS_PATH)) .andExpect(status().isOk()) - .andExpect(jsonPath(CONTENT_JSON_PATH).isArray()) - .andExpect(jsonPath(CONTENT_JSON_PATH + ".length()").value(1)); + .andExpect(jsonPath($_CONTENT).isArray()) + .andExpect(jsonPath($_CONTENT + ".length()").value(1)); } @Test @DisplayName("Should successfully get a book by id") @WithMockUser(roles = USER_ROLE) - @Sql(scripts = ADD_BOOKS_PATH, + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOKS_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void findBookById_Request_ReturnsBookDto() throws Exception { - mockMvc.perform(get(API_PATH_ID, BOOK_ID) + mockMvc.perform(get(API_BOOKS_PATH_ID, BOOK_ID) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath(ID_JSON_PATH, is(BOOK_ID))) + .andExpect(jsonPath($_ID, is(BOOK_ID))) .andExpect(jsonPath(TITLE_JSON_PATH, is(BOOK_TITLE))) .andExpect(jsonPath(AUTHOR_JSON_PATH, is(BOOK_AUTHOR))); } @@ -138,12 +128,12 @@ void findBookById_Request_ReturnsBookDto() throws Exception { @Test @DisplayName("Should return 404 if book not found by id") @WithMockUser(roles = USER_ROLE) - @Sql(scripts = ADD_BOOKS_PATH, + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOKS_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void findBookByInvalidId_Request_ReturnsNotFound() throws Exception { - mockMvc.perform(get(API_PATH_ID, INVALID_BOOK_ID) + mockMvc.perform(get(API_BOOKS_PATH_ID, INVALID_BOOK_ID) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound() @@ -153,12 +143,12 @@ void findBookByInvalidId_Request_ReturnsNotFound() throws Exception { @Test @DisplayName("Should successfully delete a book by id") @WithMockUser(roles = ADMIN_ROLE) - @Sql(scripts = ADD_BOOKS_PATH, + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOKS_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void deleteBook_Request_ReturnsNoContent() throws Exception { - mockMvc.perform(delete(API_PATH_ID, BOOK_ID) + mockMvc.perform(delete(API_BOOKS_PATH_ID, BOOK_ID) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()); @@ -167,12 +157,12 @@ void deleteBook_Request_ReturnsNoContent() throws Exception { @Test @DisplayName("Should return forbidden when user is deleting a book") @WithMockUser(roles = USER_ROLE) - @Sql(scripts = ADD_BOOKS_PATH, + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOKS_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void deleteBookByUser_Request_ReturnsForbidden() throws Exception { - mockMvc.perform(delete(API_PATH_ID, BOOK_ID) + mockMvc.perform(delete(API_BOOKS_PATH_ID, BOOK_ID) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isForbidden()); @@ -181,19 +171,19 @@ void deleteBookByUser_Request_ReturnsForbidden() throws Exception { @Test @DisplayName("Should successfully update a book by id") @WithMockUser(roles = ADMIN_ROLE) - @Sql(scripts = ADD_BOOKS_PATH, + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOKS_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void updateBook_Request_ReturnsBookDto() throws Exception { CreateBookRequestDto requestDto = TestUtil.createBookRequestDto(); String json = objectMapper.writeValueAsString(requestDto); - mockMvc.perform(put(API_PATH_ID, BOOK_ID) + mockMvc.perform(put(API_BOOKS_PATH_ID, BOOK_ID) .content(json) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath(ID_JSON_PATH, is(BOOK_ID))) + .andExpect(jsonPath($_ID, is(BOOK_ID))) .andExpect(jsonPath(TITLE_JSON_PATH, is(requestDto.getTitle()))) .andExpect(jsonPath(AUTHOR_JSON_PATH, is(requestDto.getAuthor())) ); @@ -202,15 +192,15 @@ void updateBook_Request_ReturnsBookDto() throws Exception { @Test @DisplayName("Should return 404 when updating invalid book") @WithMockUser(roles = ADMIN_ROLE) - @Sql(scripts = ADD_BOOKS_PATH, + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOKS_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void updateInvalidBook_Request_ReturnsNotFound() throws Exception { CreateBookRequestDto requestDto = TestUtil.createBookRequestDto(); String json = objectMapper.writeValueAsString(requestDto); - mockMvc.perform(put(API_PATH_ID, INVALID_BOOK_ID) + mockMvc.perform(put(API_BOOKS_PATH_ID, INVALID_BOOK_ID) .content(json) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound() @@ -220,15 +210,15 @@ void updateInvalidBook_Request_ReturnsNotFound() throws Exception { @Test @DisplayName("Should return forbidden when user updating a book") @WithMockUser(roles = USER_ROLE) - @Sql(scripts = ADD_BOOKS_PATH, + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOKS_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void updateBookByUser_Request_ReturnsForbidden() throws Exception { CreateBookRequestDto requestDto = TestUtil.createBookRequestDto(); String json = objectMapper.writeValueAsString(requestDto); - mockMvc.perform(put(API_PATH_ID, BOOK_ID) + mockMvc.perform(put(API_BOOKS_PATH_ID, BOOK_ID) .content(json) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isForbidden() @@ -238,19 +228,19 @@ void updateBookByUser_Request_ReturnsForbidden() throws Exception { @Test @DisplayName("Should return books matching search parameters") @WithMockUser(roles = USER_ROLE) - @Sql(scripts = ADD_BOOKS_PATH, + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOKS_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void searchBooks_ValidParameters_ReturnsMatchingBooks() throws Exception { - mockMvc.perform(get(API_SEARCH_PATH) + mockMvc.perform(get(API_BOOKS_SEARCH_PATH) .param("authors", BOOK_AUTHOR) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath(CONTENT_JSON_PATH, hasSize(1))) - .andExpect(jsonPath(CONTENT_JSON_PATH + "[0].title", is(BOOK_TITLE))) - .andExpect(jsonPath( CONTENT_JSON_PATH + "[0].author", is(BOOK_AUTHOR)) + .andExpect(jsonPath($_CONTENT, hasSize(1))) + .andExpect(jsonPath($_CONTENT + "[0].title", is(BOOK_TITLE))) + .andExpect(jsonPath( $_CONTENT + "[0].author", is(BOOK_AUTHOR)) ); } } diff --git a/src/test/java/com/origin/bookstore/controller/CategoryControllerTest.java b/src/test/java/com/origin/bookstore/controller/CategoryControllerTest.java index f65cf70..6bb55c0 100644 --- a/src/test/java/com/origin/bookstore/controller/CategoryControllerTest.java +++ b/src/test/java/com/origin/bookstore/controller/CategoryControllerTest.java @@ -12,6 +12,7 @@ import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.web.servlet.MockMvc; +import static com.origin.bookstore.util.TestConstants.*; import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -20,28 +21,6 @@ @SpringBootTest @AutoConfigureMockMvc public class CategoryControllerTest { - private static final String API_PATH = - "/categories"; - private static final String API_PATH_ID = - "/categories/{id}"; - private static final String CATEGORY_ID_BOOKS_API_PATH_ID = - "/categories/{id}/books"; - private static final String ADD_CATEGORY_PATH = - "/database/categories/add-category-to-categories-table.sql"; - private static final String REMOVE_CATEGORY_PATH = - "/database/categories/remove-category-from-categories-table.sql"; - private static final String ADD_BOOK_PATH = - "/database/books/add-books-with-categories.sql"; - private static final String REMOVE_BOOK_PATH = - "/database/books/remove-books-with-categories.sql"; - private static final String ID_JSON_PATH = - "$.id"; - private static final String NAME_JSON_PATH = - "$.name"; - private static final String CONTENT_JSON_PATH = - "$.content"; - private static final String ADMIN_ROLE = "ADMIN"; - private static final String USER_ROLE = "USER"; private static final Integer CATEGORY_ID = 2; private static final Integer INVALID_CATEGORY_ID = 456; private static final String CATEGORY_NAME = "Fiction"; @@ -56,15 +35,13 @@ public class CategoryControllerTest { @Test @DisplayName("Should successfully create category and return 201") @WithMockUser(roles = ADMIN_ROLE) - @Sql(scripts = ADD_CATEGORY_PATH, - executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_CATEGORY_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) void createCategory_Request_ReturnsCreated() throws Exception { CreateCategoryRequestDto requestDto = TestUtil.createCategoryRequestDto(); String json = objectMapper.writeValueAsString(requestDto); - mockMvc.perform(post(API_PATH) + mockMvc.perform(post(API_CATEGORY_PATH) .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isCreated()) @@ -73,13 +50,15 @@ void createCategory_Request_ReturnsCreated() throws Exception { } @Test - @DisplayName("Should return forbidden if user tries to create category") + @DisplayName("Should return forbidden if user tries to create category") @WithMockUser(roles = USER_ROLE) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) void createCategoryByUser_Request_ReturnsForbidden() throws Exception { CreateCategoryRequestDto requestDto = TestUtil.createCategoryRequestDto(); String json = objectMapper.writeValueAsString(requestDto); - mockMvc.perform(post(API_PATH) + mockMvc.perform(post(API_CATEGORY_PATH) .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isForbidden() @@ -94,7 +73,7 @@ void createCategory_Request_ReturnsBadRequest() throws Exception { new CreateCategoryRequestDto(null, "some description"); String json = objectMapper.writeValueAsString(requestDto); - mockMvc.perform(post(API_PATH) + mockMvc.perform(post(API_CATEGORY_PATH) .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isBadRequest() @@ -104,32 +83,32 @@ void createCategory_Request_ReturnsBadRequest() throws Exception { @Test @DisplayName("Should successfully get all categories") @WithMockUser(roles = USER_ROLE) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = ADD_CATEGORY_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_CATEGORY_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void findAllCategories_Request_ReturnsCategoryDtos() throws Exception { - mockMvc.perform(get(API_PATH)) + mockMvc.perform(get(API_CATEGORY_PATH)) .andExpect(status().isOk()) - .andExpect(jsonPath(CONTENT_JSON_PATH).isArray()) - .andExpect(jsonPath(CONTENT_JSON_PATH + ".length()").value(1) + .andExpect(jsonPath($_CONTENT).isArray()) + .andExpect(jsonPath($_CONTENT + ".length()").value(1) ); } @Test @DisplayName("Should successfully get a category by id") @WithMockUser(roles = USER_ROLE) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = ADD_CATEGORY_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_CATEGORY_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void findCategoryById_Request_ReturnsCategoryDto() throws Exception { - mockMvc.perform(get(API_PATH_ID, CATEGORY_ID) + mockMvc.perform(get(API_CATEGORY_PATH_ID, CATEGORY_ID) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath(ID_JSON_PATH, is(CATEGORY_ID))) + .andExpect(jsonPath($_ID, is(CATEGORY_ID))) .andExpect(jsonPath(NAME_JSON_PATH, is(CATEGORY_NAME)) ); } @@ -137,12 +116,12 @@ void findCategoryById_Request_ReturnsCategoryDto() throws Exception { @Test @DisplayName("Should return 404 if category not found by id") @WithMockUser(roles = USER_ROLE) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = ADD_CATEGORY_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_CATEGORY_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void findCategoryByInvalidId_Request_ReturnsNotFound() throws Exception { - mockMvc.perform(get(API_PATH_ID, INVALID_CATEGORY_ID) + mockMvc.perform(get(API_CATEGORY_PATH_ID, INVALID_CATEGORY_ID) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound() @@ -152,12 +131,12 @@ void findCategoryByInvalidId_Request_ReturnsNotFound() throws Exception { @Test @DisplayName("Should successfully delete a category by id") @WithMockUser(roles = ADMIN_ROLE) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = ADD_CATEGORY_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_CATEGORY_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void deleteCategory_Request_ReturnsNoContent() throws Exception { - mockMvc.perform(delete(API_PATH_ID, CATEGORY_ID) + mockMvc.perform(delete(API_CATEGORY_PATH_ID, CATEGORY_ID) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()); @@ -166,12 +145,12 @@ void deleteCategory_Request_ReturnsNoContent() throws Exception { @Test @DisplayName("Should return forbidden when user is deleting a category") @WithMockUser(roles = USER_ROLE) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = ADD_CATEGORY_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_CATEGORY_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void deleteCategoryByUser_Request_ReturnsForbidden() throws Exception { - mockMvc.perform(delete(API_PATH_ID, CATEGORY_ID) + mockMvc.perform(delete(API_CATEGORY_PATH_ID, CATEGORY_ID) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isForbidden()); @@ -180,19 +159,19 @@ void deleteCategoryByUser_Request_ReturnsForbidden() throws Exception { @Test @DisplayName("Should successfully update a category by id") @WithMockUser(roles = ADMIN_ROLE) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = ADD_CATEGORY_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_CATEGORY_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void updateCategory_Request_ReturnsCategoryDto() throws Exception { CreateCategoryRequestDto requestDto = TestUtil.createCategoryRequestDto(); String json = objectMapper.writeValueAsString(requestDto); - mockMvc.perform(put(API_PATH_ID, CATEGORY_ID) + mockMvc.perform(put(API_CATEGORY_PATH_ID, CATEGORY_ID) .content(json) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath(ID_JSON_PATH, is(CATEGORY_ID))) + .andExpect(jsonPath($_ID, is(CATEGORY_ID))) .andExpect(jsonPath(NAME_JSON_PATH, is(requestDto.name())) ); } @@ -200,15 +179,15 @@ void updateCategory_Request_ReturnsCategoryDto() throws Exception { @Test @DisplayName("Should return 404 when updating invalid category") @WithMockUser(roles = ADMIN_ROLE) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = ADD_CATEGORY_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_CATEGORY_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void updateInvalidCategory_Request_ReturnsNotFound() throws Exception { CreateCategoryRequestDto requestDto = TestUtil.createCategoryRequestDto(); String json = objectMapper.writeValueAsString(requestDto); - mockMvc.perform(put(API_PATH_ID, INVALID_CATEGORY_ID) + mockMvc.perform(put(API_CATEGORY_PATH_ID, INVALID_CATEGORY_ID) .content(json) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound() @@ -218,15 +197,15 @@ void updateInvalidCategory_Request_ReturnsNotFound() throws Exception { @Test @DisplayName("Should return forbidden when user updating a category") @WithMockUser(roles = USER_ROLE) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = ADD_CATEGORY_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_CATEGORY_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void updateCategoryByUser_Request_ReturnsForbidden() throws Exception { CreateCategoryRequestDto requestDto = TestUtil.createCategoryRequestDto(); String json = objectMapper.writeValueAsString(requestDto); - mockMvc.perform(put(API_PATH_ID, CATEGORY_ID) + mockMvc.perform(put(API_CATEGORY_PATH_ID, CATEGORY_ID) .content(json) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isForbidden() @@ -236,14 +215,14 @@ void updateCategoryByUser_Request_ReturnsForbidden() throws Exception { @Test @DisplayName("Should return books by category id") @WithMockUser(roles = USER_ROLE) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOK_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void getBooksByCategoryId_Request_ReturnsPageOfBookDtos() throws Exception { int categoryId = 4; mockMvc.perform(get(CATEGORY_ID_BOOKS_API_PATH_ID, categoryId) - .contentType(MediaType.APPLICATION_JSON)) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.content[0].title").value(BOOK_TITLE) ); @@ -252,10 +231,10 @@ void getBooksByCategoryId_Request_ReturnsPageOfBookDtos() throws Exception { @Test @DisplayName("Should return empty page by invalid category id") @WithMockUser(roles = USER_ROLE) + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOK_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) void getBooksByInvalidCategoryId_Request_ReturnsEmptyPage() throws Exception { mockMvc.perform(get(CATEGORY_ID_BOOKS_API_PATH_ID, INVALID_CATEGORY_ID) .contentType(MediaType.APPLICATION_JSON)) diff --git a/src/test/java/com/origin/bookstore/controller/OrderControllerTest.java b/src/test/java/com/origin/bookstore/controller/OrderControllerTest.java new file mode 100644 index 0000000..edefcf1 --- /dev/null +++ b/src/test/java/com/origin/bookstore/controller/OrderControllerTest.java @@ -0,0 +1,213 @@ +package com.origin.bookstore.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.origin.bookstore.dto.order.OrderRequestDto; +import com.origin.bookstore.dto.order.UpdateOrderStatusRequestDto; +import com.origin.bookstore.util.TestUtil; +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.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.web.servlet.MockMvc; +import static com.origin.bookstore.util.TestConstants.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class OrderControllerTest { + private static final Long ID = 10L; + private static final Long INCORRECT_ID = 999L; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + @WithUserDetails("user@gmail.com") + @DisplayName("Should successfully create an order") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_SHOPPINGCART_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void createOrder_ValidRequest_ReturnsOrderResponseDto() throws Exception { + OrderRequestDto requestDto = TestUtil.createOrderRequestDto(); + + mockMvc.perform(post(ORDERS_URL) + .content(objectMapper.writeValueAsString(requestDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath($_ID).value(1L)) + .andExpect(jsonPath($_STATUS).value("PENDING")) + .andExpect(jsonPath($_TOTAL).value(260)); + } + + @Test + @WithUserDetails("rudycooper@gmail.com") + @DisplayName("Should return 404 if shopping cart not found while creating order") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_USER_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void createOrderWithIncorrectCart_ValidRequest_ReturnsNotFound() throws Exception { + OrderRequestDto requestDto = TestUtil.createOrderRequestDto(); + + mockMvc.perform(post(ORDERS_URL) + .content(objectMapper.writeValueAsString(requestDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound() + ); + } + + @Test + @WithUserDetails("user@gmail.com") + @DisplayName("Should return a list of orders by user") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_ORDER_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void getAllOrders_ValidRequest_ReturnsOrderList() throws Exception { + mockMvc.perform(get(ORDERS_URL) + .param("page", "0") + .param("size", "10") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath($_CONTENT).isArray()) + .andExpect(jsonPath("$.content[0].id").value(ID)) + .andExpect(jsonPath("$.content[0].status").value("PENDING")) + .andExpect(jsonPath($_TOTAL_ELEMENTS).value(1) + ); + } + + @Test + @WithUserDetails("user@gmail.com") + @DisplayName("Should return a list of order items") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_ORDER_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void getAllOrderItemsInOrder_ValidRequest_ReturnsOrderItemsList() throws Exception { + Long orderId = ID; + + mockMvc.perform(get(ORDERS_ORDER_ID_ITEMS_URL, orderId) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0].id").value(orderId)); + } + + @Test + @WithUserDetails("user@gmail.com") + @DisplayName("Should return 404 if order is not found") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_ORDER_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void getAllOrderItemsInIncorrectOrder_ValidRequest_ReturnsNotFound() throws Exception { + mockMvc.perform(get(ORDERS_ORDER_ID_ITEMS_URL, INCORRECT_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound() + ); + } + + @Test + @WithUserDetails("user@gmail.com") + @DisplayName("Should successfully get order item by id") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_ORDER_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void getOrderItemById_ValidRequest_ReturnsOrderItem() throws Exception { + mockMvc.perform(get(ORDER_ID_ITEMS_ID_URL, ID, ID)) + .andExpect(status().isOk()) + .andExpect(jsonPath($_ID).value(ID)) + .andExpect(jsonPath($_QUANTITY).value(1)); + } + + @Test + @WithUserDetails("user@gmail.com") + @DisplayName("Should return 404 when order item id is incorrect") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_ORDER_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void getOrderItemByIncorrectId_ValidRequest_ReturnsNotFound() throws Exception { + mockMvc.perform(get(ORDER_ID_ITEMS_ID_URL, ID, INCORRECT_ID)) + .andExpect(status().isNotFound()); + } + + @Test + @WithUserDetails("admin@gmail.com") + @DisplayName("Should successfully update order status") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_ADMIN_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + @Sql(scripts = ADD_ORDER_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void updateOrderStatus_ValidRequest_ReturnsUpdatedOrder() throws Exception { + UpdateOrderStatusRequestDto requestDto = TestUtil.createUpdateOrderStatusRequestDto(); + + mockMvc.perform(patch(ORDERS_ID_URL, ID) + .content(objectMapper.writeValueAsString(requestDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(ID)) + .andExpect(jsonPath("$.status").value("DELIVERED")); + } + + @Test + @WithUserDetails("admin@gmail.com") + @DisplayName("Should return 404 if order not found while updating status") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_ADMIN_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + @Sql(scripts = ADD_ORDER_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void updateInvalidOrderStatus_ValidRequest_ReturnsNotFound() throws Exception { + UpdateOrderStatusRequestDto requestDto = TestUtil.createUpdateOrderStatusRequestDto(); + + mockMvc.perform(patch(ORDERS_ID_URL, INCORRECT_ID) + .content(objectMapper.writeValueAsString(requestDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound() + ); + } + + @Test + @WithUserDetails("user@gmail.com") + @DisplayName("Should return forbidden when updating order status as user") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_ORDER_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void updateOrderStatusByUser_ValidRequest_ReturnsForbidden() throws Exception { + UpdateOrderStatusRequestDto requestDto = TestUtil.createUpdateOrderStatusRequestDto(); + + mockMvc.perform(patch(ORDERS_ID_URL, ID) + .content(objectMapper.writeValueAsString(requestDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden() + ); + } +} diff --git a/src/test/java/com/origin/bookstore/controller/ShoppingCartControllerTest.java b/src/test/java/com/origin/bookstore/controller/ShoppingCartControllerTest.java new file mode 100644 index 0000000..478a011 --- /dev/null +++ b/src/test/java/com/origin/bookstore/controller/ShoppingCartControllerTest.java @@ -0,0 +1,144 @@ +package com.origin.bookstore.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.origin.bookstore.dto.cartitem.CartItemRequestDto; +import com.origin.bookstore.dto.cartitem.UpdateCartItemRequestDto; +import com.origin.bookstore.util.TestUtil; +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.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.web.servlet.MockMvc; +import static com.origin.bookstore.util.TestConstants.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class ShoppingCartControllerTest { + public static final Long CART_ITEM_ID = 3L; + public static final String $_ID = "$.id"; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + @WithUserDetails("user@gmail.com") + @DisplayName("Should add book to cart") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_SHOPPINGCART_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void addBookToCart_ValidRequest_ReturnsCreated() throws Exception { + CartItemRequestDto requestDto = new CartItemRequestDto(2L, 2); + + mockMvc.perform(post(CART_URL) + .content(objectMapper.writeValueAsString(requestDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()) + .andExpect(jsonPath($_ID).exists()); + } + + @Test + @WithUserDetails("rudycooper@gmail.com") + @DisplayName("Should return 404 when saving book for invalid cart") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_USER_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void addBookToInvalidCart_ValidRequest_ReturnsNotFound() throws Exception { + CartItemRequestDto requestDto = new CartItemRequestDto(1L, 5); + + mockMvc.perform(post(CART_URL) + .content(objectMapper.writeValueAsString(requestDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + } + + @Test + @WithUserDetails("user@gmail.com") + @DisplayName("Should get items from shopping cart") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_SHOPPINGCART_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void getCartItems_ReturnsShoppingCart() throws Exception { + mockMvc.perform(get(CART_URL)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.cartItems[0].bookTitle").value("name") + ); + } + + @Test + @WithUserDetails("rudycooper@gmail.com") + @DisplayName("Should return 404 when cart is not found") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_USER_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD + ) + void getCartItems_CartNotFound_ReturnsNotFound() throws Exception { + mockMvc.perform(get(CART_URL)) + .andExpect(status().isNotFound()); + } + + @Test + @WithUserDetails("user@gmail.com") + @DisplayName("Should successfully update book quantity in cart") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_SHOPPINGCART_PATH, + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void updateBookQuantityInCart_ValidRequest_ReturnsShoppingCart() throws Exception { + UpdateCartItemRequestDto cartItemRequestDto = TestUtil.createUpdateCartItemRequestDto(); + + mockMvc.perform(put(CART_ITEMS_CART_ITEM_ID_URL, CART_ITEM_ID) + .content(objectMapper.writeValueAsString(cartItemRequestDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath($_ID).exists() + ); + } + + @Test + @WithUserDetails("user@gmail.com") + @DisplayName("Should return bad request when updating negative book quantity in cart") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_SHOPPINGCART_PATH, + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void updateBookNegativeQuantityInCart_ReturnsBadRequest() throws Exception { + UpdateCartItemRequestDto cartItemRequestDto = new UpdateCartItemRequestDto(-999); + + mockMvc.perform(put(CART_ITEMS_CART_ITEM_ID_URL, CART_ITEM_ID) + .content(objectMapper.writeValueAsString(cartItemRequestDto)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest() + ); + } + + @Test + @WithUserDetails("user@gmail.com") + @DisplayName("Should delete a book from shopping cart") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_SHOPPINGCART_PATH, + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void deleteBookFromCart_ValidRequest_ReturnsNoContent() throws Exception { + mockMvc.perform(delete(CART_ITEMS_CART_ITEM_ID_URL, CART_ITEM_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent() + ); + } +} diff --git a/src/test/java/com/origin/bookstore/repository/BookRepositoryTest.java b/src/test/java/com/origin/bookstore/repository/BookRepositoryTest.java index e6f26a4..cba6619 100644 --- a/src/test/java/com/origin/bookstore/repository/BookRepositoryTest.java +++ b/src/test/java/com/origin/bookstore/repository/BookRepositoryTest.java @@ -3,7 +3,6 @@ import com.origin.bookstore.model.Book; import com.origin.bookstore.model.Category; import com.origin.bookstore.repository.book.BookRepository; -import com.origin.bookstore.repository.category.CategoryRepository; import com.origin.bookstore.util.TestUtil; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,9 +12,10 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Pageable; import org.springframework.test.context.jdbc.Sql; -import java.math.BigDecimal; import java.util.List; import java.util.Set; + +import static com.origin.bookstore.util.TestConstants.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -23,15 +23,13 @@ @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class BookRepositoryTest { - private static final String ADD_BOOK_PATH = - "/database/books/add-books-with-categories.sql"; - private static final String REMOVE_BOOK_PATH = - "/database/books/remove-books-with-categories.sql"; @Autowired private BookRepository bookRepository; @Test @DisplayName("Should save book with category and then find it by id") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) void saveThenFind_ReturnsValidBook() { Category category = TestUtil.createCategory(); @@ -46,10 +44,10 @@ void saveThenFind_ReturnsValidBook() { } @Test + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOK_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) @DisplayName("Soft deleting book by id") void delete_ShouldMarkAsDeleted() { bookRepository.deleteById(1L); @@ -58,10 +56,10 @@ void delete_ShouldMarkAsDeleted() { } @Test + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = ADD_BOOK_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_BOOK_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) @DisplayName("Should find book by category id") void findBookByCategoryId_ReturnsValidBook() { List books = bookRepository.findAllByCategoriesId(4L, Pageable.ofSize(1)).toList(); @@ -71,6 +69,8 @@ void findBookByCategoryId_ReturnsValidBook() { @Test @DisplayName("Should throw exception when saving books with same isbn") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) void saveBooksBySameIsbn_ThrowsException() { Category category = TestUtil.createCategory(); diff --git a/src/test/java/com/origin/bookstore/repository/CategoryRepositoryTest.java b/src/test/java/com/origin/bookstore/repository/CategoryRepositoryTest.java index 722d65c..f43dd7e 100644 --- a/src/test/java/com/origin/bookstore/repository/CategoryRepositoryTest.java +++ b/src/test/java/com/origin/bookstore/repository/CategoryRepositoryTest.java @@ -10,21 +10,20 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.test.context.jdbc.Sql; +import static com.origin.bookstore.util.TestConstants.ADD_CATEGORY_PATH; +import static com.origin.bookstore.util.TestConstants.CLEANUP_DB_PATH; import static org.junit.jupiter.api.Assertions.*; @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) public class CategoryRepositoryTest { - private static final String ADD_CATEGORY_PATH = - "/database/categories/add-category-to-categories-table.sql"; - private static final String REMOVE_CATEGORY_PATH = - "/database/categories/remove-category-from-categories-table.sql"; - @Autowired private CategoryRepository categoryRepository; @Test @DisplayName("Save then find a category by id") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) void saveAndFind_ValidCategory_ReturnsCategory() { Category category = TestUtil.createCategory(); @@ -32,14 +31,14 @@ void saveAndFind_ValidCategory_ReturnsCategory() { Category category1 = categoryRepository.findById(savedCategory.getId()) .orElseThrow(() -> new AssertionError("Category not found!")); - assertEquals("Chemistry", category1.getName()); + assertEquals(category.getName(), category1.getName()); } @Test + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = ADD_CATEGORY_PATH, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) - @Sql(scripts = REMOVE_CATEGORY_PATH, - executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) @DisplayName("Soft deleting category by id") void delete_ShouldMarkAsDeleted() { Long id = 1L; @@ -50,6 +49,8 @@ void delete_ShouldMarkAsDeleted() { @Test @DisplayName("Should throw exception when saving categories with same name") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) void saveCategoriesBySameName_ThrowsException() { Category category = TestUtil.createCategory(); diff --git a/src/test/java/com/origin/bookstore/repository/ShoppingCartRepositoryTest.java b/src/test/java/com/origin/bookstore/repository/ShoppingCartRepositoryTest.java new file mode 100644 index 0000000..73596d3 --- /dev/null +++ b/src/test/java/com/origin/bookstore/repository/ShoppingCartRepositoryTest.java @@ -0,0 +1,65 @@ +package com.origin.bookstore.repository; + +import com.origin.bookstore.model.ShoppingCart; +import com.origin.bookstore.model.User; +import com.origin.bookstore.repository.shoppingcart.ShoppingCartRepository; +import com.origin.bookstore.repository.user.UserRepository; +import com.origin.bookstore.util.TestUtil; +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.test.context.jdbc.Sql; +import static com.origin.bookstore.util.TestConstants.*; +import static org.junit.jupiter.api.Assertions.*; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class ShoppingCartRepositoryTest { + @Autowired + private ShoppingCartRepository shoppingCartRepository; + + @Autowired + private UserRepository userRepository; + + @Test + @DisplayName("Save shopping cart and test if it's id is equal to user id") + void saveShoppingCartAndCheckEquality_ReturnsSameIds() { + User user = userRepository.save(TestUtil.createUser()); + + ShoppingCart shoppingCart = TestUtil.createShoppingCart(); + shoppingCart.setUser(user); + + ShoppingCart shoppingCart1 = shoppingCartRepository.save(shoppingCart); + + assertEquals(shoppingCart1.getId(), user.getId()); + } + + @Test + @DisplayName("Soft deleting shopping cart by id") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_SHOPPINGCART_PATH, + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void softDeleteShoppingCartById_ShouldMarkAsDeleted() { + shoppingCartRepository.deleteById(1L); + + assertTrue(shoppingCartRepository.findById(1L).isEmpty(), + "Shopping cart should be soft deleted!"); + } + + @Test + @DisplayName("Should return the shopping cart by user") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_SHOPPINGCART_PATH, + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void findShoppingCartByUser_ReturnsShoppingCart() { + User user = TestUtil.createUser(); + user.setId(5L); + + assertTrue(shoppingCartRepository.findByUser(user).isPresent(), + "Shopping cart is not found by user id"); + } +} diff --git a/src/test/java/com/origin/bookstore/repository/UserRepositoryTest.java b/src/test/java/com/origin/bookstore/repository/UserRepositoryTest.java new file mode 100644 index 0000000..f09f2eb --- /dev/null +++ b/src/test/java/com/origin/bookstore/repository/UserRepositoryTest.java @@ -0,0 +1,60 @@ +package com.origin.bookstore.repository; + +import com.origin.bookstore.model.User; +import com.origin.bookstore.repository.user.UserRepository; +import com.origin.bookstore.util.TestUtil; +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.dao.DataIntegrityViolationException; +import org.springframework.test.context.jdbc.Sql; + +import static com.origin.bookstore.util.TestConstants.*; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class UserRepositoryTest { + + @Autowired + private UserRepository userRepository; + + @Test + @DisplayName("Should successfully save and find user by id") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void saveUser_ThenFindUserById_ReturnsUser() { + User user = TestUtil.createUser(); + User savedUser = userRepository.save(user); + + assertTrue(userRepository.findById(savedUser.getId()).isPresent(), "User is not found in DB!"); + } + + @Test + @DisplayName("Should throw an exception if email is occupied") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_USER_PATH, + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void saveUser_ThrowsException_WhenEmailIsNotUnique() { + User user = TestUtil.createUser(); + user.setEmail("rudycooper@gmail.com"); + + assertThrows(DataIntegrityViolationException.class, () -> userRepository.save(user)); + } + + @Test + @DisplayName("Soft deleting user by id") + @Sql(scripts = CLEANUP_DB_PATH, executionPhase = + Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = ADD_USER_PATH, + executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void softDeleteUser_ShouldMarkAsDeleted() { + userRepository.deleteById(5L); + + assertTrue(userRepository.findById(5L).isEmpty(), "User should be soft deleted!"); + } +} diff --git a/src/test/java/com/origin/bookstore/service/BookServiceTest.java b/src/test/java/com/origin/bookstore/service/BookServiceTest.java index 828b326..8256ee3 100644 --- a/src/test/java/com/origin/bookstore/service/BookServiceTest.java +++ b/src/test/java/com/origin/bookstore/service/BookServiceTest.java @@ -20,19 +20,17 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.domain.Page; 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; import java.util.List; import java.util.Optional; import java.util.Set; +import static com.origin.bookstore.util.TestConstants.pageable; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class BookServiceTest { - private static final Pageable pageable = PageRequest.of(0, 10); public static final Long VALID_BOOK_ID = 1L; public static final Long INVALID_ID = 456L; diff --git a/src/test/java/com/origin/bookstore/service/CategoryServiceTest.java b/src/test/java/com/origin/bookstore/service/CategoryServiceTest.java index 2ca940c..38bbcca 100644 --- a/src/test/java/com/origin/bookstore/service/CategoryServiceTest.java +++ b/src/test/java/com/origin/bookstore/service/CategoryServiceTest.java @@ -20,17 +20,14 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import java.util.List; import java.util.Optional; - +import static com.origin.bookstore.util.TestConstants.pageable; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class CategoryServiceTest { - private static final Pageable pageable = PageRequest.of(0, 10); public static final Long VALID_CATEGORY_ID = 1L; @Mock diff --git a/src/test/java/com/origin/bookstore/service/OrderServiceTest.java b/src/test/java/com/origin/bookstore/service/OrderServiceTest.java new file mode 100644 index 0000000..c01710e --- /dev/null +++ b/src/test/java/com/origin/bookstore/service/OrderServiceTest.java @@ -0,0 +1,224 @@ +package com.origin.bookstore.service; + +import com.origin.bookstore.dto.order.OrderRequestDto; +import com.origin.bookstore.dto.order.OrderResponseDto; +import com.origin.bookstore.dto.order.UpdateOrderStatusRequestDto; +import com.origin.bookstore.dto.orderitem.OrderItemResponseDto; +import com.origin.bookstore.exception.EntityNotFoundException; +import com.origin.bookstore.mapper.OrderItemMapper; +import com.origin.bookstore.mapper.OrderMapper; +import com.origin.bookstore.model.Book; +import com.origin.bookstore.model.CartItem; +import com.origin.bookstore.model.Order; +import com.origin.bookstore.model.OrderItem; +import com.origin.bookstore.model.ShoppingCart; +import com.origin.bookstore.model.User; +import com.origin.bookstore.repository.order.OrderRepository; +import com.origin.bookstore.repository.orderitem.OrderItemRepository; +import com.origin.bookstore.repository.shoppingcart.ShoppingCartRepository; +import com.origin.bookstore.service.impl.OrderServiceImpl; +import com.origin.bookstore.util.TestUtil; +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.Page; +import org.springframework.data.domain.PageImpl; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import static com.origin.bookstore.util.TestConstants.pageable; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class OrderServiceTest { + @Mock + private OrderMapper orderMapper; + + @Mock + private OrderRepository orderRepository; + + @Mock + private OrderItemRepository orderItemRepository; + + @Mock + private OrderItemMapper orderItemMapper; + + @Mock + private ShoppingCartService shoppingCartService; + + @Mock + private ShoppingCartRepository shoppingCartRepository; + + @InjectMocks + private OrderServiceImpl orderService; + + @Test + @DisplayName("Should successfully save an order and clear shopping cart") + void save_ValidOrder_ReturnsOrderResponseDto() { + User user = TestUtil.createUser(); + Book book = TestUtil.createBook(); + OrderRequestDto requestDto = TestUtil.createOrderRequestDto(); + ShoppingCart shoppingCart = TestUtil.createShoppingCart(); + Order order = TestUtil.createOrder(); + OrderResponseDto expectedDto = TestUtil.createOrderResponseDto(); + CartItem cartItem = TestUtil.createCartItem(); + OrderItem orderItem = TestUtil.createOrderItem(); + Set orderItems = new HashSet<>(); + orderItems.add(orderItem); + + cartItem.setBook(book); + shoppingCart.setCartItems(Set.of(cartItem)); + order.setOrderItems(orderItems); + + when(shoppingCartRepository.findByUser(user)).thenReturn(Optional.of(shoppingCart)); + when(orderMapper.toEntity(requestDto)).thenReturn(order); + when(orderItemMapper.toOrderItem(any(CartItem.class))).thenReturn(orderItem); + when(orderRepository.save(any(Order.class))).thenReturn(order); + when(orderMapper.toDto(any(Order.class))).thenReturn(expectedDto); + + OrderResponseDto actualDto = orderService.save(user, requestDto); + + assertNotNull(actualDto); + assertEquals(expectedDto, actualDto); + + verify(shoppingCartRepository).findByUser(user); + verify(shoppingCartService).clearShoppingCart(user); + verify(orderRepository).save(any(Order.class)); + verify(orderMapper).toDto(any(Order.class)); + } + + @Test + @DisplayName("Should throw exception when shopping cart not found") + void save_InvalidShoppingCart_ThrowsException() { + User user = TestUtil.createUser(); + OrderRequestDto requestDto = TestUtil.createOrderRequestDto(); + + when(shoppingCartRepository.findByUser(user)).thenReturn(Optional.empty()); + + assertThrows(EntityNotFoundException.class, + () -> orderService.save(user, requestDto)); + + verify(shoppingCartRepository).findByUser(user); + verifyNoInteractions(orderMapper, orderItemMapper, orderRepository, shoppingCartService); + } + + @Test + @DisplayName("Should return a list of orders") + void getAll_ValidOrders_ReturnsOrdersList() { + User user = TestUtil.createUser(); + Order order = TestUtil.createOrder(); + OrderResponseDto orderDto = TestUtil.createOrderResponseDto(); + + Page orderList = new PageImpl<>(List.of(order), pageable, 1); + + when(orderRepository.getOrdersByUser(user, pageable)).thenReturn(orderList); + when(orderMapper.toDto(order)).thenReturn(orderDto); + + orderService.getAllOrders(user, pageable); + + verify(orderRepository).getOrdersByUser(user, pageable); + verify(orderMapper).toDto(order); + } + + @Test + @DisplayName("Should return a list of ordersItems by orderId") + void getAll_ValidOrdersItemsById_ReturnsOrdersItemsList() { + Long orderId = 7L; + User user = TestUtil.createUser(); + user.setId(1L); + + Order order = new Order(); + order.setId(orderId); + order.setUser(user); + + OrderItem orderItem = TestUtil.createOrderItem(); + OrderItemResponseDto orderItemResponseDto = TestUtil.createOrderItemResponseDto(); + List orderItems = List.of(orderItem); + + when(orderRepository.findByIdAndUser(orderId, user)).thenReturn(Optional.of(order)); + when(orderItemRepository.findAllByOrderIdAndOrderUser(orderId, user)).thenReturn(orderItems); + when(orderItemMapper.toDto(orderItem)).thenReturn(orderItemResponseDto); + + List result = orderService.getAllOrderItems(user, orderId); + + assertNotNull(result); + assertEquals(1, result.size()); + verify(orderItemRepository).findAllByOrderIdAndOrderUser(orderId, user); + verify(orderItemMapper).toDto(orderItem); + } + + @Test + @DisplayName("Should return order item when data is valid") + void getOrderItemById_ValidData_ReturnsDto() { + User user = TestUtil.createUser(); + Long orderId = 1L; + Long orderItemId = 1L; + OrderItem orderItem = TestUtil.createOrderItem(); + OrderItemResponseDto expectedDto = TestUtil.createOrderItemResponseDto(); + + when(orderItemRepository.findByIdAndOrderIdAndOrderUser(orderItemId, orderId, user)) + .thenReturn(Optional.of(orderItem)); + when(orderItemMapper.toDto(orderItem)).thenReturn(expectedDto); + + OrderItemResponseDto actualDto = orderService.getOrderItemById(user, orderId, orderItemId); + + assertNotNull(actualDto); + assertEquals(expectedDto, actualDto); + verify(orderItemRepository).findByIdAndOrderIdAndOrderUser(orderItemId, orderId, user); + } + + @Test + @DisplayName("Should throw exception if order item not found") + void getOrderItemById_InvalidId_ThrowsException() { + User user = TestUtil.createUser(); + Long orderId = 1L; + Long orderItemId = 1L; + + when(orderItemRepository.findByIdAndOrderIdAndOrderUser(orderItemId, orderId, user)) + .thenReturn(Optional.empty()); + + assertThrows(EntityNotFoundException.class, + () -> orderService.getOrderItemById(user, orderId, orderItemId)); + verifyNoInteractions(orderItemMapper); + } + + @Test + @DisplayName("Should update order status if order exists") + void updateOrderStatus_ValidOrder_ReturnsUpdatedDto() { + Long orderId = 1L; + UpdateOrderStatusRequestDto requestDto = TestUtil.createUpdateOrderStatusRequestDto(); + Order order = TestUtil.createOrder(); + OrderResponseDto expectedDto = TestUtil.createOrderResponseDto(); + + when(orderRepository.findById(orderId)).thenReturn(Optional.of(order)); + when(orderRepository.save(order)).thenReturn(order); + when(orderMapper.toDto(order)).thenReturn(expectedDto); + + OrderResponseDto actualDto = orderService.updateOrderStatus(orderId, requestDto); + + assertNotNull(actualDto); + assertEquals(expectedDto, actualDto); + verify(orderMapper).updateOrder(requestDto, order); + verify(orderRepository).save(order); + } + + @Test + @DisplayName("Should throw exception when updating non-existent order") + void updateOrderStatus_InvalidOrderId_ThrowsException() { + Long orderId = 1L; + UpdateOrderStatusRequestDto requestDto = TestUtil.createUpdateOrderStatusRequestDto(); + + when(orderRepository.findById(orderId)).thenReturn(Optional.empty()); + + assertThrows(EntityNotFoundException.class, + () -> orderService.updateOrderStatus(orderId, requestDto)); + verify(orderRepository, never()).save(any()); + verifyNoInteractions(orderMapper); + } +} diff --git a/src/test/java/com/origin/bookstore/service/ShoppingCartServiceTest.java b/src/test/java/com/origin/bookstore/service/ShoppingCartServiceTest.java new file mode 100644 index 0000000..596edd8 --- /dev/null +++ b/src/test/java/com/origin/bookstore/service/ShoppingCartServiceTest.java @@ -0,0 +1,310 @@ +package com.origin.bookstore.service; + +import com.origin.bookstore.dto.cartitem.CartItemRequestDto; +import com.origin.bookstore.dto.cartitem.UpdateCartItemRequestDto; +import com.origin.bookstore.dto.shoppingcart.ShoppingCartResponseDto; +import com.origin.bookstore.exception.EntityNotFoundException; +import com.origin.bookstore.mapper.CartItemMapper; +import com.origin.bookstore.mapper.ShoppingCartMapper; +import com.origin.bookstore.model.Book; +import com.origin.bookstore.model.CartItem; +import com.origin.bookstore.model.ShoppingCart; +import com.origin.bookstore.model.User; +import com.origin.bookstore.repository.cartitem.CartItemRepository; +import com.origin.bookstore.repository.shoppingcart.ShoppingCartRepository; +import com.origin.bookstore.service.impl.ShoppingCartServiceImpl; +import com.origin.bookstore.util.TestUtil; +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 java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class ShoppingCartServiceTest { + @Mock + ShoppingCartRepository shoppingCartRepository; + + @Mock + CartItemMapper cartItemMapper; + + @Mock + ShoppingCartMapper shoppingCartMapper; + + @Mock + CartItemRepository cartItemRepository; + + @InjectMocks + ShoppingCartServiceImpl shoppingCartService; + + @Test + @DisplayName("Should add a cartItem to cart successfully") + void addCartItemToCart_ReturnsShoppingCartResponseDto() { + ShoppingCartResponseDto shoppingCartResponseDto = TestUtil.createShoppingCartResponseDto(); + CartItem cartItem = TestUtil.createCartItem(); + CartItemRequestDto cartItemRequestDto = TestUtil.createCartItemRequestDto(); + ShoppingCart shoppingCart = TestUtil.createShoppingCart(); + shoppingCart.setCartItems(new HashSet<>()); + User user = TestUtil.createUser(); + + when(shoppingCartRepository.findByUser(user)).thenReturn(Optional.of(shoppingCart)); + when(cartItemMapper.toEntity(cartItemRequestDto)).thenReturn(cartItem); + when(shoppingCartRepository.save(shoppingCart)).thenReturn(shoppingCart); + when(shoppingCartMapper.toDto(shoppingCart)).thenReturn(shoppingCartResponseDto); + + ShoppingCartResponseDto shoppingCartResponseDto1 = shoppingCartService.addBookToCart(user, cartItemRequestDto); + + assertEquals(shoppingCartResponseDto, shoppingCartResponseDto1); + verify(shoppingCartRepository).findByUser(user); + verify(cartItemMapper).toEntity(cartItemRequestDto); + verify(shoppingCartRepository).save(shoppingCart); + verify(shoppingCartMapper).toDto(shoppingCart); + } + + @Test + @DisplayName("Should throw and exception if shopping cart not found") + void addCartItemToInvalidCart_ThrowsException() { + CartItemRequestDto cartItemRequestDto = TestUtil.createCartItemRequestDto(); + ShoppingCart shoppingCart = TestUtil.createShoppingCart(); + shoppingCart.setCartItems(new HashSet<>()); + User user = TestUtil.createUser(); + + when(shoppingCartRepository.findByUser(user)).thenReturn(Optional.empty()); + EntityNotFoundException ex = assertThrows(EntityNotFoundException.class, + () -> shoppingCartService.addBookToCart(user, cartItemRequestDto) + ); + + assertEquals("Shopping cart by user id: " + + user.getId() + " not found", ex.getMessage()); + verify(shoppingCartRepository).findByUser(user); + } + + @Test + @DisplayName("Quantity should be updated if item is in cart") + void addExistingItemToCart_UpdatesQuantity() { + User user = TestUtil.createUser(); + Book book = TestUtil.createBook(); + book.setId(7L); + + CartItem existingItem = new CartItem(); + existingItem.setBook(book); + existingItem.setQuantity(4); + + ShoppingCart shoppingCart = TestUtil.createShoppingCart(); + shoppingCart.setCartItems(Set.of(existingItem)); + + CartItemRequestDto cartItemRequestDto = + new CartItemRequestDto(book.getId(), 3); + + when(shoppingCartRepository.findByUser(user)) + .thenReturn(Optional.of(shoppingCart)); + when(shoppingCartRepository.save(shoppingCart)) + .thenReturn(shoppingCart); + when(shoppingCartMapper.toDto(shoppingCart)) + .thenReturn(TestUtil.createShoppingCartResponseDto()); + + shoppingCartService.addBookToCart(user, cartItemRequestDto); + + assertEquals(7, existingItem.getQuantity(), + "Quantity must've been updated without adding new cartItem!"); + verify(cartItemMapper, never()).toEntity(any()); + } + + @Test + @DisplayName("Should return a shopping cart by user") + void getShoppingCartByUser_ReturnsShoppingCart() { + User user = TestUtil.createUser(); + ShoppingCart shoppingCart = TestUtil.createShoppingCart(); + ShoppingCartResponseDto responseDto = TestUtil.createShoppingCartResponseDto(); + + when(shoppingCartRepository.findByUser(user)) + .thenReturn(Optional.of(shoppingCart)); + when(shoppingCartMapper.toDto(shoppingCart)) + .thenReturn(responseDto); + + shoppingCartService.getShoppingCartByUserId(user); + + verify(shoppingCartRepository).findByUser(user); + verify(shoppingCartMapper).toDto(shoppingCart); + } + + @Test + @DisplayName("Should throw an exception if shopping cart not found") + void getShoppingCartByInvalidUser_ThrowsException() { + User user = TestUtil.createUser(); + ShoppingCart shoppingCart = TestUtil.createShoppingCart(); + + when(shoppingCartRepository.findByUser(user)) + .thenReturn(Optional.empty()); + + EntityNotFoundException ex = assertThrows(EntityNotFoundException.class, + () -> shoppingCartService.getShoppingCartByUserId(user)); + + assertEquals("Shopping cart by user id: " + + user.getId() + " not found", ex.getMessage()); + verify(shoppingCartRepository).findByUser(user); + } + + @Test + @DisplayName("Should create a new shopping cart for user") + void createShoppingCartForUser_CreatesNewShoppingCart() { + User user = TestUtil.createUser(); + ShoppingCart shoppingCart = TestUtil.createShoppingCart(); + shoppingCart.setUser(user); + + when(shoppingCartRepository.save(any(ShoppingCart.class))) + .thenReturn(shoppingCart); + + shoppingCartService.createShoppingCartForUser(user); + verify(shoppingCartRepository).save(any(ShoppingCart.class)); + } + + @Test + @DisplayName("Should update book quantity in cart successfully") + void updateBookQuantityInCart_ValidRequest_ReturnsResponseDto() { + User user = TestUtil.createUser(); + Long cartItemId = 1L; + UpdateCartItemRequestDto requestDto = TestUtil.createUpdateCartItemRequestDto(); + + CartItem cartItem = TestUtil.createCartItem(); + ShoppingCart shoppingCart = TestUtil.createShoppingCart(); + ShoppingCartResponseDto expectedDto = TestUtil.createShoppingCartResponseDto(); + + when(cartItemRepository.findByIdAndShoppingCartUserId(cartItemId, user.getId())) + .thenReturn(Optional.of(cartItem)); + when(shoppingCartRepository.findByUser(user)) + .thenReturn(Optional.of(shoppingCart)); + when(shoppingCartMapper.toDto(shoppingCart)) + .thenReturn(expectedDto); + + ShoppingCartResponseDto actualDto = + shoppingCartService.updateBookQuantityInCart(user, cartItemId, requestDto); + + assertEquals(expectedDto, actualDto); + assertEquals(10, cartItem.getQuantity()); + + verify(cartItemRepository).findByIdAndShoppingCartUserId(cartItemId, user.getId()); + verify(shoppingCartRepository).findByUser(user); + } + + @Test + @DisplayName("Should throw exception when cart item is not found") + void updateBookQuantityInCart_CartItemNotFound_ThrowsException() { + User user = TestUtil.createUser(); + Long cartItemId = 99L; + UpdateCartItemRequestDto requestDto = TestUtil.createUpdateCartItemRequestDto(); + + when(cartItemRepository.findByIdAndShoppingCartUserId(cartItemId, user.getId())) + .thenReturn(Optional.empty()); + + EntityNotFoundException ex = + assertThrows(EntityNotFoundException.class, + () -> shoppingCartService.updateBookQuantityInCart(user, cartItemId, requestDto)); + + assertEquals("Can't find cart item by id: 99", ex.getMessage()); + verify(cartItemRepository).findByIdAndShoppingCartUserId(cartItemId, user.getId()); + verifyNoMoreInteractions(shoppingCartRepository, shoppingCartMapper); + } + + @Test + @DisplayName("Should throw exception when shopping cart is not found") + void updateBookQuantityInCart_ShoppingCartNotFound_ThrowsException() { + User user = TestUtil.createUser(); + Long cartItemId = 1L; + UpdateCartItemRequestDto requestDto = TestUtil.createUpdateCartItemRequestDto(); + + CartItem cartItem = TestUtil.createCartItem(); + + when(cartItemRepository.findByIdAndShoppingCartUserId(cartItemId, user.getId())) + .thenReturn(Optional.of(cartItem)); + when(shoppingCartRepository.findByUser(user)) + .thenReturn(Optional.empty()); + + EntityNotFoundException ex = + assertThrows(EntityNotFoundException.class, + () -> shoppingCartService.updateBookQuantityInCart(user, cartItemId, requestDto)); + + assertEquals("Can't find shopping cart by user id: " + user.getId(), + ex.getMessage()); + verify(cartItemRepository).findByIdAndShoppingCartUserId(cartItemId, user.getId()); + verify(shoppingCartRepository).findByUser(user); + verifyNoMoreInteractions(shoppingCartMapper); + } + + @Test + @DisplayName("Should throw exception if cart item is not found") + void deleteBookFromCart_InvalidCartItem_ThrowsException() { + User user = TestUtil.createUser(); + Long cartItemId = 42L; + + when(cartItemRepository.findByIdAndShoppingCartUserId(cartItemId, user.getId())) + .thenReturn(Optional.empty()); + + EntityNotFoundException ex = + assertThrows(EntityNotFoundException.class, + () -> shoppingCartService.deleteBookFromCart(user, cartItemId)); + + assertEquals("Can't find cart item by id: 42", ex.getMessage()); + verify(cartItemRepository).findByIdAndShoppingCartUserId(cartItemId, user.getId()); + verifyNoMoreInteractions(cartItemRepository); + } + + @Test + @DisplayName("Should delete cart item when it exists") + void deleteBookFromCart_ValidCartItem_DeletesSuccessfully() { + User user = TestUtil.createUser(); + Long cartItemId = 1L; + + CartItem cartItem = TestUtil.createCartItem(); + + when(cartItemRepository.findByIdAndShoppingCartUserId(cartItemId, user.getId())) + .thenReturn(Optional.of(cartItem)); + + shoppingCartService.deleteBookFromCart(user, cartItemId); + + verify(cartItemRepository).findByIdAndShoppingCartUserId(cartItemId, user.getId()); + verify(cartItemRepository).delete(cartItem); + } + + @Test + @DisplayName("Should throw exception when shopping cart is not found") + void clearShoppingCart_ShoppingCartNotFound_ThrowsException() { + User user = TestUtil.createUser(); + + when(shoppingCartRepository.findByUser(user)) + .thenReturn(Optional.empty()); + + EntityNotFoundException ex = + assertThrows(EntityNotFoundException.class, + () -> shoppingCartService.clearShoppingCart(user)); + + assertEquals("Can't find shopping cart by user id: " + user.getId(), + ex.getMessage()); + verify(shoppingCartRepository).findByUser(user); + verifyNoMoreInteractions(cartItemRepository, shoppingCartMapper); + } + + @Test + @DisplayName("Should clear cart items when it exists") + void clearShoppingCart_ValidShoppingCart_EmptiesCart() { + User user = TestUtil.createUser(); + Set items = new HashSet<>(); + items.add(TestUtil.createCartItem()); + + ShoppingCart shoppingCart = TestUtil.createShoppingCart(); + shoppingCart.setCartItems(items); + when(shoppingCartRepository.findByUser(user)) + .thenReturn(Optional.of(shoppingCart)); + + shoppingCartService.clearShoppingCart(user); + + assertTrue(shoppingCart.getCartItems().isEmpty()); + verify(shoppingCartRepository).findByUser(user); + } +} diff --git a/src/test/java/com/origin/bookstore/service/UserServiceTest.java b/src/test/java/com/origin/bookstore/service/UserServiceTest.java new file mode 100644 index 0000000..c7e5e17 --- /dev/null +++ b/src/test/java/com/origin/bookstore/service/UserServiceTest.java @@ -0,0 +1,117 @@ +package com.origin.bookstore.service; + +import com.origin.bookstore.dto.user.UserRegistrationRequestDto; +import com.origin.bookstore.dto.user.UserResponseDto; +import com.origin.bookstore.exception.EntityNotFoundException; +import com.origin.bookstore.exception.RegistrationException; +import com.origin.bookstore.mapper.UserMapper; +import com.origin.bookstore.model.Role; +import com.origin.bookstore.model.User; +import com.origin.bookstore.repository.role.RoleRepository; +import com.origin.bookstore.repository.user.UserRepository; +import com.origin.bookstore.service.impl.UserServiceImpl; +import com.origin.bookstore.util.TestUtil; +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.security.crypto.password.PasswordEncoder; +import java.util.Optional; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class UserServiceTest { + @Mock + private UserRepository userRepository; + + @Mock + private UserMapper userMapper; + + @Mock + private RoleRepository roleRepository; + + @Mock + private PasswordEncoder passwordEncoder; + + @Mock + private ShoppingCartService shoppingCartService; + + @InjectMocks + private UserServiceImpl userService; + + @Test + @DisplayName("Should successfully register a new user") + void register_ValidRequest_ReturnsUserResponseDto() { + UserRegistrationRequestDto requestDto = TestUtil.createUserRegistrationRequestDto(); + User user = TestUtil.createUser(); + Role role = new Role(); + UserResponseDto expectedDto = TestUtil.createUserResponseDto(); + + when(userRepository.existsByEmail(requestDto.getEmail())).thenReturn(false); + when(userMapper.toModel(requestDto)).thenReturn(user); + when(passwordEncoder.encode(requestDto.getPassword())).thenReturn("encodedPassword"); + when(roleRepository.findByName(Role.RoleName.ROLE_USER)).thenReturn(Optional.of(role)); + when(userRepository.save(user)).thenReturn(user); + when(userMapper.toDto(user)).thenReturn(expectedDto); + + UserResponseDto actualDto = userService.save(requestDto); + + assertNotNull(actualDto); + assertEquals(requestDto.getEmail(), actualDto.getEmail()); + assertEquals("encodedPassword", user.getPassword()); + assertTrue(user.getRoles().contains(role)); + verify(userRepository).existsByEmail(requestDto.getEmail()); + verify(passwordEncoder).encode(anyString()); + verify(roleRepository).findByName(Role.RoleName.ROLE_USER); + verify(userRepository).save(user); + verify(shoppingCartService).createShoppingCartForUser(user); + } + + @Test + @DisplayName("Should throw exception when the e‑mail already exists") + void register_DuplicateEmail_ThrowsRegistrationException() { + UserRegistrationRequestDto requestDto = TestUtil.createUserRegistrationRequestDto(); + + when(userRepository.existsByEmail(requestDto.getEmail())).thenReturn(true); + + RegistrationException ex = + assertThrows(RegistrationException.class, () -> userService.save(requestDto)); + + assertEquals( + "User with email " + requestDto.getEmail() + " already exists. ", + ex.getMessage()); + + verify(userRepository).existsByEmail(requestDto.getEmail()); + verifyNoMoreInteractions(userRepository, userMapper, + passwordEncoder, roleRepository, shoppingCartService); + } + + @Test + @DisplayName("Should throw exception when ROLE_USER is not found") + void registerWith_InvalidRole_ThrowsException() { + UserRegistrationRequestDto requestDto = TestUtil.createUserRegistrationRequestDto(); + + when(userRepository.existsByEmail(requestDto.getEmail())).thenReturn(false); + when(userMapper.toModel(requestDto)).thenReturn(TestUtil.createUser()); + when(passwordEncoder.encode(anyString())).thenReturn("encodedPassword"); + when(roleRepository.findByName(Role.RoleName.ROLE_USER)) + .thenReturn(Optional.empty()); + + EntityNotFoundException ex = + assertThrows(EntityNotFoundException.class, + () -> userService.save(requestDto)); + + assertEquals( + "Role " + Role.RoleName.ROLE_USER + " not found", + ex.getMessage()); + + verify(userRepository).existsByEmail(requestDto.getEmail()); + verify(roleRepository).findByName(Role.RoleName.ROLE_USER); + verifyNoMoreInteractions(userRepository, userMapper, + passwordEncoder, roleRepository, shoppingCartService); + } +} \ No newline at end of file diff --git a/src/test/java/com/origin/bookstore/util/TestConstants.java b/src/test/java/com/origin/bookstore/util/TestConstants.java new file mode 100644 index 0000000..269f00f --- /dev/null +++ b/src/test/java/com/origin/bookstore/util/TestConstants.java @@ -0,0 +1,73 @@ +package com.origin.bookstore.util; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +public class TestConstants { + public static final String ADD_USER_PATH = + "/database/users/add-user-to-users-table.sql"; + public static final String ADD_ADMIN_PATH = + "/database/users/add-admin-to-users-table.sql"; + public static final String ADD_BOOK_PATH = + "/database/books/add-books-with-categories.sql"; + public static final String REMOVE_BOOK_PATH = + "/database/books/remove-books-with-categories.sql"; + public static final String ADD_CATEGORY_PATH = + "/database/categories/add-category-to-categories-table.sql"; + public static final String REMOVE_CATEGORY_PATH = + "/database/categories/remove-category-from-categories-table.sql"; + public static final String REMOVE_USERS_PATH = + "/database/users/remove-users-from-users-table.sql"; + public static final String ADD_SHOPPINGCART_PATH = + "/database/shoppingcarts/add-shopping-cart-with-user-to-tables.sql"; + public static final String REMOVE_SHOPPINGCART_PATH = + "/database/shoppingcarts/remove-shopping-cart-with-user-from-tables.sql"; + public static final String ADD_ORDER_PATH = + "/database/orders/add-order-to-orders-table.sql"; + public static final String REMOVE_ORDER_PATH = + "/database/orders/remove-order-from-orders-table.sql"; + public static final String CLEANUP_DB_PATH = + "/database/cleanup-db.sql"; + + public static final String ADMIN_ROLE = "ADMIN"; + public static final String USER_ROLE = "USER"; + public static final String $_TOTAL_ELEMENTS = "$.totalElements"; + public static final String $_CONTENT = "$.content"; + public static final String $_ID = "$.id"; + public static final String $_QUANTITY = "$.quantity"; + public static final String $_STATUS = "$.status"; + public static final String $_TOTAL = "$.total"; + + public static final String API_CATEGORY_PATH = + "/categories"; + public static final String API_CATEGORY_PATH_ID = + "/categories/{id}"; + public static final String CATEGORY_ID_BOOKS_API_PATH_ID = + "/categories/{id}/books"; + public static final String NAME_JSON_PATH = + "$.name"; + public static final String REGISTRATION_PATH = + "/auth/registration"; + public static final String LOGIN_PATH = + "/auth/login"; + public static final String API_BOOKS_PATH = + "/books"; + public static final String API_BOOKS_PATH_ID = + "/books/{id}"; + public static final String API_BOOKS_SEARCH_PATH = + "/books/search"; + public static final String ORDER_ID_ITEMS_ID_URL = + "/orders/{orderId}/items/{id}"; + public static final String ORDERS_ORDER_ID_ITEMS_URL = + "/orders/{orderId}/items"; + public static final String ORDERS_ID_URL = + "/orders/{id}"; + public static final String ORDERS_URL = + "/orders"; + public static final String CART_URL = + "/cart"; + public static final String CART_ITEMS_CART_ITEM_ID_URL = + "/cart/items/{cartItemId}"; + + public static final Pageable pageable = PageRequest.of(0, 10); +} diff --git a/src/test/java/com/origin/bookstore/util/TestUtil.java b/src/test/java/com/origin/bookstore/util/TestUtil.java index 8fc94b2..68b71ab 100644 --- a/src/test/java/com/origin/bookstore/util/TestUtil.java +++ b/src/test/java/com/origin/bookstore/util/TestUtil.java @@ -2,11 +2,29 @@ import com.origin.bookstore.dto.book.BookDto; import com.origin.bookstore.dto.book.CreateBookRequestDto; +import com.origin.bookstore.dto.cartitem.CartItemRequestDto; +import com.origin.bookstore.dto.cartitem.CartItemResponseDto; +import com.origin.bookstore.dto.cartitem.UpdateCartItemRequestDto; import com.origin.bookstore.dto.category.CategoryDto; import com.origin.bookstore.dto.category.CreateCategoryRequestDto; +import com.origin.bookstore.dto.order.OrderRequestDto; +import com.origin.bookstore.dto.order.OrderResponseDto; +import com.origin.bookstore.dto.order.UpdateOrderStatusRequestDto; +import com.origin.bookstore.dto.orderitem.OrderItemResponseDto; +import com.origin.bookstore.dto.shoppingcart.ShoppingCartResponseDto; +import com.origin.bookstore.dto.user.UserRegistrationRequestDto; +import com.origin.bookstore.dto.user.UserResponseDto; import com.origin.bookstore.model.Book; +import com.origin.bookstore.model.CartItem; import com.origin.bookstore.model.Category; +import com.origin.bookstore.model.Order; +import com.origin.bookstore.model.OrderItem; +import com.origin.bookstore.model.Role; +import com.origin.bookstore.model.ShoppingCart; +import com.origin.bookstore.model.User; + import java.math.BigDecimal; +import java.time.LocalDateTime; import java.util.Set; public class TestUtil { @@ -59,4 +77,100 @@ public static CategoryDto createCategoryDto() { "Books for chemistry" ); } + + public static User createUser() { + return User.builder() + .email("arthur_morgan@gmail.com") + .password("fe054fdb41fe50de342640ecda4c8d" + + "d111c17db843b0c2afad053dcfcc4b5a4f") + .firstName("Arthur") + .lastName("Morgan") + .build(); + } + + public static CartItem createCartItem() { + return CartItem.builder() + .build(); + } + + public static ShoppingCart createShoppingCart() { + return ShoppingCart.builder() + .build(); + } + + public static ShoppingCartResponseDto createShoppingCartResponseDto() { + return new ShoppingCartResponseDto(); + } + + public static CartItemResponseDto createCartItemResponseDto() { + return new CartItemResponseDto( + 1L, + 1L, + "BookName", + 10 + ); + } + + public static CartItemRequestDto createCartItemRequestDto() { + return new CartItemRequestDto( + 1L, + 10 + ); + } + + public static UpdateCartItemRequestDto createUpdateCartItemRequestDto() { + return new UpdateCartItemRequestDto(10); + } + + public static UserRegistrationRequestDto createUserRegistrationRequestDto() { + return UserRegistrationRequestDto.builder() + .email("arthur_morgan@gmail.com") + .password("somePassword") + .repeatPassword("somePassword") + .firstName("Arthur") + .lastName("Morgan") + .build(); + } + + public static UserResponseDto createUserResponseDto() { + return UserResponseDto.builder() + .email("arthur_morgan@gmail.com") + .password("baae90bd064867ab28f034d6ed40ef14684012e4c0567181eaee3494a2358695") + .firstName("Arthur") + .lastName("Morgan") + .build(); + } + + public static Order createOrder() { + return Order.builder() + .id(5L) + .orderDateTime(LocalDateTime.now()) + .total(BigDecimal.valueOf(66)) + .shippingAddress("3828 Piermont Dr NE, Albuquerque, New Mexico") + .build(); + } + + public static OrderRequestDto createOrderRequestDto() { + return new OrderRequestDto("3828 Piermont Dr NE, Albuquerque, New Mexico"); + } + + public static OrderResponseDto createOrderResponseDto() { + return OrderResponseDto.builder() + .orderDateTime(LocalDateTime.now()) + .build(); + } + + public static OrderItem createOrderItem() { + return OrderItem.builder() + .price(BigDecimal.valueOf(10)) + .build(); + } + + public static OrderItemResponseDto createOrderItemResponseDto() { + return new OrderItemResponseDto(1L, 1L, 5); + } + + public static UpdateOrderStatusRequestDto createUpdateOrderStatusRequestDto() { + return new UpdateOrderStatusRequestDto("DELIVERED"); + } } diff --git a/src/test/resources/database/books/remove-books-with-categories.sql b/src/test/resources/database/books/remove-books-with-categories.sql deleted file mode 100644 index 255b1e6..0000000 --- a/src/test/resources/database/books/remove-books-with-categories.sql +++ /dev/null @@ -1,5 +0,0 @@ -DELETE FROM books_categories; - -DELETE FROM books; - -DELETE FROM categories; diff --git a/src/test/resources/database/categories/remove-category-from-categories-table.sql b/src/test/resources/database/categories/remove-category-from-categories-table.sql deleted file mode 100644 index b5685e7..0000000 --- a/src/test/resources/database/categories/remove-category-from-categories-table.sql +++ /dev/null @@ -1 +0,0 @@ -DELETE FROM categories; diff --git a/src/test/resources/database/cleanup-db.sql b/src/test/resources/database/cleanup-db.sql new file mode 100644 index 0000000..9b369e4 --- /dev/null +++ b/src/test/resources/database/cleanup-db.sql @@ -0,0 +1,9 @@ +DELETE FROM order_items; +DELETE FROM orders; +DELETE FROM cart_items; +DELETE FROM shopping_carts; +DELETE FROM books_categories; +DELETE FROM books; +DELETE FROM users_roles; +DELETE FROM users; +DELETE FROM categories; \ No newline at end of file diff --git a/src/test/resources/database/orders/add-order-to-orders-table.sql b/src/test/resources/database/orders/add-order-to-orders-table.sql new file mode 100644 index 0000000..30552f8 --- /dev/null +++ b/src/test/resources/database/orders/add-order-to-orders-table.sql @@ -0,0 +1,14 @@ +INSERT INTO users (id, email, password, first_name, last_name, shipping_address) + VALUES (10, 'user@gmail.com', '$2a$10$2V9pW1S6G7H/vV9G.V9G.OuY.M.WqXoKkC1XoKkC1XoKkC1XoKkC', 'Bob', 'Developer', 'Moldova, Chisinau'); + +INSERT INTO users_roles (user_id, role_id) +VALUES (10, 2); + +INSERT INTO books (id, title, author, isbn, price, description) +VALUES (10, 'Thinking in Java', 'Bruce Eckel', '978-0131872486', 49.99, 'Classic Java book'); + +INSERT INTO orders (id, user_id, status, total, order_date_time, shipping_address) +VALUES (10, 10, 'PENDING', 49.99, '2026-04-10 14:00:00', 'Moldova, Chisinau'); + +INSERT INTO order_items (id, order_id, book_id, quantity, price) +VALUES (10, 10, 10, 1, 49.99); \ No newline at end of file diff --git a/src/test/resources/database/shoppingcarts/add-shopping-cart-with-user-to-tables.sql b/src/test/resources/database/shoppingcarts/add-shopping-cart-with-user-to-tables.sql new file mode 100644 index 0000000..a4623ae --- /dev/null +++ b/src/test/resources/database/shoppingcarts/add-shopping-cart-with-user-to-tables.sql @@ -0,0 +1,14 @@ +INSERT INTO users (id, email, password, first_name, last_name) +VALUES (5, 'user@gmail.com', '9df52223ed4894c4c3b774550e00819375540a579843eea778417d78cebb9f4b', 'John', 'Doe'); + +INSERT INTO users_roles (user_id, role_id) +VALUES (5, 2); + +INSERT INTO shopping_carts (id, user_id) +VALUES (5, 5); + +INSERT INTO books (id, title, author, isbn, price) +VALUES (2, 'name', 'author', '284-1843729473', 25.99); + +INSERT INTO cart_items (id, shopping_cart_id, book_id, quantity) +VALUES (3, 5, 2, 10) \ No newline at end of file diff --git a/src/test/resources/database/users/add-admin-to-users-table.sql b/src/test/resources/database/users/add-admin-to-users-table.sql new file mode 100644 index 0000000..c21e69e --- /dev/null +++ b/src/test/resources/database/users/add-admin-to-users-table.sql @@ -0,0 +1,5 @@ +INSERT INTO users (id, email, password, first_name, last_name) +VALUES (3, 'admin@gmail.com', '9df52223ed4894c4c3b774550e00819375540a579843eea778417d78cebb9f4b', 'Admin', 'Admin'); + +INSERT INTO users_roles (user_id, role_id) +VALUES (3, 1) \ No newline at end of file diff --git a/src/test/resources/database/users/add-user-to-users-table.sql b/src/test/resources/database/users/add-user-to-users-table.sql new file mode 100644 index 0000000..55b7c85 --- /dev/null +++ b/src/test/resources/database/users/add-user-to-users-table.sql @@ -0,0 +1,5 @@ +INSERT INTO users (id, email, password, first_name, last_name) +VALUES (7, 'rudycooper@gmail.com', '$2a$08$n4TvfZDh6IH6QnJ9wQrAzO6gW3Yq6dt0QIltCOD4FO9pIX9YOP5C2', 'Rudy', 'Cooper'); + +INSERT INTO users_roles (user_id, role_id) +VALUES (7, 2); \ No newline at end of file