diff --git a/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/config/web/CorsConfig.java b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/config/web/CorsConfig.java index 3458b14..779daf0 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/config/web/CorsConfig.java +++ b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/config/web/CorsConfig.java @@ -40,6 +40,7 @@ * * * @author diegosneves + * @since 1.0.0 */ @Configuration public class CorsConfig { diff --git a/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/config/web/OpenApiConfig.java b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/config/web/OpenApiConfig.java index 04c85e5..9095b7c 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/config/web/OpenApiConfig.java +++ b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/config/web/OpenApiConfig.java @@ -18,6 +18,7 @@ *

* * @author diegosneves + * @since 1.0.0 */ @Configuration public class OpenApiConfig { diff --git a/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/config/web/WebSecurityConfig.java b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/config/web/WebSecurityConfig.java index 805d539..fb8b894 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/config/web/WebSecurityConfig.java +++ b/src/main/java/diegosneves/github/conectardoacoes/adapters/rest/config/web/WebSecurityConfig.java @@ -11,6 +11,7 @@ * A classe {@link WebSecurityConfig} tem a responsabilidade de definir as configurações de segurança web desta aplicação. * * @author diegosneves + * @since 1.0.0 */ @Configuration @EnableWebSecurity diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/Shelter.java b/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/Shelter.java index 8d5461a..ccbc438 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/Shelter.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/Shelter.java @@ -6,18 +6,17 @@ import diegosneves.github.conectardoacoes.core.exception.ShelterCreationFailureException; import diegosneves.github.conectardoacoes.core.exception.UuidUtilsException; import diegosneves.github.conectardoacoes.core.utils.UuidUtils; +import diegosneves.github.conectardoacoes.core.utils.ValidationUtils; import java.util.ArrayList; import java.util.List; -import static java.util.Objects.isNull; - /** * Implementação da interface {@link ShelterContract}, que representa um abrigo na aplicação. * Uma instância desta classe representa um abrigo com uma identificação, um nome de referência, um endereço, um usuário responsável, e uma lista de doações. * * @author diegoneves - * @version 1.0.0 + * @since 1.0.0 * @see ShelterContract */ public class Shelter implements ShelterContract { @@ -75,31 +74,11 @@ private void validateData() throws ShelterCreationFailureException { } catch (UuidUtilsException e) { throw new ShelterCreationFailureException(ID_VALIDATION_FAILURE, e); } - this.checkNotNullAndNotEmptyOrThrowException(this.shelterName, SHELTER_NAME_REQUIRED_ERROR); - this.checkNotNullAndNotEmptyOrThrowException(this.address, ADDRESS_REQUIRED_ERROR); - this.checkNotNullAndNotEmptyOrThrowException(this.responsibleUser, RESPONSIBLE_REQUIRED_ERROR); + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(this.shelterName, SHELTER_NAME_REQUIRED_ERROR, ShelterCreationFailureException.class); + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(this.address, ADDRESS_REQUIRED_ERROR, ShelterCreationFailureException.class); + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(this.responsibleUser, RESPONSIBLE_REQUIRED_ERROR, ShelterCreationFailureException.class); } - /** - * Este é um método genérico usado para verificar se o objeto fornecido é nulo ou, se é uma instância de String, se é vazio. - * Em ambos os casos, ele lançará uma exceção {@link ShelterCreationFailureException}. - *

- * Este método é particularmente útil para validar os detalhes do abrigo durante a criação de um novo abrigo. - * Assegura que os valores de todos os campos necessários estão presentes e não são nulos ou vazios. - * - * @param O tipo de objeto a ser verificado. - * @param object O objeto a ser verificado. - * @param errorMessage A mensagem de erro a ser anexada à exceção em caso de falha de validação. - * @throws ShelterCreationFailureException Se o objeto fornecido for nulo ou, se for uma instância de String, se for vazio. - */ - private void checkNotNullAndNotEmptyOrThrowException(T object, String errorMessage) throws ShelterCreationFailureException { - if (isNull(object)) { - throw new ShelterCreationFailureException(errorMessage); - } - if (object instanceof String && ((String) object).trim().isEmpty()) { - throw new ShelterCreationFailureException(errorMessage); - } - } @Override public String getId() { @@ -121,21 +100,26 @@ public UserContract getUser() { return this.responsibleUser; } + @Override + public List getDonations() { + return this.donations; + } + @Override public void changeShelterName(String shelterName) throws ShelterCreationFailureException { - this.checkNotNullAndNotEmptyOrThrowException(shelterName, SHELTER_NAME_REQUIRED_ERROR); + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(shelterName, SHELTER_NAME_REQUIRED_ERROR, ShelterCreationFailureException.class); this.shelterName = shelterName; } @Override public void changeAddress(Address address) throws ShelterCreationFailureException { - this.checkNotNullAndNotEmptyOrThrowException(address, ADDRESS_REQUIRED_ERROR); + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(address, ADDRESS_REQUIRED_ERROR, ShelterCreationFailureException.class); this.address = address; } @Override public void addDonation(Donation donation) throws ShelterCreationFailureException { - this.checkNotNullAndNotEmptyOrThrowException(donation, DONATION_REQUIRED_ERROR); + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(donation, DONATION_REQUIRED_ERROR, ShelterCreationFailureException.class); this.donations.add(donation); } } diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/ShelterContract.java b/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/ShelterContract.java index 97515dd..c6a0e34 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/ShelterContract.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/ShelterContract.java @@ -5,6 +5,8 @@ import diegosneves.github.conectardoacoes.core.domain.user.entity.UserContract; import diegosneves.github.conectardoacoes.core.exception.ShelterCreationFailureException; +import java.util.List; + /** * Interface {@link ShelterContract} define os métodos que representam os principais comportamentos e a divulgação de informações de uma entidade {@link Shelter}. *

@@ -12,7 +14,7 @@ * além da adição de doações e recuperação de um usuário responsável pelo abrigo. * * @author diegoneves - * @version 1.0.0 + * @since 1.0.0 */ public interface ShelterContract { @@ -37,6 +39,21 @@ public interface ShelterContract { */ Address getAddress(); + /** + * Retorna uma lista de todas as doações associadas ao Abrigo. + *

+ * Cada item na lista é um objeto {@link Donation}, que representa uma + * doação feita ao Abrigo. A doação contém informações sobre a descrição + * e a quantidade da doação. + *

+ * Este método não recebe nenhum parâmetro. + * + * @return Uma lista de objetos {@link Donation} que representam as + * doações feitas ao Abrigo. Se não houver doações, este método retornará + * uma lista vazia. + */ + List getDonations(); + /** * Este método é usado para alterar o endereço do abrigo. *

diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/value/Address.java b/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/value/Address.java index b45ccff..8d72060 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/value/Address.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/value/Address.java @@ -14,7 +14,7 @@ * Este classe é utilizada na criação de entidades tais como {@link Shelter}, que precisam de uma representação de endereço. * * @author diegoneves - * @version 1.0.0 + * @since 1.0.0 */ @Getter public class Address { diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/value/Donation.java b/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/value/Donation.java index 300f84b..1037214 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/value/Donation.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/value/Donation.java @@ -8,7 +8,7 @@ * Uma doação é caracterizada pela sua descrição e quantidade. * * @author diegoneves - * @version 1.0.0 + * @since 1.0.0 */ @Getter public class Donation { diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/factory/ShelterFactory.java b/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/factory/ShelterFactory.java index 4c02e26..50cb3fe 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/factory/ShelterFactory.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/factory/ShelterFactory.java @@ -3,12 +3,39 @@ import diegosneves.github.conectardoacoes.core.domain.shelter.entity.Shelter; import diegosneves.github.conectardoacoes.core.domain.shelter.entity.value.Address; import diegosneves.github.conectardoacoes.core.domain.user.entity.UserContract; +import diegosneves.github.conectardoacoes.core.exception.ShelterCreationFailureException; import diegosneves.github.conectardoacoes.core.utils.UuidUtils; +/** + * Classe {@link ShelterFactory} responsável por fornecer métodos para criar instâncias da classe {@link Shelter}. + *

+ * Esta classe é um exemplo de aplicação do padrão de projeto Factory, que fornece um método estático para a criação de instâncias de {@link Shelter}. + * Neste caso, a criação do objeto Shelter é abstraída para a esta classe. + *

+ * Esta classe destaca a declaração de um método público estático create, responsável pela criação de uma instance de {@link Shelter}. + * + * @author diegoneves + * @since 1.0.0 + * @see Shelter + */ public class ShelterFactory { private ShelterFactory() {} + /** + * Construtor estático para a classe {@link Shelter}. + *

+ * Este método é responsável por criar uma nova instancia de {@link Shelter} com um UUID gerado dinamicamente, juntamente com os detalhes fornecidos. + *

+ * Este método utiliza o método {@link UuidUtils#generateUuid} para gerar um UUID único para o novo objeto {@link Shelter}. + * + * @param shelterName A string que representa o nome do abrigo. + * @param address A instância de {@link Address} que representa o endereço do abrigo. + * @param responsibleUser A instância de {@link UserContract} quem representa o usuário responsável pelo abrigo. + * @return {@link Shelter} A nova instância de Shelter com os detalhes fornecidos. + * @throws ShelterCreationFailureException se qualquer informação do Abrigo fornecida for inválida. + * + */ public static Shelter create(String shelterName, Address address, UserContract responsibleUser) { return new Shelter(UuidUtils.generateUuid(), shelterName, address, responsibleUser); } diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/shared/repository/ShelterRepository.java b/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/shared/repository/ShelterRepository.java new file mode 100644 index 0000000..5f77e14 --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/core/domain/shelter/shared/repository/ShelterRepository.java @@ -0,0 +1,29 @@ +package diegosneves.github.conectardoacoes.core.domain.shelter.shared.repository; + +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.Shelter; +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.ShelterContract; +import diegosneves.github.conectardoacoes.core.repository.RepositoryContract; + +/** + * A interface {@link ShelterRepository} herda da interface {@link RepositoryContract}. + * Ela define o contrato para um repositório que persiste e recupera as entidades {@link ShelterContract}. + *

+ * Tem as seguintes operações básicas: + * - Encontrar uma entidade {@link Shelter} pelo seu identificador único + * - Encontrar todas as instâncias da entidade {@link Shelter} + * - Salvar uma instância da entidade {@link Shelter} + * - Deletar uma entidade {@link Shelter} através seu identificador único + * + *

+ * O uso desta interface é específico para operações relacionados com objeto {@link ShelterContract} + * e as suas implementações devem ser consideradas para trabalhar com dados da entidade Shelter. + *

+ * + * @author diegoneves + * @since 1.0.0 + * @see RepositoryContract + * @see Shelter + */ +public interface ShelterRepository extends RepositoryContract { + +} diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/entity/User.java b/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/entity/User.java index b6b8427..79faa7c 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/entity/User.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/entity/User.java @@ -4,6 +4,7 @@ import diegosneves.github.conectardoacoes.core.exception.UserCreationFailureException; import diegosneves.github.conectardoacoes.core.exception.UuidUtilsException; import diegosneves.github.conectardoacoes.core.utils.UuidUtils; +import diegosneves.github.conectardoacoes.core.utils.ValidationUtils; /** * Representa um usuário dentro do sistema. @@ -30,7 +31,7 @@ *

* * @author diegoneves - * @version 1.0 + * @since 1.0.0 */ public class User implements UserContract { @@ -75,25 +76,18 @@ public User(String id, String userName, String email, UserProfile userProfile, S * faltando, em branco ou é inválido */ private void validateData() throws UserCreationFailureException { - if (this.userProfile == null) { - throw new UserCreationFailureException(PROFILE_NOT_PROVIDED); - } - if (this.userName == null || this.userName.isBlank()) { - throw new UserCreationFailureException(String.format(USERNAME_REQUIRED, this.userProfile)); - } + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(this.userProfile, PROFILE_NOT_PROVIDED, UserCreationFailureException.class); + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(this.userName, String.format(USERNAME_REQUIRED, this.userProfile), UserCreationFailureException.class); try { UuidUtils.isValidUUID(this.id); } catch (UuidUtilsException e) { throw new UserCreationFailureException(USER_ID_REQUIRED, e); } - if (this.email == null || this.email.isBlank()) { - throw new UserCreationFailureException(EMAIL_NOT_PROVIDED); - } - if (this.userPassword == null || this.userPassword.isBlank()) { - throw new UserCreationFailureException(PASSWORD_NOT_PROVIDED); - } + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(this.email, EMAIL_NOT_PROVIDED, UserCreationFailureException.class); + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(this.userPassword, PASSWORD_NOT_PROVIDED, UserCreationFailureException.class); } + @Override public String getId() { return this.id; @@ -121,17 +115,13 @@ public String getEmail() { @Override public void changeUserPassword(String password) throws UserCreationFailureException { - if (password == null || password.isBlank()) { - throw new UserCreationFailureException(PASSWORD_NOT_PROVIDED); - } + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(password, PASSWORD_NOT_PROVIDED, UserCreationFailureException.class); this.userPassword = password; } @Override public void changeUserName(String updatedUsername) { - if (updatedUsername == null || updatedUsername.isBlank()) { - throw new UserCreationFailureException(String.format(USERNAME_REQUIRED, this.userProfile)); - } + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(updatedUsername, String.format(USERNAME_REQUIRED, this.userProfile), UserCreationFailureException.class); this.userName = updatedUsername; } } diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/entity/UserContract.java b/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/entity/UserContract.java index a22780d..5941059 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/entity/UserContract.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/entity/UserContract.java @@ -14,7 +14,7 @@ * {@link UserCreationFailureException}. * * @author diegoneves - * @version 1.0 + * @since 1.0.0 * @see UserCreationFailureException * @see RuntimeException */ diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/entity/value/UserProfile.java b/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/entity/value/UserProfile.java index eeb0e64..1bbd7a1 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/entity/value/UserProfile.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/entity/value/UserProfile.java @@ -5,7 +5,7 @@ * Os perfis disponíveis são {@code 'Doador'} e {@code 'Beneficiário'}. * * @author diegoneves - * @version 1.0.0 + * @since 1.0.0 */ public enum UserProfile { diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/factory/UserFactory.java b/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/factory/UserFactory.java index 965dd29..cf210bc 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/factory/UserFactory.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/factory/UserFactory.java @@ -2,14 +2,45 @@ import diegosneves.github.conectardoacoes.core.domain.user.entity.User; import diegosneves.github.conectardoacoes.core.domain.user.entity.value.UserProfile; +import diegosneves.github.conectardoacoes.core.exception.UserCreationFailureException; import diegosneves.github.conectardoacoes.core.utils.UuidUtils; +/** + * Classe de utilidade para a criação de usuário. + *

+ * Oferece um método estático para criar um usuário com todas as informações necessárias + * como nome de usuário, e-mail, perfil de usuário e senha. + * Além disso, gera um identificador UUID para o novo usuário. + *

+ *

+ * Esta classe foi projetada para ser usada em todo o código que precisa criar um novo usuário, + * permitindo uma abordagem consistente para a criação de usuário. + *

+ *

+ * Esta classe não pode ser instanciada. + *

+ * + * @author diegoneves + * @since 1.0.0 + * @see User + */ public class UserFactory { private UserFactory() { } + /** + * Cria um novo usuário com todas as informações necessárias e um identificador UUID gerado. + *

+ * Este método utiliza o método {@link UuidUtils#generateUuid} para gerar um UUID único para o novo objeto {@link User}. + * @param username O nome de usuário desejado para o novo usuário. Não deve ser nulo ou vazio. + * @param email O e-mail do novo usuário. Não deve ser nulo ou vazio. + * @param userProfile O perfil do usuário {@link UserProfile} para o novo usuário. Não deve ser nulo. + * @param password A senha para o novo usuário. Não deve ser nula ou vazia. + * @return O usuário criado com todas as informações fornecidas e um identificador UUID. + * @throws UserCreationFailureException se qualquer informação de usuário fornecida for inválida. + */ public static User create(String username, String email, UserProfile userProfile, String password) { return new User(UuidUtils.generateUuid(), username, email, userProfile, password); } diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/shared/repository/UserRepository.java b/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/shared/repository/UserRepository.java new file mode 100644 index 0000000..5f12dae --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/core/domain/user/shared/repository/UserRepository.java @@ -0,0 +1,31 @@ +package diegosneves.github.conectardoacoes.core.domain.user.shared.repository; + +import diegosneves.github.conectardoacoes.core.domain.user.entity.UserContract; +import diegosneves.github.conectardoacoes.core.repository.RepositoryContract; + +/** + * A interface {@link UserRepository} herda de {@link RepositoryContract} que é parametrizada com {@link UserContract}. + * Ela fornece funcionalidades específicas ao usuário como adicionar, atualizar, deletar e procurar informações de usuários no banco de dados. + * + *

Os métodos herdados de {@link RepositoryContract} devem ser implementados em uma classe de repositório de usuário para fornecer o impacto + * adequado para cada ação correspondente no banco de dados. + *

Isso inclui encontrar usuário(s) por id, salvar usuário, deletar usuário pelo id e buscar todos os usuários. + *

Os detalhes de cada operação são como segue: + * + *

+ * + *

Caso as operações de pesquisa não possam encontrar usuário(s) correspondente, eles retornarão um valor nulo ou uma lista vazia. + * + * @author diegoneves + * @since 1.0.0 + * @see RepositoryContract + * @see UserContract + */ +public interface UserRepository extends RepositoryContract { + +} diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/enums/ExceptionDetails.java b/src/main/java/diegosneves/github/conectardoacoes/core/enums/ExceptionDetails.java index 338850a..37b5886 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/enums/ExceptionDetails.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/enums/ExceptionDetails.java @@ -19,15 +19,18 @@ *

* * @author diegoneves - * @version 1.0.0 + * @since 1.0.0 */ public enum ExceptionDetails { USER_CREATION_ERROR("Um erro ocorreu ao tentar criar um usuario devido ao seguinte motivo: %s"), + USER_MANIPULATION_ERROR("Ocorreu um erro no UserService ao tentar manipular um usuário. Motivo: %s"), + SHELTER_MANIPULATION_ERROR("Ocorreu um erro no ShelterService ao tentar manipular um abrigo. Motivo: %s"), DONATION_CREATION_ERROR("Um erro ocorreu ao tentar registrar uma doação devido ao seguinte motivo: %s"), SHELTER_CREATION_ERROR("Um erro ocorreu ao tentar criar um Abrigo devido ao seguinte motivo: %s"), ADDRESS_CREATION_ERROR("Um erro ocorreu ao tentar criar um endereço devido ao seguinte motivo: %s"), - INVALID_UUID_FORMAT_MESSAGE("O ID %s precisa estar no formato UUID"); + INVALID_UUID_FORMAT_MESSAGE("O ID %s precisa estar no formato UUID"), + EXCEPTION_TYPE_NOT_THROWN("Não foi possível lançar a exceção do tipo %s"); private final String message; diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/exception/AddressCreationFailureException.java b/src/main/java/diegosneves/github/conectardoacoes/core/exception/AddressCreationFailureException.java index 7a0434e..635b159 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/exception/AddressCreationFailureException.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/exception/AddressCreationFailureException.java @@ -23,7 +23,7 @@ *

* @author diegosneves * @see RuntimeException - * @version 1.0 + * @since 1.0.0 */ public class AddressCreationFailureException extends RuntimeException { diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/exception/DonationRegisterFailureException.java b/src/main/java/diegosneves/github/conectardoacoes/core/exception/DonationRegisterFailureException.java index 2724266..c975f44 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/exception/DonationRegisterFailureException.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/exception/DonationRegisterFailureException.java @@ -17,7 +17,7 @@ * String para o construtor ao instanciar um novo objeto {@link DonationRegisterFailureException}. *

* @author diegoneves - * @version 1.0 + * @since 1.0.0 * @see RuntimeException */ public class DonationRegisterFailureException extends RuntimeException { diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/exception/ShelterCreationFailureException.java b/src/main/java/diegosneves/github/conectardoacoes/core/exception/ShelterCreationFailureException.java index 2c57cee..5da63e2 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/exception/ShelterCreationFailureException.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/exception/ShelterCreationFailureException.java @@ -38,7 +38,7 @@ *

* @author diegosneves * @see RuntimeException - * @version 1.0 + * @since 1.0.0 */ public class ShelterCreationFailureException extends RuntimeException { diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/exception/ShelterServiceFailureException.java b/src/main/java/diegosneves/github/conectardoacoes/core/exception/ShelterServiceFailureException.java new file mode 100644 index 0000000..6da0365 --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/core/exception/ShelterServiceFailureException.java @@ -0,0 +1,68 @@ +package diegosneves.github.conectardoacoes.core.exception; + +import diegosneves.github.conectardoacoes.core.enums.ExceptionDetails; + +/** + * Esta é uma classe de exceção personalizada que estende a {@link RuntimeException}. + * É usado especialmente para lidar com erros que ocorrem durante a criação de um abrigo. + *

+ * A classe contém uma constante de ERROR, que define o detalhe da exceção. Esta constante é do tipo {@link ExceptionDetails} e + * é inicializada com o valor {@link ExceptionDetails#SHELTER_MANIPULATION_ERROR}. + *

+ * Possui dois construtores: + *

+ * 1. Que aceita apenas uma String como argumento, que serve como mensagem para a exceção. + *

+ * 2. Que aceita uma String e um {@link Throwable} como argumentos. A String serve como mensagem para a exceção e o {@link Throwable} é a causa que levou à exceção. + * + *

+ * Exemplo de uso: + *

+ *

+ *     {@code
+ * if (AlgumaValidacaoFalhar) {
+ *    throw new ShelterServiceFailureException("Detalhe da falha");
+ * }
+ * // código de criação de um Abrigo
+ * }
+ * 
+ *
+ *     {@code
+ * try {
+ *    // código de criação de um Abrigo
+ * } catch (AlgumaExcecao e) {
+ *    throw new ShelterServiceFailureException("Detalhe da falha", e);
+ * }
+ * }
+ * 
+ *

+ * @author diegosneves + * @see RuntimeException + * @since 1.0.0 + */ +public class ShelterServiceFailureException extends RuntimeException { + + public static final ExceptionDetails ERROR = ExceptionDetails.SHELTER_MANIPULATION_ERROR; + + /** + * Construtor que aceita uma mensagem como argumento e chama o construtor da superclasse com a + * mensagem de erro construída a partir de {@code ERROR} e a mensagem dada. + * + * @param message Detalhe adicional específico desta instância de exceção. + */ + public ShelterServiceFailureException(String message) { + super(ERROR.buildMessage(message)); + } + + /** + * Construtor que aceita uma mensagem e uma causa como argumentos e chama o construtor da superclasse com a + * mensagem de erro construída a partir de ERROR, a mensagem dada e a causa da exceção. + * + * @param message Detalhe adicional específico desta instância de exceção. + * @param cause A causa raiz que levou a esta exceção. + */ + public ShelterServiceFailureException(String message, Throwable cause) { + super(ERROR.buildMessage(message), cause); + } + +} diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/exception/UserCreationFailureException.java b/src/main/java/diegosneves/github/conectardoacoes/core/exception/UserCreationFailureException.java index 68eb2e6..b0dd167 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/exception/UserCreationFailureException.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/exception/UserCreationFailureException.java @@ -29,7 +29,7 @@ * *

* @author diegosneves - * @version 1.0 + * @since 1.0.0 */ public class UserCreationFailureException extends RuntimeException { diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/exception/UserServiceFailureException.java b/src/main/java/diegosneves/github/conectardoacoes/core/exception/UserServiceFailureException.java new file mode 100644 index 0000000..e03bd27 --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/core/exception/UserServiceFailureException.java @@ -0,0 +1,60 @@ +package diegosneves.github.conectardoacoes.core.exception; + +import diegosneves.github.conectardoacoes.core.enums.ExceptionDetails; + +/** + * Esta é uma classe de exceção personalizada que estende a {@link RuntimeException}. + * É usado especialmente para lidar com erros que ocorrem durante a criação de um usuário. + *

+ * A classe contém uma constante de ERROR, que define o detalhe da exceção. Esta constante é do tipo ExceptionDetails e + * é inicializada com o valor {@link ExceptionDetails#USER_MANIPULATION_ERROR USER_MANIPULATION_ERROR}. + *

+ * Possui dois construtores: + *

+ * 1. Que aceita apenas uma String como argumento, que serve como mensagem para a exceção. + *

+ * 2. Que aceita uma String e um {@link Throwable} como argumentos. A String serve como mensagem para a exceção e o {@link Throwable} é a causa que levou à exceção. + * + *

+ * Exemplo de uso: + *

+ *

+ *     {@code
+ * try {
+ *    // código de criação de usuário
+ * } catch (AlgumaExcecao e) {
+ *    throw new UserServiceFailureException("Detalhe da falha", e);
+ * }
+ * }
+ * 
+ *

+ * @author diegosneves + * @since 1.0.0 + * @see RuntimeException + */ +public class UserServiceFailureException extends RuntimeException { + + public static final ExceptionDetails ERROR = ExceptionDetails.USER_MANIPULATION_ERROR; + + /** + * Construtor que aceita uma mensagem como argumento e chama o construtor da superclasse com a + * mensagem de erro construída a partir de ERROR e a mensagem dada. + * + * @param message Detalhe adicional específico desta instância de exceção. + */ + public UserServiceFailureException(String message) { + super(ERROR.buildMessage(message)); + } + + /** + * Construtor que aceita uma mensagem e uma causa como argumentos e chama o construtor da superclasse com a + * mensagem de erro construída a partir de ERROR, a mensagem dada e a causa da exceção. + * + * @param message Detalhe adicional específico desta instância de exceção. + * @param cause A causa raiz que levou a esta exceção. + */ + public UserServiceFailureException(String message, Throwable cause) { + super(ERROR.buildMessage(message), cause); + } + +} diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/exception/UuidUtilsException.java b/src/main/java/diegosneves/github/conectardoacoes/core/exception/UuidUtilsException.java index 4fb1a99..0fb6246 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/exception/UuidUtilsException.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/exception/UuidUtilsException.java @@ -15,7 +15,7 @@ * * @author diegoneves * @see RuntimeException - * @version 1.0.0 + * @since 1.0.0 */ public class UuidUtilsException extends RuntimeException { diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/exception/ValidationUtilsException.java b/src/main/java/diegosneves/github/conectardoacoes/core/exception/ValidationUtilsException.java new file mode 100644 index 0000000..7e56048 --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/core/exception/ValidationUtilsException.java @@ -0,0 +1,36 @@ +package diegosneves.github.conectardoacoes.core.exception; + +import diegosneves.github.conectardoacoes.core.enums.ExceptionDetails; + +/** + * Esta é uma classe de exceção personalizada que estende a classe {@link RuntimeException}. + * É lançado quando um erro relacionado a uma exceção personalizada não pode ser lançada. + *

+ * A classe inclui um único campo estático, {@code ERROR}, que especifica o tipo de erro. + * + *

+ * Note: O construtor desta classe constrói a mensagem de exceção através do método {@code buildMessage} + * disponível no objeto ERROR de tipo {@link ExceptionDetails}. + *

+ * + * @author diegoneves + * @see RuntimeException + * @since 1.0.0 + */ +public class ValidationUtilsException extends RuntimeException { + + public static final ExceptionDetails ERROR = ExceptionDetails.EXCEPTION_TYPE_NOT_THROWN; + + /** + * Construtor que cria uma instância da exceção {@link ValidationUtilsException} com a mensagem e causa fornecidas. + * A mensagem é usada para criar uma mensagem de erro mais detalhada, passada para a superclasse, {@link RuntimeException}. + * + * @param message A string que representa a mensagem detalhada da exceção, usada para criar uma mensagem de erro mais detalhada. + * @param cause A causa raiz da exceção, tipicamente uma instância de {@link Throwable} que levou à ocorrência desta exceção. + * Esta informação é usada para depuração e rastreamento do erro. + */ + public ValidationUtilsException(String message, Throwable cause) { + super(ERROR.buildMessage(message), cause); + } + +} diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/repository/RepositoryContract.java b/src/main/java/diegosneves/github/conectardoacoes/core/repository/RepositoryContract.java new file mode 100644 index 0000000..25b1cc9 --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/core/repository/RepositoryContract.java @@ -0,0 +1,55 @@ +package diegosneves.github.conectardoacoes.core.repository; + +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Esta é uma interface de contrato de repositório genérica que define operações CRUD básicas. + * A interface do repositório é genérica e pode ser usada com qualquer tipo de entidade. + * + *

+ * Esta interface define as seguintes operações: + * - Encontrar uma entidade pelo seu identificador único ({@code String}) + * - Encontrar todas as instâncias de uma determinada entidade + * - Salvar uma instância de uma entidade + * - Deletar uma entidade pelo seu identificador único ({@code String}) + * + * @param o tipo de entidade com a qual essa interface de repositório trabalha. + * @author diegoneves + * @since 1.0.0 + */ +public interface RepositoryContract { + + /** + * Encontra uma entidade pelo seu identificador. + * + * @param id o identificador único da entidade que deve ser procurada. + * @return a entidade encontrada ou {@code null} se nenhuma entidade com o identificador especificado pôde ser encontrada. + */ + T findById(String id); + + /** + * Encontra todas as entidades de um determinado tipo. + * + * @return uma lista contendo todas as entidades ou uma lista vazia se não houver entidades. + */ + List findAll(); + + /** + * Salva uma entidade. + * + * @param entity a entidade que deve ser salva. + * @return a entidade salva. + * @throws IllegalArgumentException se a entidade passada como parâmetro for {@code null}. + */ + T save(T entity); + + /** + * Deleta uma entidade pelo seu identificador. + * + * @param id o identificador único da entidade que deve ser deletada. + * @throws NoSuchElementException se nenhuma entidade com o identificador especificado pôde ser encontrada. + */ + void deleteById(String id); + +} diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/service/ShelterService.java b/src/main/java/diegosneves/github/conectardoacoes/core/service/ShelterService.java new file mode 100644 index 0000000..4a48ade --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/core/service/ShelterService.java @@ -0,0 +1,183 @@ +package diegosneves.github.conectardoacoes.core.service; + +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.Shelter; +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.ShelterContract; +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.value.Address; +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.value.Donation; +import diegosneves.github.conectardoacoes.core.domain.shelter.factory.ShelterFactory; +import diegosneves.github.conectardoacoes.core.domain.shelter.shared.repository.ShelterRepository; +import diegosneves.github.conectardoacoes.core.domain.user.entity.UserContract; +import diegosneves.github.conectardoacoes.core.exception.ShelterCreationFailureException; +import diegosneves.github.conectardoacoes.core.exception.ShelterServiceFailureException; +import diegosneves.github.conectardoacoes.core.exception.UuidUtilsException; +import diegosneves.github.conectardoacoes.core.utils.UuidUtils; +import diegosneves.github.conectardoacoes.core.utils.ValidationUtils; + +import java.util.List; + +/** + * A classe de serviço {@link ShelterService} é responsável pelas operações de negócios relacionadas a abrigos. + * Essas operações incluem criação de abrigos, busca por abrigos e alterações nos atributos dos abrigos. + * Esta classe implementa a interface {@link ShelterServiceContract}. + * + * @author diegoneves + * @since 1.0.0 + * @see ShelterServiceContract + * @see ShelterRepository + */ +public class ShelterService implements ShelterServiceContract { + + public static final String INVALID_SHELTER_ID_MESSAGE = "A ID especificada para o abrigo é inválida."; + public static final String INVALID_SHELTER_NAME_ERROR_MESSAGE = "O nome do Abrigo fornecido é inválido."; + public static final String ERROR_MESSAGE_ADDRESS_NULL = "O Endereço fornecido não deve ser nulo"; + public static final String DONATION_REQUIRED_ERROR_MESSAGE = "A Doação fornecida deve ser válida."; + + private final ShelterRepository shelterRepository; + + public ShelterService(ShelterRepository shelterRepository) { + this.shelterRepository = shelterRepository; + } + + /** + * Este método cria um novo {@link ShelterContract} utilizando {@link ShelterFactory#create}. + * O novo objeto {@link Shelter} é salvo usando o método {@link ShelterRepository#save}. + * + * @param shelterName O nome do abrigo como uma string. + * @param address uma instancia do objeto {@link Address} representando o endereço do abrigo + * @param responsibleUser um objeto {@link UserContract} representando o usuário responsável pelo abrigo + * @return um novo objeto {@link ShelterContract} + * @throws ShelterCreationFailureException quando uma falha ocorre durante a criação do {@link ShelterContract} + * @see ShelterFactory + * @see ShelterRepository + */ + @Override + public ShelterContract createShelter(String shelterName, Address address, UserContract responsibleUser) throws ShelterCreationFailureException { + Shelter newShelter = ShelterFactory.create(shelterName, address, responsibleUser); + return this.shelterRepository.save(newShelter); + } + + /** + * Retorna um objeto {@link ShelterContract} baseado no ID do abrigo fornecido. + * + * @param shelterId O ID do abrigo como uma string. + * @return um objeto {@link ShelterContract} que corresponde ao ID do abrigo fornecido + * @throws ShelterServiceFailureException quando uma falha ocorre durante a busca pelo abrigo + */ + @Override + public ShelterContract getShelter(String shelterId) throws ShelterServiceFailureException { + validateShelterId(shelterId); + return this.shelterRepository.findById(shelterId); + } + + /** + * Valida o ID do abrigo fornecido. Primeiro, verifica se o ID do abrigo é nulo ou vazio usando + * {@link ValidationUtils#checkNotNullAndNotEmptyOrThrowException}. Em seguida, tenta verificar se o ID do abrigo é um UUID válido + * usando {@link UuidUtils#isValidUUID}. + * Se qualquer uma dessas verificações falhar, ele lançará uma {@link ShelterServiceFailureException} com uma mensagem que indica + * que o ID do abrigo fornecido é inválido. + * + * @param shelterId O ID do abrigo a ser validado. + * @throws ShelterServiceFailureException Se o ID do abrigo fornecido for nulo, vazio ou não for um UUID válido. + */ + private static void validateShelterId(String shelterId) throws ShelterServiceFailureException { + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(shelterId, INVALID_SHELTER_ID_MESSAGE, ShelterServiceFailureException.class); + try { + UuidUtils.isValidUUID(shelterId); + } catch (UuidUtilsException e) { + throw new ShelterServiceFailureException(INVALID_SHELTER_ID_MESSAGE, e); + } + } + + /** + * Este método é usado para alterar o nome do abrigo identificado pelo ID fornecido. + *

+ * Primeiro, o método valida se o novo nome não é nulo nem vazio usando + * {@link ValidationUtils#checkNotNullAndNotEmptyOrThrowException}. Se a validação falhar, + * uma {@link ShelterServiceFailureException} é lançada com uma mensagem indicando que o novo nome do abrigo é inválido. + *

+ * O nome do abrigo recuperado é então alterado para o novo nome fornecido usando o método + * {@link ShelterContract#changeShelterName}. + *

+ * Finalmente, o abrigo com o nome atualizado é salvo no repositório usando {@link ShelterRepository#save}. + * + * @param shelterId O ID do abrigo cujo nome será alterado. Deve ser uma identificação válida de um abrigo existente. + * @param newName O novo nome para o abrigo. Ele não pode ser {@code null} ou uma String vazia. + * @throws ShelterServiceFailureException Se o novo nome do abrigo fornecido for inválido (ou seja, nulo ou vazio), + * ou se ocorrer um problema ao recuperar o abrigo com o ID fornecido ou ao salvar o abrigo com o nome alterado. + */ + @Override + public void changeShelterName(String shelterId, String newName) throws ShelterServiceFailureException { + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(newName, INVALID_SHELTER_NAME_ERROR_MESSAGE, ShelterServiceFailureException.class); + ShelterContract updatedShelter = this.getShelter(shelterId); + updatedShelter.changeShelterName(newName); + this.shelterRepository.save(updatedShelter); + } + + /** + * Este método é usado para alterar o endereço do abrigo identificado pelo ID fornecido. + *

+ * Primeiro, o método valida se o novo endereço não é nulo usando + * {@link ValidationUtils#checkNotNullAndNotEmptyOrThrowException}. Se a validação falhar, + * uma {@link ShelterServiceFailureException} é lançada com uma mensagem indicando que o novo endereço do abrigo é inválido. + *

+ * O endereço do abrigo recuperado é então alterado para o novo endereço fornecido usando o método + * {@link ShelterContract#changeAddress}. + *

+ * Finalmente, o abrigo com o endereço atualizado é salvo no repositório usando {@link ShelterRepository#save}. + * + * @param shelterId O ID do abrigo cujo endereço será alterado. Deve ser uma identificação válida de um abrigo existente. + * @param address O novo endereço para o abrigo. Ele não pode ser {@code null} ou uma instância inválida de {@link Address}. + * @throws ShelterServiceFailureException Se o novo endereço do abrigo fornecido for inválido (ou seja, null ou instância inválida), + * ou se ocorrer um problema ao recuperar o abrigo com o ID fornecido ou ao salvar o abrigo com o endereço alterado. + */ + @Override + public void changeAddress(String shelterId, Address address) throws ShelterServiceFailureException { + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(address, ERROR_MESSAGE_ADDRESS_NULL, ShelterServiceFailureException.class); + ShelterContract updatedShelter = this.getShelter(shelterId); + updatedShelter.changeAddress(address); + this.shelterRepository.save(updatedShelter); + } + + /** + * Este método é responsável por adicionar uma {@link Donation} a um {@link ShelterContract} específico, identificado por seu id. + *

+ * Primeiro, ele confirma que o objeto {@link Donation} não é nulo usando + * {@link ValidationUtils#checkNotNullAndNotEmptyOrThrowException}. Se este objeto {@link Donation} for nulo, + * ele lança uma {@link ShelterServiceFailureException}. + *

+ * Em seguida, ele usa o id do abrigo fornecido para obter o {@link ShelterContract} correspondente. + * Este {@link ShelterContract} é então atualizado adicionando a {@link Donation} fornecida. + *

+ * Finalmente, o {@link ShelterContract} atualizado é salvo no repositorio usando {@link ShelterRepository#save}. + * + * @param shelterId O ID do abrigo ao qual a doação será adicionada. Deve ser uma identificação válida de um abrigo existente. + * @param donation Uma instancia do objeto {@link Donation} representando a doação a ser adicionada. + * @throws ShelterServiceFailureException Se a doação fornecida for inválida (ou seja, nula ou vazia), + * ou se ocorrer um problema ao recuperar o abrigo com o ID fornecido ou ao salvar o abrigo com a doação adicionada. + */ + @Override + public void addDonation(String shelterId, Donation donation) throws ShelterServiceFailureException { + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(donation, DONATION_REQUIRED_ERROR_MESSAGE, ShelterServiceFailureException.class); + ShelterContract updatedShelter = this.getShelter(shelterId); + updatedShelter.addDonation(donation); + this.shelterRepository.save(updatedShelter); + } + + /** + * Este método é responsável por recuperar a lista de {@link Donation} de um {@link ShelterContract} específico, identificado por seu id. + *

+ * Primeiro, ele usa o id do abrigo fornecido para obter o {@link ShelterContract} correspondente. + * A lista de {@link Donation} deste {@link ShelterContract} específico é então retornada. + *

+ * Note que este método pode lançar uma {@link ShelterServiceFailureException}. Isto ocorrerá se houver um problema ao tentar recuperar o {@link ShelterContract} com o id fornecido. + * + * @param shelterId O ID do abrigo do qual as doações serão recuperadas. Deve ser uma identificação válida de um abrigo existente. + * @return Uma lista de {@link Donation} que foram feitas para o abrigo identificado pelo id fornecido. + * @throws ShelterServiceFailureException Se ocorrer um problema ao tentar recuperar o {@link ShelterContract} com o id fornecido. + */ + @Override + public List getDonations(String shelterId) throws ShelterServiceFailureException { + ShelterContract shelter = this.getShelter(shelterId); + return shelter.getDonations(); + } +} diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/service/ShelterServiceContract.java b/src/main/java/diegosneves/github/conectardoacoes/core/service/ShelterServiceContract.java new file mode 100644 index 0000000..78e9db7 --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/core/service/ShelterServiceContract.java @@ -0,0 +1,78 @@ +package diegosneves.github.conectardoacoes.core.service; + +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.Shelter; +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.ShelterContract; +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.value.Address; +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.value.Donation; +import diegosneves.github.conectardoacoes.core.domain.user.entity.UserContract; +import diegosneves.github.conectardoacoes.core.exception.ShelterCreationFailureException; +import diegosneves.github.conectardoacoes.core.exception.ShelterServiceFailureException; + +import java.util.List; + +/** + * Interface {@link ShelterServiceContract} define vários métodos que manipulam um objeto {@link Shelter}. + * Isso inclui criar um abrigo, alterar o nome e o endereço de um abrigo, adicionar uma doação e obter detalhes de um abrigo. + * + * @author diegoneves + * @since 1.0.0 + */ +public interface ShelterServiceContract { + + /** + * Cria um novo {@link Shelter} usando o nome do abrigo, endereço e o objeto do usuário responsável fornecidos. + * + * @param shelterName nome do abrigo como uma String + * @param address uma instância do objeto {@link Address} representando o endereço do abrigo + * @param responsibleUser um objeto {@link UserContract} representando o usuário responsável pelo abrigo + * @return um objeto {@link ShelterContract} do novo abrigo criado + * @throws ShelterCreationFailureException se um erro ocorrer durante a criação do abrigo + */ + ShelterContract createShelter(String shelterName, Address address, UserContract responsibleUser) throws ShelterCreationFailureException; + + /** + * Obtém os detalhes de um abrigo existente. + * + * @param shelterId ID do abrigo como uma String + * @return um objeto {@link ShelterContract} com detalhes do abrigo + * @throws ShelterServiceFailureException se um erro ocorrer durante a obtenção dos detalhes do abrigo + */ + ShelterContract getShelter(String shelterId) throws ShelterServiceFailureException; + + /** + * Muda o nome de um abrigo existente. + * + * @param shelterId ID do abrigo como uma String + * @param newName o novo nome do abrigo como uma String + * @throws ShelterServiceFailureException se um erro ocorrer durante a alteração do nome do abrigo + */ + void changeShelterName(String shelterId, String newName) throws ShelterServiceFailureException; + + /** + * Muda o endereço de um abrigo existente. + * + * @param shelterId ID do abrigo como uma String + * @param address uma instância do objeto Address representando o novo endereço do abrigo + * @throws ShelterServiceFailureException se um erro ocorrer durante a alteração do endereço do abrigo + */ + void changeAddress(String shelterId, Address address) throws ShelterServiceFailureException; + + /** + * Adiciona uma nova doação ao abrigo. + * + * @param shelterId ID do abrigo como uma String + * @param donation uma instância do objeto {@link Donation} representando a doação a ser adicionada + * @throws ShelterServiceFailureException se um erro ocorrer durante a adição da doação ao abrigo + */ + void addDonation(String shelterId, Donation donation) throws ShelterServiceFailureException; + + /** + * Busca a lista de todas as {@link Donation} de um determinado abrigo. + * + * @param shelterId ID do abrigo como uma String + * @return Uma lista de objetos {@link Donation}, que representa todas as doações recebidas pelo abrigo. + * @throws ShelterServiceFailureException se um erro ocorrer durante a recuperação das doações. + */ + List getDonations(String shelterId) throws ShelterServiceFailureException; + +} diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/service/UserService.java b/src/main/java/diegosneves/github/conectardoacoes/core/service/UserService.java new file mode 100644 index 0000000..161df95 --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/core/service/UserService.java @@ -0,0 +1,132 @@ +package diegosneves.github.conectardoacoes.core.service; + +import diegosneves.github.conectardoacoes.core.domain.user.entity.User; +import diegosneves.github.conectardoacoes.core.domain.user.entity.UserContract; +import diegosneves.github.conectardoacoes.core.domain.user.entity.value.UserProfile; +import diegosneves.github.conectardoacoes.core.domain.user.factory.UserFactory; +import diegosneves.github.conectardoacoes.core.domain.user.shared.repository.UserRepository; +import diegosneves.github.conectardoacoes.core.exception.UserCreationFailureException; +import diegosneves.github.conectardoacoes.core.exception.UserServiceFailureException; +import diegosneves.github.conectardoacoes.core.exception.UuidUtilsException; +import diegosneves.github.conectardoacoes.core.utils.UuidUtils; +import diegosneves.github.conectardoacoes.core.utils.ValidationUtils; + +/** + * A classe {@link UserService} implementa um contratato do serviço do usuário {@link UserServiceContract}. + * Ela fornece os métodos para gerenciar usuários, incluindo criação de um novo usuário, recuperação de um usuário pelo seu ID, + * alteração da senha do usuário e alteração do nome de usuário. + *

Isso é feito por meio da interação com o repositório de usuários {@link UserRepository}, onde os dados do usuário são armazenados. + *

Os métodos implementados nesta classe realizam checagens de validação para garantir que os dados do usuário sejam válidos. + * Se quaisquer dados inválidos forem fornecidos, como um ID de usuário, senha ou nome de usuário nulo ou em branco, + * eles lançarão uma exceção {@link UserServiceFailureException}. + * + * @author diegoneves + * @see UserServiceContract + * @see UserRepository + * @see UserServiceFailureException + * @since 1.0.0 + */ +public class UserService implements UserServiceContract { + + public static final String INVALID_IDENTIFIER_ERROR_MESSAGE = "Falha ao recuperar o usuário. Identificador fornecido possui valor inválido"; + public static final String INVALID_NEW_PASSWORD_MESSAGE = "A nova senha informada é invalida"; + public static final String USER_NOT_FOUND_MESSAGE = "Usuário não encontrado"; + public static final String USERNAME_INVALID_ERROR_MESSAGE = "O novo nome de usuário informado é inválido."; + + private final UserRepository userRepository; + + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + /** + * Cria um novo usuário com os detalhes fornecidos e armazena no repositório de usuários. + * + * @param username O nome de usuário para o novo usuário. + * @param email O email para o novo usuário. + * @param userProfile O perfil do novo usuário. + * @param password A senha para o novo usuário. + * @return A instância de {@link UserContract} que representa o usuário criado. + * @throws UserCreationFailureException se alguma informação fornecida for inválida. + */ + @Override + public UserContract createUser(String username, String email, UserProfile userProfile, String password) throws UserCreationFailureException { + User newUser = UserFactory.create(username, email, userProfile, password); + return this.userRepository.save(newUser); + } + + /** + * Recupera um usuário pelo seu ID do repositório de usuários. + * + * @param userId O ID do usuário a ser recuperado. + * @return A instância de {@link UserContract} que representa o usuário recuperado. + * @throws UserServiceFailureException se o ID do usuário for nulo ou em branco. + */ + @Override + public UserContract getUser(String userId) { + validateUserId(userId); + return this.userRepository.findById(userId); + } + + /** + * Valida o identificador do usuário fornecido. + * + *

Este método utiliza os utilitários fornecidos pelas classes {@link ValidationUtils} e + * {@link UuidUtils} para validar o identificador do usuário. A validação ocorre em dois passos:

+ * + * + * + *

Se qualquer uma das validações falhar, este método lança uma exceção {@link UserServiceFailureException}, + * que é uma exceção personalizada que estende a {@link RuntimeException}.

+ * + * @param userId O identificador do usuário a ser validado. + * @throws UserServiceFailureException Se o identificador do usuário for nulo, vazio ou não for um UUID válido. + * Aqui, {@link UserServiceFailureException} encapsula a exceção original ({@link UuidUtilsException}) dentro dela para fornecer + * informação contextual adicional quando o UUID não é válido. + */ + private static void validateUserId(String userId) throws UserServiceFailureException { + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(userId, INVALID_IDENTIFIER_ERROR_MESSAGE, UserServiceFailureException.class); + try { + UuidUtils.isValidUUID(userId); + } catch (UuidUtilsException e) { + throw new UserServiceFailureException(INVALID_IDENTIFIER_ERROR_MESSAGE, e); + } + } + + /** + * Altera a senha do usuário especificado. + * + * @param userId O ID do usuário cuja senha será alterada. + * @param newPassword A nova senha para o usuário. + * @throws UserServiceFailureException se o ID do usuário ou a nova senha forem nulos ou em branco, ou se o usuário não for encontrado. + */ + @Override + public void changePassword(String userId, String newPassword) { + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(newPassword, INVALID_NEW_PASSWORD_MESSAGE, UserServiceFailureException.class); + UserContract retrievedUser = this.getUser(userId); + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(retrievedUser, USER_NOT_FOUND_MESSAGE, UserServiceFailureException.class); + retrievedUser.changeUserPassword(newPassword); + this.userRepository.save(retrievedUser); + } + + /** + * Altera o nome do usuário especificado. + * + * @param userId O ID do usuário cujo nome será alterado. + * @param newUsername O novo nome de usuário para o usuário. + * @throws UserServiceFailureException se o ID do usuário ou o novo nome de usuário forem nulos ou em branco, ou se o usuário não for encontrado. + */ + @Override + public void changeUserName(String userId, String newUsername) { + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(newUsername, USERNAME_INVALID_ERROR_MESSAGE, UserServiceFailureException.class); + UserContract retrievedUser = this.getUser(userId); + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(retrievedUser, USER_NOT_FOUND_MESSAGE, UserServiceFailureException.class); + retrievedUser.changeUserName(newUsername); + this.userRepository.save(retrievedUser); + } +} diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/service/UserServiceContract.java b/src/main/java/diegosneves/github/conectardoacoes/core/service/UserServiceContract.java new file mode 100644 index 0000000..53f0ad8 --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/core/service/UserServiceContract.java @@ -0,0 +1,55 @@ +package diegosneves.github.conectardoacoes.core.service; + +import diegosneves.github.conectardoacoes.core.domain.user.entity.UserContract; +import diegosneves.github.conectardoacoes.core.domain.user.entity.value.UserProfile; +import diegosneves.github.conectardoacoes.core.exception.UserCreationFailureException; +import diegosneves.github.conectardoacoes.core.exception.UserServiceFailureException; + +/** + * Interface para o contrato de serviço do usuário. Define os métodos funcionais para todos os serviços de usuário. + * + * @author diegoneves + * @since 1.0.0 + */ +public interface UserServiceContract { + + /** + * Método para criar usuário. + * + * @param username nome de usuário exclusivo. + * @param email e-mail do usuário. + * @param userProfile perfil do usuário. + * @param password senha da conta do usuário. + * @return Um {@link UserContract} instância contendo detalhes do usuário. + * @throws UserCreationFailureException se ocorrer um erro ao criar o usuário. + */ + UserContract createUser(String username, String email, UserProfile userProfile, String password) throws UserCreationFailureException; + + /** + * Método para obter detalhes do usuário. + * + * @param userId ID único do usuário. + * @return A {@link UserContract} instância contendo detalhes do usuário. + * @throws UserServiceFailureException se ocorrer um erro ao buscar detalhes do usuário. + */ + UserContract getUser(String userId); + + /** + * Método para alterar a senha do usuário. + * + * @param userId ID único do usuário. + * @param newPassword Nova senha do usuário. + * @throws UserServiceFailureException se ocorrer um erro ao alterar a senha do usuário. + */ + void changePassword(String userId, String newPassword); + + /** + * Método para alterar o nome de usuário. + * + * @param userId ID único do usuário. + * @param newUsername Novo nome de usuário. + * @throws UserServiceFailureException se ocorrer um erro ao alterar o nome de usuário. + */ + void changeUserName(String userId, String newUsername); + +} diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/utils/UuidUtils.java b/src/main/java/diegosneves/github/conectardoacoes/core/utils/UuidUtils.java index 9ccceb4..4bf2884 100644 --- a/src/main/java/diegosneves/github/conectardoacoes/core/utils/UuidUtils.java +++ b/src/main/java/diegosneves/github/conectardoacoes/core/utils/UuidUtils.java @@ -10,6 +10,7 @@ * Esta classe fornece métodos estáticos para gerar um novo {@link UUID} e para validar um {@link UUID} existente. * * @author diegoneves + * @since 1.0.0 */ @Slf4j public class UuidUtils { diff --git a/src/main/java/diegosneves/github/conectardoacoes/core/utils/ValidationUtils.java b/src/main/java/diegosneves/github/conectardoacoes/core/utils/ValidationUtils.java new file mode 100644 index 0000000..ae90b3d --- /dev/null +++ b/src/main/java/diegosneves/github/conectardoacoes/core/utils/ValidationUtils.java @@ -0,0 +1,55 @@ +package diegosneves.github.conectardoacoes.core.utils; + +import diegosneves.github.conectardoacoes.core.exception.ValidationUtilsException; + +import java.lang.reflect.Constructor; + +/** + * A classe {@link ValidationUtils} é um utilitário que fornece métodos para validar dados de entrada. + * Contém todos os métodos estáticos e não pode ser instanciada. + * + * @author diegoneves + * @since 1.0.0 + */ +public class ValidationUtils { + + private ValidationUtils() { + } + + /** + * Valida se o objeto especificado é nulo ou vazio, caso seja uma instância de String. + * Lança uma exceção com a mensagem de erro fornecida, se uma destas condições for verdadeira. + * Este método pode ser utilizado para a validação de entradas onde dados são obrigatórios. + * + * @param o tipo de objeto a ser verificado + * @param object o objeto a ser validado + * @param errorMessage a mensagem de erro a ser anexada à exceção em caso de falhas na validação + * @param customException a classe da exceção RuntimeException a ser lançada + * @throws RuntimeException se o objeto fornecido for nulo ou se fora uma instância de String e estiver vazia + */ + public static void checkNotNullAndNotEmptyOrThrowException(T object, String errorMessage, Class customException) throws RuntimeException { + if (object == null) { + throwException(errorMessage, customException); + } + if (object instanceof String && ((String) object).trim().isEmpty()) { + throwException(errorMessage, customException); + } + } + + /** + * Lança uma exceção customizada com a mensagem de erro fornecida. + * + * @param message a mensagem de erro a ser anexada à exceção + * @param runtimeExceptionClass a classe da exceção RuntimeException a ser lançada + * @throws ValidationUtilsException se houve um erro ao tentar lançar a exceção + */ + private static void throwException(String message, Class runtimeExceptionClass) { + try { + Constructor exceptionConstructor = runtimeExceptionClass.getConstructor(String.class); + throw exceptionConstructor.newInstance(message); + } catch (ReflectiveOperationException e) { + throw new ValidationUtilsException(runtimeExceptionClass.getSimpleName(), e); + } + } + +} diff --git a/src/test/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/ShelterTest.java b/src/test/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/ShelterTest.java index 0f3fc2d..53ce521 100644 --- a/src/test/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/ShelterTest.java +++ b/src/test/java/diegosneves/github/conectardoacoes/core/domain/shelter/entity/ShelterTest.java @@ -186,12 +186,10 @@ void shouldThrowExceptionWhenChangeAddressWithNullAddress() { @SneakyThrows void shouldAddDonation() { Donation newDonation = new Donation("Doação", 1); - Field field = Shelter.class.getDeclaredField("donations"); - field.setAccessible(true); this.shelter.addDonation(newDonation); - List actualDonations = (List) field.get(this.shelter); + List actualDonations = this.shelter.getDonations(); assertNotNull(actualDonations); assertFalse(actualDonations.isEmpty()); diff --git a/src/test/java/diegosneves/github/conectardoacoes/core/service/ShelterServiceTest.java b/src/test/java/diegosneves/github/conectardoacoes/core/service/ShelterServiceTest.java new file mode 100644 index 0000000..4e2da2e --- /dev/null +++ b/src/test/java/diegosneves/github/conectardoacoes/core/service/ShelterServiceTest.java @@ -0,0 +1,462 @@ +package diegosneves.github.conectardoacoes.core.service; + +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.Shelter; +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.ShelterContract; +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.value.Address; +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.value.Donation; +import diegosneves.github.conectardoacoes.core.domain.shelter.shared.repository.ShelterRepository; +import diegosneves.github.conectardoacoes.core.domain.user.entity.User; +import diegosneves.github.conectardoacoes.core.domain.user.entity.value.UserProfile; +import diegosneves.github.conectardoacoes.core.exception.ShelterCreationFailureException; +import diegosneves.github.conectardoacoes.core.exception.ShelterServiceFailureException; +import diegosneves.github.conectardoacoes.core.exception.UuidUtilsException; +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.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.lang.reflect.Field; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +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 ShelterServiceTest { + + public static final String SHELTER_IDENTIFIER = "89142bda-7b0c-4421-af28-f9cadb316024"; + public static final String USER_UUID = "0ed6e6a1-882c-4d2d-83ee-67034ee1ab9f"; + public static final String USERNAME = "Fulano"; + public static final String USER_EMAIL = "teste@email.com"; + public static final String USER_PASSWORD = "senha"; + public static final String SHELTER_NAME = "Abrigo"; + public static final String ADDRESS_STREET = "Rua"; + public static final String BUILDING_NUMBER = "54"; + public static final String NEIGHBORHOOD = "Bairro"; + public static final String SHELTER_CITY = "Canoas"; + public static final String STATE_ABBREVIATION = "RS"; + public static final String SHELTER_ZIPCODE = "95000000"; + + @InjectMocks + private ShelterService service; + + @Mock + private ShelterRepository repository; + + @Captor + private ArgumentCaptor shelterCaptor; + + private Shelter shelter; + private User user; + private Address address; + + @BeforeEach + void setUp() { + this.user = new User(USER_UUID, USERNAME, USER_EMAIL, UserProfile.BENEFICIARY, USER_PASSWORD); + this.address = new Address(ADDRESS_STREET, BUILDING_NUMBER, NEIGHBORHOOD, SHELTER_CITY, STATE_ABBREVIATION, SHELTER_ZIPCODE); + this.shelter = new Shelter(SHELTER_IDENTIFIER, SHELTER_NAME, this.address, this.user); + } + + @Test + void shouldReturnShelterContract() { + when(this.repository.save(any(ShelterContract.class))).thenReturn(this.shelter); + + ShelterContract actual = this.service.createShelter(SHELTER_NAME, this.address, this.user); + + verify(this.repository, times(1)).save(this.shelterCaptor.capture()); + + assertNotNull(actual); + assertNotNull(this.shelterCaptor.getValue()); + assertEquals(this.shelter, actual); + ShelterContract expected = this.shelterCaptor.getValue(); + assertTrue(UuidUtils.isValidUUID(expected.getId())); + assertEquals(SHELTER_NAME, expected.getShelterName()); + assertEquals(this.user, expected.getUser()); + assertEquals(this.address, expected.getAddress()); + } + + @Test + void shouldThrowShelterCreationFailureExceptionWhenShelterNameIsNull() { + + Exception actual = assertThrows(Exception.class, + () -> this.service.createShelter(null, this.address, this.user)); + + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(actual); + assertEquals(ShelterCreationFailureException.class, actual.getClass()); + } + + @Test + void shouldThrowShelterCreationFailureExceptionWhenShelterNameIsEmpty() { + + Exception actual = assertThrows(Exception.class, + () -> this.service.createShelter("", this.address, this.user)); + + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(actual); + assertEquals(ShelterCreationFailureException.class, actual.getClass()); + } + + @Test + void shouldThrowShelterCreationFailureExceptionWhenShelterAddressIsNull() { + + Exception actual = assertThrows(Exception.class, + () -> this.service.createShelter(SHELTER_NAME, null, this.user)); + + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(actual); + assertEquals(ShelterCreationFailureException.class, actual.getClass()); + } + + @Test + void shouldThrowShelterCreationFailureExceptionWhenShelterUserIsNull() { + + Exception actual = assertThrows(Exception.class, + () -> this.service.createShelter(SHELTER_NAME, this.address, null)); + + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(actual); + assertEquals(ShelterCreationFailureException.class, actual.getClass()); + } + + @Test + void shouldRetrieveShelterContractUsingGivenShelterIdentifier() { + when(this.repository.findById(SHELTER_IDENTIFIER)).thenReturn(this.shelter); + + ShelterContract actual = this.service.getShelter(SHELTER_IDENTIFIER); + + verify(this.repository, times(1)).findById(SHELTER_IDENTIFIER); + + assertNotNull(actual); + assertEquals(this.shelter, actual); + } + + @Test + void shouldRetrieveNullShelterContractUsingGivenShelterIdentifier() { + when(this.repository.findById(SHELTER_IDENTIFIER)).thenReturn(null); + + ShelterContract actual = this.service.getShelter(SHELTER_IDENTIFIER); + + verify(this.repository, times(1)).findById(SHELTER_IDENTIFIER); + + assertNull(actual); + } + + @Test + void shouldThrowExceptionWhenGivenNullShelterId() { + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.getShelter(null)); + + verify(this.repository, never()).findById(anyString()); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_ID_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowExceptionWhenGivenEmptyShelterId() { + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.getShelter("")); + + verify(this.repository, never()).findById(anyString()); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_ID_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowExceptionWhenGivenInvalidShelterId() { + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.getShelter("idInvalid")); + + verify(this.repository, never()).findById(anyString()); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_ID_MESSAGE), exception.getMessage()); + assertEquals(UuidUtilsException.class, exception.getCause().getClass()); + } + + @Test + void shouldChangeShelterNameWhenGivenValidShelterIdentifier() { + String newShelterName = "newShelterName"; + when(this.repository.findById(SHELTER_IDENTIFIER)).thenReturn(this.shelter); + + this.service.changeShelterName(SHELTER_IDENTIFIER, newShelterName); + + verify(this.repository, times(1)).findById(SHELTER_IDENTIFIER); + verify(this.repository, times(1)).save(this.shelterCaptor.capture()); + + assertNotNull(shelterCaptor.getValue()); + Shelter updatedShelter = this.shelterCaptor.getValue(); + assertEquals(newShelterName, updatedShelter.getShelterName()); + assertEquals(SHELTER_IDENTIFIER, updatedShelter.getId()); + assertEquals(this.user, updatedShelter.getUser()); + assertEquals(this.address, updatedShelter.getAddress()); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenShelterNameIsEmpty() { + String newShelterName = ""; + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.changeShelterName(SHELTER_IDENTIFIER, newShelterName)); + + verify(this.repository, never()).findById(anyString()); + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_NAME_ERROR_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenShelterNameIsNull() { + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.changeShelterName(SHELTER_IDENTIFIER, null)); + + verify(this.repository, never()).findById(anyString()); + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_NAME_ERROR_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenShelterIdIsInvalidOnChangeShelterName() { + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.changeShelterName("SHELTER_IDENTIFIER", SHELTER_NAME)); + + verify(this.repository, never()).findById(anyString()); + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_ID_MESSAGE), exception.getMessage()); + assertEquals(UuidUtilsException.class, exception.getCause().getClass()); + } + + @Test + void shouldUpdateAndSaveShelterAddressGivenValidShelterId() { + Address addressUpdate = new Address(ADDRESS_STREET, "377", NEIGHBORHOOD, "Esteio", STATE_ABBREVIATION, SHELTER_ZIPCODE); + when(this.repository.findById(SHELTER_IDENTIFIER)).thenReturn(this.shelter); + + this.service.changeAddress(SHELTER_IDENTIFIER, addressUpdate); + + verify(this.repository, times(1)).findById(SHELTER_IDENTIFIER); + verify(this.repository, times(1)).save(this.shelterCaptor.capture()); + + assertNotNull(shelterCaptor.getValue()); + Shelter updatedShelter = this.shelterCaptor.getValue(); + assertEquals(addressUpdate, updatedShelter.getAddress()); + assertEquals(SHELTER_IDENTIFIER, updatedShelter.getId()); + assertEquals(this.user, updatedShelter.getUser()); + assertNotEquals(this.address, updatedShelter.getAddress()); + assertEquals(SHELTER_NAME, updatedShelter.getShelterName()); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenShelterIdIsEmptyOnChangeAddress() { + Address addressUpdate = new Address(ADDRESS_STREET, "377", NEIGHBORHOOD, "Esteio", STATE_ABBREVIATION, SHELTER_ZIPCODE); + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.changeAddress("", addressUpdate)); + + verify(this.repository, never()).findById(anyString()); + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_ID_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenShelterIdIsNullOnChangeAddress() { + Address addressUpdate = new Address(ADDRESS_STREET, "377", NEIGHBORHOOD, "Esteio", STATE_ABBREVIATION, SHELTER_ZIPCODE); + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.changeAddress(null, addressUpdate)); + + verify(this.repository, never()).findById(anyString()); + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_ID_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenShelterIdIsInvalidOnChangeAddress() { + Address addressUpdate = new Address(ADDRESS_STREET, "377", NEIGHBORHOOD, "Esteio", STATE_ABBREVIATION, SHELTER_ZIPCODE); + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.changeAddress("SHELTER_IDENTIFIER", addressUpdate)); + + verify(this.repository, never()).findById(anyString()); + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_ID_MESSAGE), exception.getMessage()); + assertEquals(UuidUtilsException.class, exception.getCause().getClass()); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenNewAddressIsNullOnChangeAddress() { + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.changeAddress(SHELTER_IDENTIFIER, null)); + + verify(this.repository, never()).findById(anyString()); + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.ERROR_MESSAGE_ADDRESS_NULL), exception.getMessage()); + } + + @Test + void shouldAddDonationToShelter() { + Donation donation = new Donation("item", 1); + + when(this.repository.findById(SHELTER_IDENTIFIER)).thenReturn(this.shelter); + + this.service.addDonation(SHELTER_IDENTIFIER, donation); + + verify(this.repository, times(1)).findById(SHELTER_IDENTIFIER); + verify(this.repository, times(1)).save(this.shelterCaptor.capture()); + + assertNotNull(shelterCaptor.getValue()); + Shelter updatedShelter = this.shelterCaptor.getValue(); + assertFalse(updatedShelter.getDonations().isEmpty()); + assertEquals(1, updatedShelter.getDonations().size()); + assertEquals(donation, updatedShelter.getDonations().get(0)); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenShelterIdIsNullOnAddDonation() { + Donation donation = new Donation("item", 1); + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.addDonation(null, donation)); + + verify(this.repository, never()).findById(anyString()); + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_ID_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenShelterIdIsEmptyOnAddDonation() { + Donation donation = new Donation("item", 1); + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.addDonation(" ", donation)); + + verify(this.repository, never()).findById(anyString()); + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_ID_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenShelterIdIsInvalidOnAddDonation() { + Donation donation = new Donation("item", 1); + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.addDonation("SHELTER_IDENTIFIER", donation)); + + verify(this.repository, never()).findById(anyString()); + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_ID_MESSAGE), exception.getMessage()); + assertEquals(UuidUtilsException.class, exception.getCause().getClass()); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenDonationIsNullOnAddDonation() { + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, + () -> this.service.addDonation(SHELTER_IDENTIFIER, null)); + + verify(this.repository, never()).findById(anyString()); + verify(this.repository, never()).save(any(ShelterContract.class)); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.DONATION_REQUIRED_ERROR_MESSAGE), exception.getMessage()); + } + + @Test + void shouldReturnListOfDonations() { + Donation donation = new Donation("item", 1); + this.shelter.addDonation(donation); + when(this.repository.findById(SHELTER_IDENTIFIER)).thenReturn(this.shelter); + + List list = this.service.getDonations(SHELTER_IDENTIFIER); + + verify(this.repository, times(1)).findById(SHELTER_IDENTIFIER); + + assertNotNull(list); + assertFalse(this.shelter.getDonations().isEmpty()); + assertEquals(1, list.size()); + assertEquals(donation, list.get(0)); + } + + @Test + void shouldReturnListOfDonationsEmpty() { + when(this.repository.findById(SHELTER_IDENTIFIER)).thenReturn(this.shelter); + + List list = this.service.getDonations(SHELTER_IDENTIFIER); + + verify(this.repository, times(1)).findById(SHELTER_IDENTIFIER); + + assertNotNull(list); + assertTrue(this.shelter.getDonations().isEmpty()); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenShelterIdIsNullOnGetDonations() { + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, () -> this.service.getDonations(null)); + + verify(this.repository, never()).findById(anyString()); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_ID_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenShelterIdIsEmptyOnGetDonations() { + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, () -> this.service.getDonations("")); + + verify(this.repository, never()).findById(anyString()); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_ID_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowShelterServiceFailureExceptionWhenShelterIdIsInvalidOnGetDonations() { + + ShelterServiceFailureException exception = assertThrows(ShelterServiceFailureException.class, () -> this.service.getDonations("SHELTER_IDENTIFIER")); + + verify(this.repository, never()).findById(anyString()); + + assertNotNull(exception); + assertEquals(ShelterServiceFailureException.ERROR.buildMessage(ShelterService.INVALID_SHELTER_ID_MESSAGE), exception.getMessage()); + } + +} diff --git a/src/test/java/diegosneves/github/conectardoacoes/core/service/UserServiceTest.java b/src/test/java/diegosneves/github/conectardoacoes/core/service/UserServiceTest.java new file mode 100644 index 0000000..9c9054b --- /dev/null +++ b/src/test/java/diegosneves/github/conectardoacoes/core/service/UserServiceTest.java @@ -0,0 +1,502 @@ +package diegosneves.github.conectardoacoes.core.service; + +import diegosneves.github.conectardoacoes.core.domain.shelter.entity.ShelterContract; +import diegosneves.github.conectardoacoes.core.domain.user.entity.User; +import diegosneves.github.conectardoacoes.core.domain.user.entity.UserContract; +import diegosneves.github.conectardoacoes.core.domain.user.entity.value.UserProfile; +import diegosneves.github.conectardoacoes.core.domain.user.shared.repository.UserRepository; +import diegosneves.github.conectardoacoes.core.enums.ExceptionDetails; +import diegosneves.github.conectardoacoes.core.exception.ShelterCreationFailureException; +import diegosneves.github.conectardoacoes.core.exception.UserCreationFailureException; +import diegosneves.github.conectardoacoes.core.exception.UserServiceFailureException; +import diegosneves.github.conectardoacoes.core.exception.UuidUtilsException; +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.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +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(MockitoExtension.class) +class UserServiceTest { + + public static final String USER_IDENTIFIER = "89142bda-7b0c-4421-af28-f9cadb316024"; + public static final String USER_UUID = "0ed6e6a1-882c-4d2d-83ee-67034ee1ab9f"; + public static final String USERNAME = "Fulano"; + public static final String USER_EMAIL = "teste@email.com"; + public static final String USER_PASSWORD = "senha"; + + @InjectMocks + private UserService userService; + + @Mock + private UserRepository userRepository; + + @Captor + private ArgumentCaptor userCaptor; + + private User user; + + @BeforeEach + void setUp() { + this.user = new User(USER_UUID, USERNAME, USER_EMAIL, UserProfile.BENEFICIARY, USER_PASSWORD); + } + + @Test + void shouldCreateUserAndReturnCreatedUser() { + when(this.userRepository.save(any(UserContract.class))).thenReturn(this.user); + + UserContract actual = this.userService.createUser(USERNAME, USER_EMAIL, UserProfile.BENEFICIARY, USER_PASSWORD); + + verify(this.userRepository, times(1)).save(this.userCaptor.capture()); + + assertNotNull(this.userCaptor.getValue()); + User returnedUser = this.userCaptor.getValue(); + assertNotNull(actual); + assertTrue(UuidUtils.isValidUUID(returnedUser.getId())); + assertEquals(USER_UUID, actual.getId()); + assertEquals(USERNAME, returnedUser.getUsername()); + assertEquals(USERNAME, actual.getUsername()); + assertEquals(USER_EMAIL, returnedUser.getEmail()); + assertEquals(USER_EMAIL, actual.getEmail()); + assertEquals(UserProfile.BENEFICIARY, returnedUser.getUserProfile()); + assertEquals(UserProfile.BENEFICIARY, actual.getUserProfile()); + assertEquals(USER_PASSWORD, returnedUser.getUserPassword()); + assertEquals(USER_PASSWORD, actual.getUserPassword()); + } + + @Test + void shouldThrowUserCreationFailureExceptionWhenUsernameIsNull() { + + Exception actual = assertThrows(Exception.class, + () -> this.userService.createUser(null, USER_EMAIL, UserProfile.BENEFICIARY, USER_PASSWORD)); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.class, actual.getClass()); + } + + @Test + void shouldThrowUserCreationFailureExceptionWhenUsernameIsEmpty() { + + Exception actual = assertThrows(Exception.class, + () -> this.userService.createUser(" ", USER_EMAIL, UserProfile.BENEFICIARY, USER_PASSWORD)); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.class, actual.getClass()); + } + + @Test + void shouldThrowUserCreationFailureExceptionWhenUserEmailIsNull() { + + Exception actual = assertThrows(Exception.class, + () -> this.userService.createUser(USERNAME, null, UserProfile.BENEFICIARY, USER_PASSWORD)); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.class, actual.getClass()); + } + + @Test + void shouldThrowUserCreationFailureExceptionWhenUserEmailIsEmpty() { + + Exception actual = assertThrows(Exception.class, + () -> this.userService.createUser(USERNAME, "", UserProfile.BENEFICIARY, USER_PASSWORD)); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.class, actual.getClass()); + } + + @Test + void shouldThrowUserCreationFailureExceptionWhenUserProfileIsNull() { + + Exception actual = assertThrows(Exception.class, + () -> this.userService.createUser(USERNAME, USER_EMAIL, null, USER_PASSWORD)); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.class, actual.getClass()); + } + + @Test + void shouldThrowUserCreationFailureExceptionWhenUserPasswordIsNull() { + + Exception actual = assertThrows(Exception.class, + () -> this.userService.createUser(USERNAME, USER_EMAIL, UserProfile.BENEFICIARY, null)); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.class, actual.getClass()); + } + + @Test + void shouldThrowUserCreationFailureExceptionWhenUserPasswordIsEmpty() { + + Exception actual = assertThrows(Exception.class, + () -> this.userService.createUser(USERNAME, USER_EMAIL, UserProfile.BENEFICIARY, " ")); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.class, actual.getClass()); + } + + @Test + void shouldThrowExceptionWhenUsernameIsNullOnCreateUser() { + + UserCreationFailureException actual = assertThrows(UserCreationFailureException.class, + () -> this.userService.createUser(null, USER_EMAIL, UserProfile.BENEFICIARY, USER_PASSWORD)); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.ERROR.buildMessage(String.format(User.USERNAME_REQUIRED, UserProfile.BENEFICIARY)), actual.getMessage()); + } + + @Test + void shouldThrowExceptionWhenUsernameIsEmptyOnCreateUser() { + + UserCreationFailureException actual = assertThrows(UserCreationFailureException.class, + () -> this.userService.createUser(" ", USER_EMAIL, UserProfile.BENEFICIARY, USER_PASSWORD)); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.ERROR.buildMessage(String.format(User.USERNAME_REQUIRED, UserProfile.BENEFICIARY)), actual.getMessage()); + } + + @Test + void shouldThrowExceptionWhenEmailIsNullOnCreateUser() { + + UserCreationFailureException actual = assertThrows(UserCreationFailureException.class, + () -> this.userService.createUser(USERNAME, null, UserProfile.BENEFICIARY, USER_PASSWORD)); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.ERROR.buildMessage(User.EMAIL_NOT_PROVIDED), actual.getMessage()); + } + + @Test + void shouldThrowExceptionWhenEmailIsEmptyOnCreateUser() { + + UserCreationFailureException actual = assertThrows(UserCreationFailureException.class, + () -> this.userService.createUser(USERNAME, "", UserProfile.BENEFICIARY, USER_PASSWORD)); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.ERROR.buildMessage(User.EMAIL_NOT_PROVIDED), actual.getMessage()); + } + + @Test + void shouldThrowExceptionWhenUserProfileIsNullOnCreateUser() { + + UserCreationFailureException actual = assertThrows(UserCreationFailureException.class, + () -> this.userService.createUser(USERNAME, USER_EMAIL, null, USER_PASSWORD)); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.ERROR.buildMessage(User.PROFILE_NOT_PROVIDED), actual.getMessage()); + } + + @Test + void shouldThrowExceptionWhenPasswordIsNullOnCreateUser() { + + UserCreationFailureException actual = assertThrows(UserCreationFailureException.class, + () -> this.userService.createUser(USERNAME, USER_EMAIL, UserProfile.BENEFICIARY, null)); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.ERROR.buildMessage(User.PASSWORD_NOT_PROVIDED), actual.getMessage()); + } + + @Test + void shouldThrowExceptionWhenPasswordIsEmptyOnCreateUser() { + + UserCreationFailureException actual = assertThrows(UserCreationFailureException.class, + () -> this.userService.createUser(USERNAME, USER_EMAIL, UserProfile.BENEFICIARY, " ")); + + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(actual); + assertEquals(UserCreationFailureException.ERROR.buildMessage(User.PASSWORD_NOT_PROVIDED), actual.getMessage()); + } + + @Test + void shouldGetUserById() { + when(this.userRepository.findById(USER_UUID)).thenReturn(this.user); + + UserContract actual = this.userService.getUser(USER_UUID); + + verify(this.userRepository, times(1)).findById(USER_UUID); + + assertNotNull(actual); + assertEquals(this.user, actual); + } + + @Test + void shouldReturnNullWhenUserNotFoundOnGetUser() { + when(this.userRepository.findById(USER_IDENTIFIER)).thenReturn(null); + + UserContract actual = this.userService.getUser(USER_IDENTIFIER); + + verify(this.userRepository, times(1)).findById(USER_IDENTIFIER); + + assertNull(actual); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenNullUserIdProvidedOnGetUser() { + + UserServiceFailureException actual = assertThrows(UserServiceFailureException.class, () -> this.userService.getUser(null)); + + verify(this.userRepository, never()).findById(anyString()); + + assertNotNull(actual); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.INVALID_IDENTIFIER_ERROR_MESSAGE), actual.getMessage()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenEmptyUserIdProvidedOnGetUser() { + + UserServiceFailureException actual = assertThrows(UserServiceFailureException.class, () -> this.userService.getUser(" ")); + + verify(this.userRepository, never()).findById(anyString()); + + assertNotNull(actual); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.INVALID_IDENTIFIER_ERROR_MESSAGE), actual.getMessage()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenInvalidUserIdProvidedOnGetUser() { + + UserServiceFailureException actual = assertThrows(UserServiceFailureException.class, () -> this.userService.getUser("id")); + + verify(this.userRepository, never()).findById(anyString()); + + assertNotNull(actual); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.INVALID_IDENTIFIER_ERROR_MESSAGE), actual.getMessage()); + assertEquals(UuidUtilsException.class, actual.getCause().getClass()); + } + + @Test + void shouldChangeUserPasswordAndPersistTheChange() { + String newPassword = "newPassword"; + when(this.userRepository.findById(USER_UUID)).thenReturn(this.user); + + this.userService.changePassword(USER_UUID, newPassword); + + verify(this.userRepository, times(1)).findById(USER_UUID); + verify(this.userRepository, times(1)).save(this.userCaptor.capture()); + + assertNotNull(userCaptor.getValue()); + User updatedUser = userCaptor.getValue(); + assertEquals(USER_UUID, updatedUser.getId()); + assertEquals(USERNAME, updatedUser.getUsername()); + assertEquals(USER_EMAIL, updatedUser.getEmail()); + assertEquals(newPassword, updatedUser.getUserPassword()); + assertEquals(UserProfile.BENEFICIARY, updatedUser.getUserProfile()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenNewPasswordIsNull() { + + UserServiceFailureException exception = assertThrows(UserServiceFailureException.class, + () -> this.userService.changePassword(USER_UUID, null)); + + verify(this.userRepository, never()).findById(USER_UUID); + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(exception); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.INVALID_NEW_PASSWORD_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenNewPasswordIsEmpty() { + String newPassword = " "; + + UserServiceFailureException exception = assertThrows(UserServiceFailureException.class, + () -> this.userService.changePassword(USER_UUID, newPassword)); + + verify(this.userRepository, never()).findById(USER_UUID); + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(exception); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.INVALID_NEW_PASSWORD_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenUserNotFound() { + String newPassword = "newPassword"; + when(this.userRepository.findById(USER_UUID)).thenReturn(null); + + UserServiceFailureException exception = assertThrows(UserServiceFailureException.class, + () -> this.userService.changePassword(USER_UUID, newPassword)); + + verify(this.userRepository, times(1)).findById(USER_UUID); + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(exception); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.USER_NOT_FOUND_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenNullUserIdProvidedOnChangePassword() { + + UserServiceFailureException actual = assertThrows(UserServiceFailureException.class, () -> this.userService.changePassword(null, "newPassword")); + + verify(this.userRepository, never()).findById(anyString()); + + assertNotNull(actual); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.INVALID_IDENTIFIER_ERROR_MESSAGE), actual.getMessage()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenEmptyUserIdProvidedOnChangePassword() { + + UserServiceFailureException actual = assertThrows(UserServiceFailureException.class, () -> this.userService.changePassword(" ", "newPassword")); + + verify(this.userRepository, never()).findById(anyString()); + + assertNotNull(actual); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.INVALID_IDENTIFIER_ERROR_MESSAGE), actual.getMessage()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenInvalidUserIdProvidedOnChangePassword() { + + UserServiceFailureException actual = assertThrows(UserServiceFailureException.class, () -> this.userService.changePassword("Invalid", "newPassword")); + + verify(this.userRepository, never()).findById(anyString()); + + assertNotNull(actual); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.INVALID_IDENTIFIER_ERROR_MESSAGE), actual.getMessage()); + assertEquals(UuidUtilsException.class, actual.getCause().getClass()); + } + + @Test + void shouldChangeUsernameAndPersistTheChange() { + String newUsername = "newUsername"; + when(this.userRepository.findById(USER_UUID)).thenReturn(this.user); + + this.userService.changeUserName(USER_UUID, newUsername); + + verify(this.userRepository, times(1)).findById(USER_UUID); + verify(this.userRepository, times(1)).save(this.userCaptor.capture()); + + assertNotNull(userCaptor.getValue()); + User updatedUser = userCaptor.getValue(); + assertEquals(USER_UUID, updatedUser.getId()); + assertEquals(newUsername, updatedUser.getUsername()); + assertEquals(USER_EMAIL, updatedUser.getEmail()); + assertEquals(USER_PASSWORD, updatedUser.getUserPassword()); + assertEquals(UserProfile.BENEFICIARY, updatedUser.getUserProfile()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenNewUsernameIsNull() { + + UserServiceFailureException exception = assertThrows(UserServiceFailureException.class, + () -> this.userService.changeUserName(USER_UUID, null)); + + verify(this.userRepository, never()).findById(USER_UUID); + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(exception); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.USERNAME_INVALID_ERROR_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenNewUsernameIsEmpty() { + String newUsername = " "; + + UserServiceFailureException exception = assertThrows(UserServiceFailureException.class, + () -> this.userService.changeUserName(USER_UUID, newUsername)); + + verify(this.userRepository, never()).findById(USER_UUID); + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(exception); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.USERNAME_INVALID_ERROR_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenUserNotFoundOnChangeUsername() { + String newUsername = "newUsername"; + when(this.userRepository.findById(USER_UUID)).thenReturn(null); + + UserServiceFailureException exception = assertThrows(UserServiceFailureException.class, + () -> this.userService.changeUserName(USER_UUID, newUsername)); + + verify(this.userRepository, times(1)).findById(USER_UUID); + verify(this.userRepository, never()).save(any(UserContract.class)); + + assertNotNull(exception); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.USER_NOT_FOUND_MESSAGE), exception.getMessage()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenNullUserIdProvidedOnChangeUsername() { + + UserServiceFailureException actual = assertThrows(UserServiceFailureException.class, + () -> this.userService.changeUserName(null, "newUsername")); + + verify(this.userRepository, never()).findById(anyString()); + + assertNotNull(actual); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.INVALID_IDENTIFIER_ERROR_MESSAGE), actual.getMessage()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenEmptyUserIdProvidedOnChangeUsername() { + + UserServiceFailureException actual = assertThrows(UserServiceFailureException.class, + () -> this.userService.changeUserName(" ", "newUsername")); + + verify(this.userRepository, never()).findById(anyString()); + + assertNotNull(actual); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.INVALID_IDENTIFIER_ERROR_MESSAGE), actual.getMessage()); + } + + @Test + void shouldThrowUserServiceFailureExceptionWhenInvalidUserIdProvidedOnChangeUsername() { + + UserServiceFailureException actual = assertThrows(UserServiceFailureException.class, + () -> this.userService.changeUserName("ID", "newUsername")); + + verify(this.userRepository, never()).findById(anyString()); + + assertNotNull(actual); + assertEquals(UserServiceFailureException.ERROR.buildMessage(UserService.INVALID_IDENTIFIER_ERROR_MESSAGE), actual.getMessage()); + assertEquals(UuidUtilsException.class, actual.getCause().getClass()); + } + +} diff --git a/src/test/java/diegosneves/github/conectardoacoes/core/utils/ValidationUtilsTest.java b/src/test/java/diegosneves/github/conectardoacoes/core/utils/ValidationUtilsTest.java new file mode 100644 index 0000000..1cbe45d --- /dev/null +++ b/src/test/java/diegosneves/github/conectardoacoes/core/utils/ValidationUtilsTest.java @@ -0,0 +1,98 @@ +package diegosneves.github.conectardoacoes.core.utils; + +import diegosneves.github.conectardoacoes.core.exception.ValidationUtilsException; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.*; + +class ValidationUtilsTest { + + public static final String NULL_VALUE_ERROR_MESSAGE = "Valor Nulo"; + public static final String EMPTY_STRING = " "; + public static final String EMPTY_VALUE_ERROR = "Valor vazio"; + public static final String UNEXPECTED_EXCEPTION = "Exceção não deveria ter sido lançada"; + + private String value; + + @BeforeEach + void setUp() { + this.value = EMPTY_STRING; + } + + @Test + void isNullOrEmpty() { + this.value = "Teste"; + try { + ValidationUtils.checkNotNullAndNotEmptyOrThrowException(this.value, EMPTY_VALUE_ERROR, IllegalArgumentException.class); + } catch (Exception e) { + fail(UNEXPECTED_EXCEPTION); + } + } + + @Test + void checkStringNotNull() { + this.value = null; + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> ValidationUtils.checkNotNullAndNotEmptyOrThrowException(this.value, NULL_VALUE_ERROR_MESSAGE, IllegalArgumentException.class)); + + assertNotNull(exception); + assertEquals(NULL_VALUE_ERROR_MESSAGE, exception.getMessage()); + } + + @Test + void checkStringNotNullWithValidateUtilsException() { + this.value = null; + + ValidationUtilsException exception = assertThrows(ValidationUtilsException.class, + () -> ValidationUtils.checkNotNullAndNotEmptyOrThrowException(this.value, NULL_VALUE_ERROR_MESSAGE, NoMethodFoundException.class)); + + assertNotNull(exception); + assertEquals(ValidationUtilsException.ERROR.buildMessage(NoMethodFoundException.class.getSimpleName()), exception.getMessage()); + } + + @Test + void checkStringNotEmptyWithValidateUtilsException() { + + ValidationUtilsException exception = assertThrows(ValidationUtilsException.class, + () -> ValidationUtils.checkNotNullAndNotEmptyOrThrowException(this.value, EMPTY_VALUE_ERROR, NoMethodFoundException.class)); + + assertNotNull(exception); + assertEquals(ValidationUtilsException.ERROR.buildMessage(NoMethodFoundException.class.getSimpleName()), exception.getMessage()); + } + + @Test + void checkStringNotEmpty() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> ValidationUtils.checkNotNullAndNotEmptyOrThrowException(this.value, EMPTY_VALUE_ERROR, IllegalArgumentException.class)); + + assertNotNull(exception); + assertEquals(EMPTY_VALUE_ERROR, exception.getMessage()); + } + + @Test + @SneakyThrows + void shouldThrowNoSuchMethodException() { + Method method = ValidationUtils.class.getDeclaredMethod("throwException", String.class, Class.class); + method.setAccessible(true); + + InvocationTargetException exception = assertThrows(InvocationTargetException.class, + () -> method.invoke(ValidationUtils.class, this.value, NoMethodFoundException.class)); + + assertNotNull(exception); + assertInstanceOf(NoSuchMethodException.class, exception.getCause().getCause()); + assertEquals(ValidationUtilsException.ERROR.buildMessage(NoMethodFoundException.class.getSimpleName()), exception.getTargetException().getMessage()); + } + + static class NoMethodFoundException extends RuntimeException { + + public NoMethodFoundException(Throwable cause) { + super(cause); + } + } + +}