From 6c7af2e953c5c7c123ce2e405b183e8c49086124 Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 17:58:55 +0530 Subject: [PATCH] feat : use vlads BaseJpaRepository and polish (#1592) * feat : use vlads BaseJpaRepository and polish * implement code review comments * adds test case --- jpa/boot-data-customsequence/pom.xml | 2 +- .../custom/sequence/config/JpaConfig.java | 11 +++ .../custom/sequence/config/SwaggerConfig.java | 2 +- .../custom/sequence/config/WebMvcConfig.java | 3 +- .../custom/sequence/entities/Customer.java | 12 +++- .../custom/sequence/entities/Order.java | 5 +- .../sequence/mapper/CustomerMapper.java | 70 ++++++++++++++++++- .../custom/sequence/mapper/OrderMapper.java | 29 +++++++- .../model/request/CustomerRequest.java | 7 ++ .../sequence/model/request/OrderRequest.java | 8 +++ .../model/response/CustomerResponse.java | 16 ++++- .../CustomerResponseWithOutOrder.java | 3 + .../model/response/OrderResponse.java | 2 +- .../OrderResponseWithOutCustomer.java | 10 +++ .../repositories/CustomerRepository.java | 6 +- .../repositories/OrderRepository.java | 12 +++- .../sequence/services/CustomerService.java | 36 ++++++++-- .../sequence/services/OrderService.java | 41 +++++++++-- .../custom/sequence/web/api/CustomerAPI.java | 26 ++++++- .../web/controllers/CustomerController.java | 16 ++--- .../web/controllers/OrderController.java | 22 +++--- .../custom/sequence/SchemaValidationTest.java | 3 +- .../services/CustomerServiceTest.java | 12 +++- .../sequence/services/OrderServiceTest.java | 38 ++++++++-- .../web/controllers/CustomerControllerIT.java | 2 +- .../controllers/CustomerControllerTest.java | 58 ++++++++++----- .../web/controllers/OrderControllerIT.java | 54 +++++++++----- .../web/controllers/OrderControllerTest.java | 53 +++++++------- .../src/test/resources/logback-test.xml | 30 ++++---- 29 files changed, 455 insertions(+), 134 deletions(-) create mode 100644 jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/JpaConfig.java create mode 100644 jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/request/CustomerRequest.java create mode 100644 jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/request/OrderRequest.java create mode 100644 jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/CustomerResponseWithOutOrder.java create mode 100644 jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/OrderResponseWithOutCustomer.java diff --git a/jpa/boot-data-customsequence/pom.xml b/jpa/boot-data-customsequence/pom.xml index 8de37b006..534018cbb 100644 --- a/jpa/boot-data-customsequence/pom.xml +++ b/jpa/boot-data-customsequence/pom.xml @@ -224,7 +224,7 @@ - 1.24.0 + 1.25.2 diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/JpaConfig.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/JpaConfig.java new file mode 100644 index 000000000..b742fc25f --- /dev/null +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/JpaConfig.java @@ -0,0 +1,11 @@ +package com.example.custom.sequence.config; + +import io.hypersistence.utils.spring.repository.BaseJpaRepositoryImpl; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@Configuration +@EnableJpaRepositories( + repositoryBaseClass = BaseJpaRepositoryImpl.class, + basePackages = "com.example.custom.sequence.repositories") +public class JpaConfig {} diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/SwaggerConfig.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/SwaggerConfig.java index 3aeb9e3ca..6ba9ad16d 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/SwaggerConfig.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/SwaggerConfig.java @@ -9,4 +9,4 @@ @OpenAPIDefinition( info = @Info(title = "boot-jpa-customsequence", version = "v1"), servers = @Server(url = "/")) -public class SwaggerConfig {} +class SwaggerConfig {} diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/WebMvcConfig.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/WebMvcConfig.java index 27b364cab..0830a77fc 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/WebMvcConfig.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/WebMvcConfig.java @@ -5,9 +5,10 @@ import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -@Configuration +@Configuration(proxyBeanMethods = false) @RequiredArgsConstructor public class WebMvcConfig implements WebMvcConfigurer { + private final ApplicationProperties properties; @Override diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/Customer.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/Customer.java index 7eb30749b..e43c647e6 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/Customer.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/Customer.java @@ -9,7 +9,6 @@ import jakarta.persistence.Id; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; -import jakarta.validation.constraints.NotEmpty; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -33,7 +32,6 @@ public class Customer { private String id; @Column(nullable = false) - @NotEmpty(message = "Text cannot be empty") private String text; @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true) @@ -43,6 +41,16 @@ public Customer(String text) { this.text = text; } + public void addOrder(Order order) { + orders.add(order); + order.setCustomer(this); + } + + public void removeOrder(Order removedOrder) { + orders.remove(removedOrder); + removedOrder.setCustomer(null); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/Order.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/Order.java index 6966f39c0..efb8638e1 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/Order.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/entities/Order.java @@ -10,7 +10,6 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; -import jakarta.validation.constraints.NotEmpty; import java.util.Objects; import lombok.AllArgsConstructor; import lombok.Getter; @@ -32,7 +31,6 @@ public class Order { private String id; @Column(nullable = false) - @NotEmpty(message = "Text cannot be empty") private String text; @ManyToOne(fetch = FetchType.LAZY) @@ -44,7 +42,8 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; Order order = (Order) o; - return id != null && Objects.equals(id, order.id); + return Objects.equals(text, order.text) + && Objects.equals(customer.getId(), order.customer.getId()); } @Override diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/mapper/CustomerMapper.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/mapper/CustomerMapper.java index 21974eaa5..351af323a 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/mapper/CustomerMapper.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/mapper/CustomerMapper.java @@ -1,12 +1,80 @@ package com.example.custom.sequence.mapper; import com.example.custom.sequence.entities.Customer; +import com.example.custom.sequence.entities.Order; +import com.example.custom.sequence.model.request.CustomerRequest; import com.example.custom.sequence.model.response.CustomerResponse; +import com.example.custom.sequence.repositories.OrderRepository; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; @Service public class CustomerMapper { + + private final OrderMapper orderMapper; + private final OrderRepository orderRepository; + + public CustomerMapper(OrderMapper orderMapper, OrderRepository orderRepository) { + this.orderMapper = orderMapper; + this.orderRepository = orderRepository; + } + public CustomerResponse mapToResponse(Customer saved) { - return new CustomerResponse(saved.getId(), saved.getText()); + return new CustomerResponse( + saved.getId(), + saved.getText(), + orderMapper.mapToResponseListWithOutCustomer(saved.getOrders())); + } + + public Customer mapToEntity(CustomerRequest customerRequest) { + Customer customer = new Customer(customerRequest.text()); + customerRequest + .orders() + .forEach(orderRequest -> customer.addOrder(orderMapper.mapToEntity(orderRequest))); + return customer; + } + + public void updateCustomerFromRequest(CustomerRequest customerRequest, Customer foundCustomer) { + foundCustomer.setText(customerRequest.text()); + List removedOrders = new ArrayList<>(foundCustomer.getOrders()); + List ordersFromRequest = + customerRequest.orders().stream() + .map( + orderRequest -> + orderMapper.mapToEntityWithCustomer( + orderRequest, foundCustomer)) + .collect(Collectors.toList()); + removedOrders.removeAll(ordersFromRequest); + + for (Order removedOrder : removedOrders) { + foundCustomer.removeOrder(removedOrder); + } + + List newOrders = new ArrayList<>(ordersFromRequest); + newOrders.removeAll(foundCustomer.getOrders()); + + ordersFromRequest.removeAll(newOrders); + + for (Order existingOrder : ordersFromRequest) { + existingOrder.setCustomer(foundCustomer); + // manually set the id of the existing order to avoid creating a new order instead of + // updating the existing one + for (Order foundOrder : foundCustomer.getOrders()) { + if (foundOrder.getText().equals(existingOrder.getText())) { + existingOrder.setId(foundOrder.getId()); + break; + } + } + Order mergedOrder = orderRepository.merge(existingOrder); + foundCustomer + .getOrders() + .set(foundCustomer.getOrders().indexOf(mergedOrder), mergedOrder); + } + + for (Order newOrder : newOrders) { + foundCustomer.addOrder(newOrder); + } } } diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/mapper/OrderMapper.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/mapper/OrderMapper.java index 307df8ad8..8d4efbcf4 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/mapper/OrderMapper.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/mapper/OrderMapper.java @@ -2,8 +2,11 @@ import com.example.custom.sequence.entities.Customer; import com.example.custom.sequence.entities.Order; -import com.example.custom.sequence.model.response.CustomerResponse; +import com.example.custom.sequence.model.request.OrderRequest; +import com.example.custom.sequence.model.response.CustomerResponseWithOutOrder; import com.example.custom.sequence.model.response.OrderResponse; +import com.example.custom.sequence.model.response.OrderResponseWithOutCustomer; +import java.util.List; import org.springframework.stereotype.Service; @Service @@ -14,6 +17,28 @@ public OrderResponse getOrderResponse(Order order) { return new OrderResponse( order.getId(), order.getText(), - new CustomerResponse(customer.getId(), customer.getText())); + new CustomerResponseWithOutOrder(customer.getId(), customer.getText())); + } + + public Order mapToEntity(OrderRequest orderRequest) { + Order order = new Order(); + order.setText(orderRequest.text()); + return order; + } + + public List mapToResponseList(List orders) { + return orders.stream().map(this::getOrderResponse).toList(); + } + + public List mapToResponseListWithOutCustomer(List orders) { + return orders.stream() + .map(order -> new OrderResponseWithOutCustomer(order.getId(), order.getText())) + .toList(); + } + + public Order mapToEntityWithCustomer(OrderRequest orderRequest, Customer foundCustomer) { + Order order = mapToEntity(orderRequest); + order.setCustomer(foundCustomer); + return order; } } diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/request/CustomerRequest.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/request/CustomerRequest.java new file mode 100644 index 000000000..1ff7719d6 --- /dev/null +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/request/CustomerRequest.java @@ -0,0 +1,7 @@ +package com.example.custom.sequence.model.request; + +import jakarta.validation.constraints.NotBlank; +import java.util.List; + +public record CustomerRequest( + @NotBlank(message = "Text cannot be empty") String text, List orders) {} diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/request/OrderRequest.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/request/OrderRequest.java new file mode 100644 index 000000000..88b07373e --- /dev/null +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/request/OrderRequest.java @@ -0,0 +1,8 @@ +package com.example.custom.sequence.model.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; + +public record OrderRequest( + @NotEmpty(message = "Text cannot be empty") String text, + @NotBlank(message = "CustomerId cannot be blank") String customerId) {} diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/CustomerResponse.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/CustomerResponse.java index ca15a1cf7..75e5fa860 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/CustomerResponse.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/CustomerResponse.java @@ -1,3 +1,17 @@ package com.example.custom.sequence.model.response; -public record CustomerResponse(String id, String text) {} +import java.util.List; + +/** + * Response DTO representing customer information with their associated orders. + * + * @param id Customer's unique identifier + * @param text Customer's descriptive text + * @param orderResponses List of associated orders, never null but may be empty + */ +public record CustomerResponse( + String id, String text, List orderResponses) { + public CustomerResponse { + orderResponses = orderResponses == null ? List.of() : orderResponses; + } +} diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/CustomerResponseWithOutOrder.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/CustomerResponseWithOutOrder.java new file mode 100644 index 000000000..771c242fc --- /dev/null +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/CustomerResponseWithOutOrder.java @@ -0,0 +1,3 @@ +package com.example.custom.sequence.model.response; + +public record CustomerResponseWithOutOrder(String id, String text) {} diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/OrderResponse.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/OrderResponse.java index 706c10cf3..b0a2f9d07 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/OrderResponse.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/OrderResponse.java @@ -1,3 +1,3 @@ package com.example.custom.sequence.model.response; -public record OrderResponse(String id, String text, CustomerResponse customer) {} +public record OrderResponse(String id, String text, CustomerResponseWithOutOrder customer) {} diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/OrderResponseWithOutCustomer.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/OrderResponseWithOutCustomer.java new file mode 100644 index 000000000..ed0ecdd0d --- /dev/null +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/response/OrderResponseWithOutCustomer.java @@ -0,0 +1,10 @@ +package com.example.custom.sequence.model.response; + +/** + * Response model representing an order without its associated customer details. Used in scenarios + * where customer information is not needed in the response. + * + * @param id The unique identifier of the order + * @param orderDescription The description or details of the order + */ +public record OrderResponseWithOutCustomer(String id, String orderDescription) {} diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/repositories/CustomerRepository.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/repositories/CustomerRepository.java index d4aa23efb..58f31da1d 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/repositories/CustomerRepository.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/repositories/CustomerRepository.java @@ -1,16 +1,16 @@ package com.example.custom.sequence.repositories; import com.example.custom.sequence.entities.Customer; +import io.hypersistence.utils.spring.repository.BaseJpaRepository; import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -public interface CustomerRepository extends JpaRepository { +public interface CustomerRepository extends BaseJpaRepository { @EntityGraph(attributePaths = "orders") Optional findById(String id); @@ -26,4 +26,6 @@ public interface CustomerRepository extends JpaRepository { @Query(value = "select c.id from Customer c ", countQuery = "select count(c) from Customer c") Page findAllCustomerIds(Pageable pageable); + + void deleteAllInBatch(); } diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/repositories/OrderRepository.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/repositories/OrderRepository.java index 8dec6158d..ed3086d17 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/repositories/OrderRepository.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/repositories/OrderRepository.java @@ -1,13 +1,21 @@ package com.example.custom.sequence.repositories; import com.example.custom.sequence.entities.Order; +import io.hypersistence.utils.spring.repository.BaseJpaRepository; import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.ListPagingAndSortingRepository; import org.springframework.data.repository.query.Param; -public interface OrderRepository extends JpaRepository { +public interface OrderRepository + extends BaseJpaRepository, ListPagingAndSortingRepository { @Query("select o from Order o join fetch o.customer where o.id = :id") Optional findById(@Param("id") String id); + + void deleteAllInBatch(); + + @EntityGraph(attributePaths = "customer") + Optional findByIdAndCustomer_Id(String id, String customerId); } diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/services/CustomerService.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/services/CustomerService.java index 851b2681d..14e9de331 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/services/CustomerService.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/services/CustomerService.java @@ -2,12 +2,12 @@ import com.example.custom.sequence.entities.Customer; import com.example.custom.sequence.mapper.CustomerMapper; +import com.example.custom.sequence.model.request.CustomerRequest; import com.example.custom.sequence.model.response.CustomerResponse; import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.repositories.CustomerRepository; import java.util.List; import java.util.Optional; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -16,14 +16,17 @@ import org.springframework.transaction.annotation.Transactional; @Service -@Transactional -@RequiredArgsConstructor +@Transactional(readOnly = true) public class CustomerService { private final CustomerRepository customerRepository; private final CustomerMapper customerMapper; - @Transactional(readOnly = true) + public CustomerService(CustomerRepository customerRepository, CustomerMapper customerMapper) { + this.customerRepository = customerRepository; + this.customerMapper = customerMapper; + } + public PagedResult findAllCustomers( int pageNo, int pageSize, String sortBy, String sortDir) { Sort sort = @@ -52,12 +55,33 @@ public Optional findCustomerById(String id) { return customerRepository.findById(id).map(customerMapper::mapToResponse); } - public CustomerResponse saveCustomer(Customer customer) { - Customer saved = customerRepository.save(customer); + @Transactional + public CustomerResponse saveCustomer(CustomerRequest customerRequest) { + Customer customer = customerMapper.mapToEntity(customerRequest); + Customer saved = customerRepository.persist(customer); return customerMapper.mapToResponse(saved); } + @Transactional + public Optional updateCustomerById( + String id, CustomerRequest customerRequest) { + return customerRepository + .findById(id) + .map( + foundCustomer -> { + customerMapper.updateCustomerFromRequest( + customerRequest, foundCustomer); + return customerMapper.mapToResponse( + customerRepository.merge(foundCustomer)); + }); + } + + @Transactional public void deleteCustomerById(String id) { customerRepository.deleteById(id); } + + public Optional findById(String customerId) { + return customerRepository.findById(customerId); + } } diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/services/OrderService.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/services/OrderService.java index f1caf7e3f..7621cd5a2 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/services/OrderService.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/services/OrderService.java @@ -2,12 +2,12 @@ import com.example.custom.sequence.entities.Order; import com.example.custom.sequence.mapper.OrderMapper; +import com.example.custom.sequence.model.request.OrderRequest; import com.example.custom.sequence.model.response.OrderResponse; import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.repositories.OrderRepository; import java.util.List; import java.util.Optional; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -16,13 +16,22 @@ import org.springframework.transaction.annotation.Transactional; @Service -@Transactional -@RequiredArgsConstructor +@Transactional(readOnly = true) public class OrderService { private final OrderRepository orderRepository; + private final CustomerService customerService; private final OrderMapper orderMapper; + public OrderService( + OrderRepository orderRepository, + CustomerService customerService, + OrderMapper orderMapper) { + this.orderRepository = orderRepository; + this.customerService = customerService; + this.orderMapper = orderMapper; + } + public PagedResult findAllOrders( int pageNo, int pageSize, String sortBy, String sortDir) { Sort sort = @@ -52,10 +61,32 @@ public Optional findOrderById(String id) { return orderRepository.findById(id).map(orderMapper::getOrderResponse); } - public OrderResponse saveOrder(Order order) { - return orderMapper.getOrderResponse(orderRepository.save(order)); + @Transactional + public Optional saveOrder(OrderRequest orderRequest) { + return customerService + .findById(orderRequest.customerId()) + .map( + customer -> { + Order order = new Order(); + order.setText(orderRequest.text()); + order.setCustomer(customer); + return orderMapper.getOrderResponse(orderRepository.persist(order)); + }); + } + + @Transactional + public Optional updateOrderById(String id, OrderRequest orderRequest) { + return orderRepository + .findByIdAndCustomer_Id(id, orderRequest.customerId()) + .map( + order -> { + order.setText(orderRequest.text()); + return orderMapper.getOrderResponse( + orderRepository.mergeAndFlush(order)); + }); } + @Transactional public void deleteOrderById(String id) { orderRepository.deleteById(id); } diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/web/api/CustomerAPI.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/web/api/CustomerAPI.java index 448b1c4e0..22f4490ca 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/web/api/CustomerAPI.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/web/api/CustomerAPI.java @@ -1,12 +1,14 @@ package com.example.custom.sequence.web.api; import com.example.custom.sequence.entities.Customer; +import com.example.custom.sequence.model.request.CustomerRequest; import com.example.custom.sequence.model.response.CustomerResponse; import com.example.custom.sequence.model.response.PagedResult; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -86,6 +88,28 @@ ResponseEntity getCustomerById( @Operation( operationId = "createCustomer", tags = {"customer-controller"}, + requestBody = + @io.swagger.v3.oas.annotations.parameters.RequestBody( + required = true, + content = + @Content( + schema = + @Schema(implementation = CustomerRequest.class), + examples = { + @ExampleObject( + value = + """ + { + "text": "sample customer", + "orders": [ + { + // order fields + "text": "sample order" + } + ] + } + """) + })), responses = { @ApiResponse( responseCode = "201", @@ -104,5 +128,5 @@ ResponseEntity getCustomerById( schema = @Schema(implementation = ProblemDetail.class)) }) }) - CustomerResponse createCustomer(@Valid @RequestBody(required = true) Customer customer); + CustomerResponse createCustomer(@Valid @RequestBody(required = true) CustomerRequest customer); } diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/web/controllers/CustomerController.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/web/controllers/CustomerController.java index 4c30c97b9..b9e075e96 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/web/controllers/CustomerController.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/web/controllers/CustomerController.java @@ -1,6 +1,7 @@ package com.example.custom.sequence.web.controllers; import com.example.custom.sequence.entities.Customer; +import com.example.custom.sequence.model.request.CustomerRequest; import com.example.custom.sequence.model.response.CustomerResponse; import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.services.CustomerService; @@ -58,20 +59,17 @@ public ResponseEntity getCustomerById(@PathVariable String id) @PostMapping @ResponseStatus(HttpStatus.CREATED) @Override - public CustomerResponse createCustomer(@RequestBody @Validated Customer customer) { - return customerService.saveCustomer(customer); + public CustomerResponse createCustomer( + @RequestBody @Validated CustomerRequest customerRequest) { + return customerService.saveCustomer(customerRequest); } @PutMapping("/{id}") public ResponseEntity updateCustomer( - @PathVariable String id, @RequestBody Customer customer) { + @PathVariable String id, @RequestBody CustomerRequest customerRequest) { return customerService - .findCustomerById(id) - .map( - customerObj -> { - customer.setId(id); - return ResponseEntity.ok(customerService.saveCustomer(customer)); - }) + .updateCustomerById(id, customerRequest) + .map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.notFound().build()); } diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/web/controllers/OrderController.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/web/controllers/OrderController.java index c926466ca..9b7a3fcb9 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/web/controllers/OrderController.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/web/controllers/OrderController.java @@ -1,6 +1,6 @@ package com.example.custom.sequence.web.controllers; -import com.example.custom.sequence.entities.Order; +import com.example.custom.sequence.model.request.OrderRequest; import com.example.custom.sequence.model.response.OrderResponse; import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.services.OrderService; @@ -17,7 +17,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @RestController @@ -53,21 +52,20 @@ public ResponseEntity getOrderById(@PathVariable String id) { } @PostMapping - @ResponseStatus(HttpStatus.CREATED) - public OrderResponse createOrder(@RequestBody @Validated Order order) { - return orderService.saveOrder(order); + public ResponseEntity createOrder( + @RequestBody @Validated OrderRequest orderRequest) { + return orderService + .saveOrder(orderRequest) + .map(order -> ResponseEntity.status(HttpStatus.CREATED).body(order)) + .orElseGet(() -> ResponseEntity.notFound().build()); } @PutMapping("/{id}") public ResponseEntity updateOrder( - @PathVariable String id, @RequestBody Order order) { + @PathVariable String id, @RequestBody OrderRequest orderRequest) { return orderService - .findOrderById(id) - .map( - orderObj -> { - order.setId(id); - return ResponseEntity.ok(orderService.saveOrder(order)); - }) + .updateOrderById(id, orderRequest) + .map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.notFound().build()); } diff --git a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/SchemaValidationTest.java b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/SchemaValidationTest.java index 5780958ab..e645aee45 100644 --- a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/SchemaValidationTest.java +++ b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/SchemaValidationTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.example.custom.sequence.common.ContainersConfig; +import com.example.custom.sequence.config.JpaConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; import org.junit.jupiter.api.Test; @@ -12,7 +13,7 @@ import org.springframework.context.annotation.Import; @DataJpaTest(properties = {"spring.jpa.hibernate.ddl-auto=validate"}) -@Import(ContainersConfig.class) +@Import({ContainersConfig.class, JpaConfig.class}) @AutoConfigureTestDatabase class SchemaValidationTest { diff --git a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/services/CustomerServiceTest.java b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/services/CustomerServiceTest.java index 53500770e..571c5be6b 100644 --- a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/services/CustomerServiceTest.java +++ b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/services/CustomerServiceTest.java @@ -8,6 +8,7 @@ import com.example.custom.sequence.entities.Customer; import com.example.custom.sequence.mapper.CustomerMapper; +import com.example.custom.sequence.model.request.CustomerRequest; import com.example.custom.sequence.model.response.CustomerResponse; import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.repositories.CustomerRepository; @@ -72,10 +73,11 @@ void findCustomerById() { @Test void saveCustomer() { // given - given(customerRepository.save(getCustomer())).willReturn(getCustomer()); + given(customerMapper.mapToEntity(getCustomerRequest())).willReturn(getCustomer()); + given(customerRepository.persist(getCustomer())).willReturn(getCustomer()); given(customerMapper.mapToResponse(getCustomer())).willReturn(getCustomerResponse()); // when - CustomerResponse persistedCustomer = customerService.saveCustomer(getCustomer()); + CustomerResponse persistedCustomer = customerService.saveCustomer(getCustomerRequest()); // then assertThat(persistedCustomer).isNotNull(); assertThat(persistedCustomer.id()).isEqualTo("CUS_1"); @@ -99,7 +101,11 @@ private Customer getCustomer() { return customer; } + private CustomerRequest getCustomerRequest() { + return new CustomerRequest("junitTest", List.of()); + } + private CustomerResponse getCustomerResponse() { - return new CustomerResponse("CUS_1", "junitTest"); + return new CustomerResponse("CUS_1", "junitTest", List.of()); } } diff --git a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/services/OrderServiceTest.java b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/services/OrderServiceTest.java index 961f738fc..7572ae471 100644 --- a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/services/OrderServiceTest.java +++ b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/services/OrderServiceTest.java @@ -1,15 +1,18 @@ package com.example.custom.sequence.services; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.times; import static org.mockito.BDDMockito.verify; import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.Mockito.never; import com.example.custom.sequence.entities.Customer; import com.example.custom.sequence.entities.Order; import com.example.custom.sequence.mapper.OrderMapper; -import com.example.custom.sequence.model.response.CustomerResponse; +import com.example.custom.sequence.model.request.OrderRequest; +import com.example.custom.sequence.model.response.CustomerResponseWithOutOrder; import com.example.custom.sequence.model.response.OrderResponse; import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.repositories.OrderRepository; @@ -30,6 +33,7 @@ class OrderServiceTest { @Mock private OrderRepository orderRepository; + @Mock private CustomerService customerService; @Mock private OrderMapper orderMapper; @InjectMocks private OrderService orderService; @@ -73,14 +77,29 @@ void findOrderById() { @Test void saveOrder() { // given - given(orderRepository.save(getOrder())).willReturn(getOrder()); + given(customerService.findById("1")) + .willReturn(Optional.of(new Customer("1", "custText", List.of()))); + given(orderRepository.persist(getOrder())).willReturn(getOrder()); given(orderMapper.getOrderResponse(getOrder())).willReturn(getOrderResponse()); // when - OrderResponse persistedOrder = orderService.saveOrder(getOrder()); + Optional persistedOrder = orderService.saveOrder(getOrderReq()); // then - assertThat(persistedOrder).isNotNull(); - assertThat(persistedOrder.id()).isEqualTo("1"); - assertThat(persistedOrder.text()).isEqualTo("junitText"); + assertThat(persistedOrder.isPresent()).isNotNull(); + assertThat(persistedOrder.get().id()).isEqualTo("1"); + assertThat(persistedOrder.get().text()).isEqualTo("junitText"); + } + + @Test + void saveOrderWhenCustomerNotFound() { + // given + given(customerService.findById("1")).willReturn(Optional.empty()); + + // when + Optional persistedOrder = orderService.saveOrder(getOrderReq()); + + // then + assertThat(persistedOrder).isEmpty(); + verify(orderRepository, never()).persist(any()); } @Test @@ -105,6 +124,11 @@ private Order getOrder() { } private OrderResponse getOrderResponse() { - return new OrderResponse("1", "junitText", new CustomerResponse("1", "custText")); + return new OrderResponse( + "1", "junitText", new CustomerResponseWithOutOrder("1", "custText")); + } + + private OrderRequest getOrderReq() { + return new OrderRequest("junitText", "1"); } } diff --git a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/CustomerControllerIT.java b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/CustomerControllerIT.java index 82dcdf7af..5a4851292 100644 --- a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/CustomerControllerIT.java +++ b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/CustomerControllerIT.java @@ -36,7 +36,7 @@ void setUp() { customerList.add(new Customer("First Customer")); customerList.add(new Customer("Second Customer")); customerList.add(new Customer("Third Customer")); - customerList = customerRepository.saveAll(customerList); + customerList = customerRepository.persistAll(customerList); } @Test diff --git a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/CustomerControllerTest.java b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/CustomerControllerTest.java index b734bfc8b..1547a1bbb 100644 --- a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/CustomerControllerTest.java +++ b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/CustomerControllerTest.java @@ -16,6 +16,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.example.custom.sequence.entities.Customer; +import com.example.custom.sequence.model.request.CustomerRequest; import com.example.custom.sequence.model.response.CustomerResponse; import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.services.CustomerService; @@ -27,11 +28,11 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(controllers = CustomerController.class) @@ -40,7 +41,7 @@ class CustomerControllerTest { @Autowired private MockMvc mockMvc; - @MockBean private CustomerService customerService; + @MockitoBean private CustomerService customerService; @Autowired private ObjectMapper objectMapper; @@ -76,7 +77,7 @@ void shouldFetchAllCustomers() throws Exception { @Test void shouldFindCustomerById() throws Exception { String customerId = "CUS_1"; - CustomerResponse customer = new CustomerResponse(customerId, "text 1"); + CustomerResponse customer = new CustomerResponse(customerId, "text 1", List.of()); given(customerService.findCustomerById(customerId)).willReturn(Optional.of(customer)); this.mockMvc @@ -97,29 +98,31 @@ void shouldReturn404WhenFetchingNonExistingCustomer() throws Exception { @Test void shouldCreateNewCustomer() throws Exception { - given(customerService.saveCustomer(any(Customer.class))) - .willReturn(new CustomerResponse("CUS_1", "some text")); - Customer customer = new Customer("CUS_1", "some text", new ArrayList<>()); + CustomerRequest customerRequest = new CustomerRequest("some text", new ArrayList<>()); + + given(customerService.saveCustomer(any(CustomerRequest.class))) + .willReturn(new CustomerResponse("CUS_1", "some text", List.of())); + this.mockMvc .perform( post("/api/customers") .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(customer))) + .content(objectMapper.writeValueAsString(customerRequest))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id", notNullValue())) - .andExpect(jsonPath("$.text", is(customer.getText()))); + .andExpect(jsonPath("$.text", is(customerRequest.text()))); } @Test void shouldReturn400WhenCreateNewCustomerWithoutText() throws Exception { - Customer customer = new Customer(null, null, new ArrayList<>()); + CustomerRequest customerRequest = new CustomerRequest(null, new ArrayList<>()); this.mockMvc .perform( post("/api/customers") .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(customer))) + .content(objectMapper.writeValueAsString(customerRequest))) .andExpect(status().isBadRequest()) .andExpect(header().string("Content-Type", is("application/problem+json"))) .andExpect(jsonPath("$.type", is("about:blank"))) @@ -133,21 +136,42 @@ void shouldReturn400WhenCreateNewCustomerWithoutText() throws Exception { .andReturn(); } + @Test + void shouldReturn400WhenCreateNewCustomerWithEmptyText() throws Exception { + CustomerRequest customerRequest = new CustomerRequest("", new ArrayList<>()); + this.mockMvc + .perform( + post("/api/customers") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(customerRequest))) + .andExpect(status().isBadRequest()) + .andExpect(header().string("Content-Type", is("application/problem+json"))) + .andExpect(jsonPath("$.type", is("about:blank"))) + .andExpect(jsonPath("$.title", is("Constraint Violation"))) + .andExpect(jsonPath("$.status", is(400))) + .andExpect(jsonPath("$.detail", is("Invalid request content."))) + .andExpect(jsonPath("$.instance", is("/api/customers"))) + .andExpect(jsonPath("$.violations", hasSize(1))) + .andExpect(jsonPath("$.violations[0].field", is("text"))) + .andExpect(jsonPath("$.violations[0].message", is("Text cannot be empty"))); + } + @Test void shouldUpdateCustomer() throws Exception { String customerId = "CUS_1"; - CustomerResponse customer = new CustomerResponse(customerId, "Updated text"); - given(customerService.findCustomerById(customerId)).willReturn(Optional.of(customer)); - given(customerService.saveCustomer(any(Customer.class))) - .willReturn(new CustomerResponse("CUS_1", "Updated text")); + CustomerResponse customerResponse = + new CustomerResponse(customerId, "Updated text", List.of()); + CustomerRequest customerRequest = new CustomerRequest("Updated text", List.of()); + given(customerService.updateCustomerById(customerId, customerRequest)) + .willReturn(Optional.of(customerResponse)); this.mockMvc .perform( put("/api/customers/{id}", customerId) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(customer))) + .content(objectMapper.writeValueAsString(customerRequest))) .andExpect(status().isOk()) - .andExpect(jsonPath("$.text", is(customer.text()))); + .andExpect(jsonPath("$.text", is(customerResponse.text()))); } @Test @@ -167,7 +191,7 @@ void shouldReturn404WhenUpdatingNonExistingCustomer() throws Exception { @Test void shouldDeleteCustomer() throws Exception { String customerId = "CUS_1"; - CustomerResponse customer = new CustomerResponse(customerId, "Some text"); + CustomerResponse customer = new CustomerResponse(customerId, "Some text", List.of()); given(customerService.findCustomerById(customerId)).willReturn(Optional.of(customer)); doNothing().when(customerService).deleteCustomerById(customerId); diff --git a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/OrderControllerIT.java b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/OrderControllerIT.java index 179a88868..108d0a7f0 100644 --- a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/OrderControllerIT.java +++ b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/OrderControllerIT.java @@ -15,9 +15,9 @@ import com.example.custom.sequence.common.AbstractIntegrationTest; import com.example.custom.sequence.entities.Customer; import com.example.custom.sequence.entities.Order; +import com.example.custom.sequence.model.request.OrderRequest; import com.example.custom.sequence.repositories.CustomerRepository; import com.example.custom.sequence.repositories.OrderRepository; -import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,15 +36,22 @@ class OrderControllerIT extends AbstractIntegrationTest { @BeforeEach void setUp() { orderRepository.deleteAllInBatch(); - customerRepository.deleteAll(); - Customer cust = new Customer("customer1"); - customer = customerRepository.save(cust); - - orderList = new ArrayList<>(); - orderList.add(new Order(null, "First Order", customer)); - orderList.add(new Order(null, "Second Order", customer)); - orderList.add(new Order(null, "Third Order", customer)); - orderList = orderRepository.saveAll(orderList); + customerRepository.deleteAllInBatch(); + customer = createTestCustomer(); + orderList = createTestOrders(customer); + } + + private Customer createTestCustomer() { + return customerRepository.persist(new Customer("customer1")); + } + + private List createTestOrders(Customer customer) { + List orders = + List.of( + new Order(null, "First Order", customer), + new Order(null, "Second Order", customer), + new Order(null, "Third Order", customer)); + return orderRepository.persistAll(orders); } @Test @@ -76,7 +83,7 @@ void shouldFindOrderById() throws Exception { @Test void shouldCreateNewOrder() throws Exception { - Order order = new Order(null, "New Order", customer); + OrderRequest order = new OrderRequest("New Order", customer.getId()); this.mockMvc .perform( post("/api/orders") @@ -85,12 +92,25 @@ void shouldCreateNewOrder() throws Exception { .andExpect(status().isCreated()) .andExpect(jsonPath("$.id", notNullValue(), String.class)) .andExpect(jsonPath("$.id", hasLength(9))) - .andExpect(jsonPath("$.text", is(order.getText()))); + .andExpect(jsonPath("$.text", is(order.text()))); + } + + @Test + void shouldReturn400WhenUpdatingOrderWithInvalidCustomerId() throws Exception { + OrderRequest orderRequest = new OrderRequest("Updated Order", "INVALID_ID"); + Order order = orderList.getFirst(); + + this.mockMvc + .perform( + put("/api/orders/{id}", order.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(orderRequest))) + .andExpect(status().isNotFound()); } @Test void shouldReturn400WhenCreateNewOrderWithoutText() throws Exception { - Order order = new Order(null, null, null); + OrderRequest order = new OrderRequest(null, "CUS_1"); this.mockMvc .perform( @@ -115,17 +135,17 @@ void shouldReturn400WhenCreateNewOrderWithoutText() throws Exception { @Test void shouldUpdateOrder() throws Exception { - Order order = orderList.getFirst(); - order.setText("Updated Order"); + OrderRequest orderRequest = new OrderRequest("Updated Order", customer.getId()); + Order order = orderList.getFirst(); this.mockMvc .perform( put("/api/orders/{id}", order.getId()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(order))) + .content(objectMapper.writeValueAsString(orderRequest))) .andExpect(status().isOk()) .andExpect(jsonPath("$.id", is(order.getId()), String.class)) - .andExpect(jsonPath("$.text", is(order.getText()))); + .andExpect(jsonPath("$.text", is(orderRequest.text()))); } @Test diff --git a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/OrderControllerTest.java b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/OrderControllerTest.java index 5aed9b269..c18dc617c 100644 --- a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/OrderControllerTest.java +++ b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/web/controllers/OrderControllerTest.java @@ -4,7 +4,6 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.Matchers.hasSize; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.doNothing; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -17,7 +16,8 @@ import com.example.custom.sequence.entities.Customer; import com.example.custom.sequence.entities.Order; -import com.example.custom.sequence.model.response.CustomerResponse; +import com.example.custom.sequence.model.request.OrderRequest; +import com.example.custom.sequence.model.response.CustomerResponseWithOutOrder; import com.example.custom.sequence.model.response.OrderResponse; import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.services.OrderService; @@ -30,11 +30,11 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(controllers = OrderController.class) @@ -43,7 +43,7 @@ class OrderControllerTest { @Autowired private MockMvc mockMvc; - @MockBean private OrderService orderService; + @MockitoBean private OrderService orderService; @Autowired private ObjectMapper objectMapper; @@ -52,7 +52,7 @@ class OrderControllerTest { @BeforeEach void setUp() { - customer = new Customer(null, "customer1", new ArrayList<>()); + customer = new Customer("CUST_01", "customer1", new ArrayList<>()); this.orderList = new ArrayList<>(); this.orderList.add(new Order("1", "text 1", customer)); this.orderList.add(new Order("2", "text 2", customer)); @@ -80,11 +80,14 @@ void shouldFetchAllOrders() throws Exception { private static @NotNull PagedResult getOrderResponsePagedResult() { List orderResponseList = new ArrayList<>(); orderResponseList.add( - new OrderResponse("1", "text 1", new CustomerResponse("1", "customer1"))); + new OrderResponse( + "1", "text 1", new CustomerResponseWithOutOrder("1", "customer1"))); orderResponseList.add( - new OrderResponse("2", "text 2", new CustomerResponse("1", "customer1"))); + new OrderResponse( + "2", "text 2", new CustomerResponseWithOutOrder("1", "customer1"))); orderResponseList.add( - new OrderResponse("3", "text 3", new CustomerResponse("1", "customer1"))); + new OrderResponse( + "3", "text 3", new CustomerResponseWithOutOrder("1", "customer1"))); Page page = new PageImpl<>(orderResponseList); return new PagedResult<>(page); } @@ -96,7 +99,7 @@ void shouldFindOrderById() throws Exception { new OrderResponse( orderId, "text 1", - new CustomerResponse(customer.getId(), customer.getText())); + new CustomerResponseWithOutOrder(customer.getId(), customer.getText())); given(orderService.findOrderById(orderId)).willReturn(Optional.of(order)); this.mockMvc @@ -115,23 +118,23 @@ void shouldReturn404WhenFetchingNonExistingOrder() throws Exception { @Test void shouldCreateNewOrder() throws Exception { - given(orderService.saveOrder(any(Order.class))) - .willReturn(new OrderResponse("1", "some text", null)); - Order order = new Order("1", "some text", customer); + OrderRequest orderRequest = new OrderRequest("some text", customer.getId()); + given(orderService.saveOrder(orderRequest)) + .willReturn(Optional.of(new OrderResponse("1", "some text", null))); this.mockMvc .perform( post("/api/orders") .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(order))) + .content(objectMapper.writeValueAsString(orderRequest))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id", notNullValue())) - .andExpect(jsonPath("$.text", is(order.getText()))); + .andExpect(jsonPath("$.text", is(orderRequest.text()))); } @Test void shouldReturn400WhenCreateNewOrderWithoutText() throws Exception { - Order order = new Order(null, null, null); + OrderRequest order = new OrderRequest(null, null); this.mockMvc .perform( @@ -145,9 +148,11 @@ void shouldReturn400WhenCreateNewOrderWithoutText() throws Exception { .andExpect(jsonPath("$.status", is(400))) .andExpect(jsonPath("$.detail", is("Invalid request content."))) .andExpect(jsonPath("$.instance", is("/api/orders"))) - .andExpect(jsonPath("$.violations", hasSize(1))) - .andExpect(jsonPath("$.violations[0].field", is("text"))) - .andExpect(jsonPath("$.violations[0].message", is("Text cannot be empty"))) + .andExpect(jsonPath("$.violations", hasSize(2))) + .andExpect(jsonPath("$.violations[0].field", is("customerId"))) + .andExpect(jsonPath("$.violations[0].message", is("CustomerId cannot be blank"))) + .andExpect(jsonPath("$.violations[1].field", is("text"))) + .andExpect(jsonPath("$.violations[1].message", is("Text cannot be empty"))) .andReturn(); } @@ -158,16 +163,16 @@ void shouldUpdateOrder() throws Exception { new OrderResponse( orderId, "Updated text", - new CustomerResponse(customer.getId(), customer.getText())); - given(orderService.findOrderById(orderId)).willReturn(Optional.of(orderResponse)); - given(orderService.saveOrder(any(Order.class))) - .willReturn(new OrderResponse("1", "Updated text", null)); + new CustomerResponseWithOutOrder(customer.getId(), customer.getText())); + OrderRequest orderRequest = new OrderRequest("Updated text", customer.getId()); + given(orderService.updateOrderById(orderId, orderRequest)) + .willReturn(Optional.of(orderResponse)); this.mockMvc .perform( put("/api/orders/{id}", orderResponse.id()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(orderResponse))) + .content(objectMapper.writeValueAsString(orderRequest))) .andExpect(status().isOk()) .andExpect(jsonPath("$.text", is(orderResponse.text()))); } @@ -193,7 +198,7 @@ void shouldDeleteOrder() throws Exception { new OrderResponse( orderId, "Some text", - new CustomerResponse(customer.getId(), customer.getText())); + new CustomerResponseWithOutOrder(customer.getId(), customer.getText())); given(orderService.findOrderById(orderId)).willReturn(Optional.of(order)); doNothing().when(orderService).deleteOrderById(order.id()); diff --git a/jpa/boot-data-customsequence/src/test/resources/logback-test.xml b/jpa/boot-data-customsequence/src/test/resources/logback-test.xml index 1378a823a..07f6e94c2 100644 --- a/jpa/boot-data-customsequence/src/test/resources/logback-test.xml +++ b/jpa/boot-data-customsequence/src/test/resources/logback-test.xml @@ -1,14 +1,16 @@ - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n - - - - - - - - - - + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + + +