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..3335c2583 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/controller/BeerController.java @@ -0,0 +1,60 @@ +package guru.springframework.spring6restmvc.controller; + +import guru.springframework.spring6restmvc.model.Beer; +import guru.springframework.spring6restmvc.services.BeerService; +import lombok.AllArgsConstructor; +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; + +@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 patchBeer(@RequestBody Beer beer,@PathVariable("beerId") UUID beerId) { + beerService.patchBeerById(beerId,beer); + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @DeleteMapping(BEER_PATH_ID) + public ResponseEntity DeleteBeer(@PathVariable("beerId") UUID beerId){ + beerService.deleteBeerById(beerId); + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + @PutMapping(BEER_PATH_ID) + public ResponseEntity updateBeer(@RequestBody Beer beer,@PathVariable("beerId") UUID beerId){ + beerService.updateBeer(beer,beerId); + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @PostMapping(BEER_PATH) + public ResponseEntity saveNewBeer(@RequestBody Beer beer){ + Beer savedBeer=beerService.save(beer); + HttpHeaders header=new HttpHeaders(); + header.add("Location",BEER_PATH + savedBeer.getId().toString()); + return new ResponseEntity(header,HttpStatus.CREATED); + } + + @GetMapping(BEER_PATH) + public List listBeers(){ + return beerService.listBeers(); + } + + @GetMapping(BEER_PATH_ID) + public Beer getById(@PathVariable("beerId")UUID beerId){ + + return beerService.getById(beerId); + } + +} \ No newline at end of file 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..b735f13ea --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/controller/CustomerController.java @@ -0,0 +1,51 @@ +package guru.springframework.spring6restmvc.controller; + +import guru.springframework.spring6restmvc.model.Customer; +import guru.springframework.spring6restmvc.services.CustomerService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@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; + @DeleteMapping(CUSTOMER_PATH_ID) + public ResponseEntity deleteById(@PathVariable("customerId") UUID customerId){ + customerService.deleteById(customerId); + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + + @PutMapping(CUSTOMER_PATH_ID) + public ResponseEntity updateCustomer(@PathVariable("customerId") UUID customerId, @RequestBody Customer customer){ + customerService.updateCustomerById(customer, customerId); + return new ResponseEntity(HttpStatus.NO_CONTENT); + } + @PostMapping(CUSTOMER_PATH) + public ResponseEntity saveCustomer(@RequestBody Customer customer){ + Customer savedCustomer= customerService.saveCustomer(customer); + HttpHeaders header=new HttpHeaders(); + header.add("Location",CUSTOMER_PATH+ savedCustomer.getId().toString()); + return new ResponseEntity(header, HttpStatus.CREATED); + } + + @GetMapping(CUSTOMER_PATH) + public List listCustomers(){ + return customerService.listCustomers(); + } + @GetMapping(CUSTOMER_PATH_ID) + public Customer getById(@PathVariable("customerId") UUID customerId){ + + return customerService.getById(customerId); + } + + +} \ No newline at end of file diff --git a/src/main/java/guru/springframework/spring6restmvc/model/Beer.java b/src/main/java/guru/springframework/spring6restmvc/model/Beer.java new file mode 100644 index 000000000..5643b84d4 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/model/Beer.java @@ -0,0 +1,22 @@ +package guru.springframework.spring6restmvc.model; + +import lombok.Builder; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +@Builder +@Data +public class Beer { + private UUID id; + private Integer version; + private String beerName; + private BeerStyle beerStyle; + private String upc; + private Integer quantityOnHand; + private BigDecimal price; + private LocalDateTime createdDate; + private LocalDateTime updateDate; +} \ No newline at end of file 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..7d6bffe26 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/model/BeerStyle.java @@ -0,0 +1,5 @@ +package guru.springframework.spring6restmvc.model; + +public enum BeerStyle { + LAGER, PILSNER, STOUT, GOSE, PORTER, ALE, WHEAT, IPA, PALE_ALE, SAISON +} \ No newline at end of file diff --git a/src/main/java/guru/springframework/spring6restmvc/model/Customer.java b/src/main/java/guru/springframework/spring6restmvc/model/Customer.java new file mode 100644 index 000000000..1d19af44a --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/model/Customer.java @@ -0,0 +1,22 @@ +package guru.springframework.spring6restmvc.model; + +import lombok.Builder; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.UUID; + +@Data +@Builder +public class Customer { + + private UUID Id; + private String customerName; + private BigDecimal version; + private LocalDate createdDate; + private LocalDate lastModifiedDate; + + + +} \ No newline at end of file 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..338e42459 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/services/BeerService.java @@ -0,0 +1,20 @@ +package guru.springframework.spring6restmvc.services; + +import guru.springframework.spring6restmvc.model.Beer; + +import java.util.List; +import java.util.UUID; + +public interface BeerService { + + Beer getById(UUID Id); + List listBeers(); + + Beer save(Beer beer); + + void updateBeer(Beer beer, UUID beerId); + + void deleteBeerById(UUID beerId); + + void patchBeerById(UUID beerId, Beer beer); +} \ No newline at end of file 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..ccce7e227 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/services/BeerServiceImpl.java @@ -0,0 +1,122 @@ +package guru.springframework.spring6restmvc.services; + +import guru.springframework.spring6restmvc.model.Beer; +import guru.springframework.spring6restmvc.model.BeerStyle; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.*; + +@Service +public class BeerServiceImpl implements BeerService { + private Map beerMap; + + public BeerServiceImpl(){ + this.beerMap= new HashMap<>(); + + Beer beer1=Beer.builder() + .id(UUID.randomUUID()) + .upc("h") + .price(new BigDecimal(12.3)) + .version(2) + .beerName("asmara beer") + .quantityOnHand(123) + .beerStyle(BeerStyle.ALE) + .createdDate(LocalDateTime.now()) + .updateDate(LocalDateTime.now()) + .build(); + + Beer beer2=Beer.builder() + .id(UUID.randomUUID()) + .upc("h") + .price(new BigDecimal(12.3)) + .version(2) + .beerName("asmara beer") + .quantityOnHand(123) + .beerStyle(BeerStyle.ALE) + .createdDate(LocalDateTime.now()) + .updateDate(LocalDateTime.now()) + .build(); + + Beer beer3=Beer.builder() + .id(UUID.randomUUID()) + .upc("h") + .price(new BigDecimal(12.3)) + .version(2) + .beerName("asmara beer") + .quantityOnHand(123) + .beerStyle(BeerStyle.ALE) + .createdDate(LocalDateTime.now()) + .updateDate(LocalDateTime.now()) + .build(); + beerMap.put(beer1.getId(),beer1); + beerMap.put(beer2.getId(),beer2); + beerMap.put(beer3.getId(),beer3); + } + + @Override + public Beer getById(UUID Id) { + return beerMap.get(Id); + } + + @Override + public List listBeers(){ + return new ArrayList<>(beerMap.values()); + } + + @Override + public Beer save(Beer beer) { + + Beer savedBeer=Beer.builder() + .id(UUID.randomUUID()) + .upc(beer.getUpc()) + .price(new BigDecimal(12.3)) + .version(2) + .beerName(beer.getBeerName()) + .quantityOnHand(123) + .beerStyle(BeerStyle.ALE) + .createdDate(LocalDateTime.now()) + .updateDate(LocalDateTime.now()) + .build(); + beerMap.put(savedBeer.getId(),savedBeer); + return savedBeer; + } + + @Override + public void updateBeer(Beer beer, UUID beerId) { + Beer existingBeer=beerMap.get(beerId); + existingBeer.setBeerName(beer.getBeerName()); + existingBeer.setPrice(beer.getPrice()); + } + + @Override + public void deleteBeerById(UUID beerId) { + beerMap.remove(beerId); + } + + @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()); + } + + } + + +} \ No newline at end of file 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..e64fda6e3 --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/services/CustomerService.java @@ -0,0 +1,21 @@ +package guru.springframework.spring6restmvc.services; + +import guru.springframework.spring6restmvc.model.Customer; + +import java.util.List; +import java.util.UUID; + +public interface CustomerService { + + + List listCustomers(); + Customer getById(UUID Id); + + Customer saveCustomer(Customer customer); + + void deleteById(UUID customerId); + + void patchCustomerById(UUID customerId, Customer customer); + + void updateCustomerById(Customer customer,UUID customerId); +} \ No newline at end of file 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..f12c22aea --- /dev/null +++ b/src/main/java/guru/springframework/spring6restmvc/services/CustomerServiceImpl.java @@ -0,0 +1,96 @@ +package guru.springframework.spring6restmvc.services; + +import guru.springframework.spring6restmvc.model.Customer; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.*; + +@Service +public class CustomerServiceImpl implements CustomerService{ + private Map customerMap; + public CustomerServiceImpl(){ + this.customerMap= new HashMap<>(); + + Customer customer1= Customer.builder() + .customerName("henos") + .Id(UUID.randomUUID()) + .createdDate(LocalDate.now()) + .lastModifiedDate(LocalDate.now()) + .version(new BigDecimal(1)) + .build(); + + Customer customer2= Customer.builder() + .customerName("lidya") + .Id(UUID.randomUUID()) + .createdDate(LocalDate.now()) + .lastModifiedDate(LocalDate.now()) + .version(new BigDecimal(1)) + .build(); + Customer customer3= Customer.builder() + .customerName("ghirmay") + .Id(UUID.randomUUID()) + .createdDate(LocalDate.now()) + .lastModifiedDate(LocalDate.now()) + .version(new BigDecimal(1)) + .build(); + + 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.getCustomerName())) { + existing.setCustomerName(customer.getCustomerName()); + } + } + + @Override + public List listCustomers() { + return new ArrayList<>(customerMap.values()); + } + + @Override + public Customer getById(UUID Id) { + return customerMap.get(Id); + } + + + + @Override + public Customer saveCustomer(Customer customer) { + Customer customerSaved= Customer.builder() + .customerName(customer.getCustomerName()) + .Id(UUID.randomUUID()) + .createdDate(LocalDate.now()) + .lastModifiedDate(LocalDate.now()) + .version(new BigDecimal(1)) + .build(); + customerMap.put(customerSaved.getId(),customerSaved); + return customerSaved; + } + + + + @Override + public void updateCustomerById(Customer customer,UUID customerId) { + Customer existingCustomer= customerMap.get(customerId); + existingCustomer.setCustomerName(customer.getCustomerName()); + existingCustomer.setVersion(customer.getVersion()); + + } + + + @Override + public void deleteById(UUID customerId){ + customerMap.remove(customerId); + } + + +} \ No newline at end of file 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..959f914af --- /dev/null +++ b/src/test/java/guru/springframework/spring6restmvc/controller/BeerControllerTest.java @@ -0,0 +1,116 @@ +package guru.springframework.spring6restmvc.controller; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import guru.springframework.spring6restmvc.model.Beer; +import guru.springframework.spring6restmvc.services.BeerService; +import guru.springframework.spring6restmvc.services.BeerServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +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.*; + + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@WebMvcTest(BeerController.class) +public class BeerControllerTest { + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @MockBean + BeerService beerService; + + + BeerServiceImpl beerServiceImpl; + @BeforeEach + void setUp(){ + beerServiceImpl= new BeerServiceImpl(); + } + + @Test + void deleteById()throws Exception{ + Beer beer=beerServiceImpl.listBeers().get(0); + + mockMvc.perform(delete("/api/v1/beer/"+beer.getId()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + ArgumentCaptor uuidArgumentCaptor= ArgumentCaptor.forClass(UUID.class); + + verify(beerService).deleteBeerById(uuidArgumentCaptor.capture()); + + assertThat(beer.getId()).isEqualTo(uuidArgumentCaptor.getValue()); + + } + @Test + void updateBeer() throws Exception{ + Beer beer=beerServiceImpl.listBeers().get(0); + mockMvc.perform(put("/api/v1/beer/"+beer.getId()) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(beer)) + ).andExpect(status().isNoContent()); + + verify(beerService).updateBeer(any(Beer.class),any(UUID.class)); + + } + @Test + void testCreateNewBeer() throws Exception { + Beer beer = beerServiceImpl.listBeers().get(0); + beer.setVersion(null); + beer.setId(null); + + given(beerService.save(any(Beer.class))).willReturn(beerServiceImpl.listBeers().get(1)); + + mockMvc.perform(post("/api/v1/beer") + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(beer))) + .andExpect(status().isCreated()) + .andExpect(header().exists("Location")); + + + } + @Test + void listBeers() throws Exception{ + + given(beerService.listBeers()).willReturn(beerServiceImpl.listBeers()); + mockMvc.perform(get("/api/v1/beer") + .accept(MediaType.APPLICATION_JSON)) + .andExpect( content().contentType(MediaType.APPLICATION_JSON) ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()",is(3))); + } + @Test + void getById() throws Exception{ + Beer testBeer=beerServiceImpl.listBeers().get(0); + + given(beerService.getById(any(UUID.class))).willReturn(testBeer); + + mockMvc.perform(get("/api/v1/beer/" + UUID.randomUUID()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + } + +} \ No newline at end of file diff --git a/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java b/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java new file mode 100644 index 000000000..95002229c --- /dev/null +++ b/src/test/java/guru/springframework/spring6restmvc/controller/CustomerControllerTest.java @@ -0,0 +1,123 @@ +package guru.springframework.spring6restmvc.controller; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import guru.springframework.spring6restmvc.model.Customer; +import guru.springframework.spring6restmvc.services.CustomerService; +import guru.springframework.spring6restmvc.services.CustomerServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +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) +public class CustomerControllerTest { + + @Autowired + MockMvc mockMvc; + + @Autowired + ObjectMapper objectMapper; + + @MockBean + CustomerService customerService; + + CustomerServiceImpl customerServiceImpl; + + @BeforeEach + void setUp() { + customerServiceImpl = new CustomerServiceImpl(); + } + + @Captor + ArgumentCaptor uuidArgumentCaptor; + + @Test + void testDeleteCustomer() throws Exception { + Customer customer = customerServiceImpl.listCustomers().get(0); + + mockMvc.perform(delete("/api/v1/customer/" + customer.getId()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + + verify(customerService).deleteById(uuidArgumentCaptor.capture()); + + assertThat(customer.getId()).isEqualTo(uuidArgumentCaptor.getValue()); + } + + @Test + void testUpdateCustomer() throws Exception { + Customer customer = customerServiceImpl.listCustomers().get(0); + + mockMvc.perform(put(CustomerController.CUSTOMER_PATH+ "/" + customer.getId()) + .content(objectMapper.writeValueAsString(customer)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + + + + verify(customerService).updateCustomerById(any(Customer.class),uuidArgumentCaptor.capture()); + + assertThat(customer.getId()).isEqualTo(uuidArgumentCaptor.getValue()); + } + + @Test + void testCreateCustomer() throws Exception { + Customer customer = customerServiceImpl.listCustomers().get(0); + customer.setId(null); + customer.setVersion(null); + + given(customerService.saveCustomer(any(Customer.class))) + .willReturn(customerServiceImpl.listCustomers().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.listCustomers()).willReturn(customerServiceImpl.listCustomers()); + + mockMvc.perform(get("/api/v1/customer") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(content().contentType(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.listCustomers().get(0); + + given(customerService.getById(customer.getId())).willReturn(customer); + + mockMvc.perform(get(CustomerController.CUSTOMER_PATH + "/" + customer.getId()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.customerName", is(customer.getCustomerName()))); + + } + +} \ No newline at end of file