diff --git a/README.md b/README.md
index c658f965d..f7e733058 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 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/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/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..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
@@ -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;
@@ -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 efb8638e1..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
@@ -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;
@@ -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/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..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
@@ -2,12 +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 lombok.extern.slf4j.Slf4j;
+import jakarta.validation.groups.Default;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
@@ -24,7 +25,6 @@
@RestController
@RequestMapping("/api/customers")
-@Slf4j
public class CustomerController implements CustomerAPI {
private final CustomerService customerService;
@@ -60,13 +60,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 +79,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..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
@@ -1,11 +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 lombok.extern.slf4j.Slf4j;
+import jakarta.validation.groups.Default;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
@@ -21,7 +22,6 @@
@RestController
@RequestMapping("/api/orders")
-@Slf4j
public class OrderController {
private final OrderService orderService;
@@ -53,7 +53,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 +63,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..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
@@ -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,9 +88,14 @@ 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");
+ 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");
}
@@ -102,10 +107,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..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
@@ -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,7 +16,9 @@
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.OrderResponseWithOutCustomer;
import com.example.custom.sequence.model.response.PagedResult;
import com.example.custom.sequence.services.CustomerService;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -115,8 +116,9 @@ void shouldCreateNewCustomer() throws Exception {
}
@Test
- void shouldReturn400WhenCreateNewCustomerWithoutText() throws Exception {
- CustomerRequest customerRequest = new CustomerRequest(null, new ArrayList<>());
+ void shouldReturn400WhenCreateNewCustomerWithInvalidData() throws Exception {
+ CustomerRequest customerRequest =
+ new CustomerRequest(null, List.of(new OrderRequest("ORD_1", null)));
this.mockMvc
.perform(
@@ -160,8 +162,12 @@ void shouldReturn400WhenCreateNewCustomerWithEmptyText() throws Exception {
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());
+ 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))
.willReturn(Optional.of(customerResponse));
@@ -171,7 +177,33 @@ 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
+ 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
@@ -191,20 +223,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..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
@@ -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,175 @@ 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())));
+ 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 shouldReturn400WhenUpdatingOrderWithInvalidCustomerId() throws Exception {
- OrderRequest orderRequest = new OrderRequest("Updated Order", "INVALID_ID");
+ void shouldReturn400WhenCreateNewOrderWithoutText() throws Exception {
+ 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.mockMvc
- .perform(
- put("/api/orders/{id}", order.getId())
- .contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(orderRequest)))
- .andExpect(status().isNotFound());
+ 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 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();
+ void shouldReturn400WhenUpdatingOrderWithInvalidCustomerId() throws Exception {
+ OrderRequest orderRequest = new OrderRequest("Updated Order", "INVALID_ID");
+ Order order = orderList.getFirst();
+
+ this.mockMvcTester
+ .put()
+ .uri("/api/orders/{id}", order.getId())
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(orderRequest))
+ .assertThat()
+ .hasStatus(HttpStatus.NOT_FOUND);
}
@Test
- void shouldUpdateOrder() throws Exception {
+ void shouldReturn404WhenUpdatingNonExistentOrder() throws Exception {
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(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}", "NON_EXISTENT_ID")
+ .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())));
+ var orderId = order.getId();
+
+ this.mockMvcTester
+ .delete()
+ .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());
+ });
+
+ // 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 c18dc617c..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;
@@ -52,7 +54,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));
@@ -180,15 +182,17 @@ void shouldUpdateOrder() throws Exception {
@Test
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());
+ given(orderService.updateOrderById(orderId, orderRequest)).willReturn(Optional.empty());
this.mockMvc
.perform(
put("/api/orders/{id}", orderId)
.contentType(MediaType.APPLICATION_JSON)
- .content(objectMapper.writeValueAsString(order)))
+ .content(objectMapper.writeValueAsString(orderRequest)))
.andExpect(status().isNotFound());
+
+ verify(orderService, times(1)).updateOrderById(orderId, orderRequest);
}
@Test