From ca1301e6719700386b392bf410c774d637132e89 Mon Sep 17 00:00:00 2001 From: davida Date: Sat, 6 Dec 2025 10:09:41 -0600 Subject: [PATCH 1/2] commit after assignment 8 --- README.md | 22 +-- pom.xml | 60 +++++-- .../Spring7RestMvcApplication.java} | 6 +- .../controller/BeerController.java | 77 +++++++++ .../controller/CustomerController.java | 72 ++++++++ .../spring7restmvc/model/Beer.java | 43 +++++ .../spring7restmvc/model/BeerStyle.java | 8 + .../spring7restmvc/model/Customer.java | 30 ++++ .../spring7restmvc/services/BeerService.java | 24 +++ .../services/BeerServiceImpl.java | 154 ++++++++++++++++++ .../services/CustomerService.java | 24 +++ .../services/CustomerServiceImpl.java | 105 ++++++++++++ src/main/resources/application.properties | 1 + .../Spring7RestMvcApplicationTests.java} | 4 +- .../controller/BeerControllerTest.java | 142 ++++++++++++++++ .../controller/CustomerControllerTest.java | 154 ++++++++++++++++++ 16 files changed, 897 insertions(+), 29 deletions(-) rename src/main/java/guru/springframework/{spring7webapp/Spring7WebappApplication.java => spring7restmvc/Spring7RestMvcApplication.java} (56%) create mode 100644 src/main/java/guru/springframework/spring7restmvc/controller/BeerController.java create mode 100644 src/main/java/guru/springframework/spring7restmvc/controller/CustomerController.java create mode 100644 src/main/java/guru/springframework/spring7restmvc/model/Beer.java create mode 100644 src/main/java/guru/springframework/spring7restmvc/model/BeerStyle.java create mode 100644 src/main/java/guru/springframework/spring7restmvc/model/Customer.java create mode 100644 src/main/java/guru/springframework/spring7restmvc/services/BeerService.java create mode 100644 src/main/java/guru/springframework/spring7restmvc/services/BeerServiceImpl.java create mode 100644 src/main/java/guru/springframework/spring7restmvc/services/CustomerService.java create mode 100644 src/main/java/guru/springframework/spring7restmvc/services/CustomerServiceImpl.java rename src/test/java/guru/springframework/{spring7webapp/Spring7WebappApplicationTests.java => spring7restmvc/Spring7RestMvcApplicationTests.java} (65%) create mode 100644 src/test/java/guru/springframework/spring7restmvc/controller/BeerControllerTest.java create mode 100644 src/test/java/guru/springframework/spring7restmvc/controller/CustomerControllerTest.java diff --git a/README.md b/README.md index 38d9f05ee..6ecf16ec1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Spring Framework 7: Beginner to Guru +## Spring 7 Rest MVC This repository is for an example application built in my [Spring Framework 7 - Beginner to Guru](https://www.udemy.com/course/spring-framework-6-beginner-to-guru/?referralCode=2BD0B7B7B6B511D699A9) online course @@ -9,20 +10,20 @@ As you work through the course, please feel free to fork this repository to your to source code changes. If you encounter a problem you can compare your code to the lesson code. [See this link for help with compares](https://github.com/springframeworkguru/spring5webapp/wiki#getting-an-error-but-cannot-find-what-is-different-from-lesson-source-code) ## Spring Framework 7: Beginner to Guru Course Wiki -Got a question about your Spring Framework 7 course? [Checkout these FAQs!](https://github.com/springframeworkguru/spring5webapp/wiki) +Got a question about your Spring Framework 6 course? [Checkout these FAQs!](https://github.com/springframeworkguru/spring5webapp/wiki) +## Getting Your Development Environment Setup ### Recommended Versions -| Recommended | Reference | Notes | -|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Oracle Java 25 JDK | [Download](https://www.oracle.com/java/technologies/downloads/#java25) | Java 17 or higher is required for Spring Framework 6+. Java 25 is recommended for the course. | -| IntelliJ 2024 or Higher | [Download](https://www.jetbrains.com/idea/download/) | Ultimate Edition recommended. Students can get a free 120 trial license [here](https://github.com/springframeworkguru/spring5webapp/wiki/Which-IDE-to-Use%3F#how-do-i-get-the-free-120-day-trial-to-intellij-ultimate) | -| Maven 3.9.11 or higher | [Download](https://maven.apache.org/download.cgi) | [Installation Instructions](https://maven.apache.org/install.html) | -| Gradle 8.14 or higher | [Download](https://gradle.org/install/) | | -| Git 2.39 or higher | [Download](https://git-scm.com/downloads) | | -| Git GUI Clients | [Downloads](https://git-scm.com/downloads/guis) | Not required. But can be helpful if new to Git. SourceTree is a good option for Mac and Windows users. | +| Recommended | Reference | Notes | +|-------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Oracle Java 25 JDK | [Download](https://www.oracle.com/java/technologies/downloads/#java21) | Java 17 or higher is required for Spring Framework 6+. Java 25 is recommended for the course. | +| IntelliJ 2025 or Higher | [Download](https://www.jetbrains.com/idea/download/) | Ultimate Edition recommended. Students can get a free 120 trial license [here](https://github.com/springframeworkguru/spring5webapp/wiki/Which-IDE-to-Use%3F#how-do-i-get-the-free-120-day-trial-to-intellij-ultimate) | +| Maven 3.9.11 or higher | [Download](https://maven.apache.org/download.cgi) | [Installation Instructions](https://maven.apache.org/install.html) | +| Gradle 8.14 or higher | [Download](https://gradle.org/install/) | | +| Git 2.39 or higher | [Download](https://git-scm.com/downloads) | | +| Git GUI Clients | [Downloads](https://git-scm.com/downloads/guis) | Not required. But can be helpful if new to Git. SourceTree is a good option for Mac and Windows users. | ## All Spring Framework Guru Courses - ### AI Courses * [Spring AI: Beginner to Guru](https://www.udemy.com/course/spring-ai-beginner-to-guru/?referralCode=EF8DB31C723FFC8E2751) * [Vibe Coding FullStake with Spring Boot and React Using Junie](https://www.udemy.com/course/jetbrains-junie/?referralCode=74BE8C5825CB296D2C57) @@ -32,6 +33,7 @@ Got a question about your Spring Framework 7 course? [Checkout these FAQs!](http ### Spring Framework 6 * [Spring AI: Beginner to Guru](https://www.udemy.com/course/spring-ai-beginner-to-guru/?referralCode=EF8DB31C723FFC8E2751) +* [Spring AI: Beginner to Guru](https://www.udemy.com/course/spring-ai-beginner-to-guru/?referralCode=EF8DB31C723FFC8E2751) * [Hibernate and Spring Data JPA: Beginner to Guru](https://www.udemy.com/course/hibernate-and-spring-data-jpa-beginner-to-guru/?referralCode=251C4C865302C7B1BB8F) * [API First Engineering with Spring Boot](https://www.udemy.com/course/api-first-engineering-with-spring-boot/?referralCode=C6DAEE7338215A2CF276) * [Introduction to Kafka with Spring Boot](https://www.udemy.com/course/introduction-to-kafka-with-spring-boot/?referralCode=15118530CA63AD1AF16D) diff --git a/pom.xml b/pom.xml index 6905f5a7d..19b0f9b4e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,33 +9,30 @@ guru.springframework - spring-7-webapp + spring-7-rest-mvc 0.0.1-SNAPSHOT - spring-7-webapp - Spring 7 Web App - + spring-7-rest-mvc + Spring 7 Rest MVC 25 - - - org.springframework.boot - spring-boot-starter-data-jpa - org.springframework.boot spring-boot-starter-webmvc + - com.h2database - h2 + org.springframework.boot + spring-boot-devtools runtime + true + - org.springframework.boot - spring-boot-starter-data-jpa-test - test + org.projectlombok + lombok + true org.springframework.boot @@ -46,10 +43,45 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + org.springframework.boot spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + diff --git a/src/main/java/guru/springframework/spring7webapp/Spring7WebappApplication.java b/src/main/java/guru/springframework/spring7restmvc/Spring7RestMvcApplication.java similarity index 56% rename from src/main/java/guru/springframework/spring7webapp/Spring7WebappApplication.java rename to src/main/java/guru/springframework/spring7restmvc/Spring7RestMvcApplication.java index b885e0ea3..46846b83d 100644 --- a/src/main/java/guru/springframework/spring7webapp/Spring7WebappApplication.java +++ b/src/main/java/guru/springframework/spring7restmvc/Spring7RestMvcApplication.java @@ -1,13 +1,13 @@ -package guru.springframework.spring7webapp; +package guru.springframework.spring7restmvc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class Spring7WebappApplication { +public class Spring7RestMvcApplication { public static void main(String[] args) { - SpringApplication.run(Spring7WebappApplication.class, args); + SpringApplication.run(Spring7RestMvcApplication.class, args); } } diff --git a/src/main/java/guru/springframework/spring7restmvc/controller/BeerController.java b/src/main/java/guru/springframework/spring7restmvc/controller/BeerController.java new file mode 100644 index 000000000..b4c8ade95 --- /dev/null +++ b/src/main/java/guru/springframework/spring7restmvc/controller/BeerController.java @@ -0,0 +1,77 @@ +package guru.springframework.spring7restmvc.controller; + +import guru.springframework.spring7restmvc.services.BeerService; +import guru.springframework.spring7restmvc.model.Beer; +import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +/** + * Created by jt, Spring Framework Guru. + */ +@Slf4j +@RequiredArgsConstructor +@RestController +public class BeerController { + + public static final String BEER_PATH = "/api/v1/beer"; + public static final String BEER_PATH_ID = BEER_PATH + "/{beerId}"; + + private final BeerService beerService; + + @PatchMapping(BEER_PATH_ID) + public ResponseEntity updateBeerPatchById(@PathVariable("beerId")UUID beerId, @RequestBody Beer beer){ + + beerService.patchBeerById(beerId, beer); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @DeleteMapping(BEER_PATH_ID) + public ResponseEntity deleteById(@PathVariable("beerId") UUID beerId){ + + beerService.deleteById(beerId); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @PutMapping(BEER_PATH_ID) + public ResponseEntity updateById(@PathVariable("beerId")UUID beerId, @RequestBody Beer beer){ + + beerService.updateBeerById(beerId, beer); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @PostMapping(BEER_PATH) + public ResponseEntity handlePost(@RequestBody Beer beer){ + + Beer savedBeer = beerService.saveNewBeer(beer); + + HttpHeaders headers = new HttpHeaders(); + headers.add("Location", BEER_PATH + "/" + savedBeer.getId().toString()); + + return new ResponseEntity(headers, HttpStatus.CREATED); + } + + @GetMapping(value = BEER_PATH) + public List listBeers(){ + return beerService.listBeers(); + } + + @GetMapping(value = BEER_PATH_ID) + public 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/spring7restmvc/controller/CustomerController.java b/src/main/java/guru/springframework/spring7restmvc/controller/CustomerController.java new file mode 100644 index 000000000..0b0ad10e8 --- /dev/null +++ b/src/main/java/guru/springframework/spring7restmvc/controller/CustomerController.java @@ -0,0 +1,72 @@ +package guru.springframework.spring7restmvc.controller; + +import guru.springframework.spring7restmvc.model.Customer; +import guru.springframework.spring7restmvc.services.CustomerService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +/** + * Created by jt, Spring Framework Guru. + */ + +@RequiredArgsConstructor +@RestController +public class CustomerController { + public static final String CUSTOMER_PATH = "/api/v1/customer"; + public static final String CUSTOMER_PATH_ID = CUSTOMER_PATH + "/{customerId}"; + + private final CustomerService customerService; + + @PatchMapping(CUSTOMER_PATH_ID) + public ResponseEntity patchCustomerById(@PathVariable("customerId") UUID customerId, + @RequestBody Customer customer){ + + customerService.patchCustomerById(customerId, customer); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @DeleteMapping(CUSTOMER_PATH_ID) + public ResponseEntity deleteCustomerById(@PathVariable("customerId") UUID customerId){ + + customerService.deleteCustomerById(customerId); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @PutMapping(CUSTOMER_PATH_ID) + public ResponseEntity updateCustomerByID(@PathVariable("customerId") UUID customerId, + @RequestBody Customer customer){ + + customerService.updateCustomerById(customerId, customer); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @PostMapping(CUSTOMER_PATH) + public ResponseEntity handlePost(@RequestBody Customer customer){ + Customer savedCustomer = customerService.saveNewCustomer(customer); + + HttpHeaders headers = new HttpHeaders(); + headers.add("Location", CUSTOMER_PATH + "/" + savedCustomer.getId().toString()); + + return new ResponseEntity(headers, HttpStatus.CREATED); + } + + @GetMapping(CUSTOMER_PATH) + public List listAllCustomers(){ + return customerService.getAllCustomers(); + } + + @GetMapping(value = CUSTOMER_PATH_ID) + public Customer getCustomerById(@PathVariable("customerId") UUID id){ + return customerService.getCustomerById(id); + } + +} diff --git a/src/main/java/guru/springframework/spring7restmvc/model/Beer.java b/src/main/java/guru/springframework/spring7restmvc/model/Beer.java new file mode 100644 index 000000000..0c572a053 --- /dev/null +++ b/src/main/java/guru/springframework/spring7restmvc/model/Beer.java @@ -0,0 +1,43 @@ +package guru.springframework.spring7restmvc.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import tools.jackson.databind.annotation.JsonDeserialize; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * Created by jt, Spring Framework Guru. + */ +@JsonDeserialize(builder = Beer.BeerBuilder.class) +@Builder +@Data +public class Beer { + + @JsonProperty("id") + private UUID id; + + @JsonProperty("version") + private Integer version; + + @JsonProperty("beerName") + private String beerName; + + @JsonProperty("beerStyle") + private BeerStyle beerStyle; + + @JsonProperty("upc") + private String upc; + + @JsonProperty("quantityOnHand") + private Integer quantityOnHand; + + @JsonProperty("price") + private BigDecimal price; + + private LocalDateTime createdDate; + private LocalDateTime updateDate; +} diff --git a/src/main/java/guru/springframework/spring7restmvc/model/BeerStyle.java b/src/main/java/guru/springframework/spring7restmvc/model/BeerStyle.java new file mode 100644 index 000000000..d0f5d858a --- /dev/null +++ b/src/main/java/guru/springframework/spring7restmvc/model/BeerStyle.java @@ -0,0 +1,8 @@ +package guru.springframework.spring7restmvc.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/spring7restmvc/model/Customer.java b/src/main/java/guru/springframework/spring7restmvc/model/Customer.java new file mode 100644 index 000000000..90a91c17f --- /dev/null +++ b/src/main/java/guru/springframework/spring7restmvc/model/Customer.java @@ -0,0 +1,30 @@ +package guru.springframework.spring7restmvc.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import tools.jackson.databind.annotation.JsonDeserialize; + +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * Created by jt, Spring Framework Guru. + */ +@JsonDeserialize(builder = Customer.CustomerBuilder.class) +@Data +@Builder +public class Customer { + + @JsonProperty("name") + private String name; + + @JsonProperty("id") + private UUID id; + + @JsonProperty("version") + private Integer version; + + private LocalDateTime createdDate; + private LocalDateTime updateDate; +} diff --git a/src/main/java/guru/springframework/spring7restmvc/services/BeerService.java b/src/main/java/guru/springframework/spring7restmvc/services/BeerService.java new file mode 100644 index 000000000..e4cc26586 --- /dev/null +++ b/src/main/java/guru/springframework/spring7restmvc/services/BeerService.java @@ -0,0 +1,24 @@ +package guru.springframework.spring7restmvc.services; + +import guru.springframework.spring7restmvc.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/spring7restmvc/services/BeerServiceImpl.java b/src/main/java/guru/springframework/spring7restmvc/services/BeerServiceImpl.java new file mode 100644 index 000000000..67842cf92 --- /dev/null +++ b/src/main/java/guru/springframework/spring7restmvc/services/BeerServiceImpl.java @@ -0,0 +1,154 @@ +package guru.springframework.spring7restmvc.services; + +import guru.springframework.spring7restmvc.model.Beer; +import guru.springframework.spring7restmvc.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/spring7restmvc/services/CustomerService.java b/src/main/java/guru/springframework/spring7restmvc/services/CustomerService.java new file mode 100644 index 000000000..9b476d8d2 --- /dev/null +++ b/src/main/java/guru/springframework/spring7restmvc/services/CustomerService.java @@ -0,0 +1,24 @@ +package guru.springframework.spring7restmvc.services; + +import guru.springframework.spring7restmvc.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/spring7restmvc/services/CustomerServiceImpl.java b/src/main/java/guru/springframework/spring7restmvc/services/CustomerServiceImpl.java new file mode 100644 index 000000000..46f7fc55e --- /dev/null +++ b/src/main/java/guru/springframework/spring7restmvc/services/CustomerServiceImpl.java @@ -0,0 +1,105 @@ +package guru.springframework.spring7restmvc.services; + +import guru.springframework.spring7restmvc.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/spring7webapp/Spring7WebappApplicationTests.java b/src/test/java/guru/springframework/spring7restmvc/Spring7RestMvcApplicationTests.java similarity index 65% rename from src/test/java/guru/springframework/spring7webapp/Spring7WebappApplicationTests.java rename to src/test/java/guru/springframework/spring7restmvc/Spring7RestMvcApplicationTests.java index 98527369f..55dd2aec5 100644 --- a/src/test/java/guru/springframework/spring7webapp/Spring7WebappApplicationTests.java +++ b/src/test/java/guru/springframework/spring7restmvc/Spring7RestMvcApplicationTests.java @@ -1,10 +1,10 @@ -package guru.springframework.spring7webapp; +package guru.springframework.spring7restmvc; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -class Spring7WebappApplicationTests { +class Spring7RestMvcApplicationTests { @Test void contextLoads() { diff --git a/src/test/java/guru/springframework/spring7restmvc/controller/BeerControllerTest.java b/src/test/java/guru/springframework/spring7restmvc/controller/BeerControllerTest.java new file mode 100644 index 000000000..7ccf2e198 --- /dev/null +++ b/src/test/java/guru/springframework/spring7restmvc/controller/BeerControllerTest.java @@ -0,0 +1,142 @@ +package guru.springframework.spring7restmvc.controller; + +import guru.springframework.spring7restmvc.model.Beer; +import guru.springframework.spring7restmvc.services.BeerService; +import guru.springframework.spring7restmvc.services.BeerServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import tools.jackson.databind.ObjectMapper; + +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(BeerController.class) +@ExtendWith(MockitoExtension.class) +class BeerControllerTest { + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @MockitoBean + BeerService beerService; + + BeerServiceImpl beerServiceImpl; + + @Captor + ArgumentCaptor uuidArgumentCaptor; + + @Captor + ArgumentCaptor beerArgumentCaptor; + + @BeforeEach + void setUp() { + beerServiceImpl = new BeerServiceImpl(); + } + + @Test + void testPatchBeer() throws Exception { + 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() throws Exception { + Beer testBeer = beerServiceImpl.listBeers().get(0); + + 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/spring7restmvc/controller/CustomerControllerTest.java b/src/test/java/guru/springframework/spring7restmvc/controller/CustomerControllerTest.java new file mode 100644 index 000000000..245d81d55 --- /dev/null +++ b/src/test/java/guru/springframework/spring7restmvc/controller/CustomerControllerTest.java @@ -0,0 +1,154 @@ +package guru.springframework.spring7restmvc.controller; + +import guru.springframework.spring7restmvc.model.Customer; +import guru.springframework.spring7restmvc.services.CustomerService; +import guru.springframework.spring7restmvc.services.CustomerServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import tools.jackson.databind.ObjectMapper; + +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) +@ExtendWith(MockitoExtension.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 60651536643a3c43a4e8e5b75413397473c6c08b Mon Sep 17 00:00:00 2001 From: davida Date: Sat, 6 Dec 2025 10:24:22 -0600 Subject: [PATCH 2/2] commit after assignment 8 --- .../controller/BeerController.java | 3 +- .../controller/CustomerController.java | 2 +- .../controller/ExceptionController.java | 16 ++++++++++ .../controller/NotFoundException.java | 29 +++++++++++++++++++ .../spring7restmvc/services/BeerService.java | 3 +- .../services/BeerServiceImpl.java | 4 +-- .../services/CustomerService.java | 3 +- .../services/CustomerServiceImpl.java | 4 +-- .../controller/BeerControllerTest.java | 12 +++++++- .../controller/CustomerControllerTest.java | 12 +++++++- 10 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 src/main/java/guru/springframework/spring7restmvc/controller/ExceptionController.java create mode 100644 src/main/java/guru/springframework/spring7restmvc/controller/NotFoundException.java diff --git a/src/main/java/guru/springframework/spring7restmvc/controller/BeerController.java b/src/main/java/guru/springframework/spring7restmvc/controller/BeerController.java index b4c8ade95..3f7b23679 100644 --- a/src/main/java/guru/springframework/spring7restmvc/controller/BeerController.java +++ b/src/main/java/guru/springframework/spring7restmvc/controller/BeerController.java @@ -66,12 +66,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/spring7restmvc/controller/CustomerController.java b/src/main/java/guru/springframework/spring7restmvc/controller/CustomerController.java index 0b0ad10e8..4b5da43e8 100644 --- a/src/main/java/guru/springframework/spring7restmvc/controller/CustomerController.java +++ b/src/main/java/guru/springframework/spring7restmvc/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/spring7restmvc/controller/ExceptionController.java b/src/main/java/guru/springframework/spring7restmvc/controller/ExceptionController.java new file mode 100644 index 000000000..07d499f67 --- /dev/null +++ b/src/main/java/guru/springframework/spring7restmvc/controller/ExceptionController.java @@ -0,0 +1,16 @@ +package guru.springframework.spring7restmvc.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +/** + * Created by jt, Spring Framework Guru. + */ +@ControllerAdvice +public class ExceptionController { + @ExceptionHandler(NotFoundException.class) + public ResponseEntity handleNotFoundException(){ + return ResponseEntity.notFound().build(); + } +} diff --git a/src/main/java/guru/springframework/spring7restmvc/controller/NotFoundException.java b/src/main/java/guru/springframework/spring7restmvc/controller/NotFoundException.java new file mode 100644 index 000000000..dc00e05ce --- /dev/null +++ b/src/main/java/guru/springframework/spring7restmvc/controller/NotFoundException.java @@ -0,0 +1,29 @@ +package guru.springframework.spring7restmvc.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/spring7restmvc/services/BeerService.java b/src/main/java/guru/springframework/spring7restmvc/services/BeerService.java index e4cc26586..8ba8cd2ff 100644 --- a/src/main/java/guru/springframework/spring7restmvc/services/BeerService.java +++ b/src/main/java/guru/springframework/spring7restmvc/services/BeerService.java @@ -3,6 +3,7 @@ import guru.springframework.spring7restmvc.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/spring7restmvc/services/BeerServiceImpl.java b/src/main/java/guru/springframework/spring7restmvc/services/BeerServiceImpl.java index 67842cf92..67456d6f6 100644 --- a/src/main/java/guru/springframework/spring7restmvc/services/BeerServiceImpl.java +++ b/src/main/java/guru/springframework/spring7restmvc/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/spring7restmvc/services/CustomerService.java b/src/main/java/guru/springframework/spring7restmvc/services/CustomerService.java index 9b476d8d2..54e67aa18 100644 --- a/src/main/java/guru/springframework/spring7restmvc/services/CustomerService.java +++ b/src/main/java/guru/springframework/spring7restmvc/services/CustomerService.java @@ -3,6 +3,7 @@ import guru.springframework.spring7restmvc.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/spring7restmvc/services/CustomerServiceImpl.java b/src/main/java/guru/springframework/spring7restmvc/services/CustomerServiceImpl.java index 46f7fc55e..23a829fc5 100644 --- a/src/main/java/guru/springframework/spring7restmvc/services/CustomerServiceImpl.java +++ b/src/main/java/guru/springframework/spring7restmvc/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/spring7restmvc/controller/BeerControllerTest.java b/src/test/java/guru/springframework/spring7restmvc/controller/BeerControllerTest.java index 7ccf2e198..58a62253e 100644 --- a/src/test/java/guru/springframework/spring7restmvc/controller/BeerControllerTest.java +++ b/src/test/java/guru/springframework/spring7restmvc/controller/BeerControllerTest.java @@ -18,6 +18,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 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/spring7restmvc/controller/CustomerControllerTest.java b/src/test/java/guru/springframework/spring7restmvc/controller/CustomerControllerTest.java index 245d81d55..f14f94d6f 100644 --- a/src/test/java/guru/springframework/spring7restmvc/controller/CustomerControllerTest.java +++ b/src/test/java/guru/springframework/spring7restmvc/controller/CustomerControllerTest.java @@ -18,6 +18,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; @@ -129,11 +130,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))