From 5ffbe6c2253892ad3b198c8408357251d030393e Mon Sep 17 00:00:00 2001 From: Ivaylo Ivanov Date: Wed, 25 Mar 2020 07:53:57 +0100 Subject: [PATCH] US09: Make it possible to search for owners using the API --- .../individual/endpoint/OwnerEndpoint.java | 22 +++++ .../endpoint/mapper/OwnerMapper.java | 12 +++ .../individual/persistence/OwnerDao.java | 18 +++- .../persistence/impl/OwnerJdbcDao.java | 40 +++++++++ .../individual/service/OwnerService.java | 17 ++++ .../service/impl/SimpleOwnerService.java | 16 ++++ .../assignment/individual/util/Validator.java | 6 ++ .../integration/OwnerEndpointTest.java | 86 +++++++++++++++++++ .../unit/persistence/OwnerDaoTestBase.java | 45 ++++++++++ .../unit/service/OwnerServiceTest.java | 13 +++ 10 files changed, 274 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/endpoint/OwnerEndpoint.java b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/endpoint/OwnerEndpoint.java index c238001..ae7a46b 100644 --- a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/endpoint/OwnerEndpoint.java +++ b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/endpoint/OwnerEndpoint.java @@ -7,6 +7,8 @@ import at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException; import at.ac.tuwien.sepm.assignment.individual.service.OwnerService; import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.Map; import at.ac.tuwien.sepm.assignment.individual.util.ValidationException; import org.slf4j.Logger; @@ -43,6 +45,26 @@ public class OwnerEndpoint { } } + @GetMapping + @ResponseStatus(HttpStatus.OK) + public List getAll(@RequestParam Map filters) { + try { + if(filters.isEmpty()) { + LOGGER.info("GET " + BASE_URL); + return ownerMapper.entityListToDtoList(ownerService.getAll()); + } else { + LOGGER.info("GET " + BASE_URL + "/ with filters" + filters.entrySet()); + return ownerMapper.entityListToDtoList(ownerService.getFiltered(filters)); + } + } catch (NotFoundException e) { + LOGGER.error(e.getMessage()); + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No owners found"); + } catch (ValidationException e) { + LOGGER.error(e.getMessage()); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "The request contains filters with bad values: " + e.getMessage()); + } + } + @PostMapping @ResponseStatus(HttpStatus.CREATED) public OwnerDto addOwner(@RequestBody OwnerDto owner) { diff --git a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/endpoint/mapper/OwnerMapper.java b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/endpoint/mapper/OwnerMapper.java index 77f61d5..7c019fd 100644 --- a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/endpoint/mapper/OwnerMapper.java +++ b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/endpoint/mapper/OwnerMapper.java @@ -4,6 +4,9 @@ import at.ac.tuwien.sepm.assignment.individual.endpoint.dto.OwnerDto; import at.ac.tuwien.sepm.assignment.individual.entity.Owner; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.List; + @Component public class OwnerMapper { @@ -14,4 +17,13 @@ public class OwnerMapper { public Owner dtoToEntity(OwnerDto owner) { return new Owner(owner.getName()); } + + public List entityListToDtoList(List ownerEntities) { + List ownerDtos = new ArrayList<>(); + + for(Owner owner: ownerEntities) { + ownerDtos.add(entityToDto(owner)); + } + return ownerDtos; + } } diff --git a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/OwnerDao.java b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/OwnerDao.java index c51d87e..cc93184 100644 --- a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/OwnerDao.java +++ b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/OwnerDao.java @@ -4,7 +4,8 @@ import at.ac.tuwien.sepm.assignment.individual.entity.Owner; import at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException; import org.springframework.dao.DataAccessException; -import java.io.IOException; +import java.util.List; +import java.util.Map; public interface OwnerDao { @@ -16,6 +17,21 @@ public interface OwnerDao { */ Owner findOneById(Long id); + /** + * @return a list of all owners in the system + * @throws NotFoundException will be thrown if no owners are present in the database + * @throws DataAccessException will be thrown if something goes wrong during the database access + */ + List getAll() throws NotFoundException; + + /** + * @param filters to use for filtering the owners + * @return a list of all owners that fill the criteria + * @throws NotFoundException wil be thrown if no owners fill the criteria + * @throws DataAccessException will be thrown if something goes wrong during the database access + */ + List getFiltered(Map filters) throws NotFoundException; + /** * @param owner that specifies the owner to add * @return the newly created owner diff --git a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/impl/OwnerJdbcDao.java b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/impl/OwnerJdbcDao.java index e589cc9..744b074 100644 --- a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/impl/OwnerJdbcDao.java +++ b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/persistence/impl/OwnerJdbcDao.java @@ -11,6 +11,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -23,6 +24,7 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; @@ -54,6 +56,44 @@ public class OwnerJdbcDao implements OwnerDao { return owners.get(0); } + @Override + public List getAll() throws NotFoundException { + LOGGER.trace("Get all owners"); + final String sql = "SELECT * FROM " + TABLE_NAME; + List owners = jdbcTemplate.query(sql, new Object[] { }, this::mapRow); + + if(owners.isEmpty()) throw new NotFoundException("No owners found in the database"); + + return owners; + } + + @Override + public List getFiltered(Map filters) throws NotFoundException { + LOGGER.trace("Get all owners with filters " + filters.entrySet()); + final String sql = "SELECT * FROM " + TABLE_NAME + " WHERE UPPER(name) LIKE :name"; + + // Create a list to hold the results + List owners = new ArrayList<>(); + + // Create a map to hold the sql filters with all values set as wildcards + Map queryFilters = new HashMap<>(); + queryFilters.put("name", "%_%"); + + // Go through the supplied filters and find set values + if(filters.get("name") != null) + queryFilters.replace("name", '%' + filters.get("name").toUpperCase() + '%'); + + // Create an map sql parameter source for use in the query + MapSqlParameterSource sqlMap = new MapSqlParameterSource(); + sqlMap.addValues(queryFilters); + + owners = namedParameterJdbcTemplate.query(sql, sqlMap, this::mapRow); + + if(owners.isEmpty()) throw new NotFoundException("No owners found in the database"); + + return owners; + } + @Override public Owner addOwner(Owner owner) { LOGGER.trace("Add owner {}", owner); diff --git a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/service/OwnerService.java b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/service/OwnerService.java index 21fcf1c..3b765da 100644 --- a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/service/OwnerService.java +++ b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/service/OwnerService.java @@ -5,6 +5,9 @@ import at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException; import at.ac.tuwien.sepm.assignment.individual.util.ValidationException; import org.springframework.dao.DataAccessException; +import java.util.List; +import java.util.Map; + public interface OwnerService { @@ -16,6 +19,20 @@ public interface OwnerService { */ Owner findOneById(Long id); + /** + * @return a list of all owners in the database + * @throws NotFoundException will be thrown if there are no owners in the database + */ + List getAll() throws NotFoundException; + + /** + * @param filters to use for filtering the owners + * @return a list of all owners that fill the criteria + * @throws NotFoundException will be thrown if no owners fill the criteria + * @throws ValidationException will be thrown if the filter contains bad values + */ + List getFiltered(Map filters) throws NotFoundException, ValidationException; + /** * @param owner to create * @return the new owner diff --git a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/service/impl/SimpleOwnerService.java b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/service/impl/SimpleOwnerService.java index d544fba..f6c4195 100644 --- a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/service/impl/SimpleOwnerService.java +++ b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/service/impl/SimpleOwnerService.java @@ -8,6 +8,9 @@ import at.ac.tuwien.sepm.assignment.individual.util.ValidationException; import at.ac.tuwien.sepm.assignment.individual.util.Validator; import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.Map; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -33,6 +36,19 @@ public class SimpleOwnerService implements OwnerService { return ownerDao.findOneById(id); } + @Override + public List getAll() throws NotFoundException { + LOGGER.trace("getAll()"); + return ownerDao.getAll(); + } + + @Override + public List getFiltered(Map filters) throws NotFoundException, ValidationException { + LOGGER.trace("getFiltered({})", filters.entrySet()); + this.validator.validateOwnerFilter(filters); + return ownerDao.getFiltered(filters); + } + @Override public Owner addOwner(Owner owner) throws ValidationException, DataAccessException { LOGGER.trace("addOwner({})", owner); diff --git a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/util/Validator.java b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/util/Validator.java index 2870f85..af9eaf7 100644 --- a/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/util/Validator.java +++ b/backend/src/main/java/at/ac/tuwien/sepm/assignment/individual/util/Validator.java @@ -26,6 +26,12 @@ public class Validator { } } + public void validateOwnerFilter(Map filters) { + if(filters.get("name") == null || filters.get("name").isEmpty()) { + throw new ValidationException("Owner name cannot be empty"); + } + } + public void validateNewHorse(Horse horse) throws ValidationException { if(horse.getName() == null || horse.getScore() == 0 || horse.getBirthday() == null || horse.getRace() == null || horse.getImagePath() == null){ throw new ValidationException("All or some required values missing: name, score, birthday, race, imagePath"); diff --git a/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/integration/OwnerEndpointTest.java b/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/integration/OwnerEndpointTest.java index 1e21484..163d8aa 100644 --- a/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/integration/OwnerEndpointTest.java +++ b/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/integration/OwnerEndpointTest.java @@ -16,8 +16,10 @@ import org.springframework.http.*; import org.springframework.test.context.ActiveProfiles; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; import java.sql.Date; +import java.util.List; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") @@ -32,6 +34,90 @@ public class OwnerEndpointTest { private int port; + @Test + @DisplayName("Searching for all owners with no filters, given two owners should return HTTP 200 and both ownerss") + public void searchingOwners_noFiltersGivenTwoOwners_shouldReturnStatus200AndOwners() { + // Create the owners + OwnerDto newOwner = new OwnerDto("Chad"); + + HttpEntity request = new HttpEntity<>(newOwner); + ResponseEntity firstOwner = REST_TEMPLATE + .exchange(BASE_URL + port + OWNER_URL, HttpMethod.POST, request, OwnerDto.class); + + newOwner = new OwnerDto("Gigachad"); + + request = new HttpEntity<>(newOwner); + ResponseEntity secondOwner = REST_TEMPLATE + .exchange(BASE_URL + port + OWNER_URL, HttpMethod.POST, request, OwnerDto.class); + + // Get all owners and save in list + // https://stackoverflow.com/a/31947188 + ResponseEntity> allOwners = REST_TEMPLATE + .exchange(BASE_URL + port + OWNER_URL, HttpMethod.GET, null, new ParameterizedTypeReference>() {}); + + assertEquals(allOwners.getStatusCode(), HttpStatus.OK); + assertTrue(allOwners.getBody().contains(firstOwner.getBody())); + assertTrue(allOwners.getBody().contains(secondOwner.getBody())); + } + + @Test + @DisplayName("Searching for all owners with correct filters, given two owners should return HTTP 200 and one owner") + public void searchingOwners_withCorrectFiltersGivenTwoOwners_shouldReturnStatus200AndOwner() { + // Create the owners + OwnerDto newOwner = new OwnerDto("Chad"); + + HttpEntity request = new HttpEntity<>(newOwner); + ResponseEntity firstOwner = REST_TEMPLATE + .exchange(BASE_URL + port + OWNER_URL, HttpMethod.POST, request, OwnerDto.class); + + newOwner = new OwnerDto("Lad"); + + request = new HttpEntity<>(newOwner); + ResponseEntity secondOwner = REST_TEMPLATE + .exchange(BASE_URL + port + OWNER_URL, HttpMethod.POST, request, OwnerDto.class); + + // Get all owners and save in list + // https://stackoverflow.com/a/25434451 + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(BASE_URL + port + OWNER_URL) + .queryParam("name", "chad"); + + ResponseEntity> allOwners = REST_TEMPLATE.exchange( + builder.toUriString(), + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {} + ); + + assertEquals(allOwners.getStatusCode(), HttpStatus.OK); + assertTrue(allOwners.getBody().contains(firstOwner.getBody())); + } + + @Test + @DisplayName("Searching for all owners with incorrect filters, given two owners should return HTTP 400") + public void searchingOwners_withIncorrectFiltersGivenTwoOwners_shouldReturnStatus400() { + // Create the owners + OwnerDto newOwner = new OwnerDto("Chad"); + + HttpEntity request = new HttpEntity<>(newOwner); + ResponseEntity firstOwner = REST_TEMPLATE + .exchange(BASE_URL + port + OWNER_URL, HttpMethod.POST, request, OwnerDto.class); + + newOwner = new OwnerDto("Lad"); + + request = new HttpEntity<>(newOwner); + ResponseEntity secondOwner = REST_TEMPLATE + .exchange(BASE_URL + port + OWNER_URL, HttpMethod.POST, request, OwnerDto.class); + + // Get all owners and save in list + // https://stackoverflow.com/a/25434451 + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(BASE_URL + port + OWNER_URL) + .queryParam("name", ""); + + assertThrows(HttpClientErrorException.BadRequest.class, () -> + REST_TEMPLATE + .exchange(builder.toUriString(), HttpMethod.GET, null, new ParameterizedTypeReference() {})); + } + @Test @DisplayName("Adding a new owner with the correct parameters will return HTTP 201 and the new OwnerDto") public void addingNewOwner_correctParameters_shouldReturnStatus201AndOwner() { diff --git a/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/persistence/OwnerDaoTestBase.java b/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/persistence/OwnerDaoTestBase.java index 5e0e14d..02d7a4d 100644 --- a/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/persistence/OwnerDaoTestBase.java +++ b/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/persistence/OwnerDaoTestBase.java @@ -16,6 +16,9 @@ import org.springframework.dao.DataIntegrityViolationException; import java.io.IOException; import java.sql.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public abstract class OwnerDaoTestBase { @@ -25,6 +28,48 @@ public abstract class OwnerDaoTestBase { @Autowired HorseDao horseDao; + @Test + @DisplayName("Getting all owners given two owners should return a list with the owners") + public void gettingAllOwners_givenTwoOwners_shouldReturnOwners() { + // Create the owners + Owner firstOwner = ownerDao.addOwner(new Owner("Chad")); + Owner secondOwner = ownerDao.addOwner(new Owner("Lad")); + + // Test if the owners are present + List allOwners = ownerDao.getAll(); + assertTrue(allOwners.contains(firstOwner)); + assertTrue(allOwners.contains(secondOwner)); + } + + @Test + @DisplayName("Searching all owners with correct filters given two owners should return a list with the owner") + public void searchingOwners_withCorrectFiltersGivenTwoOwners_shouldReturnOwner() { + // Create the owners + Owner firstOwner = ownerDao.addOwner(new Owner("Brad")); + Owner secondOwner = ownerDao.addOwner(new Owner("Becky")); + + // Test if the owners are present + Map filters = new HashMap(); + filters.put("name", "brad"); + + List allOwners = ownerDao.getFiltered(filters); + assertTrue(allOwners.contains(firstOwner)); + } + + @Test + @DisplayName("Searching all owners with incorrect filters given two owners should throw NotFoundException") + public void searchingOwners_withIncorrectFiltersGivenTwoOwners_shouldThrowNotFound() { + // Create the owners + Owner firstOwner = ownerDao.addOwner(new Owner("Brad")); + Owner secondOwner = ownerDao.addOwner(new Owner("Stacy")); + + // Test if the owners are present + Map filters = new HashMap(); + filters.put("name", "Tester Owner"); + + assertThrows(NotFoundException.class, () -> ownerDao.getFiltered(filters)); + } + @Test @DisplayName("Finding owner by non-existing ID should throw NotFoundException") public void findingOwnerById_nonExisting_shouldThrowNotFoundException() { diff --git a/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/service/OwnerServiceTest.java b/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/service/OwnerServiceTest.java index 0de9a44..b1313be 100644 --- a/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/service/OwnerServiceTest.java +++ b/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/service/OwnerServiceTest.java @@ -13,6 +13,9 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; +import java.util.HashMap; +import java.util.Map; + import static org.junit.jupiter.api.Assertions.assertEquals; @ExtendWith(SpringExtension.class) @@ -67,4 +70,14 @@ public class OwnerServiceTest { newOwner.setName(""); assertThrows(ValidationException.class, () -> ownerService.updateOwner(newOwner)); } + + @Test + @DisplayName("Searching all owners with incorrect filters given no owners should throw ValidationException") + public void searchingOwners_withIncorrectFiltersGivenNoOwners_shouldThrowValidation() { + // Test for exception + Map filters = new HashMap(); + filters.put("name", ""); + + assertThrows(ValidationException.class, () -> ownerService.getFiltered(filters)); + } }