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 0347957..52cda7f 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 @@ -2,12 +2,16 @@ package at.ac.tuwien.sepm.assignment.individual.endpoint; import at.ac.tuwien.sepm.assignment.individual.endpoint.dto.OwnerDto; 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.exception.NotFoundException; import at.ac.tuwien.sepm.assignment.individual.service.OwnerService; import java.lang.invoke.MethodHandles; + +import at.ac.tuwien.sepm.assignment.individual.util.ValidationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; @@ -36,4 +40,22 @@ public class OwnerEndpoint { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Error during reading owner", e); } } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public OwnerDto addHorse(@RequestBody OwnerDto owner) { + LOGGER.info("POST " + BASE_URL); + try { + Owner ownerEntity = ownerMapper.dtoToEntity(owner); + return ownerMapper.entityToDto(ownerService.addOwner(ownerEntity)); + } catch (ValidationException e) { + LOGGER.error(e.getMessage()); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "Error during adding new owner: " + e.getMessage()); + } catch (DataAccessException e) { + LOGGER.error(e.getMessage()); + throw new ResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY, + "Something went wrong during the communication with the database"); + } + } } 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 78d141b..77f61d5 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 @@ -11,5 +11,7 @@ public class OwnerMapper { return new OwnerDto(owner.getId(), owner.getName(), owner.getCreatedAt(), owner.getUpdatedAt()); } - + public Owner dtoToEntity(OwnerDto owner) { + return new Owner(owner.getName()); + } } 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 8c12dda..820e757 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 @@ -14,4 +14,11 @@ public interface OwnerDao { */ Owner findOneById(Long id); + /** + * @param owner that specifies the owner to add + * @return the newly created horse + * @throws DataAccessException will be thrown if something goes wrong during the database access. + */ + Owner addOwner(Owner 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 78a9db6..d435af5 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 @@ -4,14 +4,20 @@ 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 java.lang.invoke.MethodHandles; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.time.LocalDateTime; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; @Repository @@ -39,6 +45,49 @@ public class OwnerJdbcDao implements OwnerDao { return owners.get(0); } + @Override + public Owner addOwner(Owner owner) { + LOGGER.trace("Add owner {}", owner); + final String sql = "INSERT INTO " + TABLE_NAME + "(name, created_at, updated_at) VALUES(?,?,?)"; + + try { + // Check if the constraints are violated + this.validateOwner(owner); + + LocalDateTime currentTime = LocalDateTime.now(); + + owner.setCreatedAt(currentTime); + owner.setUpdatedAt(currentTime); + + // Create a key holder to get the key of the new record + KeyHolder keyHolder = new GeneratedKeyHolder(); + + int changes = jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); + + ps.setString(1, owner.getName()); + ps.setObject(2, owner.getCreatedAt()); + ps.setObject(3, owner.getUpdatedAt()); + return ps; + }, keyHolder); + + if (changes == 0) + throw new DataAccessException("Creating owner failed, no rows affected") {}; + + owner.setId(((Number)keyHolder.getKeys().get("id")).longValue()); + + return owner; + + } catch (DataAccessException e) { + // We are doing this in order to not change the exception type + throw new DataAccessException("Adding new records failed", e) {}; + } + } + + private void validateOwner(Owner owner) throws DataIntegrityViolationException { + if(owner.getName() == null || owner.getName().isEmpty()) + throw new DataIntegrityViolationException("Required parameters for owner missing"); + } private Owner mapRow(ResultSet resultSet, int i) throws SQLException { final Owner owner = new 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 5c983e1..b22677a 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 @@ -2,6 +2,8 @@ package at.ac.tuwien.sepm.assignment.individual.service; 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.util.ValidationException; +import org.springframework.dao.DataAccessException; public interface OwnerService { @@ -14,4 +16,12 @@ public interface OwnerService { */ Owner findOneById(Long id); + /** + * @param owner to create + * @return the new owner + * @throws ValidationException will be thrown if something goes wrong during verification. + * @throws DataAccessException will be thrown if the horse could not be saved in the database. + */ + Owner addOwner(Owner owner) throws ValidationException, DataAccessException; + } 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 da8cb64..3dd7345 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 @@ -3,11 +3,13 @@ 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.persistence.OwnerDao; 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.Validator; import java.lang.invoke.MethodHandles; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Service; @Service @@ -29,4 +31,10 @@ public class SimpleOwnerService implements OwnerService { return ownerDao.findOneById(id); } + @Override + public Owner addOwner(Owner owner) throws ValidationException, DataAccessException { + LOGGER.trace("addOwner({})", owner); + this.validator.validateNewOwner(owner); + return ownerDao.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 f64cca7..febe9df 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 @@ -15,6 +15,9 @@ public class Validator { public void validateNewOwner(Owner owner) throws ValidationException { + if(owner.getName() == null || owner.getName().isEmpty()) { + throw new ValidationException("Required value name missing"); + } } public void validateUpdateOwner(Owner owner) throws ValidationException { 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 new file mode 100644 index 0000000..584867d --- /dev/null +++ b/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/integration/OwnerEndpointTest.java @@ -0,0 +1,42 @@ +package at.ac.tuwien.sepm.assignment.individual.integration; + +import static org.junit.jupiter.api.Assertions.*; + +import at.ac.tuwien.sepm.assignment.individual.endpoint.dto.OwnerDto; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.*; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.client.RestTemplate; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +public class OwnerEndpointTest { + @Value("${spring.upload.path}") + private String FILE_BASE_PATH; + private static final RestTemplate REST_TEMPLATE = new RestTemplate(); + private static final String BASE_URL = "http://localhost:"; + private static final String OWNER_URL = "/owners"; + + @LocalServerPort + private int port; + + + @Test + @DisplayName("Adding a new owner with the correct parameters will return HTTP 201 and the new OwnerDto") + public void addingNewOwner_correctParameters_shouldReturnStatus201AndOwner() { + OwnerDto newOwner = new OwnerDto("Chad"); + + HttpEntity request = new HttpEntity<>(newOwner); + ResponseEntity response = REST_TEMPLATE + .exchange(BASE_URL + port + OWNER_URL, HttpMethod.POST, request, OwnerDto.class); + + // Compare everything except ids and timestamps + assertEquals(response.getStatusCode(), HttpStatus.CREATED); + assertEquals(newOwner.getName(), response.getBody().getName()); + } +} + 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 c6eed44..ccf5501 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 @@ -2,11 +2,13 @@ package at.ac.tuwien.sepm.assignment.individual.unit.persistence; import static org.junit.jupiter.api.Assertions.*; +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 org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; public abstract class OwnerDaoTestBase { @@ -17,7 +19,21 @@ public abstract class OwnerDaoTestBase { @DisplayName("Finding owner by non-existing ID should throw NotFoundException") public void findingOwnerById_nonExisting_shouldThrowNotFoundException() { assertThrows(NotFoundException.class, - () -> ownerDao.findOneById(1L)); + () -> ownerDao.findOneById(0L)); } + @Test + @DisplayName("Adding a new owner with the correct parameters should return the owner") + public void addingNewOwner_correctParameters_shouldReturnOwner() { + Owner newOwner = new Owner("Chad"); + Owner savedOwner = ownerDao.addOwner(newOwner); + assertEquals(newOwner, savedOwner); + } + + @Test + @DisplayName("Adding a new horse with the incorrect parameters should throw DataAccessException") + public void addingNewOwner_incorrectParameters_shouldThrowDataAccess() { + Owner newOwner = new Owner(""); + assertThrows(DataAccessException.class, () -> ownerDao.addOwner(newOwner)); + } } 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 new file mode 100644 index 0000000..fb73b96 --- /dev/null +++ b/backend/src/test/java/at/ac/tuwien/sepm/assignment/individual/unit/service/OwnerServiceTest.java @@ -0,0 +1,39 @@ +package at.ac.tuwien.sepm.assignment.individual.unit.service; + +import static org.junit.jupiter.api.Assertions.*; + +import at.ac.tuwien.sepm.assignment.individual.entity.Owner; +import at.ac.tuwien.sepm.assignment.individual.service.OwnerService; +import at.ac.tuwien.sepm.assignment.individual.util.ValidationException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith(SpringExtension.class) +@SpringBootTest +@ActiveProfiles("test") +public class OwnerServiceTest { + @Autowired + OwnerService ownerService; + + @Test + @DisplayName("Adding a new owner with the correct parameters will return the new owner") + public void addingNewOwner_correctParameters_shouldReturnOwner() { + Owner newOwner = new Owner("Chad"); + Owner savedOwner = ownerService.addOwner(newOwner); + assertEquals(newOwner, savedOwner); + } + + @Test + @DisplayName("Adding a new owner with the incorrect parameters will throw a ValidationException") + public void addingNewOwner_incorrectParameters_shouldThrowValidation() { + Owner newOwner = new Owner(""); + assertThrows(ValidationException.class, () -> ownerService.addOwner(newOwner)); + } +}