From d34246a87e1fb22cd4eb9451da1b086df1b4d23a Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 15:29:00 +0000 Subject: [PATCH 1/6] feat : use SQLStatementValidator and ValidationGroups --- .../sequence/config/{ => db}/JpaConfig.java | 2 +- .../LazyConnectionDataSourceProxyConfig.java | 2 +- ...xedNumberFormattedSequenceIdGenerator.java | 2 +- .../{ => db}/StringPrefixedSequence.java | 2 +- .../custom/sequence/entities/Customer.java | 2 +- .../custom/sequence/entities/Order.java | 2 +- .../sequence/mapper/CustomerMapper.java | 6 + .../model/request/CustomerRequest.java | 4 +- .../sequence/model/request/OrderRequest.java | 8 +- .../model/request/ValidationGroups.java | 8 + .../sequence/services/CustomerService.java | 7 +- .../web/controllers/CustomerController.java | 17 +- .../web/controllers/OrderController.java | 9 +- ...ot.autoconfigure.AutoConfiguration.imports | 2 +- .../custom/sequence/SchemaValidationTest.java | 2 +- .../common/AbstractIntegrationTest.java | 4 +- .../services/CustomerServiceTest.java | 13 +- .../web/controllers/CustomerControllerIT.java | 300 +++++++++++++----- .../controllers/CustomerControllerTest.java | 42 ++- .../web/controllers/OrderControllerIT.java | 222 ++++++++----- .../web/controllers/OrderControllerTest.java | 4 +- 21 files changed, 460 insertions(+), 200 deletions(-) rename jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/{ => db}/JpaConfig.java (89%) rename jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/{ => db}/LazyConnectionDataSourceProxyConfig.java (97%) rename jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/{ => db}/StringPrefixedNumberFormattedSequenceIdGenerator.java (97%) rename jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/{ => db}/StringPrefixedSequence.java (91%) create mode 100644 jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/request/ValidationGroups.java 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/db/JpaConfig.java similarity index 89% rename from jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/JpaConfig.java rename to jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/db/JpaConfig.java index b742fc25f..cc059ab2d 100644 --- 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/db/JpaConfig.java @@ -1,4 +1,4 @@ -package com.example.custom.sequence.config; +package com.example.custom.sequence.config.db; import io.hypersistence.utils.spring.repository.BaseJpaRepositoryImpl; import org.springframework.context.annotation.Configuration; diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/LazyConnectionDataSourceProxyConfig.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/db/LazyConnectionDataSourceProxyConfig.java similarity index 97% rename from jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/LazyConnectionDataSourceProxyConfig.java rename to jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/db/LazyConnectionDataSourceProxyConfig.java index 3c9ee6167..3ff4a184f 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/LazyConnectionDataSourceProxyConfig.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/db/LazyConnectionDataSourceProxyConfig.java @@ -1,4 +1,4 @@ -package com.example.custom.sequence.config; +package com.example.custom.sequence.config.db; import io.hypersistence.utils.logging.InlineQueryLogEntryCreator; import javax.sql.DataSource; diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/StringPrefixedNumberFormattedSequenceIdGenerator.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/db/StringPrefixedNumberFormattedSequenceIdGenerator.java similarity index 97% rename from jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/StringPrefixedNumberFormattedSequenceIdGenerator.java rename to jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/db/StringPrefixedNumberFormattedSequenceIdGenerator.java index 12dcf44a5..26bc79a94 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/StringPrefixedNumberFormattedSequenceIdGenerator.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/db/StringPrefixedNumberFormattedSequenceIdGenerator.java @@ -1,4 +1,4 @@ -package com.example.custom.sequence.config; +package com.example.custom.sequence.config.db; import java.io.Serializable; import java.lang.reflect.Member; diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/StringPrefixedSequence.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/db/StringPrefixedSequence.java similarity index 91% rename from jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/StringPrefixedSequence.java rename to jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/db/StringPrefixedSequence.java index c9546dd6d..6edbd35ff 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/StringPrefixedSequence.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/db/StringPrefixedSequence.java @@ -1,4 +1,4 @@ -package com.example.custom.sequence.config; +package com.example.custom.sequence.config.db; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; 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 e43c647e6..d3d8b6197 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 @@ -1,6 +1,6 @@ package com.example.custom.sequence.entities; -import com.example.custom.sequence.config.StringPrefixedSequence; +import com.example.custom.sequence.config.db.StringPrefixedSequence; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; 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 efb8638e1..f93c77bea 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 @@ -1,6 +1,6 @@ package com.example.custom.sequence.entities; -import com.example.custom.sequence.config.StringPrefixedSequence; +import com.example.custom.sequence.config.db.StringPrefixedSequence; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; 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 351af323a..277c76f6e 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 @@ -30,6 +30,9 @@ public CustomerResponse mapToResponse(Customer saved) { public Customer mapToEntity(CustomerRequest customerRequest) { Customer customer = new Customer(customerRequest.text()); + if (customerRequest.orders() == null) { + return customer; + } customerRequest .orders() .forEach(orderRequest -> customer.addOrder(orderMapper.mapToEntity(orderRequest))); @@ -38,6 +41,9 @@ public Customer mapToEntity(CustomerRequest customerRequest) { public void updateCustomerFromRequest(CustomerRequest customerRequest, Customer foundCustomer) { foundCustomer.setText(customerRequest.text()); + if (customerRequest.orders() == null) { + return; + } List removedOrders = new ArrayList<>(foundCustomer.getOrders()); List ordersFromRequest = customerRequest.orders().stream() 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 index 1ff7719d6..71358bc50 100644 --- 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 @@ -1,7 +1,9 @@ package com.example.custom.sequence.model.request; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import java.util.List; public record CustomerRequest( - @NotBlank(message = "Text cannot be empty") String text, List orders) {} + @NotBlank(message = "Text cannot be empty") String text, + @Valid 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 index 88b07373e..699154ae9 100644 --- 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 @@ -1,8 +1,10 @@ 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) {} + @NotBlank(message = "Text cannot be empty") String text, + @NotBlank( + message = "CustomerId cannot be blank", + groups = ValidationGroups.GroupCheck.class) + String customerId) {} diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/request/ValidationGroups.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/request/ValidationGroups.java new file mode 100644 index 000000000..2c05a8412 --- /dev/null +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/model/request/ValidationGroups.java @@ -0,0 +1,8 @@ +package com.example.custom.sequence.model.request; + +public interface ValidationGroups { + + interface SkipGroupCheck {} + + interface GroupCheck {} +} 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 14e9de331..33565163c 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 @@ -77,8 +77,11 @@ public Optional updateCustomerById( } @Transactional - public void deleteCustomerById(String id) { - customerRepository.deleteById(id); + public Optional deleteCustomerById(String id) { + Optional optionalCustomer = findCustomerById(id); + optionalCustomer.ifPresent( + customerResponse -> customerRepository.deleteById(customerResponse.id())); + return optionalCustomer; } public Optional findById(String customerId) { 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 b9e075e96..5350d6125 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 @@ -2,11 +2,13 @@ import com.example.custom.sequence.entities.Customer; import com.example.custom.sequence.model.request.CustomerRequest; +import com.example.custom.sequence.model.request.ValidationGroups; import com.example.custom.sequence.model.response.CustomerResponse; import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.services.CustomerService; import com.example.custom.sequence.utils.AppConstants; import com.example.custom.sequence.web.api.CustomerAPI; +import jakarta.validation.groups.Default; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -60,13 +62,16 @@ public ResponseEntity getCustomerById(@PathVariable String id) @ResponseStatus(HttpStatus.CREATED) @Override public CustomerResponse createCustomer( - @RequestBody @Validated CustomerRequest customerRequest) { + @RequestBody @Validated(value = {Default.class, ValidationGroups.SkipGroupCheck.class}) + CustomerRequest customerRequest) { return customerService.saveCustomer(customerRequest); } @PutMapping("/{id}") public ResponseEntity updateCustomer( - @PathVariable String id, @RequestBody CustomerRequest customerRequest) { + @PathVariable String id, + @RequestBody @Validated(value = {Default.class, ValidationGroups.GroupCheck.class}) + CustomerRequest customerRequest) { return customerService .updateCustomerById(id, customerRequest) .map(ResponseEntity::ok) @@ -76,12 +81,8 @@ public ResponseEntity updateCustomer( @DeleteMapping("/{id}") public ResponseEntity deleteCustomer(@PathVariable String id) { return customerService - .findCustomerById(id) - .map( - customer -> { - customerService.deleteCustomerById(id); - return ResponseEntity.ok(customer); - }) + .deleteCustomerById(id) + .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 9b7a3fcb9..71ea85c63 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,10 +1,12 @@ package com.example.custom.sequence.web.controllers; import com.example.custom.sequence.model.request.OrderRequest; +import com.example.custom.sequence.model.request.ValidationGroups; import com.example.custom.sequence.model.response.OrderResponse; import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.services.OrderService; import com.example.custom.sequence.utils.AppConstants; +import jakarta.validation.groups.Default; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -53,7 +55,8 @@ public ResponseEntity getOrderById(@PathVariable String id) { @PostMapping public ResponseEntity createOrder( - @RequestBody @Validated OrderRequest orderRequest) { + @RequestBody @Validated(value = {Default.class, ValidationGroups.GroupCheck.class}) + OrderRequest orderRequest) { return orderService .saveOrder(orderRequest) .map(order -> ResponseEntity.status(HttpStatus.CREATED).body(order)) @@ -62,7 +65,9 @@ public ResponseEntity createOrder( @PutMapping("/{id}") public ResponseEntity updateOrder( - @PathVariable String id, @RequestBody OrderRequest orderRequest) { + @PathVariable String id, + @RequestBody @Validated(value = {Default.class, ValidationGroups.GroupCheck.class}) + OrderRequest orderRequest) { return orderService .updateOrderById(id, orderRequest) .map(ResponseEntity::ok) diff --git a/jpa/boot-data-customsequence/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/jpa/boot-data-customsequence/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 74fc78390..3a2e7c66a 100644 --- a/jpa/boot-data-customsequence/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/jpa/boot-data-customsequence/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1 @@ -com.example.custom.sequence.config.LazyConnectionDataSourceProxyConfig +com.example.custom.sequence.config.db.LazyConnectionDataSourceProxyConfig 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 e645aee45..a6fa8af9b 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,7 +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.example.custom.sequence.config.db.JpaConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; import org.junit.jupiter.api.Test; diff --git a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/common/AbstractIntegrationTest.java b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/common/AbstractIntegrationTest.java index ea775141b..24d640844 100644 --- a/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/common/AbstractIntegrationTest.java +++ b/jpa/boot-data-customsequence/src/test/java/com/example/custom/sequence/common/AbstractIntegrationTest.java @@ -9,7 +9,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.assertj.MockMvcTester; /** * Base class for integration tests providing test infrastructure including: - Configured DataSource @@ -23,7 +23,7 @@ @AutoConfigureMockMvc public abstract class AbstractIntegrationTest { - @Autowired protected MockMvc mockMvc; + @Autowired protected MockMvcTester mockMvcTester; @Autowired protected ObjectMapper objectMapper; 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 571c5be6b..5c2b74721 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 @@ -9,8 +9,8 @@ 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.model.request.OrderRequest; +import com.example.custom.sequence.model.response.*; import com.example.custom.sequence.repositories.CustomerRepository; import java.util.List; import java.util.Optional; @@ -88,6 +88,8 @@ void saveCustomer() { void deleteCustomerById() { // given willDoNothing().given(customerRepository).deleteById("CUS_1"); + given(customerRepository.findById("CUS_1")).willReturn(Optional.of(getCustomer())); + given(customerMapper.mapToResponse(getCustomer())).willReturn(getCustomerResponse()); // when customerService.deleteCustomerById("CUS_1"); // then @@ -102,10 +104,13 @@ private Customer getCustomer() { } private CustomerRequest getCustomerRequest() { - return new CustomerRequest("junitTest", List.of()); + return new CustomerRequest("junitTest", List.of(new OrderRequest("ORD_1", "junitTest"))); } private CustomerResponse getCustomerResponse() { - return new CustomerResponse("CUS_1", "junitTest", List.of()); + return new CustomerResponse( + "CUS_1", + "junitTest", + List.of(new OrderResponseWithOutCustomer("ORD_1", "junitTest"))); } } 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 5a4851292..8699070e3 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 @@ -1,26 +1,26 @@ package com.example.custom.sequence.web.controllers; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.hasLength; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.startsWith; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.assertj.core.api.Assertions.assertThat; import com.example.custom.sequence.common.AbstractIntegrationTest; import com.example.custom.sequence.entities.Customer; +import com.example.custom.sequence.model.request.CustomerRequest; +import com.example.custom.sequence.model.request.OrderRequest; +import com.example.custom.sequence.model.response.CustomerResponse; +import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.repositories.CustomerRepository; +import com.fasterxml.jackson.core.JsonProcessingException; +import io.hypersistence.utils.jdbc.validator.SQLStatementCountValidator; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Objects; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ProblemDetail; class CustomerControllerIT extends AbstractIntegrationTest { @@ -37,92 +37,248 @@ void setUp() { customerList.add(new Customer("Second Customer")); customerList.add(new Customer("Third Customer")); customerList = customerRepository.persistAll(customerList); + + SQLStatementCountValidator.reset(); } @Test - void shouldFetchAllCustomers() throws Exception { - this.mockMvc - .perform(get("/api/customers")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.size()", is(customerList.size()))) - .andExpect(jsonPath("$.totalElements", is(3))) - .andExpect(jsonPath("$.pageNumber", is(1))) - .andExpect(jsonPath("$.totalPages", is(1))) - .andExpect(jsonPath("$.isFirst", is(true))) - .andExpect(jsonPath("$.isLast", is(true))) - .andExpect(jsonPath("$.hasNext", is(false))) - .andExpect(jsonPath("$.hasPrevious", is(false))); + void shouldFetchAllCustomers() { + + this.mockMvcTester + .get() + .uri("/api/customers") + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(PagedResult.class) + .satisfies( + pagedResult -> { + assertThat(pagedResult.data()).hasSize(3); + assertThat(pagedResult.totalElements()).isEqualTo(3); + assertThat(pagedResult.pageNumber()).isEqualTo(1); + assertThat(pagedResult.totalPages()).isEqualTo(1); + assertThat(pagedResult.isFirst()).isTrue(); + assertThat(pagedResult.isLast()).isTrue(); + assertThat(pagedResult.hasNext()).isFalse(); + assertThat(pagedResult.hasPrevious()).isFalse(); + }); + + SQLStatementCountValidator.assertSelectCount(2); + SQLStatementCountValidator.assertTotalCount(2); } @Test - void shouldFindCustomerById() throws Exception { + void shouldFindCustomerById() { Customer customer = customerList.getFirst(); String customerId = customer.getId(); - this.mockMvc - .perform(get("/api/customers/{id}", customerId)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id", is(customer.getId()))) - .andExpect(jsonPath("$.text", is(customer.getText()))); + this.mockMvcTester + .get() + .uri("/api/customers/{id}", customerId) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(CustomerResponse.class) + .satisfies( + customerResponse -> { + assertThat(customerResponse.id()).isEqualTo(customer.getId()); + assertThat(customerResponse.text()).isEqualTo(customer.getText()); + assertThat(customerResponse.orderResponses()).isEmpty(); + }); + + SQLStatementCountValidator.assertSelectCount(1); + SQLStatementCountValidator.assertTotalCount(1); } @Test void shouldCreateNewCustomer() throws Exception { - Customer customer = new Customer("New Customer"); - this.mockMvc - .perform( - post("/api/customers") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(customer))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.id", startsWith("CUS"))) - .andExpect(jsonPath("$.id", hasLength(8))) - .andExpect(jsonPath("$.text", is(customer.getText()))); + CustomerRequest customerRequest = new CustomerRequest("New Customer", null); + + this.mockMvcTester + .post() + .uri("/api/customers") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(customerRequest)) + .assertThat() + .hasStatus(HttpStatus.CREATED) + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(CustomerResponse.class) + .satisfies( + customerResponse -> { + assertThat(customerResponse.id()).startsWith("CUS").hasSize(8); + assertThat(customerResponse.text()).isEqualTo(customerRequest.text()); + assertThat(customerResponse.orderResponses()).isEmpty(); + }); + + SQLStatementCountValidator.assertSelectCount(0); + SQLStatementCountValidator.assertInsertCount(1); + SQLStatementCountValidator.assertTotalCount(1); + + assertThat(customerRepository.count()).isEqualTo(4); } @Test - void shouldReturn400WhenCreateNewCustomerWithoutText() throws Exception { - Customer customer = new Customer(null); - - this.mockMvc - .perform( - post("/api/customers") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(customer))) - .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"))) - .andReturn(); + void shouldReturn400WhenCreateNewCustomerWithoutText() throws JsonProcessingException { + CustomerRequest customerRequest = + new CustomerRequest(null, List.of(new OrderRequest("First Order", null))); + + this.mockMvcTester + .post() + .uri("/api/customers") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(customerRequest)) + .assertThat() + .hasStatus(HttpStatus.BAD_REQUEST) + .hasContentType(MediaType.APPLICATION_PROBLEM_JSON) + .bodyJson() + .convertTo(ProblemDetail.class) + .satisfies( + errorResponse -> { + assertThat(errorResponse.getType().toString()).isEqualTo("about:blank"); + assertThat(errorResponse.getTitle()).isEqualTo("Constraint Violation"); + assertThat(errorResponse.getStatus()).isEqualTo(400); + assertThat(errorResponse.getDetail()) + .isEqualTo("Invalid request content."); + assertThat( + Objects.requireNonNull(errorResponse.getInstance()) + .toString()) + .isEqualTo("/api/customers"); + assertThat(errorResponse.getProperties()).hasSize(1); + Object violations = errorResponse.getProperties().get("violations"); + assertThat(violations).isNotNull(); + assertThat(violations).isInstanceOf(List.class); + assertThat((List) violations).hasSize(1); + assertThat(((List) violations).getFirst()) + .isInstanceOf(LinkedHashMap.class); + LinkedHashMap violation = + (LinkedHashMap) ((List) violations).getFirst(); + assertThat(violation.get("field")).isEqualTo("text"); + assertThat(violation.get("message")).isEqualTo("Text cannot be empty"); + }); + + SQLStatementCountValidator.assertTotalCount(0); + + assertThat(customerRepository.count()).isEqualTo(3); } @Test void shouldUpdateCustomer() throws Exception { Customer customer = customerList.getFirst(); - customer.setText("Updated Customer"); - - this.mockMvc - .perform( - put("/api/customers/{id}", customer.getId()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(customer))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.text", is(customer.getText()))); + CustomerRequest customerRequest = new CustomerRequest("Updated Customer", null); + + this.mockMvcTester + .put() + .uri("/api/customers/{id}", customer.getId()) + .content(objectMapper.writeValueAsString(customerRequest)) + .contentType(MediaType.APPLICATION_JSON) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(CustomerResponse.class) + .satisfies( + customerResponse -> { + assertThat(customerResponse.text()).isEqualTo("Updated Customer"); + assertThat(customerResponse.orderResponses()).isEmpty(); + }); + + // select for customer + SQLStatementCountValidator.assertSelectCount(1); + // update for customer table + SQLStatementCountValidator.assertUpdateCount(1); + SQLStatementCountValidator.assertInsertCount(0); + SQLStatementCountValidator.assertDeleteCount(0); + + List orders = new ArrayList<>(); + orders.add(new OrderRequest("First Order", customer.getId())); + orders.add(new OrderRequest("Second Order", customer.getId())); + + customerRequest = new CustomerRequest("Updated Customer1", orders); + + SQLStatementCountValidator.reset(); + + this.mockMvcTester + .put() + .uri("/api/customers/{id}", customer.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(customerRequest)) + .assertThat() + .hasContentType(MediaType.APPLICATION_JSON) + .hasStatusOk() + .bodyJson() + .convertTo(CustomerResponse.class) + .satisfies( + customerResponse -> { + assertThat(customerResponse.text()).isEqualTo("Updated Customer1"); + assertThat(customerResponse.orderResponses()).isNotEmpty().hasSize(2); + }); + // select for customer and 2 for orders sequence + SQLStatementCountValidator.assertSelectCount(3); + // update for customer table + SQLStatementCountValidator.assertUpdateCount(1); + // bulk insert for orders + SQLStatementCountValidator.assertInsertCount(1); + SQLStatementCountValidator.assertDeleteCount(0); + + orders = new ArrayList<>(); + orders.add(new OrderRequest("Third Order", customer.getId())); + orders.add(new OrderRequest("Second Order", customer.getId())); + + customerRequest = new CustomerRequest("Updated Customer1", orders); + + SQLStatementCountValidator.reset(); + + this.mockMvcTester + .put() + .uri("/api/customers/{id}", customer.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(customerRequest)) + .assertThat() + .hasContentType(MediaType.APPLICATION_JSON) + .hasStatusOk() + .bodyJson() + .convertTo(CustomerResponse.class) + .satisfies( + customerResponse -> { + assertThat(customerResponse.text()).isEqualTo("Updated Customer1"); + assertThat(customerResponse.orderResponses()).isNotEmpty().hasSize(2); + }); + // select for customer + SQLStatementCountValidator.assertSelectCount(1); + // update for customer table + SQLStatementCountValidator.assertUpdateCount(0); + // bulk insert for orders + SQLStatementCountValidator.assertInsertCount(1); + // delete for first order + SQLStatementCountValidator.assertDeleteCount(1); } @Test - void shouldDeleteCustomer() throws Exception { + void shouldDeleteCustomer() { Customer customer = customerList.getFirst(); - this.mockMvc - .perform(delete("/api/customers/{id}", customer.getId())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.text", is(customer.getText()))); + this.mockMvcTester + .delete() + .uri("/api/customers/{id}", customer.getId()) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(CustomerResponse.class) + .satisfies( + customerResponse -> + assertThat(customerResponse.text()).isEqualTo(customer.getText())); + + // select for customer + SQLStatementCountValidator.assertSelectCount(1); + SQLStatementCountValidator.assertUpdateCount(0); + SQLStatementCountValidator.assertInsertCount(0); + // delete for customer + SQLStatementCountValidator.assertDeleteCount(1); + + assertThat(customerRepository.findById(customer.getId())).isEmpty(); } } 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 1547a1bbb..30defa286 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 @@ -6,7 +6,6 @@ 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; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -17,6 +16,7 @@ import com.example.custom.sequence.entities.Customer; import com.example.custom.sequence.model.request.CustomerRequest; +import com.example.custom.sequence.model.request.OrderRequest; import com.example.custom.sequence.model.response.CustomerResponse; import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.services.CustomerService; @@ -116,7 +116,8 @@ void shouldCreateNewCustomer() throws Exception { @Test void shouldReturn400WhenCreateNewCustomerWithoutText() throws Exception { - CustomerRequest customerRequest = new CustomerRequest(null, new ArrayList<>()); + CustomerRequest customerRequest = + new CustomerRequest(null, List.of(new OrderRequest("ORD_1", null))); this.mockMvc .perform( @@ -161,7 +162,8 @@ void shouldUpdateCustomer() throws Exception { String customerId = "CUS_1"; CustomerResponse customerResponse = new CustomerResponse(customerId, "Updated text", List.of()); - CustomerRequest customerRequest = new CustomerRequest("Updated text", List.of()); + CustomerRequest customerRequest = + new CustomerRequest("Updated text", List.of(new OrderRequest("ORD_1", customerId))); given(customerService.updateCustomerById(customerId, customerRequest)) .willReturn(Optional.of(customerResponse)); @@ -174,6 +176,29 @@ void shouldUpdateCustomer() throws Exception { .andExpect(jsonPath("$.text", is(customerResponse.text()))); } + @Test + void shouldReturn400WhenUpdateCustomerWithEmpty() throws Exception { + String customerId = "CUS_1"; + CustomerRequest customerRequest = + new CustomerRequest("Updated text", List.of(new OrderRequest("ORD_1", null))); + + this.mockMvc + .perform( + put("/api/customers/{id}", customerId) + .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/CUS_1"))) + .andExpect(jsonPath("$.violations", hasSize(1))) + .andExpect(jsonPath("$.violations[0].field", is("orders[0].customerId"))) + .andExpect(jsonPath("$.violations[0].message", is("CustomerId cannot be blank"))); + } + @Test void shouldReturn404WhenUpdatingNonExistingCustomer() throws Exception { String customerId = "CUS_1"; @@ -191,20 +216,21 @@ void shouldReturn404WhenUpdatingNonExistingCustomer() throws Exception { @Test void shouldDeleteCustomer() throws Exception { String customerId = "CUS_1"; - CustomerResponse customer = new CustomerResponse(customerId, "Some text", List.of()); - given(customerService.findCustomerById(customerId)).willReturn(Optional.of(customer)); - doNothing().when(customerService).deleteCustomerById(customerId); + CustomerResponse customerResponse = + new CustomerResponse(customerId, "Some text", List.of()); + given(customerService.deleteCustomerById(customerId)) + .willReturn(Optional.of(customerResponse)); this.mockMvc .perform(delete("/api/customers/{id}", customerId)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.text", is(customer.text()))); + .andExpect(jsonPath("$.text", is(customerResponse.text()))); } @Test void shouldReturn404WhenDeletingNonExistingCustomer() throws Exception { String customerId = "CUS_1"; - given(customerService.findCustomerById(customerId)).willReturn(Optional.empty()); + given(customerService.deleteCustomerById(customerId)).willReturn(Optional.empty()); this.mockMvc .perform(delete("/api/customers/{id}", 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 108d0a7f0..886e8f357 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 @@ -1,29 +1,24 @@ package com.example.custom.sequence.web.controllers; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.Matchers.hasLength; -import static org.hamcrest.Matchers.hasSize; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.assertj.core.api.Assertions.assertThat; 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.model.response.OrderResponse; +import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.repositories.CustomerRepository; import com.example.custom.sequence.repositories.OrderRepository; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Objects; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ProblemDetail; class OrderControllerIT extends AbstractIntegrationTest { @@ -55,107 +50,158 @@ private List createTestOrders(Customer customer) { } @Test - void shouldFetchAllOrders() throws Exception { - this.mockMvc - .perform(get("/api/orders")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.size()", is(orderList.size()))) - .andExpect(jsonPath("$.totalElements", is(3))) - .andExpect(jsonPath("$.pageNumber", is(1))) - .andExpect(jsonPath("$.totalPages", is(1))) - .andExpect(jsonPath("$.isFirst", is(true))) - .andExpect(jsonPath("$.isLast", is(true))) - .andExpect(jsonPath("$.hasNext", is(false))) - .andExpect(jsonPath("$.hasPrevious", is(false))); + void shouldFetchAllOrders() { + + this.mockMvcTester + .get() + .uri("/api/orders") + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(PagedResult.class) + .satisfies( + pagedResult -> { + assertThat(pagedResult.data()).hasSize(3); + assertThat(pagedResult.totalElements()).isEqualTo(3); + assertThat(pagedResult.pageNumber()).isEqualTo(1); + assertThat(pagedResult.totalPages()).isEqualTo(1); + assertThat(pagedResult.isFirst()).isTrue(); + assertThat(pagedResult.isLast()).isTrue(); + assertThat(pagedResult.hasNext()).isFalse(); + assertThat(pagedResult.hasPrevious()).isFalse(); + }); } @Test - void shouldFindOrderById() throws Exception { + void shouldFindOrderById() { Order order = orderList.getFirst(); String orderId = order.getId(); - this.mockMvc - .perform(get("/api/orders/{id}", orderId)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id", is(order.getId()), String.class)) - .andExpect(jsonPath("$.text", is(order.getText()))); + this.mockMvcTester + .get() + .uri("/api/orders/{id}", orderId) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(OrderResponse.class) + .satisfies( + orderResponse -> { + assertThat(orderResponse.id()).isEqualTo(orderId); + assertThat(orderResponse.text()).isEqualTo(order.getText()); + }); } @Test void shouldCreateNewOrder() throws Exception { - OrderRequest order = new OrderRequest("New Order", customer.getId()); - this.mockMvc - .perform( - post("/api/orders") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(order))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.id", notNullValue(), String.class)) - .andExpect(jsonPath("$.id", hasLength(9))) - .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()); + OrderRequest orderRequest = new OrderRequest("New Order", customer.getId()); + + this.mockMvcTester + .post() + .uri("/api/orders") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(orderRequest)) + .assertThat() + .hasStatus(HttpStatus.CREATED) + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(OrderResponse.class) + .satisfies( + orderResponse -> { + assertThat(orderResponse.id()).isNotNull().hasSize(9); + assertThat(orderResponse.text()).isEqualTo(orderRequest.text()); + }); } @Test void shouldReturn400WhenCreateNewOrderWithoutText() throws Exception { - OrderRequest order = new OrderRequest(null, "CUS_1"); - - this.mockMvc - .perform( - post("/api/orders") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(order))) - .andExpect(status().isBadRequest()) - .andExpect( - header().string( - HttpHeaders.CONTENT_TYPE, - is(MediaType.APPLICATION_PROBLEM_JSON_VALUE))) - .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/orders"))) - .andExpect(jsonPath("$.violations", hasSize(1))) - .andExpect(jsonPath("$.violations[0].field", is("text"))) - .andExpect(jsonPath("$.violations[0].message", is("Text cannot be empty"))) - .andReturn(); + OrderRequest orderRequest = new OrderRequest(null, "CUS_1"); + + this.mockMvcTester + .post() + .uri("/api/orders") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(orderRequest)) + .assertThat() + .hasStatus(HttpStatus.BAD_REQUEST) + .hasContentType(MediaType.APPLICATION_PROBLEM_JSON) + .bodyJson() + .convertTo(ProblemDetail.class) + .satisfies( + problem -> { + assertThat(problem.getType().toString()).isEqualTo("about:blank"); + assertThat(problem.getTitle()).isEqualTo("Constraint Violation"); + assertThat(problem.getStatus()).isEqualTo(400); + assertThat(problem.getDetail()).isEqualTo("Invalid request content."); + assertThat(Objects.requireNonNull(problem.getInstance()).toString()) + .isEqualTo("/api/orders"); + assertThat(problem.getProperties()).hasSize(1); + Object violations = problem.getProperties().get("violations"); + assertThat(violations).isNotNull(); + assertThat(violations).isInstanceOf(List.class); + assertThat((List) violations).hasSize(1); + assertThat(((List) violations).getFirst()) + .isInstanceOf(LinkedHashMap.class); + LinkedHashMap violation = + (LinkedHashMap) ((List) violations).getFirst(); + assertThat(violation.get("field")).isEqualTo("text"); + assertThat(violation.get("message")).isEqualTo("Text cannot be empty"); + }); } @Test void shouldUpdateOrder() throws Exception { OrderRequest orderRequest = new OrderRequest("Updated Order", customer.getId()); + Order order = orderList.getFirst(); + + this.mockMvcTester + .put() + .uri("/api/orders/{id}", order.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(orderRequest)) + .assertThat() + .hasStatus(HttpStatus.OK) + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(OrderResponse.class) + .satisfies( + orderResponse -> { + assertThat(orderResponse.id()).isEqualTo(order.getId()); + assertThat(orderResponse.text()).isEqualTo(orderRequest.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().isOk()) - .andExpect(jsonPath("$.id", is(order.getId()), String.class)) - .andExpect(jsonPath("$.text", is(orderRequest.text()))); + + this.mockMvcTester + .put() + .uri("/api/orders/{id}", order.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(orderRequest)) + .assertThat() + .hasStatus(HttpStatus.NOT_FOUND); } @Test - void shouldDeleteOrder() throws Exception { + void shouldDeleteOrder() { Order order = orderList.getFirst(); - this.mockMvc - .perform(delete("/api/orders/{id}", order.getId())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id", is(order.getId()), String.class)) - .andExpect(jsonPath("$.text", is(order.getText()))); + this.mockMvcTester + .delete() + .uri("/api/orders/{id}", order.getId()) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(OrderResponse.class) + .satisfies( + orderResponse -> { + assertThat(orderResponse.id()).isEqualTo(order.getId()); + assertThat(orderResponse.text()).isEqualTo(order.getText()); + }); } } 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 c18dc617c..b276e0bbd 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 @@ -181,13 +181,13 @@ void shouldUpdateOrder() throws Exception { void shouldReturn404WhenUpdatingNonExistingOrder() throws Exception { String orderId = "1"; given(orderService.findOrderById(orderId)).willReturn(Optional.empty()); - Order order = new Order(orderId, "Updated text", customer); + OrderRequest orderRequest = new OrderRequest("Updated text", customer.getId()); this.mockMvc .perform( put("/api/orders/{id}", orderId) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(order))) + .content(objectMapper.writeValueAsString(orderRequest))) .andExpect(status().isNotFound()); } From d86bc51a1021b1e32369153f7fa1dce6d800eb63 Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 15:43:28 +0000 Subject: [PATCH 2/6] delombok --- README.md | 2 +- jpa/boot-data-customsequence/pom.xml | 5 -- .../config/ApplicationProperties.java | 55 +++++++++++++++++-- .../custom/sequence/config/Initializer.java | 11 ++-- .../custom/sequence/config/WebMvcConfig.java | 6 +- .../custom/sequence/entities/Customer.java | 49 +++++++++++++---- .../custom/sequence/entities/Order.java | 43 ++++++++++++--- .../web/controllers/CustomerController.java | 2 - .../web/controllers/OrderController.java | 2 - .../web/controllers/OrderControllerTest.java | 2 +- 10 files changed, 138 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index c658f965d..80aaa5948 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ The following table list all sample codes related to the spring boot integration | [Rabbit Mq Implementation](./boot-rabbitmq-thymeleaf) | The application, demonstrates how rabbitmq works with producer side acknowledgement | Completed | | [Spring Batch Implementation](./batch-boot-jpa-sample) | The application, demonstrates implementing Spring Batch 5 using simple config and creating batch tables using liquibase | Completed | | [Rest API Documentation with examples](./boot-rest-docs-sample) | This application, demonstrates ability to generate pdf API documentation using spring rest docs | Completed | -| [Custom SequenceNumber and LazyConnectionDataSourceProxy for db connection improvement](./jpa/boot-data-customsequence) | This application, demonstrated ability to create custom sequences, using datasource-proxy and LazyConnectionDataSourceProxy for db connection improvement using mariadb | Completed | +| [Custom SequenceNumber and LazyConnectionDataSourceProxy for db connection improvement](./jpa/boot-data-customsequence) | This application, demonstrated ability to create custom sequences, using datasource-proxy, LazyConnectionDataSourceProxy for db connection improvement using mariadb, SQLStatementCountValidator for validating SQLCounts and ValidationGroups for dynamic Validation | Completed | | [KeySet pagination and dynamic search](./jpa/keyset-pagination/blaze-persistence) | Implements KeySet Pagination using Blaze Persistence and enable dynamic search using specifications | Completed | For More info about this repository, Please visit [here](https://rajadilipkolli.github.io/my-spring-boot-experiments/) diff --git a/jpa/boot-data-customsequence/pom.xml b/jpa/boot-data-customsequence/pom.xml index 534018cbb..32098b0cf 100644 --- a/jpa/boot-data-customsequence/pom.xml +++ b/jpa/boot-data-customsequence/pom.xml @@ -60,11 +60,6 @@ spring-boot-configuration-processor true - - org.projectlombok - lombok - true - diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/ApplicationProperties.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/ApplicationProperties.java index f6404562e..ad649883c 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/ApplicationProperties.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/ApplicationProperties.java @@ -1,19 +1,66 @@ package com.example.custom.sequence.config; -import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; -@Data @ConfigurationProperties("application") public class ApplicationProperties { - private Cors cors = new Cors(); - @Data + @NestedConfigurationProperty private Cors cors = new Cors(); + + public Cors getCors() { + return cors; + } + + public void setCors(Cors cors) { + this.cors = cors; + } + public static class Cors { private String pathPattern = "/api/**"; private String allowedMethods = "*"; private String allowedHeaders = "*"; private String allowedOriginPatterns = "*"; private boolean allowCredentials = true; + + public String getPathPattern() { + return pathPattern; + } + + public void setPathPattern(String pathPattern) { + this.pathPattern = pathPattern; + } + + public String getAllowedMethods() { + return allowedMethods; + } + + public void setAllowedMethods(String allowedMethods) { + this.allowedMethods = allowedMethods; + } + + public String getAllowedHeaders() { + return allowedHeaders; + } + + public void setAllowedHeaders(String allowedHeaders) { + this.allowedHeaders = allowedHeaders; + } + + public String getAllowedOriginPatterns() { + return allowedOriginPatterns; + } + + public void setAllowedOriginPatterns(String allowedOriginPatterns) { + this.allowedOriginPatterns = allowedOriginPatterns; + } + + public boolean isAllowCredentials() { + return allowCredentials; + } + + public void setAllowCredentials(boolean allowCredentials) { + this.allowCredentials = allowCredentials; + } } } diff --git a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/Initializer.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/Initializer.java index 324633337..0f1801156 100644 --- a/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/Initializer.java +++ b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/config/Initializer.java @@ -1,17 +1,20 @@ package com.example.custom.sequence.config; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component -@RequiredArgsConstructor -@Slf4j public class Initializer implements CommandLineRunner { + private static final Logger log = LoggerFactory.getLogger(Initializer.class); private final ApplicationProperties properties; + public Initializer(ApplicationProperties properties) { + this.properties = properties; + } + @Override public void run(String... args) { log.info("Running Initializer....."); 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 0830a77fc..1701b0a63 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 @@ -1,16 +1,18 @@ package com.example.custom.sequence.config; -import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration(proxyBeanMethods = false) -@RequiredArgsConstructor public class WebMvcConfig implements WebMvcConfigurer { private final ApplicationProperties properties; + public WebMvcConfig(ApplicationProperties properties) { + this.properties = properties; + } + @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(properties.getCors().getPathPattern()) 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 d3d8b6197..344411be1 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 @@ -12,18 +12,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; import org.hibernate.Hibernate; @Entity @Table(name = "customers") -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor public class Customer { @Id @@ -37,18 +29,55 @@ public class Customer { @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true) private List orders = new ArrayList<>(); + public Customer() {} + + public Customer(String id, String text, List orders) { + this.id = id; + this.text = text; + this.orders = orders; + } + public Customer(String text) { this.text = text; } - public void addOrder(Order order) { + public String getId() { + return id; + } + + public Customer setId(String id) { + this.id = id; + return this; + } + + public String getText() { + return text; + } + + public Customer setText(String text) { + this.text = text; + return this; + } + + public List getOrders() { + return orders; + } + + public Customer setOrders(List orders) { + this.orders = orders; + return this; + } + + public Customer addOrder(Order order) { orders.add(order); order.setCustomer(this); + return this; } - public void removeOrder(Order removedOrder) { + public Customer removeOrder(Order removedOrder) { orders.remove(removedOrder); removedOrder.setCustomer(null); + return this; } @Override 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 f93c77bea..8e8e6e9ab 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 @@ -11,18 +11,10 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import java.util.Objects; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; import org.hibernate.Hibernate; @Entity @Table(name = "orders") -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor public class Order { @Id @@ -37,6 +29,41 @@ public class Order { @JoinColumn(name = "customer_id") private Customer customer; + public Order() {} + + public Order(String id, String text, Customer customer) { + this.id = id; + this.text = text; + this.customer = customer; + } + + public String getId() { + return id; + } + + public Order setId(String id) { + this.id = id; + return this; + } + + public String getText() { + return text; + } + + public Order setText(String text) { + this.text = text; + return this; + } + + public Customer getCustomer() { + return customer; + } + + public Order setCustomer(Customer customer) { + this.customer = customer; + return this; + } + @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/web/controllers/CustomerController.java b/jpa/boot-data-customsequence/src/main/java/com/example/custom/sequence/web/controllers/CustomerController.java index 5350d6125..e2c9c9ee5 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 @@ -9,7 +9,6 @@ import com.example.custom.sequence.utils.AppConstants; import com.example.custom.sequence.web.api.CustomerAPI; import jakarta.validation.groups.Default; -import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -26,7 +25,6 @@ @RestController @RequestMapping("/api/customers") -@Slf4j public class CustomerController implements CustomerAPI { private final CustomerService customerService; 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 71ea85c63..7465f04fe 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 @@ -7,7 +7,6 @@ import com.example.custom.sequence.services.OrderService; import com.example.custom.sequence.utils.AppConstants; import jakarta.validation.groups.Default; -import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -23,7 +22,6 @@ @RestController @RequestMapping("/api/orders") -@Slf4j public class OrderController { private final OrderService orderService; 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 b276e0bbd..788c784db 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 @@ -52,7 +52,7 @@ class OrderControllerTest { @BeforeEach void setUp() { - customer = new Customer("CUST_01", "customer1", new ArrayList<>()); + customer = new Customer("CUST_01", "customer1", List.of()); this.orderList = new ArrayList<>(); this.orderList.add(new Order("1", "text 1", customer)); this.orderList.add(new Order("2", "text 2", customer)); From 2c547fbb3616ba270c3774de951815d4f1ecce71 Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 15:59:21 +0000 Subject: [PATCH 3/6] implement code review comments --- README.md | 6 +++++- .../sequence/services/CustomerServiceTest.java | 5 ++++- .../web/controllers/CustomerControllerTest.java | 13 ++++++++++--- .../sequence/web/controllers/OrderControllerIT.java | 8 ++++++-- .../web/controllers/OrderControllerTest.java | 6 +++++- 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 80aaa5948..620c6d382 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,11 @@ The following table list all sample codes related to the spring boot integration | [Rabbit Mq Implementation](./boot-rabbitmq-thymeleaf) | The application, demonstrates how rabbitmq works with producer side acknowledgement | Completed | | [Spring Batch Implementation](./batch-boot-jpa-sample) | The application, demonstrates implementing Spring Batch 5 using simple config and creating batch tables using liquibase | Completed | | [Rest API Documentation with examples](./boot-rest-docs-sample) | This application, demonstrates ability to generate pdf API documentation using spring rest docs | Completed | -| [Custom SequenceNumber and LazyConnectionDataSourceProxy for db connection improvement](./jpa/boot-data-customsequence) | This application, demonstrated ability to create custom sequences, using datasource-proxy, LazyConnectionDataSourceProxy for db connection improvement using mariadb, SQLStatementCountValidator for validating SQLCounts and ValidationGroups for dynamic Validation | Completed | ++| [Custom SequenceNumber and LazyConnectionDataSourceProxy for db connection improvement](./jpa/boot-data-customsequence) | This application demonstrates: ++ - Custom sequence generation ++ - Database connection optimization using datasource-proxy and LazyConnectionDataSourceProxy with MariaDB ++ - SQL query validation using SQLStatementCountValidator ++ - Dynamic validation using ValidationGroups | Completed | | [KeySet pagination and dynamic search](./jpa/keyset-pagination/blaze-persistence) | Implements KeySet Pagination using Blaze Persistence and enable dynamic search using specifications | Completed | For More info about this repository, Please visit [here](https://rajadilipkolli.github.io/my-spring-boot-experiments/) 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 5c2b74721..e9593822c 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 @@ -91,8 +91,11 @@ void deleteCustomerById() { given(customerRepository.findById("CUS_1")).willReturn(Optional.of(getCustomer())); given(customerMapper.mapToResponse(getCustomer())).willReturn(getCustomerResponse()); // when - customerService.deleteCustomerById("CUS_1"); + Optional response = customerService.deleteCustomerById("CUS_1"); // then + assertThat(response).isPresent(); + assertThat(response.get().id()).isEqualTo("CUS_1"); + assertThat(response.get().text()).isEqualTo("junitTest"); verify(customerRepository, times(1)).deleteById("CUS_1"); } 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 30defa286..9e106b007 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 @@ -18,6 +18,7 @@ import com.example.custom.sequence.model.request.CustomerRequest; import com.example.custom.sequence.model.request.OrderRequest; import com.example.custom.sequence.model.response.CustomerResponse; +import com.example.custom.sequence.model.response.OrderResponseWithOutCustomer; import com.example.custom.sequence.model.response.PagedResult; import com.example.custom.sequence.services.CustomerService; import com.fasterxml.jackson.databind.ObjectMapper; @@ -115,7 +116,7 @@ void shouldCreateNewCustomer() throws Exception { } @Test - void shouldReturn400WhenCreateNewCustomerWithoutText() throws Exception { + void shouldReturn400WhenCreateNewCustomerWithInvalidData() throws Exception { CustomerRequest customerRequest = new CustomerRequest(null, List.of(new OrderRequest("ORD_1", null))); @@ -161,7 +162,10 @@ void shouldReturn400WhenCreateNewCustomerWithEmptyText() throws Exception { void shouldUpdateCustomer() throws Exception { String customerId = "CUS_1"; CustomerResponse customerResponse = - new CustomerResponse(customerId, "Updated text", List.of()); + new CustomerResponse( + customerId, + "Updated text", + List.of(new OrderResponseWithOutCustomer("ORD_1", "New Order"))); CustomerRequest customerRequest = new CustomerRequest("Updated text", List.of(new OrderRequest("ORD_1", customerId))); given(customerService.updateCustomerById(customerId, customerRequest)) @@ -173,7 +177,10 @@ void shouldUpdateCustomer() throws Exception { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(customerRequest))) .andExpect(status().isOk()) - .andExpect(jsonPath("$.text", is(customerResponse.text()))); + .andExpect(jsonPath("$.text", is(customerResponse.text()))) + .andExpect(jsonPath("$.orderResponses", hasSize(1))) + .andExpect(jsonPath("$.orderResponses[0].id", is("ORD_1"))) + .andExpect(jsonPath("$.orderResponses[0].orderDescription", is("New Order"))); } @Test 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 886e8f357..090e5e9ba 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 @@ -189,10 +189,11 @@ void shouldReturn400WhenUpdatingOrderWithInvalidCustomerId() throws Exception { @Test void shouldDeleteOrder() { Order order = orderList.getFirst(); + var orderId = order.getId(); this.mockMvcTester .delete() - .uri("/api/orders/{id}", order.getId()) + .uri("/api/orders/{id}", orderId) .assertThat() .hasStatusOk() .hasContentType(MediaType.APPLICATION_JSON) @@ -200,8 +201,11 @@ void shouldDeleteOrder() { .convertTo(OrderResponse.class) .satisfies( orderResponse -> { - assertThat(orderResponse.id()).isEqualTo(order.getId()); + assertThat(orderResponse.id()).isEqualTo(orderId); assertThat(orderResponse.text()).isEqualTo(order.getText()); }); + + // Verify order is deleted from database + assertThat(orderRepository.findById(orderId)).isEmpty(); } } 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 788c784db..a291c771a 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 @@ -6,6 +6,8 @@ import static org.hamcrest.Matchers.hasSize; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -180,8 +182,8 @@ void shouldUpdateOrder() throws Exception { @Test void shouldReturn404WhenUpdatingNonExistingOrder() throws Exception { String orderId = "1"; - given(orderService.findOrderById(orderId)).willReturn(Optional.empty()); OrderRequest orderRequest = new OrderRequest("Updated text", customer.getId()); + given(orderService.updateOrderById(orderId, orderRequest)).willReturn(Optional.empty()); this.mockMvc .perform( @@ -189,6 +191,8 @@ void shouldReturn404WhenUpdatingNonExistingOrder() throws Exception { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(orderRequest))) .andExpect(status().isNotFound()); + + verify(orderService, times(1)).updateOrderById(orderId, orderRequest); } @Test From 98e014ca605ac2d258da9d53c6a83b53c78641a8 Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 16:00:38 +0000 Subject: [PATCH 4/6] fix indendation --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 620c6d382..761fbe1a2 100644 --- a/README.md +++ b/README.md @@ -37,11 +37,11 @@ The following table list all sample codes related to the spring boot integration | [Rabbit Mq Implementation](./boot-rabbitmq-thymeleaf) | The application, demonstrates how rabbitmq works with producer side acknowledgement | Completed | | [Spring Batch Implementation](./batch-boot-jpa-sample) | The application, demonstrates implementing Spring Batch 5 using simple config and creating batch tables using liquibase | Completed | | [Rest API Documentation with examples](./boot-rest-docs-sample) | This application, demonstrates ability to generate pdf API documentation using spring rest docs | Completed | -+| [Custom SequenceNumber and LazyConnectionDataSourceProxy for db connection improvement](./jpa/boot-data-customsequence) | This application demonstrates: -+ - Custom sequence generation -+ - Database connection optimization using datasource-proxy and LazyConnectionDataSourceProxy with MariaDB -+ - SQL query validation using SQLStatementCountValidator -+ - Dynamic validation using ValidationGroups | Completed | +| [Custom SequenceNumber and LazyConnectionDataSourceProxy for db connection improvement](./jpa/boot-data-customsequence) | This application demonstrates: + - Custom sequence generation + - Database connection optimization using datasource-proxy and LazyConnectionDataSourceProxy with MariaDB + - SQL query validation using SQLStatementCountValidator + - Dynamic validation using ValidationGroups | Completed | | [KeySet pagination and dynamic search](./jpa/keyset-pagination/blaze-persistence) | Implements KeySet Pagination using Blaze Persistence and enable dynamic search using specifications | Completed | For More info about this repository, Please visit [here](https://rajadilipkolli.github.io/my-spring-boot-experiments/) From 0cd139fae18de7d5c78032e894c2213d95b5d627 Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 16:37:11 +0000 Subject: [PATCH 5/6] fix : formatting issue --- README.md | 9 ++++----- .../sequence/web/controllers/OrderControllerIT.java | 13 +++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 761fbe1a2..7dd42e261 100644 --- a/README.md +++ b/README.md @@ -37,11 +37,10 @@ The following table list all sample codes related to the spring boot integration | [Rabbit Mq Implementation](./boot-rabbitmq-thymeleaf) | The application, demonstrates how rabbitmq works with producer side acknowledgement | Completed | | [Spring Batch Implementation](./batch-boot-jpa-sample) | The application, demonstrates implementing Spring Batch 5 using simple config and creating batch tables using liquibase | Completed | | [Rest API Documentation with examples](./boot-rest-docs-sample) | This application, demonstrates ability to generate pdf API documentation using spring rest docs | Completed | -| [Custom SequenceNumber and LazyConnectionDataSourceProxy for db connection improvement](./jpa/boot-data-customsequence) | This application demonstrates: - - Custom sequence generation - - Database connection optimization using datasource-proxy and LazyConnectionDataSourceProxy with MariaDB - - SQL query validation using SQLStatementCountValidator - - Dynamic validation using ValidationGroups | Completed | +| [Custom SequenceNumber and LazyConnectionDataSourceProxy for db connection improvement](./jpa/boot-data-customsequence) | This application demonstrates, Custom sequence generation, + Database connection optimization using datasource-proxy and LazyConnectionDataSourceProxy with MariaDB, + SQL query validation using SQLStatementCountValidator, + Dynamic validation using ValidationGroups | Completed | | [KeySet pagination and dynamic search](./jpa/keyset-pagination/blaze-persistence) | Implements KeySet Pagination using Blaze Persistence and enable dynamic search using specifications | Completed | For More info about this repository, Please visit [here](https://rajadilipkolli.github.io/my-spring-boot-experiments/) 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 090e5e9ba..875efbd10 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 @@ -186,6 +186,19 @@ void shouldReturn400WhenUpdatingOrderWithInvalidCustomerId() throws Exception { .hasStatus(HttpStatus.NOT_FOUND); } + @Test + void shouldReturn404WhenUpdatingNonExistentOrder() throws Exception { + OrderRequest orderRequest = new OrderRequest("Updated Order", customer.getId()); + + this.mockMvcTester + .put() + .uri("/api/orders/{id}", "NON_EXISTENT_ID") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(orderRequest)) + .assertThat() + .hasStatus(HttpStatus.NOT_FOUND); + } + @Test void shouldDeleteOrder() { Order order = orderList.getFirst(); From b42f0753db03d452d4a7cc33bd5624f47feea687 Mon Sep 17 00:00:00 2001 From: Raja Kolli Date: Sun, 22 Dec 2024 22:16:52 +0530 Subject: [PATCH 6/6] Update README.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 7dd42e261..f7e733058 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,7 @@ The following table list all sample codes related to the spring boot integration | [Rabbit Mq Implementation](./boot-rabbitmq-thymeleaf) | The application, demonstrates how rabbitmq works with producer side acknowledgement | Completed | | [Spring Batch Implementation](./batch-boot-jpa-sample) | The application, demonstrates implementing Spring Batch 5 using simple config and creating batch tables using liquibase | Completed | | [Rest API Documentation with examples](./boot-rest-docs-sample) | This application, demonstrates ability to generate pdf API documentation using spring rest docs | Completed | -| [Custom SequenceNumber and LazyConnectionDataSourceProxy for db connection improvement](./jpa/boot-data-customsequence) | This application demonstrates, Custom sequence generation, - Database connection optimization using datasource-proxy and LazyConnectionDataSourceProxy with MariaDB, - SQL query validation using SQLStatementCountValidator, - Dynamic validation using ValidationGroups | Completed | +| [Custom SequenceNumber and LazyConnectionDataSourceProxy for db connection improvement](./jpa/boot-data-customsequence) | This application demonstrates: Custom sequence generation, Database connection optimization using datasource-proxy and LazyConnectionDataSourceProxy with MariaDB, SQL query validation using SQLStatementCountValidator, Dynamic validation using ValidationGroups | Completed | | [KeySet pagination and dynamic search](./jpa/keyset-pagination/blaze-persistence) | Implements KeySet Pagination using Blaze Persistence and enable dynamic search using specifications | Completed | For More info about this repository, Please visit [here](https://rajadilipkolli.github.io/my-spring-boot-experiments/)