From a7f6fecb0286e7bac219552ad9d3aa7819ea1a0c Mon Sep 17 00:00:00 2001 From: Gustavo Meneses Gois Date: Sat, 29 Mar 2025 09:31:13 +0000 Subject: [PATCH 01/10] section 6 - Spring MVC Rest Services --- pom.xml | 5 + .../controller/BeerController.java | 74 +++++++++ .../controller/CustomerController.java | 70 ++++++++ .../spring6restmvc/model/Beer.java | 25 +++ .../spring6restmvc/model/BeerStyle.java | 8 + .../spring6restmvc/model/Customer.java | 21 +++ .../spring6restmvc/services/BeerService.java | 24 +++ .../services/BeerServiceImpl.java | 154 ++++++++++++++++++ .../services/CustomerService.java | 24 +++ .../services/CustomerServiceImpl.java | 105 ++++++++++++ src/main/resources/application.properties | 1 + .../controller/BeerControllerTest.java | 23 +++ 12 files changed, 534 insertions(+) create mode 100644 src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/model/Beer.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/model/BeerStyle.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/model/Customer.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/services/BeerService.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/services/BeerServiceImpl.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/services/CustomerService.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceImpl.java create mode 100644 src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java diff --git a/pom.xml b/pom.xml index 504188e96..a33239825 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,11 @@ spring-boot-starter-web + + org.springframework.boot + spring-boot-devtools + + org.projectlombok lombok 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..6e972f198 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java @@ -0,0 +1,74 @@ +package guru.springframework.spring6restmvc.controller; + +import guru.springframework.spring6restmvc.model.Beer; +import guru.springframework.spring6restmvc.services.BeerService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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. + */ +@Slf4j +@AllArgsConstructor +@RestController +@RequestMapping("/api/v1/beer") +public class BeerController { + private final BeerService beerService; + + @PatchMapping("{beerId}") + public ResponseEntity updateBeerPatchById(@PathVariable("beerId")UUID beerId, @RequestBody Beer beer){ + + beerService.patchBeerById(beerId, beer); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @DeleteMapping("{beerId}") + public ResponseEntity deleteById(@PathVariable("beerId") UUID beerId){ + + beerService.deleteById(beerId); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @PutMapping("{beerId}") + public ResponseEntity updateById(@PathVariable("beerId")UUID beerId, @RequestBody Beer beer){ + + beerService.updateBeerById(beerId, beer); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @PostMapping + //@RequestMapping(method = RequestMethod.POST) + public ResponseEntity handlePost(@RequestBody Beer beer){ + + Beer savedBeer = beerService.saveNewBeer(beer); + + HttpHeaders headers = new HttpHeaders(); + headers.add("Location", "/api/v1/beer/" + savedBeer.getId().toString()); + + return new ResponseEntity(headers, HttpStatus.CREATED); + } + + @RequestMapping(method = RequestMethod.GET) + public List listBeers(){ + return beerService.listBeers(); + } + + @RequestMapping(value = "{beerId}", method = RequestMethod.GET) + public Beer getBeerById(@PathVariable("beerId") UUID beerId){ + + log.debug("Get Beer by Id - in controller"); + + return beerService.getBeerById(beerId); + } + +} 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..0799c5a47 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java @@ -0,0 +1,70 @@ +package guru.springframework.spring6restmvc.controller; + +import guru.springframework.spring6restmvc.model.Customer; +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. + */ +@RequestMapping("/api/v1/customer") +@RequiredArgsConstructor +@RestController +public class CustomerController { + + private final CustomerService customerService; + + @PatchMapping("{customerId}") + public ResponseEntity patchCustomerById(@PathVariable("customerId") UUID customerId, + @RequestBody Customer customer){ + + customerService.patchCustomerById(customerId, customer); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @DeleteMapping("{customerId}") + public ResponseEntity deleteCustomerById(@PathVariable("customerId") UUID customerId){ + + customerService.deleteCustomerById(customerId); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @PutMapping("{customerId}") + public ResponseEntity updateCustomerByID(@PathVariable("customerId") UUID customerId, + @RequestBody Customer customer){ + + customerService.updateCustomerById(customerId, customer); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @PostMapping + public ResponseEntity handlePost(@RequestBody Customer customer){ + Customer savedCustomer = customerService.saveNewCustomer(customer); + + HttpHeaders headers = new HttpHeaders(); + headers.add("Location", "/api/v1/customer/" + savedCustomer.getId().toString()); + + return new ResponseEntity(headers, HttpStatus.CREATED); + } + + @RequestMapping(method = RequestMethod.GET) + public List listAllCustomers(){ + return customerService.getAllCustomers(); + } + + @RequestMapping(value = "{customerId}", method = RequestMethod.GET) + public Customer getCustomerById(@PathVariable("customerId") UUID id){ + return customerService.getCustomerById(id); + } + +} diff --git a/src/main/java/guru/springframework/spring6restmvc/model/Beer.java b/src/main/java/guru/springframework/spring6restmvc/model/Beer.java new file mode 100644 index 000000000..1aed54c28 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/model/Beer.java @@ -0,0 +1,25 @@ +package guru.springframework.spring6restmvc.model; + +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 Beer { + private UUID id; + private Integer version; + private String beerName; + private BeerStyle beerStyle; + private String upc; + private Integer quantityOnHand; + 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/Customer.java b/src/main/java/guru/springframework/spring6restmvc/model/Customer.java new file mode 100644 index 000000000..195ae3e72 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/model/Customer.java @@ -0,0 +1,21 @@ +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 Customer { + + private String name; + private UUID id; + private Integer version; + private LocalDateTime createdDate; + private LocalDateTime updateDate; +} 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..426739d82 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/services/BeerService.java @@ -0,0 +1,24 @@ +package guru.springframework.spring6restmvc.services; + +import guru.springframework.spring6restmvc.model.Beer; + +import java.util.List; +import java.util.UUID; + +/** + * Created by jt, Spring Framework Guru. + */ +public interface BeerService { + + List listBeers(); + + Beer getBeerById(UUID id); + + Beer saveNewBeer(Beer beer); + + void updateBeerById(UUID beerId, Beer beer); + + void deleteById(UUID beerId); + + void patchBeerById(UUID beerId, Beer 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..d1dcd4edf --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceImpl.java @@ -0,0 +1,154 @@ +package guru.springframework.spring6restmvc.services; + +import guru.springframework.spring6restmvc.model.Beer; +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<>(); + + Beer beer1 = Beer.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(); + + Beer beer2 = Beer.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(); + + Beer beer3 = Beer.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 void patchBeerById(UUID beerId, Beer beer) { + Beer 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()); + } + } + + @Override + public void deleteById(UUID beerId) { + beerMap.remove(beerId); + } + + @Override + public void updateBeerById(UUID beerId, Beer beer) { + Beer existing = beerMap.get(beerId); + existing.setBeerName(beer.getBeerName()); + existing.setPrice(beer.getPrice()); + existing.setUpc(beer.getUpc()); + existing.setQuantityOnHand(beer.getQuantityOnHand()); + } + + @Override + public List listBeers(){ + return new ArrayList<>(beerMap.values()); + } + + @Override + public Beer getBeerById(UUID id) { + + log.debug("Get Beer by Id - in service. Id: " + id.toString()); + + return beerMap.get(id); + } + + @Override + public Beer saveNewBeer(Beer beer) { + + Beer savedBeer = Beer.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/CustomerService.java b/src/main/java/guru/springframework/spring6restmvc/services/CustomerService.java new file mode 100644 index 000000000..70eaaca19 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/services/CustomerService.java @@ -0,0 +1,24 @@ +package guru.springframework.spring6restmvc.services; + +import guru.springframework.spring6restmvc.model.Customer; + +import java.util.List; +import java.util.UUID; + +/** + * Created by jt, Spring Framework Guru. + */ +public interface CustomerService { + + Customer getCustomerById(UUID uuid); + + List getAllCustomers(); + + Customer saveNewCustomer(Customer customer); + + void updateCustomerById(UUID customerId, Customer customer); + + void deleteCustomerById(UUID customerId); + + void patchCustomerById(UUID customerId, Customer 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..587d74ea2 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceImpl.java @@ -0,0 +1,105 @@ +package guru.springframework.spring6restmvc.services; + +import guru.springframework.spring6restmvc.model.Customer; +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() { + Customer customer1 = Customer.builder() + .id(UUID.randomUUID()) + .name("Customer 1") + .version(1) + .createdDate(LocalDateTime.now()) + .updateDate(LocalDateTime.now()) + .build(); + + Customer customer2 = Customer.builder() + .id(UUID.randomUUID()) + .name("Customer 2") + .version(1) + .createdDate(LocalDateTime.now()) + .updateDate(LocalDateTime.now()) + .build(); + + Customer customer3 = Customer.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 void patchCustomerById(UUID customerId, Customer customer) { + Customer existing = customerMap.get(customerId); + + if (StringUtils.hasText(customer.getName())) { + existing.setName(customer.getName()); + } + } + + @Override + public void deleteCustomerById(UUID customerId) { + customerMap.remove(customerId); + } + + @Override + public void updateCustomerById(UUID customerId, Customer customer) { + Customer existing = customerMap.get(customerId); + existing.setName(customer.getName()); + } + + @Override + public Customer saveNewCustomer(Customer customer) { + + Customer savedCustomer = Customer.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 Customer getCustomerById(UUID uuid) { + return customerMap.get(uuid); + } + + @Override + public List getAllCustomers() { + return new ArrayList<>(customerMap.values()); + } +} + + + + + + + + + + + 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/controller/BeerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java new file mode 100644 index 000000000..114e81bcd --- /dev/null +++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java @@ -0,0 +1,23 @@ +package guru.springframework.spring6restmvc.controller; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class BeerControllerTest { + + @Autowired + BeerController beerController; + + @Test + void getBeerById() { + + System.out.println(beerController.getBeerById(UUID.randomUUID())); + + } +} \ No newline at end of file From 64b8181803b74f3cf5455206d7bce1642bd90faf Mon Sep 17 00:00:00 2001 From: Gustavo Meneses Gois Date: Sat, 29 Mar 2025 11:35:48 +0000 Subject: [PATCH 02/10] Section 7 - Spring MockMVC Test with Mockito and JUnit --- .../controller/BeerController.java | 24 +-- .../controller/CustomerController.java | 18 ++- .../controller/BeerControllerTest.java | 128 ++++++++++++++- .../controller/CustomerControllerTest.java | 151 ++++++++++++++++++ 4 files changed, 296 insertions(+), 25 deletions(-) create mode 100644 src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java diff --git a/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java b/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java index 6e972f198..fd3b7d701 100644 --- a/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java +++ b/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java @@ -2,7 +2,7 @@ import guru.springframework.spring6restmvc.model.Beer; import guru.springframework.spring6restmvc.services.BeerService; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -16,13 +16,16 @@ * Created by jt, Spring Framework Guru. */ @Slf4j -@AllArgsConstructor +@RequiredArgsConstructor @RestController -@RequestMapping("/api/v1/beer") 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("{beerId}") + @PatchMapping(BEER_PATH_ID) public ResponseEntity updateBeerPatchById(@PathVariable("beerId")UUID beerId, @RequestBody Beer beer){ beerService.patchBeerById(beerId, beer); @@ -30,7 +33,7 @@ public ResponseEntity updateBeerPatchById(@PathVariable("beerId")UUID beerId, @R return new ResponseEntity(HttpStatus.NO_CONTENT); } - @DeleteMapping("{beerId}") + @DeleteMapping(BEER_PATH_ID) public ResponseEntity deleteById(@PathVariable("beerId") UUID beerId){ beerService.deleteById(beerId); @@ -38,7 +41,7 @@ public ResponseEntity deleteById(@PathVariable("beerId") UUID beerId){ return new ResponseEntity(HttpStatus.NO_CONTENT); } - @PutMapping("{beerId}") + @PutMapping(BEER_PATH_ID) public ResponseEntity updateById(@PathVariable("beerId")UUID beerId, @RequestBody Beer beer){ beerService.updateBeerById(beerId, beer); @@ -46,24 +49,23 @@ public ResponseEntity updateById(@PathVariable("beerId")UUID beerId, @RequestBod return new ResponseEntity(HttpStatus.NO_CONTENT); } - @PostMapping - //@RequestMapping(method = RequestMethod.POST) + @PostMapping(BEER_PATH) public ResponseEntity handlePost(@RequestBody Beer beer){ Beer savedBeer = beerService.saveNewBeer(beer); HttpHeaders headers = new HttpHeaders(); - headers.add("Location", "/api/v1/beer/" + savedBeer.getId().toString()); + headers.add("Location", BEER_PATH + "/" + savedBeer.getId().toString()); return new ResponseEntity(headers, HttpStatus.CREATED); } - @RequestMapping(method = RequestMethod.GET) + @GetMapping(value = BEER_PATH) public List listBeers(){ return beerService.listBeers(); } - @RequestMapping(value = "{beerId}", method = RequestMethod.GET) + @GetMapping(value = BEER_PATH_ID) public Beer getBeerById(@PathVariable("beerId") UUID beerId){ log.debug("Get Beer by Id - in controller"); diff --git a/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java b/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java index 0799c5a47..19a465f49 100644 --- a/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java +++ b/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java @@ -14,14 +14,16 @@ /** * Created by jt, Spring Framework Guru. */ -@RequestMapping("/api/v1/customer") + @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("{customerId}") + @PatchMapping(CUSTOMER_PATH_ID) public ResponseEntity patchCustomerById(@PathVariable("customerId") UUID customerId, @RequestBody Customer customer){ @@ -30,7 +32,7 @@ public ResponseEntity patchCustomerById(@PathVariable("customerId") UUID custome return new ResponseEntity(HttpStatus.NO_CONTENT); } - @DeleteMapping("{customerId}") + @DeleteMapping(CUSTOMER_PATH_ID) public ResponseEntity deleteCustomerById(@PathVariable("customerId") UUID customerId){ customerService.deleteCustomerById(customerId); @@ -38,7 +40,7 @@ public ResponseEntity deleteCustomerById(@PathVariable("customerId") UUID custom return new ResponseEntity(HttpStatus.NO_CONTENT); } - @PutMapping("{customerId}") + @PutMapping(CUSTOMER_PATH_ID) public ResponseEntity updateCustomerByID(@PathVariable("customerId") UUID customerId, @RequestBody Customer customer){ @@ -47,22 +49,22 @@ public ResponseEntity updateCustomerByID(@PathVariable("customerId") UUID custom return new ResponseEntity(HttpStatus.NO_CONTENT); } - @PostMapping + @PostMapping(CUSTOMER_PATH) public ResponseEntity handlePost(@RequestBody Customer customer){ Customer savedCustomer = customerService.saveNewCustomer(customer); HttpHeaders headers = new HttpHeaders(); - headers.add("Location", "/api/v1/customer/" + savedCustomer.getId().toString()); + headers.add("Location", CUSTOMER_PATH + "/" + savedCustomer.getId().toString()); return new ResponseEntity(headers, HttpStatus.CREATED); } - @RequestMapping(method = RequestMethod.GET) + @GetMapping(CUSTOMER_PATH) public List listAllCustomers(){ return customerService.getAllCustomers(); } - @RequestMapping(value = "{customerId}", method = RequestMethod.GET) + @GetMapping(value = CUSTOMER_PATH_ID) public Customer getCustomerById(@PathVariable("customerId") UUID id){ return customerService.getCustomerById(id); } diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java index 114e81bcd..74fb1d7a5 100644 --- a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java +++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java @@ -1,23 +1,139 @@ package guru.springframework.spring6restmvc.controller; +import com.fasterxml.jackson.databind.ObjectMapper; +import guru.springframework.spring6restmvc.model.Beer; +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.context.SpringBootTest; +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.UUID; -import static org.junit.jupiter.api.Assertions.*; +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.*; -@SpringBootTest +@WebMvcTest(BeerController.class) class BeerControllerTest { @Autowired - BeerController beerController; + 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 { + Beer 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 { + Beer beer = beerServiceImpl.listBeers().get(0); + + 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 { + Beer beer = beerServiceImpl.listBeers().get(0); + + 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(Beer.class)); + } + + @Test + void testCreateNewBeer() throws Exception { + Beer beer = beerServiceImpl.listBeers().get(0); + beer.setVersion(null); + beer.setId(null); + + given(beerService.saveNewBeer(any(Beer.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 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 getBeerById() { + void getBeerById() throws Exception { + Beer testBeer = beerServiceImpl.listBeers().get(0); - System.out.println(beerController.getBeerById(UUID.randomUUID())); + given(beerService.getBeerById(testBeer.getId())).willReturn(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/CustomerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java new file mode 100644 index 000000000..8840eba3a --- /dev/null +++ b/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java @@ -0,0 +1,151 @@ +package guru.springframework.spring6restmvc.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import guru.springframework.spring6restmvc.model.Customer; +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.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 { + Customer 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 { + Customer customer = customerServiceImpl.getAllCustomers().get(0); + + 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 { + Customer customer = customerServiceImpl.getAllCustomers().get(0); + + 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(Customer.class)); + + assertThat(customer.getId()).isEqualTo(uuidArgumentCaptor.getValue()); + } + + @Test + void testCreateCustomer() throws Exception { + Customer customer = customerServiceImpl.getAllCustomers().get(0); + customer.setId(null); + customer.setVersion(null); + + given(customerService.saveNewCustomer(any(Customer.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 getCustomerById() throws Exception { + Customer customer = customerServiceImpl.getAllCustomers().get(0); + + given(customerService.getCustomerById(customer.getId())).willReturn(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()))); + } +} + + + + + + + + + + From b0bc0cd1b7e406365c78435216b461fcd253fd40 Mon Sep 17 00:00:00 2001 From: Gustavo Meneses Gois Date: Sat, 29 Mar 2025 12:04:07 +0000 Subject: [PATCH 03/10] Section 8 - Exception Handling with Spring MVC * 86 - Using Exception Handler * 87 - Controller Advice * 88 - Using ResponseStatus Annotation + 89 - Using Java Optional --- .../controller/BeerController.java | 3 +- .../controller/CustomerController.java | 2 +- .../controller/NotFoundException.java | 29 +++++++++++++++++++ .../spring6restmvc/services/BeerService.java | 3 +- .../services/BeerServiceImpl.java | 4 +-- .../services/CustomerService.java | 3 +- .../services/CustomerServiceImpl.java | 4 +-- .../controller/BeerControllerTest.java | 12 +++++++- .../controller/CustomerControllerTest.java | 12 +++++++- 9 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 src/main/java/guru/springframework/spring6restmvc/controller/NotFoundException.java diff --git a/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java b/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java index fd3b7d701..f01be9e31 100644 --- a/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java +++ b/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java @@ -65,12 +65,13 @@ public List listBeers(){ return beerService.listBeers(); } + @GetMapping(value = BEER_PATH_ID) public Beer getBeerById(@PathVariable("beerId") UUID beerId){ log.debug("Get Beer by Id - in controller"); - return beerService.getBeerById(beerId); + return beerService.getBeerById(beerId).orElseThrow(NotFoundException::new); } } diff --git a/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java b/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java index 19a465f49..c0e7b93a2 100644 --- a/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java +++ b/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java @@ -66,7 +66,7 @@ public List listAllCustomers(){ @GetMapping(value = CUSTOMER_PATH_ID) public Customer getCustomerById(@PathVariable("customerId") UUID id){ - return customerService.getCustomerById(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/services/BeerService.java b/src/main/java/guru/springframework/spring6restmvc/services/BeerService.java index 426739d82..a90985931 100644 --- a/src/main/java/guru/springframework/spring6restmvc/services/BeerService.java +++ b/src/main/java/guru/springframework/spring6restmvc/services/BeerService.java @@ -3,6 +3,7 @@ import guru.springframework.spring6restmvc.model.Beer; import java.util.List; +import java.util.Optional; import java.util.UUID; /** @@ -12,7 +13,7 @@ public interface BeerService { List listBeers(); - Beer getBeerById(UUID id); + Optional getBeerById(UUID id); Beer saveNewBeer(Beer beer); diff --git a/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceImpl.java b/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceImpl.java index d1dcd4edf..6bb43a13b 100644 --- a/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceImpl.java +++ b/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceImpl.java @@ -108,11 +108,11 @@ public List listBeers(){ } @Override - public Beer getBeerById(UUID id) { + public Optional getBeerById(UUID id) { log.debug("Get Beer by Id - in service. Id: " + id.toString()); - return beerMap.get(id); + return Optional.of(beerMap.get(id)); } @Override diff --git a/src/main/java/guru/springframework/spring6restmvc/services/CustomerService.java b/src/main/java/guru/springframework/spring6restmvc/services/CustomerService.java index 70eaaca19..a0c0dd084 100644 --- a/src/main/java/guru/springframework/spring6restmvc/services/CustomerService.java +++ b/src/main/java/guru/springframework/spring6restmvc/services/CustomerService.java @@ -3,6 +3,7 @@ import guru.springframework.spring6restmvc.model.Customer; import java.util.List; +import java.util.Optional; import java.util.UUID; /** @@ -10,7 +11,7 @@ */ public interface CustomerService { - Customer getCustomerById(UUID uuid); + Optional getCustomerById(UUID uuid); List getAllCustomers(); diff --git a/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceImpl.java b/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceImpl.java index 587d74ea2..272404102 100644 --- a/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceImpl.java +++ b/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceImpl.java @@ -83,8 +83,8 @@ public Customer saveNewCustomer(Customer customer) { } @Override - public Customer getCustomerById(UUID uuid) { - return customerMap.get(uuid); + public Optional getCustomerById(UUID uuid) { + return Optional.of(customerMap.get(uuid)); } @Override diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java index 74fb1d7a5..eaaef42db 100644 --- a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java +++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java @@ -16,6 +16,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -123,11 +124,20 @@ void testListBeers() throws Exception { .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 { Beer testBeer = beerServiceImpl.listBeers().get(0); - given(beerService.getBeerById(testBeer.getId())).willReturn(testBeer); + given(beerService.getBeerById(testBeer.getId())).willReturn(Optional.of(testBeer)); mockMvc.perform(get(BeerController.BEER_PATH_ID, testBeer.getId()) .accept(MediaType.APPLICATION_JSON)) diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java index 8840eba3a..23b95b081 100644 --- a/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java +++ b/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java @@ -16,6 +16,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -126,11 +127,20 @@ void listAllCustomers() throws Exception { .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 { Customer customer = customerServiceImpl.getAllCustomers().get(0); - given(customerService.getCustomerById(customer.getId())).willReturn(customer); + given(customerService.getCustomerById(customer.getId())).willReturn(Optional.of(customer)); mockMvc.perform(get(CustomerController.CUSTOMER_PATH_ID, customer.getId()) .accept(MediaType.APPLICATION_JSON)) From f1d8df498e95b5ef7d5e493dccafa2459c10509b Mon Sep 17 00:00:00 2001 From: Gustavo Meneses Gois Date: Tue, 1 Apr 2025 22:39:00 +0100 Subject: [PATCH 04/10] Secao 9 - Spring Data JPA with Spring MVC --- pom.xml | 44 +++++- .../bootstrap/BootstrapData.java | 100 +++++++++++++ .../controller/BeerController.java | 22 +-- .../controller/CustomerController.java | 22 +-- .../spring6restmvc/entities/Beer.java | 38 +++++ .../spring6restmvc/entities/Customer.java | 31 ++++ .../spring6restmvc/mappers/BeerMapper.java | 17 +++ .../mappers/CustomerMapper.java | 17 +++ .../model/{Beer.java => BeerDTO.java} | 2 +- .../model/{Customer.java => CustomerDTO.java} | 5 +- .../repositories/BeerRepository.java | 12 ++ .../repositories/CustomerRepository.java | 12 ++ .../spring6restmvc/services/BeerService.java | 14 +- .../services/BeerServiceImpl.java | 33 +++-- .../services/BeerServiceJPA.java | 102 +++++++++++++ .../services/CustomerService.java | 14 +- .../services/CustomerServiceImpl.java | 33 +++-- .../services/CustomerServiceJPA.java | 86 +++++++++++ .../bootstrap/BootstrapDataTest.java | 40 +++++ .../controller/BeerControllerIT.java | 136 +++++++++++++++++ .../controller/BeerControllerTest.java | 22 +-- .../controller/CustomerControllerIT.java | 137 ++++++++++++++++++ .../controller/CustomerControllerTest.java | 23 +-- .../repositories/BeerRepositoryTest.java | 25 ++++ .../repositories/CustomerRepositoryTest.java | 25 ++++ 25 files changed, 929 insertions(+), 83 deletions(-) create mode 100644 src/main/java/guru/springframework/spring6restmvc/bootstrap/BootstrapData.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/entities/Beer.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/entities/Customer.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/mappers/BeerMapper.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/mappers/CustomerMapper.java rename src/main/java/guru/springframework/spring6restmvc/model/{Beer.java => BeerDTO.java} (95%) rename src/main/java/guru/springframework/spring6restmvc/model/{Customer.java => CustomerDTO.java} (93%) create mode 100644 src/main/java/guru/springframework/spring6restmvc/repositories/BeerRepository.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/repositories/CustomerRepository.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/services/BeerServiceJPA.java create mode 100644 src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceJPA.java create mode 100644 src/test/java/guru/springframework/spring6restmvc/bootstrap/BootstrapDataTest.java create mode 100644 src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerIT.java create mode 100644 src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerIT.java create mode 100644 src/test/java/guru/springframework/spring6restmvc/repositories/BeerRepositoryTest.java create mode 100644 src/test/java/guru/springframework/spring6restmvc/repositories/CustomerRepositoryTest.java diff --git a/pom.xml b/pom.xml index a33239825..d12999adb 100644 --- a/pom.xml +++ b/pom.xml @@ -15,23 +15,36 @@ spring-6-rest-mvc 21 + 1.5.5.Final org.springframework.boot spring-boot-starter-web - 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 @@ -53,6 +66,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 index f01be9e31..a361dbcd2 100644 --- a/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java +++ b/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java @@ -1,6 +1,6 @@ package guru.springframework.spring6restmvc.controller; -import guru.springframework.spring6restmvc.model.Beer; +import guru.springframework.spring6restmvc.model.BeerDTO; import guru.springframework.spring6restmvc.services.BeerService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -26,7 +26,7 @@ public class BeerController { private final BeerService beerService; @PatchMapping(BEER_PATH_ID) - public ResponseEntity updateBeerPatchById(@PathVariable("beerId")UUID beerId, @RequestBody Beer beer){ + public ResponseEntity updateBeerPatchById(@PathVariable("beerId")UUID beerId, @RequestBody BeerDTO beer){ beerService.patchBeerById(beerId, beer); @@ -36,23 +36,27 @@ public ResponseEntity updateBeerPatchById(@PathVariable("beerId")UUID beerId, @R @DeleteMapping(BEER_PATH_ID) public ResponseEntity deleteById(@PathVariable("beerId") UUID beerId){ - beerService.deleteById(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, @RequestBody Beer beer){ + public ResponseEntity updateById(@PathVariable("beerId")UUID beerId, @RequestBody BeerDTO beer){ - beerService.updateBeerById(beerId, beer); + if( beerService.updateBeerById(beerId, beer).isEmpty()){ + throw new NotFoundException(); + } return new ResponseEntity(HttpStatus.NO_CONTENT); } @PostMapping(BEER_PATH) - public ResponseEntity handlePost(@RequestBody Beer beer){ + public ResponseEntity handlePost(@RequestBody BeerDTO beer){ - Beer savedBeer = beerService.saveNewBeer(beer); + BeerDTO savedBeer = beerService.saveNewBeer(beer); HttpHeaders headers = new HttpHeaders(); headers.add("Location", BEER_PATH + "/" + savedBeer.getId().toString()); @@ -61,13 +65,13 @@ public ResponseEntity handlePost(@RequestBody Beer beer){ } @GetMapping(value = BEER_PATH) - public List listBeers(){ + public List listBeers(){ return beerService.listBeers(); } @GetMapping(value = BEER_PATH_ID) - public Beer getBeerById(@PathVariable("beerId") UUID beerId){ + public BeerDTO getBeerById(@PathVariable("beerId") UUID beerId){ log.debug("Get Beer by Id - in controller"); diff --git a/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java b/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java index c0e7b93a2..56994057f 100644 --- a/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java +++ b/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java @@ -1,6 +1,6 @@ package guru.springframework.spring6restmvc.controller; -import guru.springframework.spring6restmvc.model.Customer; +import guru.springframework.spring6restmvc.model.CustomerDTO; import guru.springframework.spring6restmvc.services.CustomerService; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; @@ -25,7 +25,7 @@ public class CustomerController { @PatchMapping(CUSTOMER_PATH_ID) public ResponseEntity patchCustomerById(@PathVariable("customerId") UUID customerId, - @RequestBody Customer customer){ + @RequestBody CustomerDTO customer){ customerService.patchCustomerById(customerId, customer); @@ -35,23 +35,27 @@ public ResponseEntity patchCustomerById(@PathVariable("customerId") UUID custome @DeleteMapping(CUSTOMER_PATH_ID) public ResponseEntity deleteCustomerById(@PathVariable("customerId") UUID customerId){ - customerService.deleteCustomerById(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 Customer customer){ + @RequestBody CustomerDTO customer){ - customerService.updateCustomerById(customerId, customer); + if (customerService.updateCustomerById(customerId, customer).isEmpty()){ + throw new NotFoundException(); + } return new ResponseEntity(HttpStatus.NO_CONTENT); } @PostMapping(CUSTOMER_PATH) - public ResponseEntity handlePost(@RequestBody Customer customer){ - Customer savedCustomer = customerService.saveNewCustomer(customer); + public ResponseEntity handlePost(@RequestBody CustomerDTO customer){ + CustomerDTO savedCustomer = customerService.saveNewCustomer(customer); HttpHeaders headers = new HttpHeaders(); headers.add("Location", CUSTOMER_PATH + "/" + savedCustomer.getId().toString()); @@ -60,12 +64,12 @@ public ResponseEntity handlePost(@RequestBody Customer customer){ } @GetMapping(CUSTOMER_PATH) - public List listAllCustomers(){ + public List listAllCustomers(){ return customerService.getAllCustomers(); } @GetMapping(value = CUSTOMER_PATH_ID) - public Customer getCustomerById(@PathVariable("customerId") UUID id){ + public CustomerDTO getCustomerById(@PathVariable("customerId") UUID id){ return customerService.getCustomerById(id).orElseThrow(NotFoundException::new); } 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..710ae5970 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/entities/Beer.java @@ -0,0 +1,38 @@ +package guru.springframework.spring6restmvc.entities; + +import guru.springframework.spring6restmvc.model.BeerStyle; +import jakarta.persistence.*; +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; + private String beerName; + private BeerStyle beerStyle; + private String upc; + private Integer quantityOnHand; + 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/Beer.java b/src/main/java/guru/springframework/spring6restmvc/model/BeerDTO.java similarity index 95% rename from src/main/java/guru/springframework/spring6restmvc/model/Beer.java rename to src/main/java/guru/springframework/spring6restmvc/model/BeerDTO.java index 1aed54c28..eee5ad954 100644 --- a/src/main/java/guru/springframework/spring6restmvc/model/Beer.java +++ b/src/main/java/guru/springframework/spring6restmvc/model/BeerDTO.java @@ -12,7 +12,7 @@ */ @Builder @Data -public class Beer { +public class BeerDTO { private UUID id; private Integer version; private String beerName; diff --git a/src/main/java/guru/springframework/spring6restmvc/model/Customer.java b/src/main/java/guru/springframework/spring6restmvc/model/CustomerDTO.java similarity index 93% rename from src/main/java/guru/springframework/spring6restmvc/model/Customer.java rename to src/main/java/guru/springframework/spring6restmvc/model/CustomerDTO.java index 195ae3e72..c35be5b4c 100644 --- a/src/main/java/guru/springframework/spring6restmvc/model/Customer.java +++ b/src/main/java/guru/springframework/spring6restmvc/model/CustomerDTO.java @@ -11,10 +11,9 @@ */ @Data @Builder -public class Customer { - - private String name; +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 index a90985931..3f795410e 100644 --- a/src/main/java/guru/springframework/spring6restmvc/services/BeerService.java +++ b/src/main/java/guru/springframework/spring6restmvc/services/BeerService.java @@ -1,6 +1,6 @@ package guru.springframework.spring6restmvc.services; -import guru.springframework.spring6restmvc.model.Beer; +import guru.springframework.spring6restmvc.model.BeerDTO; import java.util.List; import java.util.Optional; @@ -11,15 +11,15 @@ */ public interface BeerService { - List listBeers(); + List listBeers(); - Optional getBeerById(UUID id); + Optional getBeerById(UUID id); - Beer saveNewBeer(Beer beer); + BeerDTO saveNewBeer(BeerDTO beer); - void updateBeerById(UUID beerId, Beer beer); + Optional updateBeerById(UUID beerId, BeerDTO beer); - void deleteById(UUID beerId); + Boolean deleteById(UUID beerId); - void patchBeerById(UUID beerId, Beer beer); + 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 index 6bb43a13b..7e0e1c047 100644 --- a/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceImpl.java +++ b/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceImpl.java @@ -1,6 +1,6 @@ package guru.springframework.spring6restmvc.services; -import guru.springframework.spring6restmvc.model.Beer; +import guru.springframework.spring6restmvc.model.BeerDTO; import guru.springframework.spring6restmvc.model.BeerStyle; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -17,12 +17,12 @@ @Service public class BeerServiceImpl implements BeerService { - private Map beerMap; + private Map beerMap; public BeerServiceImpl() { this.beerMap = new HashMap<>(); - Beer beer1 = Beer.builder() + BeerDTO beer1 = BeerDTO.builder() .id(UUID.randomUUID()) .version(1) .beerName("Galaxy Cat") @@ -34,7 +34,7 @@ public BeerServiceImpl() { .updateDate(LocalDateTime.now()) .build(); - Beer beer2 = Beer.builder() + BeerDTO beer2 = BeerDTO.builder() .id(UUID.randomUUID()) .version(1) .beerName("Crank") @@ -46,7 +46,7 @@ public BeerServiceImpl() { .updateDate(LocalDateTime.now()) .build(); - Beer beer3 = Beer.builder() + BeerDTO beer3 = BeerDTO.builder() .id(UUID.randomUUID()) .version(1) .beerName("Sunshine City") @@ -64,8 +64,8 @@ public BeerServiceImpl() { } @Override - public void patchBeerById(UUID beerId, Beer beer) { - Beer existing = beerMap.get(beerId); + public Optional patchBeerById(UUID beerId, BeerDTO beer) { + BeerDTO existing = beerMap.get(beerId); if (StringUtils.hasText(beer.getBeerName())){ existing.setBeerName(beer.getBeerName()); @@ -86,29 +86,34 @@ public void patchBeerById(UUID beerId, Beer beer) { if (StringUtils.hasText(beer.getUpc())) { existing.setUpc(beer.getUpc()); } + + return Optional.of(existing); } @Override - public void deleteById(UUID beerId) { + public Boolean deleteById(UUID beerId) { beerMap.remove(beerId); + + return true; } @Override - public void updateBeerById(UUID beerId, Beer beer) { - Beer existing = beerMap.get(beerId); + 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(){ + public List listBeers(){ return new ArrayList<>(beerMap.values()); } @Override - public Optional getBeerById(UUID id) { + public Optional getBeerById(UUID id) { log.debug("Get Beer by Id - in service. Id: " + id.toString()); @@ -116,9 +121,9 @@ public Optional getBeerById(UUID id) { } @Override - public Beer saveNewBeer(Beer beer) { + public BeerDTO saveNewBeer(BeerDTO beer) { - Beer savedBeer = Beer.builder() + BeerDTO savedBeer = BeerDTO.builder() .id(UUID.randomUUID()) .version(1) .createdDate(LocalDateTime.now()) 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 index a0c0dd084..cdadac861 100644 --- a/src/main/java/guru/springframework/spring6restmvc/services/CustomerService.java +++ b/src/main/java/guru/springframework/spring6restmvc/services/CustomerService.java @@ -1,6 +1,6 @@ package guru.springframework.spring6restmvc.services; -import guru.springframework.spring6restmvc.model.Customer; +import guru.springframework.spring6restmvc.model.CustomerDTO; import java.util.List; import java.util.Optional; @@ -11,15 +11,15 @@ */ public interface CustomerService { - Optional getCustomerById(UUID uuid); + Optional getCustomerById(UUID uuid); - List getAllCustomers(); + List getAllCustomers(); - Customer saveNewCustomer(Customer customer); + CustomerDTO saveNewCustomer(CustomerDTO customer); - void updateCustomerById(UUID customerId, Customer customer); + Optional updateCustomerById(UUID customerId, CustomerDTO customer); - void deleteCustomerById(UUID customerId); + Boolean deleteCustomerById(UUID customerId); - void patchCustomerById(UUID customerId, Customer customer); + 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 index 272404102..27a2c5b2b 100644 --- a/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceImpl.java +++ b/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceImpl.java @@ -1,6 +1,6 @@ package guru.springframework.spring6restmvc.services; -import guru.springframework.spring6restmvc.model.Customer; +import guru.springframework.spring6restmvc.model.CustomerDTO; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -13,10 +13,10 @@ @Service public class CustomerServiceImpl implements CustomerService { - private Map customerMap; + private Map customerMap; public CustomerServiceImpl() { - Customer customer1 = Customer.builder() + CustomerDTO customer1 = CustomerDTO.builder() .id(UUID.randomUUID()) .name("Customer 1") .version(1) @@ -24,7 +24,7 @@ public CustomerServiceImpl() { .updateDate(LocalDateTime.now()) .build(); - Customer customer2 = Customer.builder() + CustomerDTO customer2 = CustomerDTO.builder() .id(UUID.randomUUID()) .name("Customer 2") .version(1) @@ -32,7 +32,7 @@ public CustomerServiceImpl() { .updateDate(LocalDateTime.now()) .build(); - Customer customer3 = Customer.builder() + CustomerDTO customer3 = CustomerDTO.builder() .id(UUID.randomUUID()) .name("Customer 3") .version(1) @@ -47,29 +47,34 @@ public CustomerServiceImpl() { } @Override - public void patchCustomerById(UUID customerId, Customer customer) { - Customer existing = customerMap.get(customerId); + 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 void deleteCustomerById(UUID customerId) { + public Boolean deleteCustomerById(UUID customerId) { customerMap.remove(customerId); + + return true; } @Override - public void updateCustomerById(UUID customerId, Customer customer) { - Customer existing = customerMap.get(customerId); + public Optional updateCustomerById(UUID customerId, CustomerDTO customer) { + CustomerDTO existing = customerMap.get(customerId); existing.setName(customer.getName()); + return Optional.of(existing); } @Override - public Customer saveNewCustomer(Customer customer) { + public CustomerDTO saveNewCustomer(CustomerDTO customer) { - Customer savedCustomer = Customer.builder() + CustomerDTO savedCustomer = CustomerDTO.builder() .id(UUID.randomUUID()) .version(1) .updateDate(LocalDateTime.now()) @@ -83,12 +88,12 @@ public Customer saveNewCustomer(Customer customer) { } @Override - public Optional getCustomerById(UUID uuid) { + public Optional getCustomerById(UUID uuid) { return Optional.of(customerMap.get(uuid)); } @Override - public List getAllCustomers() { + 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/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..734c4ba78 --- /dev/null +++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerIT.java @@ -0,0 +1,136 @@ +package guru.springframework.spring6restmvc.controller; + +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.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 BeerControllerIT { + @Autowired + BeerController beerController; + + @Autowired + BeerRepository beerRepository; + + @Autowired + BeerMapper beerMapper; + + + @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 index eaaef42db..55e7a72c3 100644 --- a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java +++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java @@ -1,7 +1,7 @@ package guru.springframework.spring6restmvc.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import guru.springframework.spring6restmvc.model.Beer; +import guru.springframework.spring6restmvc.model.BeerDTO; import guru.springframework.spring6restmvc.services.BeerService; import guru.springframework.spring6restmvc.services.BeerServiceImpl; import org.junit.jupiter.api.BeforeEach; @@ -45,7 +45,7 @@ class BeerControllerTest { ArgumentCaptor uuidArgumentCaptor; @Captor - ArgumentCaptor beerArgumentCaptor; + ArgumentCaptor beerArgumentCaptor; @BeforeEach void setUp() { @@ -54,7 +54,7 @@ void setUp() { @Test void testPatchBeer() throws Exception { - Beer beer = beerServiceImpl.listBeers().get(0); + BeerDTO beer = beerServiceImpl.listBeers().get(0); Map beerMap = new HashMap<>(); beerMap.put("beerName", "New Name"); @@ -73,7 +73,9 @@ void testPatchBeer() throws Exception { @Test void testDeleteBeer() throws Exception { - Beer beer = beerServiceImpl.listBeers().get(0); + 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)) @@ -86,7 +88,9 @@ void testDeleteBeer() throws Exception { @Test void testUpdateBeer() throws Exception { - Beer beer = beerServiceImpl.listBeers().get(0); + 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) @@ -94,16 +98,16 @@ void testUpdateBeer() throws Exception { .content(objectMapper.writeValueAsString(beer))) .andExpect(status().isNoContent()); - verify(beerService).updateBeerById(any(UUID.class), any(Beer.class)); + verify(beerService).updateBeerById(any(UUID.class), any(BeerDTO.class)); } @Test void testCreateNewBeer() throws Exception { - Beer beer = beerServiceImpl.listBeers().get(0); + BeerDTO beer = beerServiceImpl.listBeers().get(0); beer.setVersion(null); beer.setId(null); - given(beerService.saveNewBeer(any(Beer.class))).willReturn(beerServiceImpl.listBeers().get(1)); + given(beerService.saveNewBeer(any(BeerDTO.class))).willReturn(beerServiceImpl.listBeers().get(1)); mockMvc.perform(post(BeerController.BEER_PATH) .accept(MediaType.APPLICATION_JSON) @@ -135,7 +139,7 @@ void getBeerByIdNotFound() throws Exception { @Test void getBeerById() throws Exception { - Beer testBeer = beerServiceImpl.listBeers().get(0); + BeerDTO testBeer = beerServiceImpl.listBeers().get(0); given(beerService.getBeerById(testBeer.getId())).willReturn(Optional.of(testBeer)); 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 index 23b95b081..f64cbfcfa 100644 --- a/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java +++ b/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java @@ -1,7 +1,7 @@ package guru.springframework.spring6restmvc.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import guru.springframework.spring6restmvc.model.Customer; +import guru.springframework.spring6restmvc.model.CustomerDTO; import guru.springframework.spring6restmvc.services.CustomerService; import guru.springframework.spring6restmvc.services.CustomerServiceImpl; import org.junit.jupiter.api.BeforeEach; @@ -50,11 +50,11 @@ void setUp() { ArgumentCaptor uuidArgumentCaptor; @Captor - ArgumentCaptor customerArgumentCaptor; + ArgumentCaptor customerArgumentCaptor; @Test void testPatchCustomer() throws Exception { - Customer customer = customerServiceImpl.getAllCustomers().get(0); + CustomerDTO customer = customerServiceImpl.getAllCustomers().get(0); Map customerMap = new HashMap<>(); customerMap.put("name", "New Name"); @@ -74,7 +74,9 @@ void testPatchCustomer() throws Exception { @Test void testDeleteCustomer() throws Exception { - Customer customer = customerServiceImpl.getAllCustomers().get(0); + 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)) @@ -87,7 +89,10 @@ void testDeleteCustomer() throws Exception { @Test void testUpdateCustomer() throws Exception { - Customer customer = customerServiceImpl.getAllCustomers().get(0); + 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)) @@ -95,18 +100,18 @@ void testUpdateCustomer() throws Exception { .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()); - verify(customerService).updateCustomerById(uuidArgumentCaptor.capture(), any(Customer.class)); + verify(customerService).updateCustomerById(uuidArgumentCaptor.capture(), any(CustomerDTO.class)); assertThat(customer.getId()).isEqualTo(uuidArgumentCaptor.getValue()); } @Test void testCreateCustomer() throws Exception { - Customer customer = customerServiceImpl.getAllCustomers().get(0); + CustomerDTO customer = customerServiceImpl.getAllCustomers().get(0); customer.setId(null); customer.setVersion(null); - given(customerService.saveNewCustomer(any(Customer.class))) + given(customerService.saveNewCustomer(any(CustomerDTO.class))) .willReturn(customerServiceImpl.getAllCustomers().get(1)); mockMvc.perform(post(CustomerController.CUSTOMER_PATH).contentType(MediaType.APPLICATION_JSON) @@ -138,7 +143,7 @@ void getCustomerByIdNotFound() throws Exception { @Test void getCustomerById() throws Exception { - Customer customer = customerServiceImpl.getAllCustomers().get(0); + CustomerDTO customer = customerServiceImpl.getAllCustomers().get(0); given(customerService.getCustomerById(customer.getId())).willReturn(Optional.of(customer)); 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..2b9ce8424 --- /dev/null +++ b/src/test/java/guru/springframework/spring6restmvc/repositories/BeerRepositoryTest.java @@ -0,0 +1,25 @@ +package guru.springframework.spring6restmvc.repositories; + +import guru.springframework.spring6restmvc.entities.Beer; +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 BeerRepositoryTest { + + @Autowired + BeerRepository beerRepository; + + @Test + void testSaveBeer() { + Beer savedBeer = beerRepository.save(Beer.builder() + .beerName("My Beer") + .build()); + + 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 From 988dfedc3614365df576e5d13ad93837995ba595 Mon Sep 17 00:00:00 2001 From: Gustavo Gois Date: Thu, 3 Apr 2025 23:33:56 +0100 Subject: [PATCH 05/10] 112 - Controller Binding Validation --- pom.xml | 4 ++++ .../spring6restmvc/controller/BeerController.java | 3 ++- .../spring6restmvc/model/BeerDTO.java | 5 +++++ .../controller/BeerControllerTest.java | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d12999adb..016a6eb7e 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-validation + org.springframework.boot spring-boot-devtools diff --git a/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java b/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java index a361dbcd2..519bfdcd7 100644 --- a/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java +++ b/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java @@ -7,6 +7,7 @@ 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; @@ -54,7 +55,7 @@ public ResponseEntity updateById(@PathVariable("beerId")UUID beerId, @RequestBod } @PostMapping(BEER_PATH) - public ResponseEntity handlePost(@RequestBody BeerDTO beer){ + public ResponseEntity handlePost(@Validated @RequestBody BeerDTO beer){ BeerDTO savedBeer = beerService.saveNewBeer(beer); diff --git a/src/main/java/guru/springframework/spring6restmvc/model/BeerDTO.java b/src/main/java/guru/springframework/spring6restmvc/model/BeerDTO.java index eee5ad954..03f67f6d2 100644 --- a/src/main/java/guru/springframework/spring6restmvc/model/BeerDTO.java +++ b/src/main/java/guru/springframework/spring6restmvc/model/BeerDTO.java @@ -1,5 +1,7 @@ package guru.springframework.spring6restmvc.model; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Data; @@ -15,6 +17,9 @@ public class BeerDTO { private UUID id; private Integer version; + + @NotBlank + @NotNull private String beerName; private BeerStyle beerStyle; private String upc; diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java index 55e7a72c3..ed5cebe51 100644 --- a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java +++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java @@ -117,6 +117,20 @@ void testCreateNewBeer() throws Exception { .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)); + + mockMvc.perform(post(BeerController.BEER_PATH) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(beerDTO))) + .andExpect(status().isBadRequest()); + } + @Test void testListBeers() throws Exception { given(beerService.listBeers()).willReturn(beerServiceImpl.listBeers()); From 17cb2c69a3524b09a625e1765bc25f25c3595738 Mon Sep 17 00:00:00 2001 From: Gustavo Gois Date: Thu, 3 Apr 2025 23:45:45 +0100 Subject: [PATCH 06/10] 113 - Custom Validation Handler --- .../controller/CustomErrorController.java | 18 ++++++++++++++++++ .../controller/BeerControllerTest.java | 7 +++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/main/java/guru/springframework/spring6restmvc/controller/CustomErrorController.java 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..22e08a5f7 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/controller/CustomErrorController.java @@ -0,0 +1,18 @@ +package guru.springframework.spring6restmvc.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +/** + * Created by jt, Spring Framework Guru. + */ +@ControllerAdvice +public class CustomErrorController { + + @ExceptionHandler(MethodArgumentNotValidException.class) + ResponseEntity handleBindErrors(MethodArgumentNotValidException exception){ + return ResponseEntity.badRequest().body(exception.getBindingResult().getFieldErrors()); + } +} diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java index ed5cebe51..78e2abc67 100644 --- a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java +++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java @@ -13,6 +13,7 @@ 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; @@ -124,11 +125,13 @@ void testCreateBeerNullBeerName() throws Exception { given(beerService.saveNewBeer(any(BeerDTO.class))).willReturn(beerServiceImpl.listBeers().get(1)); - mockMvc.perform(post(BeerController.BEER_PATH) + MvcResult mvcResult = mockMvc.perform(post(BeerController.BEER_PATH) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(beerDTO))) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()).andReturn(); + + System.out.println(mvcResult.getResponse().getContentAsString()); } @Test From 6570a19ebd595a4bf606da08176c83f1cf74da6a Mon Sep 17 00:00:00 2001 From: Gustavo Gois Date: Thu, 3 Apr 2025 23:49:52 +0100 Subject: [PATCH 07/10] 114 - Custom Error Body --- .../controller/CustomErrorController.java | 15 ++++++++++++++- .../controller/BeerControllerTest.java | 4 +++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/guru/springframework/spring6restmvc/controller/CustomErrorController.java b/src/main/java/guru/springframework/spring6restmvc/controller/CustomErrorController.java index 22e08a5f7..51578fd9b 100644 --- a/src/main/java/guru/springframework/spring6restmvc/controller/CustomErrorController.java +++ b/src/main/java/guru/springframework/spring6restmvc/controller/CustomErrorController.java @@ -5,6 +5,11 @@ 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. */ @@ -13,6 +18,14 @@ public class CustomErrorController { @ExceptionHandler(MethodArgumentNotValidException.class) ResponseEntity handleBindErrors(MethodArgumentNotValidException exception){ - return ResponseEntity.badRequest().body(exception.getBindingResult().getFieldErrors()); + + 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/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java index 78e2abc67..5be5a5046 100644 --- a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java +++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java @@ -129,7 +129,9 @@ void testCreateBeerNullBeerName() throws Exception { .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(beerDTO))) - .andExpect(status().isBadRequest()).andReturn(); + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.length()", is(2))) + .andReturn(); System.out.println(mvcResult.getResponse().getContentAsString()); } From f0e44adb7e7a6a1e7242b4967cf67564e4a3386e Mon Sep 17 00:00:00 2001 From: Gustavo Gois Date: Thu, 3 Apr 2025 23:57:55 +0100 Subject: [PATCH 08/10] 115 - JPA Validation --- .../controller/BeerController.java | 2 +- .../spring6restmvc/entities/Beer.java | 12 ++++++++++++ .../spring6restmvc/model/BeerDTO.java | 7 +++++++ .../controller/BeerControllerTest.java | 17 ++++++++++++++++- .../repositories/BeerRepositoryTest.java | 8 ++++++++ 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java b/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java index 519bfdcd7..33d7faf2f 100644 --- a/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java +++ b/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java @@ -45,7 +45,7 @@ public ResponseEntity deleteById(@PathVariable("beerId") UUID beerId){ } @PutMapping(BEER_PATH_ID) - public ResponseEntity updateById(@PathVariable("beerId")UUID beerId, @RequestBody BeerDTO beer){ + public ResponseEntity updateById(@PathVariable("beerId")UUID beerId, @Validated @RequestBody BeerDTO beer){ if( beerService.updateBeerById(beerId, beer).isEmpty()){ throw new NotFoundException(); diff --git a/src/main/java/guru/springframework/spring6restmvc/entities/Beer.java b/src/main/java/guru/springframework/spring6restmvc/entities/Beer.java index 710ae5970..3bc8ae558 100644 --- a/src/main/java/guru/springframework/spring6restmvc/entities/Beer.java +++ b/src/main/java/guru/springframework/spring6restmvc/entities/Beer.java @@ -2,6 +2,8 @@ import guru.springframework.spring6restmvc.model.BeerStyle; import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.*; import org.hibernate.annotations.UuidGenerator; @@ -28,10 +30,20 @@ public class Beer { @Version private Integer version; + + @NotNull + @NotBlank 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/BeerDTO.java b/src/main/java/guru/springframework/spring6restmvc/model/BeerDTO.java index 03f67f6d2..af858bfed 100644 --- a/src/main/java/guru/springframework/spring6restmvc/model/BeerDTO.java +++ b/src/main/java/guru/springframework/spring6restmvc/model/BeerDTO.java @@ -21,9 +21,16 @@ public class BeerDTO { @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/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java index 5be5a5046..d5e9feba4 100644 --- a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java +++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java @@ -102,6 +102,21 @@ void testUpdateBeer() throws Exception { 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); @@ -130,7 +145,7 @@ void testCreateBeerNullBeerName() throws Exception { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(beerDTO))) .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.length()", is(2))) + .andExpect(jsonPath("$.length()", is(6))) .andReturn(); System.out.println(mvcResult.getResponse().getContentAsString()); diff --git a/src/test/java/guru/springframework/spring6restmvc/repositories/BeerRepositoryTest.java b/src/test/java/guru/springframework/spring6restmvc/repositories/BeerRepositoryTest.java index 2b9ce8424..d839ecf6f 100644 --- a/src/test/java/guru/springframework/spring6restmvc/repositories/BeerRepositoryTest.java +++ b/src/test/java/guru/springframework/spring6restmvc/repositories/BeerRepositoryTest.java @@ -1,10 +1,13 @@ package guru.springframework.spring6restmvc.repositories; import guru.springframework.spring6restmvc.entities.Beer; +import guru.springframework.spring6restmvc.model.BeerStyle; 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; @DataJpaTest @@ -17,8 +20,13 @@ class BeerRepositoryTest { 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(); } From 8c35e4430463620b364a7e4eb4389d4f7db34d18 Mon Sep 17 00:00:00 2001 From: Gustavo Gois Date: Fri, 4 Apr 2025 00:00:41 +0100 Subject: [PATCH 09/10] 116 - Database Constraint Validation --- .../spring6restmvc/entities/Beer.java | 4 ++++ .../repositories/BeerRepositoryTest.java | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/main/java/guru/springframework/spring6restmvc/entities/Beer.java b/src/main/java/guru/springframework/spring6restmvc/entities/Beer.java index 3bc8ae558..808dab114 100644 --- a/src/main/java/guru/springframework/spring6restmvc/entities/Beer.java +++ b/src/main/java/guru/springframework/spring6restmvc/entities/Beer.java @@ -4,6 +4,7 @@ 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; @@ -33,6 +34,8 @@ public class Beer { @NotNull @NotBlank + @Size(max = 50) + @Column(length = 50) private String beerName; @NotNull @@ -40,6 +43,7 @@ public class Beer { @NotNull @NotBlank + @Size(max = 255) private String upc; private Integer quantityOnHand; diff --git a/src/test/java/guru/springframework/spring6restmvc/repositories/BeerRepositoryTest.java b/src/test/java/guru/springframework/spring6restmvc/repositories/BeerRepositoryTest.java index d839ecf6f..1e8039427 100644 --- a/src/test/java/guru/springframework/spring6restmvc/repositories/BeerRepositoryTest.java +++ b/src/test/java/guru/springframework/spring6restmvc/repositories/BeerRepositoryTest.java @@ -2,6 +2,7 @@ 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; @@ -9,6 +10,7 @@ import java.math.BigDecimal; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; @DataJpaTest class BeerRepositoryTest { @@ -16,6 +18,21 @@ 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() From 45ddfb61dcdf87eb84e47edfe6bf0e71cd7f720a Mon Sep 17 00:00:00 2001 From: Gustavo Gois Date: Fri, 4 Apr 2025 00:10:34 +0100 Subject: [PATCH 10/10] 117 - Controller Testing with JPA --- .../controller/CustomErrorController.java | 6 +++ .../controller/BeerControllerIT.java | 37 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/main/java/guru/springframework/spring6restmvc/controller/CustomErrorController.java b/src/main/java/guru/springframework/spring6restmvc/controller/CustomErrorController.java index 51578fd9b..af718f8a6 100644 --- a/src/main/java/guru/springframework/spring6restmvc/controller/CustomErrorController.java +++ b/src/main/java/guru/springframework/spring6restmvc/controller/CustomErrorController.java @@ -1,6 +1,7 @@ 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; @@ -16,6 +17,11 @@ @ControllerAdvice public class CustomErrorController { + @ExceptionHandler + ResponseEntity handleJPAViolations(TransactionSystemException exception){ + return ResponseEntity.badRequest().build(); + } + @ExceptionHandler(MethodArgumentNotValidException.class) ResponseEntity handleBindErrors(MethodArgumentNotValidException exception){ diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerIT.java b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerIT.java index 734c4ba78..edce2b8a8 100644 --- a/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerIT.java +++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerIT.java @@ -1,22 +1,32 @@ 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 { @@ -29,6 +39,33 @@ class BeerControllerIT { @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() {