diff --git a/requests.http b/requests.http index 50ce01e6d355d28e4c09c1c33cab6092a487ed5a..7e78e46efff37a1bdc7147e29b6198566e22b12d 100644 --- a/requests.http +++ b/requests.http @@ -1,6 +1,6 @@ ### Get all plants -GET http://localhost:8080/api/v1/plants -Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJsdWthcy5rYXJzY2hAZ214LmRlIiwiaWF0IjoxNzAwOTMzNjQ4LCJleHAiOjE3MDEwMjAwNDh9.IXoMuNecB7ARvZpEyx5SraMbdoZrHgADeHd7wAo0ddw +GET http://localhost:8080/api/v1/plants?page=0 +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJsdWthcy5rYXJzY2hAZ214LmRlIiwiaWF0IjoxNzAxMzcyNTIxLCJleHAiOjE3MDE0NTg5MjF9.8_rTh-5s4A6D1t_bdRdQFbM5RdNl2fpCLETpTQzInuc ### Get garden entries GET http://localhost:8080/api/v1/garden diff --git a/src/main/java/hdm/mi/growbros/config/Seeding.java b/src/main/java/hdm/mi/growbros/config/Seeding.java index f2ba4c5de0039925adcc4760e9247b2397589173..05bcb89164a407f555da64e1a3768201f46aa9f4 100644 --- a/src/main/java/hdm/mi/growbros/config/Seeding.java +++ b/src/main/java/hdm/mi/growbros/config/Seeding.java @@ -25,6 +25,7 @@ public class Seeding { return (args) -> { if (plantRepository.count() > 0) { log.info("Plants table is not empty, seeding will be skipped."); + log.info("If you wish to seed the database, manually clear the plants table and re-run the application."); return; } diff --git a/src/main/java/hdm/mi/growbros/controllers/PlantsController.java b/src/main/java/hdm/mi/growbros/controllers/PlantsController.java index 2011d2a09e5fce0e5073818f6261a9b81561eb46..ad8f2b4119556b4a5faf7fe43dfec2a3a5cd3e1a 100644 --- a/src/main/java/hdm/mi/growbros/controllers/PlantsController.java +++ b/src/main/java/hdm/mi/growbros/controllers/PlantsController.java @@ -1,30 +1,33 @@ package hdm.mi.growbros.controllers; +import hdm.mi.growbros.models.dto.CustomPageDto; import hdm.mi.growbros.models.plant.Plant; import hdm.mi.growbros.service.PlantsService; import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.Collection; @RestController @CrossOrigin +@RequestMapping("/api/v1") +@RequiredArgsConstructor public class PlantsController { private final PlantsService plantsService; - public PlantsController(PlantsService plantsService) { - this.plantsService = plantsService; + @GetMapping("/plants") + public CustomPageDto<Plant> getAllPlants( + @RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "pageSize", defaultValue = "25") int pageSize + ) { + return plantsService.getPlants(page, pageSize); } - @GetMapping("/api/v1/plants") - public Collection<Plant> getAllPlants() { - return plantsService.getPlants(); - } - - @GetMapping("api/v1/randomPlants/{numberOfPlants}") + @GetMapping("/randomPlants/{numberOfPlants}") public Collection<Plant> getRandomPlants(@PathVariable Integer numberOfPlants) {return plantsService.getRandomPlants(numberOfPlants);} - @PostMapping("/api/v1/plants") + @PostMapping("/plants") public void createPlant(@RequestBody @Valid Plant plant) { plantsService.createPlant(plant); } diff --git a/src/main/java/hdm/mi/growbros/models/dto/CustomPageDto.java b/src/main/java/hdm/mi/growbros/models/dto/CustomPageDto.java new file mode 100644 index 0000000000000000000000000000000000000000..943c0d302ed63bc20978bbc0029d30ac806ee37e --- /dev/null +++ b/src/main/java/hdm/mi/growbros/models/dto/CustomPageDto.java @@ -0,0 +1,6 @@ +package hdm.mi.growbros.models.dto; + +import java.util.Collection; + +public record CustomPageDto<T>(int currentPage, int pageSize, Collection<T> content) { +} diff --git a/src/main/java/hdm/mi/growbros/service/PlantsService.java b/src/main/java/hdm/mi/growbros/service/PlantsService.java index 2d83c2c5538bd1437f96bcbc363f910a9f174671..a6edc3db3333e6f21d0d40042cfa919c856f6b47 100644 --- a/src/main/java/hdm/mi/growbros/service/PlantsService.java +++ b/src/main/java/hdm/mi/growbros/service/PlantsService.java @@ -1,10 +1,13 @@ package hdm.mi.growbros.service; +import hdm.mi.growbros.models.dto.CustomPageDto; import hdm.mi.growbros.models.plant.Plant; import hdm.mi.growbros.repositories.PlantRepository; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -16,11 +19,17 @@ import java.util.Random; @RequiredArgsConstructor public class PlantsService { private final static Logger log = LoggerFactory.getLogger(PlantsService.class); + private final static int MAX_PAGE_SIZE = 100; private final PlantRepository plantRepository; - public Collection<Plant> getPlants() { - return plantRepository.findAll(); + public CustomPageDto<Plant> getPlants(int pageNumber, int pageSize) { + pageNumber = Math.max(0, pageNumber); + pageSize = Math.min(MAX_PAGE_SIZE, pageSize); + + final Pageable page = PageRequest.of(pageNumber, pageSize); + List<Plant> pageContent = plantRepository.findAll(page).getContent(); + return new CustomPageDto<>(pageNumber, pageContent.size(), pageContent); } public Collection<Plant> getRandomPlants(int numberOfPlants) { diff --git a/src/test/java/hdm/mi/growbros/service/PlantsServiceTest.java b/src/test/java/hdm/mi/growbros/service/PlantsServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..65384ad273a14d0d9b73f5dc4f31d0082013fdc1 --- /dev/null +++ b/src/test/java/hdm/mi/growbros/service/PlantsServiceTest.java @@ -0,0 +1,113 @@ +package hdm.mi.growbros.service; + +import hdm.mi.growbros.models.dto.CustomPageDto; +import hdm.mi.growbros.models.plant.Plant; +import hdm.mi.growbros.repositories.PlantRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SpringBootTest +@ActiveProfiles("test") +class PlantsServiceTest { + @MockBean + private PlantRepository plantRepository; + + @Autowired + private PlantsService plantsService; + + @Test + void verify_repositoryCalled_withCorrectPageable() { + final PageRequest correctPageable = PageRequest.of(0, 10); + PageImpl<Plant> page = new PageImpl<>(List.of()); + when(plantRepository.findAll(any(PageRequest.class))) + .thenReturn(page); + + plantsService.getPlants(0, 10); + + verify(plantRepository).findAll(correctPageable); + } + + @Test + void getPlants_withPageEmpty() { + final PageRequest pageable = PageRequest.of(0, 10); + PageImpl<Plant> page = new PageImpl<>(List.of()); + final CustomPageDto<Plant> expected = new CustomPageDto<>(0, 0, page.getContent()); + when(plantRepository.findAll(pageable)) + .thenReturn(page); + + CustomPageDto<Plant> actual = plantsService.getPlants(0, 10); + + assertAll( + () -> assertEquals(expected.currentPage(), actual.currentPage()), + () -> assertEquals(0, actual.pageSize()), + () -> assertEquals(expected.content(), actual.content()) + ); + } + + @Test + void getPlants_correctPageSize() { + int plantAmount = 5; + var plants = getMockPlants(plantAmount); + + final PageRequest pageable = PageRequest.of(0, 10); + PageImpl<Plant> page = new PageImpl<>(plants); + final CustomPageDto<Plant> expected = new CustomPageDto<>(0, plantAmount, page.getContent()); + when(plantRepository.findAll(pageable)) + .thenReturn(page); + + CustomPageDto<Plant> actual = plantsService.getPlants(0, 10); + + assertAll( + () -> assertEquals(expected.currentPage(), actual.currentPage()), + () -> assertEquals(plantAmount, actual.pageSize()), + () -> assertEquals(expected.content(), actual.content()) + ); + } + + @Test + void getPlants_pageNumberNonNegative() { + int pageSize = 10; + final PageRequest correctPageRequest = PageRequest.of(0, pageSize); + when(plantRepository.findAll(any(PageRequest.class))) + .thenReturn(new PageImpl<>(List.of())); + + plantsService.getPlants(-1, pageSize); + + // calling the service with -1 as page number, and verifying that the repository is called + // with the correct page number + verify(plantRepository).findAll(correctPageRequest); + } + + @Test + void pageSize_cappedAt_100() { + int pageNumber = 0; + final PageRequest correctPageRequest = PageRequest.of(pageNumber, 100); + when(plantRepository.findAll(any(PageRequest.class))) + .thenReturn(new PageImpl<>(List.of())); + + plantsService.getPlants(pageNumber, 1000); + + verify(plantRepository).findAll(correctPageRequest); + } + + private List<Plant> getMockPlants(int amount) { + return IntStream + .range(0, amount) + .mapToObj(index -> Plant.builder().name(String.format("My generated plant %d", index)).build()) + .toList(); + } +}