From 886b4ecaa943830a601673ee9daf70a203dcef6f Mon Sep 17 00:00:00 2001 From: Matheus Bloize Date: Tue, 11 Mar 2025 16:18:18 -0300 Subject: [PATCH] feat: complete challenge --- .../challenge/controller/PlaceController.java | 34 ++++--- .../com/clickbus/challenge/entity/Place.java | 3 +- .../queryfilters/PlaceQueryFilter.java | 10 +++ .../challenge/repository/PlaceRepository.java | 3 +- .../challenge/service/PlaceService.java | 25 ++++-- .../contoller/PlaceControllerTest.java | 88 +++++++++---------- .../challenge/service/PlaceServiceTest.java | 5 -- 7 files changed, 95 insertions(+), 73 deletions(-) create mode 100644 src/main/java/br/com/clickbus/challenge/queryfilters/PlaceQueryFilter.java diff --git a/src/main/java/br/com/clickbus/challenge/controller/PlaceController.java b/src/main/java/br/com/clickbus/challenge/controller/PlaceController.java index bbd5d5c..54be004 100644 --- a/src/main/java/br/com/clickbus/challenge/controller/PlaceController.java +++ b/src/main/java/br/com/clickbus/challenge/controller/PlaceController.java @@ -1,12 +1,13 @@ package br.com.clickbus.challenge.controller; - import br.com.clickbus.challenge.dto.PlaceDTO; import br.com.clickbus.challenge.entity.Place; import br.com.clickbus.challenge.exception.PlaceNotFoundException; +import br.com.clickbus.challenge.queryfilters.PlaceQueryFilter; import br.com.clickbus.challenge.service.PlaceService; import io.swagger.annotations.Api; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.domain.Specification; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -16,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; import javax.validation.Valid; @@ -24,29 +26,39 @@ @RequestMapping("places") public class PlaceController { + @Autowired private PlaceService service; @PostMapping - public ResponseEntity create(@RequestBody @Valid PlaceDTO dto) { - return new ResponseEntity(service.save(dto.buildPlace()).convertToDTO(), HttpStatus.CREATED); + public ResponseEntity create(@RequestBody @Valid PlaceDTO dto) { + return new ResponseEntity(service.save(dto.buildPlace()).convertToDTO(), HttpStatus.CREATED); } @GetMapping("{id}") - public ResponseEntity findById(@PathVariable Long id) { + public ResponseEntity findById(@PathVariable Long id) { return service.findById(id) - .map(place -> ResponseEntity.ok(place.convertToDTO())) - .orElseThrow(() -> new PlaceNotFoundException(HttpStatus.NOT_FOUND)); + .map(place -> ResponseEntity.ok(place.convertToDTO())) + .orElseThrow(() -> new PlaceNotFoundException(HttpStatus.NOT_FOUND)); } @GetMapping - public ResponseEntity findAll() { - Iterable places = PlaceDTO.convertToList(service.findAll()); - return ResponseEntity.ok(places); + public ResponseEntity findAll(PlaceQueryFilter filter) { + Iterable places; + if (filter.getName() != null) { + places = PlaceDTO.convertToList(service.findByName(filter.getName())); + long size = places.spliterator().getExactSizeIfKnown(); + if (filter.getName().isBlank() || size == 0) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Nenhum lugar encontrado com esse nome."); + } + } else { + places = PlaceDTO.convertToList(service.findAll()); + } + return ResponseEntity.status(HttpStatus.OK).body(places); } @PutMapping("/{id}") - public ResponseEntity alter(@PathVariable Long id, @RequestBody @Valid PlaceDTO placeDTO) { + public ResponseEntity alter(@PathVariable Long id, @RequestBody @Valid PlaceDTO placeDTO) { Place place = service.findById(id).orElseThrow(null); - return new ResponseEntity(service.alter(place, placeDTO).convertToDTO(), HttpStatus.OK); + return new ResponseEntity(service.alter(place, placeDTO).convertToDTO(), HttpStatus.OK); } } diff --git a/src/main/java/br/com/clickbus/challenge/entity/Place.java b/src/main/java/br/com/clickbus/challenge/entity/Place.java index ccfc550..f31fabc 100644 --- a/src/main/java/br/com/clickbus/challenge/entity/Place.java +++ b/src/main/java/br/com/clickbus/challenge/entity/Place.java @@ -1,10 +1,10 @@ package br.com.clickbus.challenge.entity; - import br.com.clickbus.challenge.dto.PlaceDTO; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -15,6 +15,7 @@ @Entity @Getter +@Setter @NoArgsConstructor @AllArgsConstructor public class Place { diff --git a/src/main/java/br/com/clickbus/challenge/queryfilters/PlaceQueryFilter.java b/src/main/java/br/com/clickbus/challenge/queryfilters/PlaceQueryFilter.java new file mode 100644 index 0000000..aa53490 --- /dev/null +++ b/src/main/java/br/com/clickbus/challenge/queryfilters/PlaceQueryFilter.java @@ -0,0 +1,10 @@ +package br.com.clickbus.challenge.queryfilters; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class PlaceQueryFilter { + private String name; +} diff --git a/src/main/java/br/com/clickbus/challenge/repository/PlaceRepository.java b/src/main/java/br/com/clickbus/challenge/repository/PlaceRepository.java index 5a8309f..b7b4fc6 100644 --- a/src/main/java/br/com/clickbus/challenge/repository/PlaceRepository.java +++ b/src/main/java/br/com/clickbus/challenge/repository/PlaceRepository.java @@ -2,11 +2,12 @@ import br.com.clickbus.challenge.entity.Place; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface PlaceRepository extends JpaRepository { +public interface PlaceRepository extends JpaRepository, JpaSpecificationExecutor { List findByName(String name); } diff --git a/src/main/java/br/com/clickbus/challenge/service/PlaceService.java b/src/main/java/br/com/clickbus/challenge/service/PlaceService.java index 799d1a8..cca85a6 100644 --- a/src/main/java/br/com/clickbus/challenge/service/PlaceService.java +++ b/src/main/java/br/com/clickbus/challenge/service/PlaceService.java @@ -1,16 +1,20 @@ package br.com.clickbus.challenge.service; - import br.com.clickbus.challenge.dto.PlaceDTO; import br.com.clickbus.challenge.entity.Place; import br.com.clickbus.challenge.repository.PlaceRepository; import lombok.AllArgsConstructor; + +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; import javax.validation.constraints.NotNull; + +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; -import org.apache.commons.lang3.NotImplementedException; @Service @AllArgsConstructor @@ -19,22 +23,27 @@ public class PlaceService { private PlaceRepository repository; public List findAll() { - throw new NotImplementedException("Metodo nao implementado"); + return repository.findAll(); } public Optional findById(@NotNull Long id) { - throw new NotImplementedException("Metodo nao implementado"); + return repository.findById(id); } public Place save(@NotNull Place place) { - throw new NotImplementedException("Metodo nao implementado"); + return repository.save(place); } public List findByName(@NotNull String name) { - throw new NotImplementedException("Metodo nao implementado"); + return repository.findByName(name); } - public Place alter(@NotNull Place place,@NotNull PlaceDTO placeDTO) { - throw new NotImplementedException("Metodo nao implementado"); + public Place alter(@NotNull Place place, @NotNull PlaceDTO placeDTO) { + place.setName(placeDTO.getName()); + place.setSlug(placeDTO.getSlug()); + place.setCity(placeDTO.getCity()); + place.setState(placeDTO.getState()); + place.setUpdatedAt(LocalDateTime.now()); + return repository.save(place); } } diff --git a/src/test/java/br/com/clickbus/challenge/contoller/PlaceControllerTest.java b/src/test/java/br/com/clickbus/challenge/contoller/PlaceControllerTest.java index 9a0666f..3b4ea69 100644 --- a/src/test/java/br/com/clickbus/challenge/contoller/PlaceControllerTest.java +++ b/src/test/java/br/com/clickbus/challenge/contoller/PlaceControllerTest.java @@ -1,6 +1,5 @@ package br.com.clickbus.challenge.contoller; - import br.com.clickbus.challenge.controller.PlaceController; import br.com.clickbus.challenge.dto.PlaceDTO; import br.com.clickbus.challenge.entity.Place; @@ -35,7 +34,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @ExtendWith(SpringExtension.class) @WebMvcTest(PlaceController.class) public class PlaceControllerTest { @@ -63,15 +61,14 @@ public void whenFindAllPlacesThenReturnASimpleItem() throws Exception { mockMvc.perform(get("/places") .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(print()) - .andExpect(jsonPath("$", hasSize(1))) - .andExpect(jsonPath("$[0].name", is("Butanta"))) - .andExpect(jsonPath("$[0].slug", is("bt"))) - .andExpect(jsonPath("$[0].city", is("Sao Paulo"))) - .andExpect(jsonPath("$[0].state", is("SP"))) - .andReturn().getResponse(); - + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].name", is("Butanta"))) + .andExpect(jsonPath("$[0].slug", is("bt"))) + .andExpect(jsonPath("$[0].city", is("Sao Paulo"))) + .andExpect(jsonPath("$[0].state", is("SP"))) + .andReturn().getResponse(); verify(service, atLeastOnce()).findAll(); @@ -83,13 +80,13 @@ public void whenFindByIdThenReturnOk() throws Exception { mockMvc.perform(get("/places/{id}", 1L) .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(print()) - .andExpect(jsonPath("$.name", is("Butanta"))) - .andExpect(jsonPath("$.slug", is("bt"))) - .andExpect(jsonPath("$.city", is("Sao Paulo"))) - .andExpect(jsonPath("$.state", is("SP"))) - .andReturn().getResponse(); + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(jsonPath("$.name", is("Butanta"))) + .andExpect(jsonPath("$.slug", is("bt"))) + .andExpect(jsonPath("$.city", is("Sao Paulo"))) + .andExpect(jsonPath("$.state", is("SP"))) + .andReturn().getResponse(); verify(service, atLeastOnce()).findById(anyLong()); } @@ -100,9 +97,9 @@ public void whenFindByIdThenReturnNotFound() throws Exception { mockMvc.perform(get("/places/{id}", 1L) .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()) - .andDo(print()) - .andReturn().getResponse(); + .andExpect(status().isNotFound()) + .andDo(print()) + .andReturn().getResponse(); verify(service, atLeastOnce()).findById(anyLong()); } @@ -113,30 +110,27 @@ public void whenFindByNameThenReturnOk() throws Exception { mockMvc.perform(get("/places/?name={name}", "Butanta") .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(print()) - .andExpect(jsonPath("$", hasSize(1))) - .andExpect(jsonPath("$[0].name", is("Butanta"))) - .andExpect(jsonPath("$[0].slug", is("bt"))) - .andExpect(jsonPath("$[0].city", is("Sao Paulo"))) - .andExpect(jsonPath("$[0].state", is("SP"))) - .andReturn().getResponse(); - + .andExpect(status().isOk()) + .andDo(print()) + .andExpect(jsonPath("$", hasSize(1))) + .andExpect(jsonPath("$[0].name", is("Butanta"))) + .andExpect(jsonPath("$[0].slug", is("bt"))) + .andExpect(jsonPath("$[0].city", is("Sao Paulo"))) + .andExpect(jsonPath("$[0].state", is("SP"))) + .andReturn().getResponse(); verify(service, atLeastOnce()).findByName(anyString()); } @Test public void whenFindByNameThenReturnNotFound() throws Exception { - when(service.findByName("Cotia")).thenReturn(Collections.emptyList()); mockMvc.perform(get("/places/?name={name}", "Cotia") .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()) - .andDo(print()) - .andReturn().getResponse(); - + .andExpect(status().isNotFound()) + .andDo(print()) + .andReturn().getResponse(); verify(service, atLeastOnce()).findByName(anyString()); } @@ -151,9 +145,9 @@ public void whenSaveThenReturnCreated() throws Exception { .content(objectMapper.writeValueAsString(place)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isCreated()) - .andDo(print()) - .andReturn().getResponse(); + .andExpect(status().isCreated()) + .andDo(print()) + .andReturn().getResponse(); } @Test @@ -165,9 +159,9 @@ public void whenSaveInvalidThenReturnBadRequest() throws Exception { .content(objectMapper.writeValueAsString(place)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andDo(print()) - .andReturn().getResponse(); + .andExpect(status().isBadRequest()) + .andDo(print()) + .andReturn().getResponse(); } @Test @@ -181,9 +175,9 @@ public void whenEdiWithPlaceInvalidThenReturnBadRequest() throws Exception { .content(objectMapper.writeValueAsString(place)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andDo(print()) - .andReturn().getResponse(); + .andExpect(status().isBadRequest()) + .andDo(print()) + .andReturn().getResponse(); } @Test @@ -197,8 +191,8 @@ public void whenEdiWithPlaceThenReturnOk() throws Exception { .content(objectMapper.writeValueAsString(place)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(print()) - .andReturn().getResponse(); + .andExpect(status().isOk()) + .andDo(print()) + .andReturn().getResponse(); } } diff --git a/src/test/java/br/com/clickbus/challenge/service/PlaceServiceTest.java b/src/test/java/br/com/clickbus/challenge/service/PlaceServiceTest.java index cf0f0a9..748bebe 100644 --- a/src/test/java/br/com/clickbus/challenge/service/PlaceServiceTest.java +++ b/src/test/java/br/com/clickbus/challenge/service/PlaceServiceTest.java @@ -1,6 +1,5 @@ package br.com.clickbus.challenge.service; - import br.com.clickbus.challenge.dto.PlaceDTO; import br.com.clickbus.challenge.entity.Place; import br.com.clickbus.challenge.repository.PlaceRepository; @@ -11,7 +10,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -19,14 +17,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; - @ExtendWith(MockitoExtension.class) class PlaceServiceTest { @@ -60,7 +56,6 @@ void whenFindByIdOk() { verify(repository, atLeastOnce()).findById(anyLong()); } - @Test void whenFindByIdThenReturnEmpty() { when(repository.findById(1L)).thenReturn(Optional.empty());