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