diff --git a/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/dto/DepositDTO.java b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/dto/DepositDTO.java new file mode 100644 index 0000000..8f256bf --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/dto/DepositDTO.java @@ -0,0 +1,37 @@ +package diegosneves.github.conectardoacoes.adapters.rest.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * A classe {@link DepositDTO} é um Data Transfer Object (DTO) que fornece uma maneira simples de transportar dados entre processos. + * Ele é responsável por representar a entidade "Doação" em operações onde apenas uma transferência de dados simples é necessária, + * sem comportamentos adicionais. + *

+ *

A classe {@link DepositDTO} inclui duas propriedades:

+ *
    + *
  1. description - Uma string que descreve a doação.
  2. + *
  3. amount - Um inteiro que representa a quantidade da doação.
  4. + *
+ *

+ * Ela contém os construtores {@code @AllArgsConstructor} e {@code @NoArgsConstructor}, que permitem a criação de instâncias com todos os atributos ou sem nenhum atributo, respectivamente. + * Além disso, possui os métodos {@code @getter} e {@code @setter} para cada propriedade, permitindo a recuperação e modificação de cada propriedade, respectivamente. + * A anotação {@code @Builder} implementa o padrão Builder para criar objetos da classe de maneira mais legível e segura. + * + * @author diegoneves + * @since 1.3.0 + */ +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@Builder +public class DepositDTO { + + private String description; + private Integer amount; + +} diff --git a/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/factory/DepositFactory.java b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/factory/DepositFactory.java new file mode 100644 index 0000000..6ab4c5d --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/factory/DepositFactory.java @@ -0,0 +1,44 @@ +package diegosneves.github.conectardoacoes.adapters.rest.factory; + +import diegosneves.github.conectardoacoes.adapters.rest.model.DepositEntity; +import diegosneves.github.conectardoacoes.core.utils.UuidUtils; + +/** + * A classe {@code DepositFactory} é responsável por criar instâncias da {@link DepositEntity}. + *

+ * Esta classe é utilitária e não pode ser instanciada, ou seja, todos os seus métodos são estáticos. + * O principal método desta classe é o {@code createDepositEntity}, que gera uma instância de {@link DepositEntity} + * utilizando um UUID gerado, uma descrição e um valor fornecidos. + *

+ *

+ * Uso típico: + *

{@code
+ * DepositEntity deposit = DepositFactory.createDepositEntity("Depósito de exemplo", 100);
+ * }
+ * + * @author diegoneves + * @since 1.3.0 + * @see DepositEntity + */ +public class DepositFactory { + + private DepositFactory() { + } + + /** + * Cria uma nova instância de {@link DepositEntity} com uma descrição fornecida e um valor. + *

+ * Este método gera um novo UUID usando {@link UuidUtils#generateUuid()} e cria uma nova + * instância de {@link DepositEntity} com o UUID gerado, a descrição fornecida e o valor fornecido. + *

+ * + * @param description Uma {@link String} representando a descrição do depósito. + * @param amount Um {@link Integer} representando o valor do depósito. + * @return A nova instância de {@link DepositEntity} criada com o UUID gerado, a descrição fornecida e o valor fornecido. + * @throws IllegalArgumentException se a descrição ou o valor forem nulos. + */ + public static DepositEntity createDepositEntity(String description, Integer amount) { + return new DepositEntity(UuidUtils.generateUuid(), description, amount); + } + +} diff --git a/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/service/DepositEntityService.java b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/service/DepositEntityService.java new file mode 100644 index 0000000..335f01b --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/service/DepositEntityService.java @@ -0,0 +1,27 @@ +package diegosneves.github.conectardoacoes.adapters.rest.service; + +import diegosneves.github.conectardoacoes.adapters.rest.dto.DepositDTO; +import diegosneves.github.conectardoacoes.adapters.rest.model.DepositEntity; + +/** + * Interface de serviço para operações relacionadas à entidade de depósito. + *

+ * Esta interface define os métodos necessários para a criação e manipulação + * de objetos do tipo {@link DepositEntity}. + * + * @author diegoneves + * @since 1.3.0 + */ +public interface DepositEntityService { + + /** + * Cria uma nova instância de {@link DepositEntity} com base nos dados fornecidos + * pelo objeto {@link DepositDTO}. + * + * @param dto Objeto de transferência de dados contendo as informações necessárias + * para a criação de um novo depósito. + * @return A nova instância de {@link DepositEntity} criada com os dados fornecidos. + */ + DepositEntity create(DepositDTO dto); + +} diff --git a/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/service/impl/DepositEntityServiceImpl.java b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/service/impl/DepositEntityServiceImpl.java new file mode 100644 index 0000000..ba4ad49 --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/service/impl/DepositEntityServiceImpl.java @@ -0,0 +1,89 @@ +package diegosneves.github.conectardoacoes.adapters.rest.service.impl; + +import diegosneves.github.conectardoacoes.adapters.rest.dto.DepositDTO; +import diegosneves.github.conectardoacoes.adapters.rest.exception.DepositEntityFailuresException; +import diegosneves.github.conectardoacoes.adapters.rest.factory.DepositFactory; +import diegosneves.github.conectardoacoes.adapters.rest.model.DepositEntity; +import diegosneves.github.conectardoacoes.adapters.rest.repository.DepositRepository; +import diegosneves.github.conectardoacoes.adapters.rest.service.DepositEntityService; +import diegosneves.github.conectardoacoes.core.utils.ValidationUtils; +import org.springframework.stereotype.Service; + +/** + * Classe de implementação do serviço de entidade de depósito. + * + *

+ * Esta classe fornece a implementação das operações de criação relacionadas à entidade + * de depósito. Ela utiliza o repositório de depósitos ({@link DepositRepository}) + * para salvar a nova entidade de depósito criada. + *

+ * + *

+ * A constante {@code DEPOSIT_VALIDATION_ERROR} representa um código específico de erro + * de validação dos depósitos. + *

+ * + *

+ * As operações de criação de depósitos utilizam a fábrica de depósitos + * ({@link DepositFactory}) para instanciar novos objetos de depósito a partir dos dados + * de transferência (DTO - Data Transfer Object). + *

+ * + *

+ * Esta classe é anotada com {@code @Service} para indicar que é um componente de serviço + * Spring, tornando-a detectável para a injeção de dependência. + *

+ * + * @author diegoneves + * @see DepositEntityService + * @see DepositRepository + * @see DepositFactory + * @since 1.3.0 + */ +@Service +public class DepositEntityServiceImpl implements DepositEntityService { + + private static final int DEFAULT_AMOUNT = 1; + public static final Integer DEPOSIT_VALIDATION_ERROR = 39; + + private final DepositRepository depositRepository; + + public DepositEntityServiceImpl(DepositRepository depositRepository) { + this.depositRepository = depositRepository; + } + + @Override + public DepositEntity create(DepositDTO dto) { + depositValidate(dto); + DepositEntity newDeposit = DepositFactory.createDepositEntity(dto.getDescription(), dto.getAmount()); + return this.depositRepository.save(newDeposit); + } + + /** + * Valida o objeto {@link DepositDTO} para garantir que está devidamente populado + * e contém dados válidos. + *

+ * A validação inclui a verificação se o DTO e seus campos de descrição e valor + * não são nulos ou vazios. Também garante que o valor não seja menor que o valor + * padrão, atualizando-o se necessário. + *

+ * + * @param dto o objeto {@link DepositDTO} a ser validado. + * Não deve ser nulo e deve conter uma descrição e um valor não nulos/não vazios. + * @throws DepositEntityFailuresException se algum dos campos (dto, descrição, valor) + * forem nulos ou vazios. + * @implNote O método {@link ValidationUtils#validateNotNullOrEmpty(Object, Integer, Class)} é utilizado + * para realizar as verificações de validação, lançando a exceção {@link DepositEntityFailuresException} + * com o código de erro específico {@code DEPOSIT_VALIDATION_ERROR} se alguma regra de validação for violada. + */ + private static void depositValidate(DepositDTO dto) { + ValidationUtils.validateNotNullOrEmpty(dto, DEPOSIT_VALIDATION_ERROR, DepositEntityFailuresException.class); + ValidationUtils.validateNotNullOrEmpty(dto.getDescription(), DEPOSIT_VALIDATION_ERROR, DepositEntityFailuresException.class); + ValidationUtils.validateNotNullOrEmpty(dto.getAmount(), DEPOSIT_VALIDATION_ERROR, DepositEntityFailuresException.class); + if (dto.getAmount() < DEFAULT_AMOUNT) { + dto.setAmount(DEFAULT_AMOUNT); + } + } + + +} diff --git a/src/test/java/diegosneves/github/conectardoacoes/adapters/rest/service/impl/DepositEntityServiceImplTest.java b/src/test/java/diegosneves/github/conectardoacoes/adapters/rest/service/impl/DepositEntityServiceImplTest.java new file mode 100644 index 0000000..7e12afa --- /dev/null +++ b/src/test/java/diegosneves/github/conectardoacoes/adapters/rest/service/impl/DepositEntityServiceImplTest.java @@ -0,0 +1,136 @@ +package diegosneves.github.conectardoacoes.adapters.rest.service.impl; + +import diegosneves.github.conectardoacoes.adapters.rest.dto.DepositDTO; +import diegosneves.github.conectardoacoes.adapters.rest.enums.ExceptionDetails; +import diegosneves.github.conectardoacoes.adapters.rest.exception.DepositEntityFailuresException; +import diegosneves.github.conectardoacoes.adapters.rest.model.DepositEntity; +import diegosneves.github.conectardoacoes.adapters.rest.repository.DepositRepository; +import diegosneves.github.conectardoacoes.core.utils.UuidUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +class DepositEntityServiceImplTest { + + public static final String DEPOSIT_UUID = "0f46f5b9-ab37-478b-86f5-b9ab37878b33"; + public static final String DESCRIPTION = "Item 01"; + public static final int AMOUNT = 1; + + + @InjectMocks + private DepositEntityServiceImpl service; + + @Mock + private DepositRepository repository; + + @Captor + private ArgumentCaptor depositCaptor; + + private DepositDTO depositDTO; + private DepositEntity depositEntity; + + @BeforeEach + void setUp() { + this.depositDTO = new DepositDTO(DESCRIPTION, AMOUNT); + this.depositEntity = new DepositEntity(DEPOSIT_UUID, DESCRIPTION, AMOUNT); + } + + @Test + void shouldCreateAndSaveDepositEntitySuccessfully() { + when(this.repository.save(any(DepositEntity.class))).thenReturn(this.depositEntity); + + DepositEntity actual = this.service.create(this.depositDTO); + + verify(this.repository, times(1)).save(this.depositCaptor.capture()); + + assertNotNull(actual); + assertEquals(DEPOSIT_UUID, actual.getId()); + assertEquals(DESCRIPTION, actual.getDescription()); + assertEquals(AMOUNT, actual.getAmount()); + DepositEntity capturedDeposit = this.depositCaptor.getValue(); + assertNotNull(capturedDeposit); + assertNotNull(capturedDeposit.getId()); + assertTrue(UuidUtils.isValidUUID(capturedDeposit.getId())); + assertEquals(DESCRIPTION, capturedDeposit.getDescription()); + assertEquals(AMOUNT, capturedDeposit.getAmount()); + } + + @ParameterizedTest + @ValueSource(ints = {0, -3, -58}) + void shouldSetMinimumAmountAndSaveDepositEntityWhenInvalidAmountProvided(Integer value) { + this.depositDTO.setAmount(value); + when(this.repository.save(any(DepositEntity.class))).thenReturn(this.depositEntity); + + DepositEntity actual = this.service.create(this.depositDTO); + + verify(this.repository, times(1)).save(this.depositCaptor.capture()); + + assertNotNull(actual); + assertEquals(DEPOSIT_UUID, actual.getId()); + assertEquals(DESCRIPTION, actual.getDescription()); + assertEquals(AMOUNT, actual.getAmount()); + DepositEntity capturedDeposit = this.depositCaptor.getValue(); + assertNotNull(capturedDeposit); + assertNotNull(capturedDeposit.getId()); + assertTrue(UuidUtils.isValidUUID(capturedDeposit.getId())); + assertEquals(DESCRIPTION, capturedDeposit.getDescription()); + assertEquals(AMOUNT, capturedDeposit.getAmount()); + } + + @Test + void shouldThrowExceptionWhenDescriptionIsNull(){ + this.depositDTO.setDescription(null); + + DepositEntityFailuresException exception = assertThrows(DepositEntityFailuresException.class, () -> this.service.create(this.depositDTO)); + + verify(repository, never()).save(any(DepositEntity.class)); + + assertNotNull(exception); + assertEquals(ExceptionDetails.getExceptionDetails(DepositEntityServiceImpl.DEPOSIT_VALIDATION_ERROR).formatErrorMessage(), exception.getMessage()); + assertNull(exception.getCause()); + } + + @ParameterizedTest + @ValueSource(strings = {"", " "}) + void shouldThrowExceptionWhenDescriptionIsEmptyOrBlank(String value){ + this.depositDTO.setDescription(value); + + DepositEntityFailuresException exception = assertThrows(DepositEntityFailuresException.class, () -> this.service.create(this.depositDTO)); + + verify(repository, never()).save(any(DepositEntity.class)); + + assertNotNull(exception); + assertEquals(ExceptionDetails.getExceptionDetails(DepositEntityServiceImpl.DEPOSIT_VALIDATION_ERROR).formatErrorMessage(), exception.getMessage()); + assertNull(exception.getCause()); + } + + @Test + void shouldThrowExceptionWhenAmountIsNull(){ + this.depositDTO.setAmount(null); + + DepositEntityFailuresException exception = assertThrows(DepositEntityFailuresException.class, () -> this.service.create(this.depositDTO)); + + verify(repository, never()).save(any(DepositEntity.class)); + + assertNotNull(exception); + assertEquals(ExceptionDetails.getExceptionDetails(DepositEntityServiceImpl.DEPOSIT_VALIDATION_ERROR).formatErrorMessage(), exception.getMessage()); + assertNull(exception.getCause()); + } + + +}