US08: Add an owner delete endpoint to the API

This commit is contained in:
Ivaylo Ivanov 2020-03-24 16:38:45 +01:00
parent 9c36a93dd5
commit 5c53ad4f01
9 changed files with 210 additions and 8 deletions

View File

@ -5,6 +5,7 @@ import at.ac.tuwien.sepm.assignment.individual.endpoint.mapper.OwnerMapper;
import at.ac.tuwien.sepm.assignment.individual.entity.Owner; import at.ac.tuwien.sepm.assignment.individual.entity.Owner;
import at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException; import at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException;
import at.ac.tuwien.sepm.assignment.individual.service.OwnerService; import at.ac.tuwien.sepm.assignment.individual.service.OwnerService;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import at.ac.tuwien.sepm.assignment.individual.util.ValidationException; import at.ac.tuwien.sepm.assignment.individual.util.ValidationException;
@ -12,6 +13,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
@ -81,4 +83,25 @@ public class OwnerEndpoint {
"The requested owner could not be found"); "The requested owner could not be found");
} }
} }
@DeleteMapping(value = "/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteOwner(@PathVariable("id") Long id) {
LOGGER.info("DELETE " + BASE_URL + "/{}", id);
try {
ownerService.deleteOwner(id);
} catch (DataIntegrityViolationException e) {
LOGGER.error(e.getMessage());
throw new ResponseStatusException(HttpStatus.FORBIDDEN,
"The requested owner cannot be deleted because there are horses that are assigned to him/her");
}catch (DataAccessException e) {
LOGGER.error(e.getMessage());
throw new ResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY,
"Something went wrong during the communication with the database");
} catch (NotFoundException e) {
LOGGER.error(e.getMessage());
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
"The requested owner has not been found");
}
}
} }

View File

@ -29,4 +29,11 @@ public interface OwnerDao {
* @throws DataAccessException will be thrown if something goes wrong during the database access. * @throws DataAccessException will be thrown if something goes wrong during the database access.
*/ */
Owner updateOwner(Owner owner) throws DataAccessException; Owner updateOwner(Owner owner) throws DataAccessException;
/**
* @param id of the owner to delete
* @throws DataAccessException will be thrown if something goes wrong during the database access.
* @throws NotFoundException will be thrown if the owner could not be found in the database.
*/
void deleteOwner(Long id) throws DataAccessException, NotFoundException;
} }

View File

@ -7,6 +7,7 @@ import at.ac.tuwien.sepm.assignment.individual.persistence.FileDao;
import at.ac.tuwien.sepm.assignment.individual.persistence.HorseDao; import at.ac.tuwien.sepm.assignment.individual.persistence.HorseDao;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
@ -35,6 +36,7 @@ public class HorseJdbcDao implements HorseDao {
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
private final FileDao fileDao = new HorseFileDao(); private final FileDao fileDao = new HorseFileDao();
@Autowired
public HorseJdbcDao(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) { public HorseJdbcDao(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.jdbcTemplate = jdbcTemplate; this.jdbcTemplate = jdbcTemplate;
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;

View File

@ -1,20 +1,27 @@
package at.ac.tuwien.sepm.assignment.individual.persistence.impl; package at.ac.tuwien.sepm.assignment.individual.persistence.impl;
import at.ac.tuwien.sepm.assignment.individual.entity.Horse;
import at.ac.tuwien.sepm.assignment.individual.entity.Owner; import at.ac.tuwien.sepm.assignment.individual.entity.Owner;
import at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException; import at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException;
import at.ac.tuwien.sepm.assignment.individual.persistence.OwnerDao; import at.ac.tuwien.sepm.assignment.individual.persistence.OwnerDao;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
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.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.GeneratedKeyHolder;
@ -25,6 +32,7 @@ import org.springframework.stereotype.Repository;
public class OwnerJdbcDao implements OwnerDao { public class OwnerJdbcDao implements OwnerDao {
private static final String TABLE_NAME = "Owner"; private static final String TABLE_NAME = "Owner";
private static final String HORSE_TABLE_NAME = "Horse";
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@ -92,7 +100,7 @@ public class OwnerJdbcDao implements OwnerDao {
try { try {
if(owner.getId() == null || owner.getId() == 0) if(owner.getId() == null || owner.getId() == 0)
throw new DataIntegrityViolationException("Horse Id missing or 0"); throw new DataIntegrityViolationException("Owner Id missing or 0");
this.validateOwner(owner); this.validateOwner(owner);
@ -123,11 +131,52 @@ public class OwnerJdbcDao implements OwnerDao {
} }
} }
@Override
public void deleteOwner(Long id) throws DataAccessException, NotFoundException {
Owner ownerToDelete = this.findOneById(id);
LOGGER.trace("Delete owner with id {}", id);
final String sql = "DELETE FROM " + TABLE_NAME + " WHERE id=?";
if (ownerOwnsHorses(id))
throw new DataIntegrityViolationException("Deleting owner failed, owner has horses assigned");
try {
int changes = jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(sql);
ps.setLong(1, id);
return ps;
});
if (changes == 0)
throw new DataAccessException("Deleting owner failed, no rows affected") {};
} catch(DataAccessException e){
// We are doing this in order to not change the exception type
throw new DataAccessException("Deleting records failed", e) {};
}
}
private void validateOwner(Owner owner) throws DataIntegrityViolationException { private void validateOwner(Owner owner) throws DataIntegrityViolationException {
if(owner.getName() == null || owner.getName().isEmpty()) if(owner.getName() == null || owner.getName().isEmpty())
throw new DataIntegrityViolationException("Required parameters for owner missing"); throw new DataIntegrityViolationException("Required parameters for owner missing");
} }
private boolean ownerOwnsHorses(Long ownerId) {
final String sql = "SELECT * FROM " + HORSE_TABLE_NAME + " WHERE owner_id=?";
try {
jdbcTemplate.queryForObject(sql, new Object[] {ownerId}, BeanPropertyRowMapper.newInstance(Horse.class));
} catch(EmptyResultDataAccessException e) {
// If empty, return false
return false;
} catch (IncorrectResultSizeDataAccessException e) {
// If incorrect size above 0, return true
return true;
}
return true;
}
private Owner mapRow(ResultSet resultSet, int i) throws SQLException { private Owner mapRow(ResultSet resultSet, int i) throws SQLException {
final Owner owner = new Owner(); final Owner owner = new Owner();
owner.setId(resultSet.getLong("id")); owner.setId(resultSet.getLong("id"));

View File

@ -5,8 +5,6 @@ import at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException;
import at.ac.tuwien.sepm.assignment.individual.util.ValidationException; import at.ac.tuwien.sepm.assignment.individual.util.ValidationException;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import java.io.IOException;
public interface OwnerService { public interface OwnerService {
@ -33,4 +31,11 @@ public interface OwnerService {
* @throws DataAccessException will be thrown if the owner could not be saved in the database. * @throws DataAccessException will be thrown if the owner could not be saved in the database.
*/ */
Owner updateOwner(Owner owner) throws ValidationException, DataAccessException; Owner updateOwner(Owner owner) throws ValidationException, DataAccessException;
/**
* @param id of the owner to delete
* @throws NotFoundException will be thrown if the owner could not be found in the system
* @throws DataAccessException will be thrown if the owner could not be deleted from the database
*/
void deleteOwner(Long id) throws NotFoundException, DataAccessException;
} }

View File

@ -1,10 +1,12 @@
package at.ac.tuwien.sepm.assignment.individual.service.impl; package at.ac.tuwien.sepm.assignment.individual.service.impl;
import at.ac.tuwien.sepm.assignment.individual.entity.Owner; import at.ac.tuwien.sepm.assignment.individual.entity.Owner;
import at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException;
import at.ac.tuwien.sepm.assignment.individual.persistence.OwnerDao; import at.ac.tuwien.sepm.assignment.individual.persistence.OwnerDao;
import at.ac.tuwien.sepm.assignment.individual.service.OwnerService; import at.ac.tuwien.sepm.assignment.individual.service.OwnerService;
import at.ac.tuwien.sepm.assignment.individual.util.ValidationException; import at.ac.tuwien.sepm.assignment.individual.util.ValidationException;
import at.ac.tuwien.sepm.assignment.individual.util.Validator; import at.ac.tuwien.sepm.assignment.individual.util.Validator;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -44,4 +46,10 @@ public class SimpleOwnerService implements OwnerService {
this.validator.validateUpdateOwner(owner); this.validator.validateUpdateOwner(owner);
return ownerDao.updateOwner(owner); return ownerDao.updateOwner(owner);
} }
@Override
public void deleteOwner(Long id) throws NotFoundException, DataAccessException {
LOGGER.trace("deleteOwner({})", id);
ownerDao.deleteOwner(id);
}
} }

View File

@ -11,9 +11,6 @@ import java.util.Map;
@Component @Component
public class Validator { public class Validator {
public void validateNewOwner(Owner owner) throws ValidationException { public void validateNewOwner(Owner owner) throws ValidationException {
if(owner.getName() == null || owner.getName().isEmpty()) { if(owner.getName() == null || owner.getName().isEmpty()) {
throw new ValidationException("Required value name missing"); throw new ValidationException("Required value name missing");

View File

@ -1,17 +1,24 @@
package at.ac.tuwien.sepm.assignment.individual.integration; package at.ac.tuwien.sepm.assignment.individual.integration;
import static at.ac.tuwien.sepm.assignment.individual.base.TestData.HORSE_URL;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import at.ac.tuwien.sepm.assignment.individual.endpoint.dto.HorseDto;
import at.ac.tuwien.sepm.assignment.individual.endpoint.dto.OwnerDto; import at.ac.tuwien.sepm.assignment.individual.endpoint.dto.OwnerDto;
import at.ac.tuwien.sepm.assignment.individual.enums.ERace;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*; import org.springframework.http.*;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.sql.Date;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test") @ActiveProfiles("test")
public class OwnerEndpointTest { public class OwnerEndpointTest {
@ -62,5 +69,67 @@ public class OwnerEndpointTest {
assertEquals(newOwner.getId(), response.getBody().getId()); assertEquals(newOwner.getId(), response.getBody().getId());
assertEquals(newOwner.getName(), response.getBody().getName()); assertEquals(newOwner.getName(), response.getBody().getName());
} }
}
@Test
@DisplayName("Deleting an existing owner without owners will return HTTP 204")
public void deletingOwner_existingNoOwnersOwned_shouldReturnStatus204() {
// Create the owner
OwnerDto newOwner = new OwnerDto("Chad");
HttpEntity<OwnerDto> request = new HttpEntity<>(newOwner);
ResponseEntity<OwnerDto> response = REST_TEMPLATE
.exchange(BASE_URL + port + OWNER_URL, HttpMethod.POST, request, OwnerDto.class);
// Delete and test if deleted
ResponseEntity res = REST_TEMPLATE
.exchange(BASE_URL + port + OWNER_URL + '/' + response.getBody().getId(), HttpMethod.DELETE, null, new ParameterizedTypeReference<OwnerDto>() {});
assertEquals(res.getStatusCode(), HttpStatus.NO_CONTENT);
}
@Test
@DisplayName("Deleting an existing owner without horses will return HTTP 204")
public void deletingOwner_existingNoHorsesOwned_shouldReturnStatus204() {
// Create the owner
OwnerDto newOwner = new OwnerDto("Chad");
HttpEntity<OwnerDto> request = new HttpEntity<>(newOwner);
ResponseEntity<OwnerDto> response = REST_TEMPLATE
.exchange(BASE_URL + port + OWNER_URL, HttpMethod.POST, request, OwnerDto.class);
// Delete and test if deleted
ResponseEntity res = REST_TEMPLATE
.exchange(BASE_URL + port + OWNER_URL + '/' + response.getBody().getId(), HttpMethod.DELETE, null, new ParameterizedTypeReference<OwnerDto>() {});
assertEquals(res.getStatusCode(), HttpStatus.NO_CONTENT);
}
@Test
@DisplayName("Deleting an nonexistent owner will return HTTP 404")
public void deletingOwner_nonexistent_shouldReturnStatus404() {
assertThrows(HttpClientErrorException.NotFound.class, () ->
REST_TEMPLATE
.exchange(BASE_URL + port + OWNER_URL + "/0", HttpMethod.DELETE, null, new ParameterizedTypeReference<OwnerDto>() {}));
}
@Test
@DisplayName("Deleting an exiting owner with horses will return HTTP 403")
public void deletingOwner_existingHorsesOwned_shouldReturnStatus403() {
// Create the owner
OwnerDto newOwner = new OwnerDto("Chad");
HttpEntity<OwnerDto> request = new HttpEntity<>(newOwner);
OwnerDto savedOwner = REST_TEMPLATE
.exchange(BASE_URL + port + OWNER_URL, HttpMethod.POST, request, OwnerDto.class).getBody();
// Create the horse
HorseDto newHorse = new HorseDto("Zephyr", "Nice horse", (short) 4, Date.valueOf("2020-01-01"), ERace.APPALOOSA, "files/test.png", savedOwner.getId());
request = new HttpEntity(newHorse);
REST_TEMPLATE.exchange(BASE_URL + port + HORSE_URL, HttpMethod.POST, request, HorseDto.class);
assertThrows(HttpClientErrorException.Forbidden.class, () ->
REST_TEMPLATE
.exchange(BASE_URL + port + OWNER_URL + "/" + savedOwner.getId(), HttpMethod.DELETE, null, new ParameterizedTypeReference<OwnerDto>() {}));
}
}

View File

@ -2,13 +2,17 @@ package at.ac.tuwien.sepm.assignment.individual.unit.persistence;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import at.ac.tuwien.sepm.assignment.individual.entity.Horse;
import at.ac.tuwien.sepm.assignment.individual.entity.Owner; import at.ac.tuwien.sepm.assignment.individual.entity.Owner;
import at.ac.tuwien.sepm.assignment.individual.enums.ERace;
import at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException; import at.ac.tuwien.sepm.assignment.individual.exception.NotFoundException;
import at.ac.tuwien.sepm.assignment.individual.persistence.HorseDao;
import at.ac.tuwien.sepm.assignment.individual.persistence.OwnerDao; import at.ac.tuwien.sepm.assignment.individual.persistence.OwnerDao;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import java.io.IOException; import java.io.IOException;
import java.sql.Date; import java.sql.Date;
@ -18,6 +22,9 @@ public abstract class OwnerDaoTestBase {
@Autowired @Autowired
OwnerDao ownerDao; OwnerDao ownerDao;
@Autowired
HorseDao horseDao;
@Test @Test
@DisplayName("Finding owner by non-existing ID should throw NotFoundException") @DisplayName("Finding owner by non-existing ID should throw NotFoundException")
public void findingOwnerById_nonExisting_shouldThrowNotFoundException() { public void findingOwnerById_nonExisting_shouldThrowNotFoundException() {
@ -70,4 +77,39 @@ public abstract class OwnerDaoTestBase {
newOwner.setName(""); newOwner.setName("");
assertThrows(DataAccessException.class, () -> ownerDao.updateOwner(newOwner)); assertThrows(DataAccessException.class, () -> ownerDao.updateOwner(newOwner));
} }
@Test
@DisplayName("Deleting an existing owner without horses should delete the owner")
public void deletingOwner_existingOwnerNoHorsesOwned_shouldDeleteOwner() throws IOException {
// Create the owner
Owner newOwner = new Owner("Chad");
Owner savedOwner = ownerDao.addOwner(newOwner);
// Delete the owner
ownerDao.deleteOwner(savedOwner.getId());
// Check if deleted
assertThrows(NotFoundException.class, () -> ownerDao.findOneById(savedOwner.getId()));
}
@Test
@DisplayName("Deleting an nonexistent owner should throw NotFoundException")
public void deletingOwner_nonexistent_shouldThrowNotFound() throws IOException {
assertThrows(NotFoundException.class, () -> ownerDao.deleteOwner(null));
}
@Test
@DisplayName("Deleting an existing owner with horses should throw DataIntegrityViolationException")
public void deletingHorse_existing_shouldDeleteHorse() {
// Create the owner
Owner newOwner = new Owner("Chad");
Owner savedOwner = ownerDao.addOwner(newOwner);
// Create the horse
Horse newHorse = new Horse("Zephyr", "Nice horse", (short) 4, Date.valueOf("2020-01-01"), ERace.APPALOOSA, "files/test.png", savedOwner.getId());
Horse savedHorse = horseDao.addHorse(newHorse);
// Delete the owner
assertThrows(DataIntegrityViolationException.class, () -> ownerDao.deleteOwner(savedOwner.getId()));
}
} }