diff --git a/pom.xml b/pom.xml
index 504188e96..016a6eb7e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,18 +15,40 @@
spring-6-rest-mvc
21
+ 1.5.5.Final
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-devtools
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ com.h2database
+ h2
+
org.projectlombok
lombok
true
+
+ org.mapstruct
+ mapstruct
+ ${org.mapstruct.version}
+
org.springframework.boot
spring-boot-starter-test
@@ -48,6 +70,35 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.10.1
+
+ 21
+ 21
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+ -Amapstruct.defaultComponentModel=spring
+
+
+
diff --git a/src/main/java/guru/springframework/spring6restmvc/bootstrap/BootstrapData.java b/src/main/java/guru/springframework/spring6restmvc/bootstrap/BootstrapData.java
new file mode 100644
index 000000000..0856801c0
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/bootstrap/BootstrapData.java
@@ -0,0 +1,100 @@
+package guru.springframework.spring6restmvc.bootstrap;
+
+import guru.springframework.spring6restmvc.entities.Beer;
+import guru.springframework.spring6restmvc.entities.Customer;
+import guru.springframework.spring6restmvc.model.BeerStyle;
+import guru.springframework.spring6restmvc.repositories.BeerRepository;
+import guru.springframework.spring6restmvc.repositories.CustomerRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@Component
+@RequiredArgsConstructor
+public class BootstrapData implements CommandLineRunner {
+ private final BeerRepository beerRepository;
+ private final CustomerRepository customerRepository;
+
+ @Override
+ public void run(String... args) throws Exception {
+ loadBeerData();
+ loadCustomerData();
+ }
+
+ private void loadBeerData() {
+ if (beerRepository.count() == 0){
+ Beer beer1 = Beer.builder()
+ .beerName("Galaxy Cat")
+ .beerStyle(BeerStyle.PALE_ALE)
+ .upc("12356")
+ .price(new BigDecimal("12.99"))
+ .quantityOnHand(122)
+ .createdDate(LocalDateTime.now())
+ .updateDate(LocalDateTime.now())
+ .build();
+
+ Beer beer2 = Beer.builder()
+ .beerName("Crank")
+ .beerStyle(BeerStyle.PALE_ALE)
+ .upc("12356222")
+ .price(new BigDecimal("11.99"))
+ .quantityOnHand(392)
+ .createdDate(LocalDateTime.now())
+ .updateDate(LocalDateTime.now())
+ .build();
+
+ Beer beer3 = Beer.builder()
+ .beerName("Sunshine City")
+ .beerStyle(BeerStyle.IPA)
+ .upc("12356")
+ .price(new BigDecimal("13.99"))
+ .quantityOnHand(144)
+ .createdDate(LocalDateTime.now())
+ .updateDate(LocalDateTime.now())
+ .build();
+
+ beerRepository.save(beer1);
+ beerRepository.save(beer2);
+ beerRepository.save(beer3);
+ }
+
+ }
+
+ private void loadCustomerData() {
+
+ if (customerRepository.count() == 0) {
+ Customer customer1 = Customer.builder()
+ .name("Customer 1")
+ .version(1)
+ .createdDate(LocalDateTime.now())
+ .updateDate(LocalDateTime.now())
+ .build();
+
+ Customer customer2 = Customer.builder()
+ .name("Customer 2")
+ .version(1)
+ .createdDate(LocalDateTime.now())
+ .updateDate(LocalDateTime.now())
+ .build();
+
+ Customer customer3 = Customer.builder()
+ .name("Customer 3")
+ .version(1)
+ .createdDate(LocalDateTime.now())
+ .updateDate(LocalDateTime.now())
+ .build();
+
+ customerRepository.saveAll(Arrays.asList(customer1, customer2, customer3));
+ }
+
+ }
+
+
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java b/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java
new file mode 100644
index 000000000..33d7faf2f
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java
@@ -0,0 +1,82 @@
+package guru.springframework.spring6restmvc.controller;
+
+import guru.springframework.spring6restmvc.model.BeerDTO;
+import guru.springframework.spring6restmvc.services.BeerService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@Slf4j
+@RequiredArgsConstructor
+@RestController
+public class BeerController {
+
+ public static final String BEER_PATH = "/api/v1/beer";
+ public static final String BEER_PATH_ID = BEER_PATH + "/{beerId}";
+
+ private final BeerService beerService;
+
+ @PatchMapping(BEER_PATH_ID)
+ public ResponseEntity updateBeerPatchById(@PathVariable("beerId")UUID beerId, @RequestBody BeerDTO beer){
+
+ beerService.patchBeerById(beerId, beer);
+
+ return new ResponseEntity(HttpStatus.NO_CONTENT);
+ }
+
+ @DeleteMapping(BEER_PATH_ID)
+ public ResponseEntity deleteById(@PathVariable("beerId") UUID beerId){
+
+ if(! beerService.deleteById(beerId)){
+ throw new NotFoundException();
+ }
+
+ return new ResponseEntity(HttpStatus.NO_CONTENT);
+ }
+
+ @PutMapping(BEER_PATH_ID)
+ public ResponseEntity updateById(@PathVariable("beerId")UUID beerId, @Validated @RequestBody BeerDTO beer){
+
+ if( beerService.updateBeerById(beerId, beer).isEmpty()){
+ throw new NotFoundException();
+ }
+
+ return new ResponseEntity(HttpStatus.NO_CONTENT);
+ }
+
+ @PostMapping(BEER_PATH)
+ public ResponseEntity handlePost(@Validated @RequestBody BeerDTO beer){
+
+ BeerDTO savedBeer = beerService.saveNewBeer(beer);
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("Location", BEER_PATH + "/" + savedBeer.getId().toString());
+
+ return new ResponseEntity(headers, HttpStatus.CREATED);
+ }
+
+ @GetMapping(value = BEER_PATH)
+ public List listBeers(){
+ return beerService.listBeers();
+ }
+
+
+ @GetMapping(value = BEER_PATH_ID)
+ public BeerDTO getBeerById(@PathVariable("beerId") UUID beerId){
+
+ log.debug("Get Beer by Id - in controller");
+
+ return beerService.getBeerById(beerId).orElseThrow(NotFoundException::new);
+ }
+
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/controller/CustomErrorController.java b/src/main/java/guru/springframework/spring6restmvc/controller/CustomErrorController.java
new file mode 100644
index 000000000..af718f8a6
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/controller/CustomErrorController.java
@@ -0,0 +1,37 @@
+package guru.springframework.spring6restmvc.controller;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.transaction.TransactionSystemException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@ControllerAdvice
+public class CustomErrorController {
+
+ @ExceptionHandler
+ ResponseEntity handleJPAViolations(TransactionSystemException exception){
+ return ResponseEntity.badRequest().build();
+ }
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ ResponseEntity handleBindErrors(MethodArgumentNotValidException exception){
+
+ List errorList = exception.getFieldErrors().stream()
+ .map(fieldError -> {
+ Map errorMap = new HashMap<>();
+ errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
+ return errorMap;
+ }).collect(Collectors.toList());
+
+ return ResponseEntity.badRequest().body(errorList);
+ }
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java b/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java
new file mode 100644
index 000000000..56994057f
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java
@@ -0,0 +1,76 @@
+package guru.springframework.spring6restmvc.controller;
+
+import guru.springframework.spring6restmvc.model.CustomerDTO;
+import guru.springframework.spring6restmvc.services.CustomerService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+
+@RequiredArgsConstructor
+@RestController
+public class CustomerController {
+ public static final String CUSTOMER_PATH = "/api/v1/customer";
+ public static final String CUSTOMER_PATH_ID = CUSTOMER_PATH + "/{customerId}";
+
+ private final CustomerService customerService;
+
+ @PatchMapping(CUSTOMER_PATH_ID)
+ public ResponseEntity patchCustomerById(@PathVariable("customerId") UUID customerId,
+ @RequestBody CustomerDTO customer){
+
+ customerService.patchCustomerById(customerId, customer);
+
+ return new ResponseEntity(HttpStatus.NO_CONTENT);
+ }
+
+ @DeleteMapping(CUSTOMER_PATH_ID)
+ public ResponseEntity deleteCustomerById(@PathVariable("customerId") UUID customerId){
+
+ if (!customerService.deleteCustomerById(customerId)){
+ throw new NotFoundException();
+ }
+
+ return new ResponseEntity(HttpStatus.NO_CONTENT);
+ }
+
+ @PutMapping(CUSTOMER_PATH_ID)
+ public ResponseEntity updateCustomerByID(@PathVariable("customerId") UUID customerId,
+ @RequestBody CustomerDTO customer){
+
+ if (customerService.updateCustomerById(customerId, customer).isEmpty()){
+ throw new NotFoundException();
+ }
+
+ return new ResponseEntity(HttpStatus.NO_CONTENT);
+ }
+
+ @PostMapping(CUSTOMER_PATH)
+ public ResponseEntity handlePost(@RequestBody CustomerDTO customer){
+ CustomerDTO savedCustomer = customerService.saveNewCustomer(customer);
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("Location", CUSTOMER_PATH + "/" + savedCustomer.getId().toString());
+
+ return new ResponseEntity(headers, HttpStatus.CREATED);
+ }
+
+ @GetMapping(CUSTOMER_PATH)
+ public List listAllCustomers(){
+ return customerService.getAllCustomers();
+ }
+
+ @GetMapping(value = CUSTOMER_PATH_ID)
+ public CustomerDTO getCustomerById(@PathVariable("customerId") UUID id){
+ return customerService.getCustomerById(id).orElseThrow(NotFoundException::new);
+ }
+
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/controller/NotFoundException.java b/src/main/java/guru/springframework/spring6restmvc/controller/NotFoundException.java
new file mode 100644
index 000000000..07a666823
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/controller/NotFoundException.java
@@ -0,0 +1,29 @@
+package guru.springframework.spring6restmvc.controller;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Value Not Found")
+public class NotFoundException extends RuntimeException {
+ public NotFoundException() {
+ }
+
+ public NotFoundException(String message) {
+ super(message);
+ }
+
+ public NotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public NotFoundException(Throwable cause) {
+ super(cause);
+ }
+
+ public NotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/entities/Beer.java b/src/main/java/guru/springframework/spring6restmvc/entities/Beer.java
new file mode 100644
index 000000000..808dab114
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/entities/Beer.java
@@ -0,0 +1,54 @@
+package guru.springframework.spring6restmvc.entities;
+
+import guru.springframework.spring6restmvc.model.BeerStyle;
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.*;
+import org.hibernate.annotations.UuidGenerator;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@Getter
+@Setter
+@Builder
+@Entity
+@NoArgsConstructor
+@AllArgsConstructor
+public class Beer {
+
+ @Id
+ @GeneratedValue(generator = "UUID")
+ @UuidGenerator
+ @Column(length = 36, columnDefinition = "varchar", updatable = false, nullable = false)
+ private UUID id;
+
+ @Version
+ private Integer version;
+
+ @NotNull
+ @NotBlank
+ @Size(max = 50)
+ @Column(length = 50)
+ private String beerName;
+
+ @NotNull
+ private BeerStyle beerStyle;
+
+ @NotNull
+ @NotBlank
+ @Size(max = 255)
+ private String upc;
+ private Integer quantityOnHand;
+
+ @NotNull
+ private BigDecimal price;
+ private LocalDateTime createdDate;
+ private LocalDateTime updateDate;
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/entities/Customer.java b/src/main/java/guru/springframework/spring6restmvc/entities/Customer.java
new file mode 100644
index 000000000..34f862238
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/entities/Customer.java
@@ -0,0 +1,31 @@
+package guru.springframework.spring6restmvc.entities;
+
+import jakarta.persistence.*;
+import lombok.*;
+import org.hibernate.annotations.UuidGenerator;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@Getter
+@Setter
+@Builder
+@Entity
+@AllArgsConstructor
+@NoArgsConstructor
+public class Customer {
+ @Id
+ @GeneratedValue(generator = "UUID")
+ @UuidGenerator
+ @Column(length = 36, columnDefinition = "varchar", updatable = false, nullable = false)
+ private UUID id;
+ private String name;
+
+ @Version
+ private Integer version;
+ private LocalDateTime createdDate;
+ private LocalDateTime updateDate;
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/mappers/BeerMapper.java b/src/main/java/guru/springframework/spring6restmvc/mappers/BeerMapper.java
new file mode 100644
index 000000000..015bd59da
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/mappers/BeerMapper.java
@@ -0,0 +1,17 @@
+package guru.springframework.spring6restmvc.mappers;
+
+import guru.springframework.spring6restmvc.entities.Beer;
+import guru.springframework.spring6restmvc.model.BeerDTO;
+import org.mapstruct.Mapper;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@Mapper
+public interface BeerMapper {
+
+ Beer beerDtoToBeer(BeerDTO dto);
+
+ BeerDTO beerToBeerDto(Beer beer);
+
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/mappers/CustomerMapper.java b/src/main/java/guru/springframework/spring6restmvc/mappers/CustomerMapper.java
new file mode 100644
index 000000000..eef2605c6
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/mappers/CustomerMapper.java
@@ -0,0 +1,17 @@
+package guru.springframework.spring6restmvc.mappers;
+
+import guru.springframework.spring6restmvc.entities.Customer;
+import guru.springframework.spring6restmvc.model.CustomerDTO;
+import org.mapstruct.Mapper;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@Mapper
+public interface CustomerMapper {
+
+ Customer customerDtoToCustomer(CustomerDTO dto);
+
+ CustomerDTO customerToCustomerDto(Customer customer);
+
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/model/BeerDTO.java b/src/main/java/guru/springframework/spring6restmvc/model/BeerDTO.java
new file mode 100644
index 000000000..af858bfed
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/model/BeerDTO.java
@@ -0,0 +1,37 @@
+package guru.springframework.spring6restmvc.model;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Builder;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@Builder
+@Data
+public class BeerDTO {
+ private UUID id;
+ private Integer version;
+
+ @NotBlank
+ @NotNull
+ private String beerName;
+
+ @NotNull
+ private BeerStyle beerStyle;
+
+ @NotNull
+ @NotBlank
+ private String upc;
+ private Integer quantityOnHand;
+
+ @NotNull
+ private BigDecimal price;
+ private LocalDateTime createdDate;
+ private LocalDateTime updateDate;
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/model/BeerStyle.java b/src/main/java/guru/springframework/spring6restmvc/model/BeerStyle.java
new file mode 100644
index 000000000..78a1b30df
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/model/BeerStyle.java
@@ -0,0 +1,8 @@
+package guru.springframework.spring6restmvc.model;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+public enum BeerStyle {
+ LAGER, PILSNER, STOUT, GOSE, PORTER, ALE, WHEAT, IPA, PALE_ALE, SAISON
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/model/CustomerDTO.java b/src/main/java/guru/springframework/spring6restmvc/model/CustomerDTO.java
new file mode 100644
index 000000000..c35be5b4c
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/model/CustomerDTO.java
@@ -0,0 +1,20 @@
+package guru.springframework.spring6restmvc.model;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@Data
+@Builder
+public class CustomerDTO {
+ private UUID id;
+ private String name;
+ private Integer version;
+ private LocalDateTime createdDate;
+ private LocalDateTime updateDate;
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/repositories/BeerRepository.java b/src/main/java/guru/springframework/spring6restmvc/repositories/BeerRepository.java
new file mode 100644
index 000000000..314771a4a
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/repositories/BeerRepository.java
@@ -0,0 +1,12 @@
+package guru.springframework.spring6restmvc.repositories;
+
+import guru.springframework.spring6restmvc.entities.Beer;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.UUID;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+public interface BeerRepository extends JpaRepository {
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/repositories/CustomerRepository.java b/src/main/java/guru/springframework/spring6restmvc/repositories/CustomerRepository.java
new file mode 100644
index 000000000..71c1b2ff3
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/repositories/CustomerRepository.java
@@ -0,0 +1,12 @@
+package guru.springframework.spring6restmvc.repositories;
+
+import guru.springframework.spring6restmvc.entities.Customer;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.UUID;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+public interface CustomerRepository extends JpaRepository {
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/services/BeerService.java b/src/main/java/guru/springframework/spring6restmvc/services/BeerService.java
new file mode 100644
index 000000000..3f795410e
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/services/BeerService.java
@@ -0,0 +1,25 @@
+package guru.springframework.spring6restmvc.services;
+
+import guru.springframework.spring6restmvc.model.BeerDTO;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+public interface BeerService {
+
+ List listBeers();
+
+ Optional getBeerById(UUID id);
+
+ BeerDTO saveNewBeer(BeerDTO beer);
+
+ Optional updateBeerById(UUID beerId, BeerDTO beer);
+
+ Boolean deleteById(UUID beerId);
+
+ Optional patchBeerById(UUID beerId, BeerDTO beer);
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceImpl.java b/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceImpl.java
new file mode 100644
index 000000000..7e0e1c047
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceImpl.java
@@ -0,0 +1,159 @@
+package guru.springframework.spring6restmvc.services;
+
+import guru.springframework.spring6restmvc.model.BeerDTO;
+import guru.springframework.spring6restmvc.model.BeerStyle;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.*;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@Slf4j
+@Service
+public class BeerServiceImpl implements BeerService {
+
+ private Map beerMap;
+
+ public BeerServiceImpl() {
+ this.beerMap = new HashMap<>();
+
+ BeerDTO beer1 = BeerDTO.builder()
+ .id(UUID.randomUUID())
+ .version(1)
+ .beerName("Galaxy Cat")
+ .beerStyle(BeerStyle.PALE_ALE)
+ .upc("12356")
+ .price(new BigDecimal("12.99"))
+ .quantityOnHand(122)
+ .createdDate(LocalDateTime.now())
+ .updateDate(LocalDateTime.now())
+ .build();
+
+ BeerDTO beer2 = BeerDTO.builder()
+ .id(UUID.randomUUID())
+ .version(1)
+ .beerName("Crank")
+ .beerStyle(BeerStyle.PALE_ALE)
+ .upc("12356222")
+ .price(new BigDecimal("11.99"))
+ .quantityOnHand(392)
+ .createdDate(LocalDateTime.now())
+ .updateDate(LocalDateTime.now())
+ .build();
+
+ BeerDTO beer3 = BeerDTO.builder()
+ .id(UUID.randomUUID())
+ .version(1)
+ .beerName("Sunshine City")
+ .beerStyle(BeerStyle.IPA)
+ .upc("12356")
+ .price(new BigDecimal("13.99"))
+ .quantityOnHand(144)
+ .createdDate(LocalDateTime.now())
+ .updateDate(LocalDateTime.now())
+ .build();
+
+ beerMap.put(beer1.getId(), beer1);
+ beerMap.put(beer2.getId(), beer2);
+ beerMap.put(beer3.getId(), beer3);
+ }
+
+ @Override
+ public Optional patchBeerById(UUID beerId, BeerDTO beer) {
+ BeerDTO existing = beerMap.get(beerId);
+
+ if (StringUtils.hasText(beer.getBeerName())){
+ existing.setBeerName(beer.getBeerName());
+ }
+
+ if (beer.getBeerStyle() != null) {
+ existing.setBeerStyle(beer.getBeerStyle());
+ }
+
+ if (beer.getPrice() != null) {
+ existing.setPrice(beer.getPrice());
+ }
+
+ if (beer.getQuantityOnHand() != null){
+ existing.setQuantityOnHand(beer.getQuantityOnHand());
+ }
+
+ if (StringUtils.hasText(beer.getUpc())) {
+ existing.setUpc(beer.getUpc());
+ }
+
+ return Optional.of(existing);
+ }
+
+ @Override
+ public Boolean deleteById(UUID beerId) {
+ beerMap.remove(beerId);
+
+ return true;
+ }
+
+ @Override
+ public Optional updateBeerById(UUID beerId, BeerDTO beer) {
+ BeerDTO existing = beerMap.get(beerId);
+ existing.setBeerName(beer.getBeerName());
+ existing.setPrice(beer.getPrice());
+ existing.setUpc(beer.getUpc());
+ existing.setQuantityOnHand(beer.getQuantityOnHand());
+ return Optional.of(existing);
+ }
+
+ @Override
+ public List listBeers(){
+ return new ArrayList<>(beerMap.values());
+ }
+
+ @Override
+ public Optional getBeerById(UUID id) {
+
+ log.debug("Get Beer by Id - in service. Id: " + id.toString());
+
+ return Optional.of(beerMap.get(id));
+ }
+
+ @Override
+ public BeerDTO saveNewBeer(BeerDTO beer) {
+
+ BeerDTO savedBeer = BeerDTO.builder()
+ .id(UUID.randomUUID())
+ .version(1)
+ .createdDate(LocalDateTime.now())
+ .updateDate(LocalDateTime.now())
+ .beerName(beer.getBeerName())
+ .beerStyle(beer.getBeerStyle())
+ .quantityOnHand(beer.getQuantityOnHand())
+ .upc(beer.getUpc())
+ .price(beer.getPrice())
+ .build();
+
+ beerMap.put(savedBeer.getId(), savedBeer);
+
+ return savedBeer;
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceJPA.java b/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceJPA.java
new file mode 100644
index 000000000..8e675a298
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceJPA.java
@@ -0,0 +1,102 @@
+package guru.springframework.spring6restmvc.services;
+
+import guru.springframework.spring6restmvc.mappers.BeerMapper;
+import guru.springframework.spring6restmvc.model.BeerDTO;
+import guru.springframework.spring6restmvc.repositories.BeerRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@Service
+@Primary
+@RequiredArgsConstructor
+public class BeerServiceJPA implements BeerService {
+ private final BeerRepository beerRepository;
+ private final BeerMapper beerMapper;
+
+ @Override
+ public List listBeers() {
+ return beerRepository.findAll()
+ .stream()
+ .map(beerMapper::beerToBeerDto)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Optional getBeerById(UUID id) {
+ return Optional.ofNullable(beerMapper.beerToBeerDto(beerRepository.findById(id)
+ .orElse(null)));
+ }
+
+ @Override
+ public BeerDTO saveNewBeer(BeerDTO beer) {
+ return beerMapper.beerToBeerDto(beerRepository.save(beerMapper.beerDtoToBeer(beer)));
+ }
+
+ @Override
+ public Optional updateBeerById(UUID beerId, BeerDTO beer) {
+ AtomicReference> atomicReference = new AtomicReference<>();
+
+ beerRepository.findById(beerId).ifPresentOrElse(foundBeer -> {
+ foundBeer.setBeerName(beer.getBeerName());
+ foundBeer.setBeerStyle(beer.getBeerStyle());
+ foundBeer.setUpc(beer.getUpc());
+ foundBeer.setPrice(beer.getPrice());
+ foundBeer.setQuantityOnHand(beer.getQuantityOnHand());
+ atomicReference.set(Optional.of(beerMapper
+ .beerToBeerDto(beerRepository.save(foundBeer))));
+ }, () -> {
+ atomicReference.set(Optional.empty());
+ });
+
+ return atomicReference.get();
+ }
+
+ @Override
+ public Boolean deleteById(UUID beerId) {
+ if (beerRepository.existsById(beerId)) {
+ beerRepository.deleteById(beerId);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Optional patchBeerById(UUID beerId, BeerDTO beer) {
+ AtomicReference> atomicReference = new AtomicReference<>();
+
+ beerRepository.findById(beerId).ifPresentOrElse(foundBeer -> {
+ if (StringUtils.hasText(beer.getBeerName())){
+ foundBeer.setBeerName(beer.getBeerName());
+ }
+ if (beer.getBeerStyle() != null){
+ foundBeer.setBeerStyle(beer.getBeerStyle());
+ }
+ if (StringUtils.hasText(beer.getUpc())){
+ foundBeer.setUpc(beer.getUpc());
+ }
+ if (beer.getPrice() != null){
+ foundBeer.setPrice(beer.getPrice());
+ }
+ if (beer.getQuantityOnHand() != null){
+ foundBeer.setQuantityOnHand(beer.getQuantityOnHand());
+ }
+ atomicReference.set(Optional.of(beerMapper
+ .beerToBeerDto(beerRepository.save(foundBeer))));
+ }, () -> {
+ atomicReference.set(Optional.empty());
+ });
+
+ return atomicReference.get();
+ }
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/services/CustomerService.java b/src/main/java/guru/springframework/spring6restmvc/services/CustomerService.java
new file mode 100644
index 000000000..cdadac861
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/services/CustomerService.java
@@ -0,0 +1,25 @@
+package guru.springframework.spring6restmvc.services;
+
+import guru.springframework.spring6restmvc.model.CustomerDTO;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+public interface CustomerService {
+
+ Optional getCustomerById(UUID uuid);
+
+ List getAllCustomers();
+
+ CustomerDTO saveNewCustomer(CustomerDTO customer);
+
+ Optional updateCustomerById(UUID customerId, CustomerDTO customer);
+
+ Boolean deleteCustomerById(UUID customerId);
+
+ Optional patchCustomerById(UUID customerId, CustomerDTO customer);
+}
diff --git a/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceImpl.java b/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceImpl.java
new file mode 100644
index 000000000..27a2c5b2b
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceImpl.java
@@ -0,0 +1,110 @@
+package guru.springframework.spring6restmvc.services;
+
+import guru.springframework.spring6restmvc.model.CustomerDTO;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@Service
+public class CustomerServiceImpl implements CustomerService {
+
+ private Map customerMap;
+
+ public CustomerServiceImpl() {
+ CustomerDTO customer1 = CustomerDTO.builder()
+ .id(UUID.randomUUID())
+ .name("Customer 1")
+ .version(1)
+ .createdDate(LocalDateTime.now())
+ .updateDate(LocalDateTime.now())
+ .build();
+
+ CustomerDTO customer2 = CustomerDTO.builder()
+ .id(UUID.randomUUID())
+ .name("Customer 2")
+ .version(1)
+ .createdDate(LocalDateTime.now())
+ .updateDate(LocalDateTime.now())
+ .build();
+
+ CustomerDTO customer3 = CustomerDTO.builder()
+ .id(UUID.randomUUID())
+ .name("Customer 3")
+ .version(1)
+ .createdDate(LocalDateTime.now())
+ .updateDate(LocalDateTime.now())
+ .build();
+
+ customerMap = new HashMap<>();
+ customerMap.put(customer1.getId(), customer1);
+ customerMap.put(customer2.getId(), customer2);
+ customerMap.put(customer3.getId(), customer3);
+ }
+
+ @Override
+ public Optional patchCustomerById(UUID customerId, CustomerDTO customer) {
+ CustomerDTO existing = customerMap.get(customerId);
+
+ if (StringUtils.hasText(customer.getName())) {
+ existing.setName(customer.getName());
+ }
+
+ return Optional.of(existing);
+ }
+
+ @Override
+ public Boolean deleteCustomerById(UUID customerId) {
+ customerMap.remove(customerId);
+
+ return true;
+ }
+
+ @Override
+ public Optional updateCustomerById(UUID customerId, CustomerDTO customer) {
+ CustomerDTO existing = customerMap.get(customerId);
+ existing.setName(customer.getName());
+ return Optional.of(existing);
+ }
+
+ @Override
+ public CustomerDTO saveNewCustomer(CustomerDTO customer) {
+
+ CustomerDTO savedCustomer = CustomerDTO.builder()
+ .id(UUID.randomUUID())
+ .version(1)
+ .updateDate(LocalDateTime.now())
+ .createdDate(LocalDateTime.now())
+ .name(customer.getName())
+ .build();
+
+ customerMap.put(savedCustomer.getId(), savedCustomer);
+
+ return savedCustomer;
+ }
+
+ @Override
+ public Optional getCustomerById(UUID uuid) {
+ return Optional.of(customerMap.get(uuid));
+ }
+
+ @Override
+ public List getAllCustomers() {
+ return new ArrayList<>(customerMap.values());
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceJPA.java b/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceJPA.java
new file mode 100644
index 000000000..030f5e850
--- /dev/null
+++ b/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceJPA.java
@@ -0,0 +1,86 @@
+package guru.springframework.spring6restmvc.services;
+
+import guru.springframework.spring6restmvc.mappers.CustomerMapper;
+import guru.springframework.spring6restmvc.model.CustomerDTO;
+import guru.springframework.spring6restmvc.repositories.CustomerRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+/**
+ * Created by jt, Spring Framework Guru.
+ */
+@Service
+@Primary
+@RequiredArgsConstructor
+public class CustomerServiceJPA implements CustomerService {
+ private final CustomerRepository customerRepository;
+ private final CustomerMapper customerMapper;
+
+ @Override
+ public Optional getCustomerById(UUID uuid) {
+ return Optional.ofNullable(customerMapper
+ .customerToCustomerDto(customerRepository.findById(uuid).orElse(null)));
+ }
+
+ @Override
+ public List getAllCustomers() {
+ return customerRepository.findAll().stream()
+ .map(customerMapper::customerToCustomerDto)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public CustomerDTO saveNewCustomer(CustomerDTO customer) {
+ return customerMapper.customerToCustomerDto(customerRepository
+ .save(customerMapper.customerDtoToCustomer(customer)));
+ }
+
+ @Override
+ public Optional updateCustomerById(UUID customerId, CustomerDTO customer) {
+ AtomicReference> atomicReference = new AtomicReference<>();
+
+ customerRepository.findById(customerId).ifPresentOrElse(foundCustomer -> {
+ foundCustomer.setName(customer.getName());
+ atomicReference.set(Optional.of(customerMapper
+ .customerToCustomerDto(customerRepository.save(foundCustomer))));
+ }, () -> {
+ atomicReference.set(Optional.empty());
+ });
+
+ return atomicReference.get();
+ }
+
+ @Override
+ public Boolean deleteCustomerById(UUID customerId) {
+ if(customerRepository.existsById(customerId)){
+ customerRepository.deleteById(customerId);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Optional patchCustomerById(UUID customerId, CustomerDTO customer) {
+ AtomicReference> atomicReference = new AtomicReference<>();
+
+ customerRepository.findById(customerId).ifPresentOrElse(foundCustomer -> {
+ if (StringUtils.hasText(customer.getName())){
+ foundCustomer.setName(customer.getName());
+ }
+ atomicReference.set(Optional.of(customerMapper
+ .customerToCustomerDto(customerRepository.save(foundCustomer))));
+ }, () -> {
+ atomicReference.set(Optional.empty());
+ });
+
+ return atomicReference.get();
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8b1378917..d0afd80b5 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1 +1,2 @@
+logging.level.guru.springframework=debug
diff --git a/src/test/java/guru/springframework/spring6restmvc/bootstrap/BootstrapDataTest.java b/src/test/java/guru/springframework/spring6restmvc/bootstrap/BootstrapDataTest.java
new file mode 100644
index 000000000..a812d628d
--- /dev/null
+++ b/src/test/java/guru/springframework/spring6restmvc/bootstrap/BootstrapDataTest.java
@@ -0,0 +1,40 @@
+package guru.springframework.spring6restmvc.bootstrap;
+
+import guru.springframework.spring6restmvc.repositories.BeerRepository;
+import guru.springframework.spring6restmvc.repositories.CustomerRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DataJpaTest
+class BootstrapDataTest {
+
+ @Autowired
+ BeerRepository beerRepository;
+
+ @Autowired
+ CustomerRepository customerRepository;
+
+ BootstrapData bootstrapData;
+
+ @BeforeEach
+ void setUp() {
+ bootstrapData = new BootstrapData(beerRepository, customerRepository);
+ }
+
+ @Test
+ void Testrun() throws Exception {
+ bootstrapData.run(null);
+
+ assertThat(beerRepository.count()).isEqualTo(3);
+ assertThat(customerRepository.count()).isEqualTo(3);
+ }
+}
+
+
+
+
+
diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerIT.java b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerIT.java
new file mode 100644
index 000000000..edce2b8a8
--- /dev/null
+++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerIT.java
@@ -0,0 +1,173 @@
+package guru.springframework.spring6restmvc.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import guru.springframework.spring6restmvc.entities.Beer;
+import guru.springframework.spring6restmvc.mappers.BeerMapper;
+import guru.springframework.spring6restmvc.model.BeerDTO;
+import guru.springframework.spring6restmvc.repositories.BeerRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.context.WebApplicationContext;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+class BeerControllerIT {
+ @Autowired
+ BeerController beerController;
+
+ @Autowired
+ BeerRepository beerRepository;
+
+ @Autowired
+ BeerMapper beerMapper;
+
+ @Autowired
+ ObjectMapper objectMapper;
+
+ @Autowired
+ WebApplicationContext wac;
+
+ MockMvc mockMvc;
+
+ @BeforeEach
+ void setUp() {
+ mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
+ }
+
+ @Test
+ void testPatchBeerBadName() throws Exception {
+ Beer beer = beerRepository.findAll().get(0);
+
+ Map beerMap = new HashMap<>();
+ beerMap.put("beerName", "New Name 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
+
+ mockMvc.perform(patch(BeerController.BEER_PATH_ID, beer.getId())
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(beerMap)))
+ .andExpect(status().isBadRequest());
+
+ }
+
+ @Test
+ void testDeleteByIDNotFound() {
+ assertThrows(NotFoundException.class, () -> {
+ beerController.deleteById(UUID.randomUUID());
+ });
+ }
+
+ @Rollback
+ @Transactional
+ @Test
+ void deleteByIdFound() {
+ Beer beer = beerRepository.findAll().get(0);
+
+ ResponseEntity responseEntity = beerController.deleteById(beer.getId());
+ assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(204));
+
+ assertThat(beerRepository.findById(beer.getId()).isEmpty());
+ }
+
+ @Test
+ void testUpdateNotFound() {
+ assertThrows(NotFoundException.class, () -> {
+ beerController.updateById(UUID.randomUUID(), BeerDTO.builder().build());
+ });
+ }
+
+ @Rollback
+ @Transactional
+ @Test
+ void updateExistingBeer() {
+ Beer beer = beerRepository.findAll().get(0);
+ BeerDTO beerDTO = beerMapper.beerToBeerDto(beer);
+ beerDTO.setId(null);
+ beerDTO.setVersion(null);
+ final String beerName = "UPDATED";
+ beerDTO.setBeerName(beerName);
+
+ ResponseEntity responseEntity = beerController.updateById(beer.getId(), beerDTO);
+ assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(204));
+
+ Beer updatedBeer = beerRepository.findById(beer.getId()).get();
+ assertThat(updatedBeer.getBeerName()).isEqualTo(beerName);
+ }
+
+ @Rollback
+ @Transactional
+ @Test
+ void saveNewBeerTest() {
+ BeerDTO beerDTO = BeerDTO.builder()
+ .beerName("New Beer")
+ .build();
+
+ ResponseEntity responseEntity = beerController.handlePost(beerDTO);
+
+ assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(201));
+ assertThat(responseEntity.getHeaders().getLocation()).isNotNull();
+
+ String[] locationUUID = responseEntity.getHeaders().getLocation().getPath().split("/");
+ UUID savedUUID = UUID.fromString(locationUUID[4]);
+
+ Beer beer = beerRepository.findById(savedUUID).get();
+ assertThat(beer).isNotNull();
+ }
+
+ @Test
+ void testBeerIdNotFound() {
+ assertThrows(NotFoundException.class, () -> {
+ beerController.getBeerById(UUID.randomUUID());
+ });
+ }
+
+ @Test
+ void testGetById() {
+ Beer beer = beerRepository.findAll().get(0);
+
+ BeerDTO dto = beerController.getBeerById(beer.getId());
+
+ assertThat(dto).isNotNull();
+ }
+
+ @Test
+ void testListBeers() {
+ List dtos = beerController.listBeers();
+
+ assertThat(dtos.size()).isEqualTo(3);
+ }
+
+ @Rollback
+ @Transactional
+ @Test
+ void testEmptyList() {
+ beerRepository.deleteAll();
+ List dtos = beerController.listBeers();
+
+ assertThat(dtos.size()).isEqualTo(0);
+ }
+}
+
+
+
+
+
+
+
diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java
new file mode 100644
index 000000000..d5e9feba4
--- /dev/null
+++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java
@@ -0,0 +1,187 @@
+package guru.springframework.spring6restmvc.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import guru.springframework.spring6restmvc.model.BeerDTO;
+import guru.springframework.spring6restmvc.services.BeerService;
+import guru.springframework.spring6restmvc.services.BeerServiceImpl;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.verify;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+@WebMvcTest(BeerController.class)
+class BeerControllerTest {
+
+ @Autowired
+ MockMvc mockMvc;
+
+ @Autowired
+ ObjectMapper objectMapper;
+
+ @MockitoBean
+ BeerService beerService;
+
+ BeerServiceImpl beerServiceImpl;
+
+ @Captor
+ ArgumentCaptor uuidArgumentCaptor;
+
+ @Captor
+ ArgumentCaptor beerArgumentCaptor;
+
+ @BeforeEach
+ void setUp() {
+ beerServiceImpl = new BeerServiceImpl();
+ }
+
+ @Test
+ void testPatchBeer() throws Exception {
+ BeerDTO beer = beerServiceImpl.listBeers().get(0);
+
+ Map beerMap = new HashMap<>();
+ beerMap.put("beerName", "New Name");
+
+ mockMvc.perform(patch(BeerController.BEER_PATH_ID, beer.getId())
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(beerMap)))
+ .andExpect(status().isNoContent());
+
+ verify(beerService).patchBeerById(uuidArgumentCaptor.capture(), beerArgumentCaptor.capture());
+
+ assertThat(beer.getId()).isEqualTo(uuidArgumentCaptor.getValue());
+ assertThat(beerMap.get("beerName")).isEqualTo(beerArgumentCaptor.getValue().getBeerName());
+ }
+
+ @Test
+ void testDeleteBeer() throws Exception {
+ BeerDTO beer = beerServiceImpl.listBeers().get(0);
+
+ given(beerService.deleteById(any())).willReturn(true);
+
+ mockMvc.perform(delete(BeerController.BEER_PATH_ID, beer.getId())
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isNoContent());
+
+ verify(beerService).deleteById(uuidArgumentCaptor.capture());
+
+ assertThat(beer.getId()).isEqualTo(uuidArgumentCaptor.getValue());
+ }
+
+ @Test
+ void testUpdateBeer() throws Exception {
+ BeerDTO beer = beerServiceImpl.listBeers().get(0);
+
+ given(beerService.updateBeerById(any(), any())).willReturn(Optional.of(beer));
+
+ mockMvc.perform(put(BeerController.BEER_PATH_ID, beer.getId())
+ .accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(beer)))
+ .andExpect(status().isNoContent());
+
+ verify(beerService).updateBeerById(any(UUID.class), any(BeerDTO.class));
+ }
+
+ @Test
+ void testUpdateBeerBlankName() throws Exception {
+ BeerDTO beer = beerServiceImpl.listBeers().get(0);
+ beer.setBeerName("");
+ given(beerService.updateBeerById(any(), any())).willReturn(Optional.of(beer));
+
+ mockMvc.perform(put(BeerController.BEER_PATH_ID, beer.getId())
+ .accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(beer)))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.length()", is(1)));
+
+ }
+
+ @Test
+ void testCreateNewBeer() throws Exception {
+ BeerDTO beer = beerServiceImpl.listBeers().get(0);
+ beer.setVersion(null);
+ beer.setId(null);
+
+ given(beerService.saveNewBeer(any(BeerDTO.class))).willReturn(beerServiceImpl.listBeers().get(1));
+
+ mockMvc.perform(post(BeerController.BEER_PATH)
+ .accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(beer)))
+ .andExpect(status().isCreated())
+ .andExpect(header().exists("Location"));
+ }
+
+ @Test
+ void testCreateBeerNullBeerName() throws Exception {
+
+ BeerDTO beerDTO = BeerDTO.builder().build();
+
+ given(beerService.saveNewBeer(any(BeerDTO.class))).willReturn(beerServiceImpl.listBeers().get(1));
+
+ MvcResult mvcResult = mockMvc.perform(post(BeerController.BEER_PATH)
+ .accept(MediaType.APPLICATION_JSON)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(beerDTO)))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.length()", is(6)))
+ .andReturn();
+
+ System.out.println(mvcResult.getResponse().getContentAsString());
+ }
+
+ @Test
+ void testListBeers() throws Exception {
+ given(beerService.listBeers()).willReturn(beerServiceImpl.listBeers());
+
+ mockMvc.perform(get(BeerController.BEER_PATH)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.length()", is(3)));
+ }
+
+ @Test
+ void getBeerByIdNotFound() throws Exception {
+
+ given(beerService.getBeerById(any(UUID.class))).willReturn(Optional.empty());
+
+ mockMvc.perform(get(BeerController.BEER_PATH_ID, UUID.randomUUID()))
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ void getBeerById() throws Exception {
+ BeerDTO testBeer = beerServiceImpl.listBeers().get(0);
+
+ given(beerService.getBeerById(testBeer.getId())).willReturn(Optional.of(testBeer));
+
+ mockMvc.perform(get(BeerController.BEER_PATH_ID, testBeer.getId())
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.id", is(testBeer.getId().toString())))
+ .andExpect(jsonPath("$.beerName", is(testBeer.getBeerName())));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerIT.java b/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerIT.java
new file mode 100644
index 000000000..46dca1904
--- /dev/null
+++ b/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerIT.java
@@ -0,0 +1,137 @@
+package guru.springframework.spring6restmvc.controller;
+
+import guru.springframework.spring6restmvc.entities.Customer;
+import guru.springframework.spring6restmvc.mappers.CustomerMapper;
+import guru.springframework.spring6restmvc.model.CustomerDTO;
+import guru.springframework.spring6restmvc.repositories.CustomerRepository;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@SpringBootTest
+class CustomerControllerIT {
+
+ @Autowired
+ CustomerRepository customerRepository;
+
+ @Autowired
+ CustomerController customerController;
+
+ @Autowired
+ CustomerMapper customerMapper;
+
+ @Rollback
+ @Transactional
+ @Test
+ void deleteByIdFound() {
+ Customer customer = customerRepository.findAll().get(0);
+
+ ResponseEntity responseEntity = customerController.deleteCustomerById(customer.getId());
+ assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(204));
+
+ assertThat(customerRepository.findById(customer.getId()).isEmpty());
+ }
+
+ @Test
+ void testDeleteNotFound() {
+ assertThrows(NotFoundException.class, () -> {
+ customerController.deleteCustomerById(UUID.randomUUID());
+ });
+ }
+
+ @Test
+ void testUpdateNotFound() {
+ assertThrows(NotFoundException.class, () -> {
+ customerController.updateCustomerByID(UUID.randomUUID(), CustomerDTO.builder().build());
+ });
+ }
+
+ @Rollback
+ @Transactional
+ @Test
+ void updateExistingBeer() {
+ Customer customer = customerRepository.findAll().get(0);
+ CustomerDTO customerDTO = customerMapper.customerToCustomerDto(customer);
+ customerDTO.setId(null);
+ customerDTO.setVersion(null);
+ final String customerName = "UPDATED";
+ customerDTO.setName(customerName);
+
+ ResponseEntity responseEntity = customerController.updateCustomerByID(customer.getId(), customerDTO);
+ assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(204));
+
+ Customer updatedCustomer = customerRepository.findById(customer.getId()).get();
+ assertThat(updatedCustomer.getName()).isEqualTo(customerName);
+ }
+
+ @Rollback
+ @Transactional
+ @Test
+ void saveNewBeerTest() {
+ CustomerDTO customerDTO = CustomerDTO.builder()
+ .name("TEST")
+ .build();
+
+ ResponseEntity responseEntity = customerController.handlePost(customerDTO);
+
+ assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(201));
+ assertThat(responseEntity.getHeaders().getLocation()).isNotNull();
+
+ String[] locationUUID = responseEntity.getHeaders().getLocation().getPath().split("/");
+ UUID savedUUID = UUID.fromString(locationUUID[4]);
+
+ Customer customer = customerRepository.findById(savedUUID).get();
+ assertThat(customer).isNotNull();
+ }
+
+ @Rollback
+ @Transactional
+ @Test
+ void testListAllEmptyList() {
+ customerRepository.deleteAll();
+ List dtos = customerController.listAllCustomers();
+
+ assertThat(dtos.size()).isEqualTo(0);
+ }
+
+ @Test
+ void testListAll() {
+ List dtos = customerController.listAllCustomers();
+
+ assertThat(dtos.size()).isEqualTo(3);
+ }
+
+ @Test
+ void testGetByIdNotFound() {
+ assertThrows(NotFoundException.class, () -> {
+ customerController.getCustomerById(UUID.randomUUID());
+ });
+ }
+
+ @Test
+ void testGetById() {
+ Customer customer = customerRepository.findAll().get(0);
+ CustomerDTO customerDTO = customerController.getCustomerById(customer.getId());
+ assertThat(customerDTO).isNotNull();
+ }
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java
new file mode 100644
index 000000000..f64cbfcfa
--- /dev/null
+++ b/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java
@@ -0,0 +1,166 @@
+package guru.springframework.spring6restmvc.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import guru.springframework.spring6restmvc.model.CustomerDTO;
+import guru.springframework.spring6restmvc.services.CustomerService;
+import guru.springframework.spring6restmvc.services.CustomerServiceImpl;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+import org.springframework.test.web.servlet.MockMvc;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.verify;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+@WebMvcTest(CustomerController.class)
+class CustomerControllerTest {
+
+ @MockitoBean
+ CustomerService customerService;
+
+ @Autowired
+ MockMvc mockMvc;
+
+ @Autowired
+ ObjectMapper objectMapper;
+
+ CustomerServiceImpl customerServiceImpl;
+
+ @BeforeEach
+ void setUp() {
+ customerServiceImpl = new CustomerServiceImpl();
+ }
+
+ @Captor
+ ArgumentCaptor uuidArgumentCaptor;
+
+ @Captor
+ ArgumentCaptor customerArgumentCaptor;
+
+ @Test
+ void testPatchCustomer() throws Exception {
+ CustomerDTO customer = customerServiceImpl.getAllCustomers().get(0);
+
+ Map customerMap = new HashMap<>();
+ customerMap.put("name", "New Name");
+
+ mockMvc.perform(patch( CustomerController.CUSTOMER_PATH_ID, customer.getId())
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(customerMap)))
+ .andExpect(status().isNoContent());
+
+ verify(customerService).patchCustomerById(uuidArgumentCaptor.capture(),
+ customerArgumentCaptor.capture());
+
+ assertThat(uuidArgumentCaptor.getValue()).isEqualTo(customer.getId());
+ assertThat(customerArgumentCaptor.getValue().getName())
+ .isEqualTo(customerMap.get("name"));
+ }
+
+ @Test
+ void testDeleteCustomer() throws Exception {
+ CustomerDTO customer = customerServiceImpl.getAllCustomers().get(0);
+
+ given(customerService.deleteCustomerById(any())).willReturn(true);
+
+ mockMvc.perform(delete(CustomerController.CUSTOMER_PATH_ID, customer.getId())
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(status().isNoContent());
+
+ verify(customerService).deleteCustomerById(uuidArgumentCaptor.capture());
+
+ assertThat(customer.getId()).isEqualTo(uuidArgumentCaptor.getValue());
+ }
+
+ @Test
+ void testUpdateCustomer() throws Exception {
+ CustomerDTO customer = customerServiceImpl.getAllCustomers().get(0);
+
+ given(customerService.updateCustomerById(any(), any())).willReturn(Optional.of(CustomerDTO.builder()
+ .build()));
+
+ mockMvc.perform(put(CustomerController.CUSTOMER_PATH_ID, customer.getId())
+ .content(objectMapper.writeValueAsString(customer))
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isNoContent());
+
+ verify(customerService).updateCustomerById(uuidArgumentCaptor.capture(), any(CustomerDTO.class));
+
+ assertThat(customer.getId()).isEqualTo(uuidArgumentCaptor.getValue());
+ }
+
+ @Test
+ void testCreateCustomer() throws Exception {
+ CustomerDTO customer = customerServiceImpl.getAllCustomers().get(0);
+ customer.setId(null);
+ customer.setVersion(null);
+
+ given(customerService.saveNewCustomer(any(CustomerDTO.class)))
+ .willReturn(customerServiceImpl.getAllCustomers().get(1));
+
+ mockMvc.perform(post(CustomerController.CUSTOMER_PATH).contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON)
+ .content(objectMapper.writeValueAsString(customer)))
+ .andExpect(status().isCreated())
+ .andExpect(header().exists("Location"));
+ }
+
+ @Test
+ void listAllCustomers() throws Exception {
+ given(customerService.getAllCustomers()).willReturn(customerServiceImpl.getAllCustomers());
+
+ mockMvc.perform(get(CustomerController.CUSTOMER_PATH)
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.length()", is(3)));
+ }
+
+ @Test
+ void getCustomerByIdNotFound() throws Exception {
+
+ given(customerService.getCustomerById(any(UUID.class))).willReturn(Optional.empty());
+
+ mockMvc.perform(get(CustomerController.CUSTOMER_PATH_ID, UUID.randomUUID()))
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ void getCustomerById() throws Exception {
+ CustomerDTO customer = customerServiceImpl.getAllCustomers().get(0);
+
+ given(customerService.getCustomerById(customer.getId())).willReturn(Optional.of(customer));
+
+ mockMvc.perform(get(CustomerController.CUSTOMER_PATH_ID, customer.getId())
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(jsonPath("$.name", is(customer.getName())));
+ }
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/guru/springframework/spring6restmvc/repositories/BeerRepositoryTest.java b/src/test/java/guru/springframework/spring6restmvc/repositories/BeerRepositoryTest.java
new file mode 100644
index 000000000..1e8039427
--- /dev/null
+++ b/src/test/java/guru/springframework/spring6restmvc/repositories/BeerRepositoryTest.java
@@ -0,0 +1,50 @@
+package guru.springframework.spring6restmvc.repositories;
+
+import guru.springframework.spring6restmvc.entities.Beer;
+import guru.springframework.spring6restmvc.model.BeerStyle;
+import jakarta.validation.ConstraintViolationException;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+import java.math.BigDecimal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@DataJpaTest
+class BeerRepositoryTest {
+
+ @Autowired
+ BeerRepository beerRepository;
+
+ @Test
+ void testSaveBeerNameTooLong() {
+
+ assertThrows(ConstraintViolationException.class, () -> {
+ Beer savedBeer = beerRepository.save(Beer.builder()
+ .beerName("My Beer 0123345678901233456789012334567890123345678901233456789012334567890123345678901233456789")
+ .beerStyle(BeerStyle.PALE_ALE)
+ .upc("234234234234")
+ .price(new BigDecimal("11.99"))
+ .build());
+
+ beerRepository.flush();
+ });
+ }
+
+ @Test
+ void testSaveBeer() {
+ Beer savedBeer = beerRepository.save(Beer.builder()
+ .beerName("My Beer")
+ .beerStyle(BeerStyle.PALE_ALE)
+ .upc("234234234234")
+ .price(new BigDecimal("11.99"))
+ .build());
+
+ beerRepository.flush();
+
+ assertThat(savedBeer).isNotNull();
+ assertThat(savedBeer.getId()).isNotNull();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/guru/springframework/spring6restmvc/repositories/CustomerRepositoryTest.java b/src/test/java/guru/springframework/spring6restmvc/repositories/CustomerRepositoryTest.java
new file mode 100644
index 000000000..d6848114f
--- /dev/null
+++ b/src/test/java/guru/springframework/spring6restmvc/repositories/CustomerRepositoryTest.java
@@ -0,0 +1,25 @@
+package guru.springframework.spring6restmvc.repositories;
+
+import guru.springframework.spring6restmvc.entities.Customer;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DataJpaTest
+class CustomerRepositoryTest {
+
+ @Autowired
+ CustomerRepository customerRepository;
+
+ @Test
+ void testSaveCustomer() {
+ Customer customer = customerRepository.save(Customer.builder()
+ .name("New Name")
+ .build());
+
+ assertThat(customer.getId()).isNotNull();
+
+ }
+}
\ No newline at end of file