diff --git a/README.md b/README.md index 034b949..0b4d69e 100644 --- a/README.md +++ b/README.md @@ -146,3 +146,42 @@ docker compose up --build | `MAX_FILE_SIZE` / `MAX_REQUEST_SIZE` | Límites upload | `50MB` | | `LOG_LEVEL_ROOT` / `LOG_LEVEL_OUTFITLAB` | Logging | `INFO` / `DEBUG` | | `SERVER_PORT` | Puerto interno | `8080` | + +## Testing +Utilizacion de Patron Given - When - Then +## - Given: preparar datos y mocks. Ej: + + private UserSubscriptionEntity givenExistingSubscriptionReturningEntity(String email, String planCode, int combinations) { + UserSubscriptionEntity entity = givenSubscriptionEntity(email, planCode, 10L, combinations); + when(userSubscriptionJpaRepository.findByUserEmail(email)) + .thenReturn(Optional.of(entity)); + return entity; + } +## - WHEN: ejecutar el método a probar. Ej: + + private void whenFindingSubscriptionByUserEmail(String email, UserSubscriptionEntity entity) { + when(userSubscriptionJpaRepository.findByUserEmail(email)).thenReturn(Optional.of(entity)); + } + +## - THEN: verificar resultados o excepciones. Ej: + + private void thenSubscriptionShouldHaveCombinationsUsed(UserSubscriptionModel model, int expected) { + assertThat(model).isNotNull(); + assertThat(model.getCombinationsUsed()).isEqualTo(expected); + } + +## - Test completo del metodo + @Test + void shouldReturnSubscriptionModelWhenEmailExists() throws SubscriptionNotFoundException { + String email = "test@mail.com"; + UserSubscriptionEntity entity = givenExistingSubscriptionReturningEntity(email, "BASIC", 10); + + whenFindingSubscriptionByUserEmail(email, entity); + + UserSubscriptionModel result = whenFindByUserEmail(email); + + thenSubscriptionShouldHaveCombinationsUsed(result, 10); + verify(userSubscriptionJpaRepository).findByUserEmail(email); + } + + diff --git a/src/main/java/com/outfitlab/project/domain/interfaces/repositories/GarmentRecomendationRepository.java b/src/main/java/com/outfitlab/project/domain/interfaces/repositories/GarmentRecomendationRepository.java index 21eeb12..416b3c0 100644 --- a/src/main/java/com/outfitlab/project/domain/interfaces/repositories/GarmentRecomendationRepository.java +++ b/src/main/java/com/outfitlab/project/domain/interfaces/repositories/GarmentRecomendationRepository.java @@ -12,4 +12,6 @@ public interface GarmentRecomendationRepository { void deleteRecomendationsByGarmentCode(String garmentCode); void createSugerenciasByGarmentCode(String garmentCode, String type, List sugerencias); + + void deleteRecomendationByGarmentsCode(String garmentCodePrimary, String garmentCodeSecondary, String type); } diff --git a/src/main/java/com/outfitlab/project/domain/interfaces/repositories/PrendaOcacionRepository.java b/src/main/java/com/outfitlab/project/domain/interfaces/repositories/PrendaOcacionRepository.java deleted file mode 100644 index d89cf62..0000000 --- a/src/main/java/com/outfitlab/project/domain/interfaces/repositories/PrendaOcacionRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.outfitlab.project.domain.interfaces.repositories; - -public interface PrendaOcacionRepository { - - void deleteAllPrendaOcacionByGarment(String garmentCode); -} diff --git a/src/main/java/com/outfitlab/project/domain/interfaces/repositories/PrendaOcasionRepository.java b/src/main/java/com/outfitlab/project/domain/interfaces/repositories/PrendaOcasionRepository.java new file mode 100644 index 0000000..2324506 --- /dev/null +++ b/src/main/java/com/outfitlab/project/domain/interfaces/repositories/PrendaOcasionRepository.java @@ -0,0 +1,6 @@ +package com.outfitlab.project.domain.interfaces.repositories; + +public interface PrendaOcasionRepository { + + void deleteAllPrendaOcasionByGarment(String garmentCode); +} diff --git a/src/main/java/com/outfitlab/project/domain/model/UserModel.java b/src/main/java/com/outfitlab/project/domain/model/UserModel.java index 55af922..a9450a0 100644 --- a/src/main/java/com/outfitlab/project/domain/model/UserModel.java +++ b/src/main/java/com/outfitlab/project/domain/model/UserModel.java @@ -108,6 +108,10 @@ public UserModel(long id, String email) { this.email = email; } + public UserModel() { + + } + /* * public String getPassword() { * return ""; diff --git a/src/main/java/com/outfitlab/project/domain/useCases/recomendations/DeleteAllPrendaOcacionRelatedToGarment.java b/src/main/java/com/outfitlab/project/domain/useCases/recomendations/DeleteAllPrendaOcacionRelatedToGarment.java index cb4c166..8afcf97 100644 --- a/src/main/java/com/outfitlab/project/domain/useCases/recomendations/DeleteAllPrendaOcacionRelatedToGarment.java +++ b/src/main/java/com/outfitlab/project/domain/useCases/recomendations/DeleteAllPrendaOcacionRelatedToGarment.java @@ -1,16 +1,16 @@ package com.outfitlab.project.domain.useCases.recomendations; -import com.outfitlab.project.domain.interfaces.repositories.PrendaOcacionRepository; +import com.outfitlab.project.domain.interfaces.repositories.PrendaOcasionRepository; public class DeleteAllPrendaOcacionRelatedToGarment { - private final PrendaOcacionRepository prendaOcacionRepository; + private final PrendaOcasionRepository prendaOcacionRepository; - public DeleteAllPrendaOcacionRelatedToGarment(PrendaOcacionRepository prendaOcacionRepository){ + public DeleteAllPrendaOcacionRelatedToGarment(PrendaOcasionRepository prendaOcacionRepository){ this.prendaOcacionRepository = prendaOcacionRepository; } public void execute(String garmentCode){ - this.prendaOcacionRepository.deleteAllPrendaOcacionByGarment(garmentCode); + this.prendaOcacionRepository.deleteAllPrendaOcasionByGarment(garmentCode); } } diff --git a/src/main/java/com/outfitlab/project/domain/useCases/recomendations/DeleteRecomendationByPrimaryAndSecondaryGarmentCode.java b/src/main/java/com/outfitlab/project/domain/useCases/recomendations/DeleteRecomendationByPrimaryAndSecondaryGarmentCode.java new file mode 100644 index 0000000..6d580c0 --- /dev/null +++ b/src/main/java/com/outfitlab/project/domain/useCases/recomendations/DeleteRecomendationByPrimaryAndSecondaryGarmentCode.java @@ -0,0 +1,17 @@ +package com.outfitlab.project.domain.useCases.recomendations; + +import com.outfitlab.project.domain.interfaces.repositories.GarmentRecomendationRepository; + +public class DeleteRecomendationByPrimaryAndSecondaryGarmentCode { + + private final GarmentRecomendationRepository garmentRecomendationRepository; + + public DeleteRecomendationByPrimaryAndSecondaryGarmentCode(GarmentRecomendationRepository garmentRecomendationRepository) { + this.garmentRecomendationRepository = garmentRecomendationRepository; + } + + public String execute(String garmentCodePrimary, String garmentCodeSecondary, String type) { + this.garmentRecomendationRepository.deleteRecomendationByGarmentsCode(garmentCodePrimary, garmentCodeSecondary, type); + return "Sugerencia eliminada con éxito."; + } +} diff --git a/src/main/java/com/outfitlab/project/infrastructure/config/GarmentConfig.java b/src/main/java/com/outfitlab/project/infrastructure/config/GarmentConfig.java index 8dcee9e..39b0ab3 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/config/GarmentConfig.java +++ b/src/main/java/com/outfitlab/project/infrastructure/config/GarmentConfig.java @@ -19,7 +19,7 @@ public class GarmentConfig { @Bean public GarmentRepository garmentRepository(GarmentJpaRepository jpaRepository, BrandJpaRepository brandJpaRepository, ClimaJpaRepository climaJpaRepository, ColorJpaRepository colorJpaRepository, - OcacionJpaRepository ocacionJpaRepository) { + OcasionJpaRepository ocacionJpaRepository) { return new GarmentRepositoryImpl(jpaRepository, brandJpaRepository, colorJpaRepository, climaJpaRepository, ocacionJpaRepository); } @@ -41,7 +41,7 @@ public DeleteAllFavoritesRelatedToGarment deleteAllFavoritesRelatedToGarment(Use } @Bean - public DeleteAllPrendaOcacionRelatedToGarment deleteAllPrendaOcacionRelatedToGarment(PrendaOcacionRepository prendaOcacionRepository){ + public DeleteAllPrendaOcacionRelatedToGarment deleteAllPrendaOcacionRelatedToGarment(PrendaOcasionRepository prendaOcacionRepository){ return new DeleteAllPrendaOcacionRelatedToGarment(prendaOcacionRepository); } diff --git a/src/main/java/com/outfitlab/project/infrastructure/config/RecomendationConfig.java b/src/main/java/com/outfitlab/project/infrastructure/config/RecomendationConfig.java index 6043a48..8689c51 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/config/RecomendationConfig.java +++ b/src/main/java/com/outfitlab/project/infrastructure/config/RecomendationConfig.java @@ -27,8 +27,13 @@ public DeleteGarmentRecomentationsRelatedToGarment deleteGarmentRecomentationsRe } @Bean - public PrendaOcacionRepository prendaOcacionRepository(PrendaOcacionJpaRepository prendaOcacionJpaRepository){ - return new PrendaOcacionRepositoryImpl(prendaOcacionJpaRepository); + public PrendaOcasionRepository prendaOcacionRepository(PrendaOcasionJpaRepository prendaOcacionJpaRepository){ + return new PrendaOcasionRepositoryImpl(prendaOcacionJpaRepository); + } + + @Bean + public DeleteRecomendationByPrimaryAndSecondaryGarmentCode deleteRecomendationByPrimaryAndSecondaryGarmentCode(GarmentRecomendationRepository recomendationRepository){ + return new DeleteRecomendationByPrimaryAndSecondaryGarmentCode(recomendationRepository); } @Bean @@ -37,7 +42,7 @@ public CreateSugerenciasByGarmentsCode createSugerenciasByGarmentsCode(GarmentRe } @Bean - public DeleteAllPrendaOcacionRelatedToGarment deleteAllPrendaOcacionRelatedToGarment(PrendaOcacionRepository prendaOcacionRepository){ + public DeleteAllPrendaOcacionRelatedToGarment deleteAllPrendaOcacionRelatedToGarment(PrendaOcasionRepository prendaOcacionRepository){ return new DeleteAllPrendaOcacionRelatedToGarment(prendaOcacionRepository); } @@ -47,8 +52,8 @@ public ColorRepository colorRepository(ColorJpaRepository colorJpaRepository){ } @Bean - public OcacionRepository ocacionRepository(OcacionJpaRepository ocacionJpaRepository){ - return new OcacionRepositoryImpl(ocacionJpaRepository); + public OcacionRepository ocacionRepository(OcasionJpaRepository ocacionJpaRepository){ + return new OcasionRepositoryImpl(ocacionJpaRepository); } @Bean diff --git a/src/main/java/com/outfitlab/project/infrastructure/model/MarcaEntity.java b/src/main/java/com/outfitlab/project/infrastructure/model/MarcaEntity.java index 0510472..d1f8cd3 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/model/MarcaEntity.java +++ b/src/main/java/com/outfitlab/project/infrastructure/model/MarcaEntity.java @@ -40,7 +40,10 @@ public class MarcaEntity { private String urlSite; public MarcaEntity(){} - + public MarcaEntity(String codigoMarca, String nombre) { + this.codigoMarca = codigoMarca; + this.nombre = nombre; + } public MarcaEntity(String codigoMarca, String nombre, String logoUrl, LocalDateTime createdAt, LocalDateTime updatedAt) { this.codigoMarca = codigoMarca; this.nombre = nombre; diff --git a/src/main/java/com/outfitlab/project/infrastructure/model/SubscriptionEntity.java b/src/main/java/com/outfitlab/project/infrastructure/model/SubscriptionEntity.java index 07be44c..17d28a6 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/model/SubscriptionEntity.java +++ b/src/main/java/com/outfitlab/project/infrastructure/model/SubscriptionEntity.java @@ -47,6 +47,11 @@ public class SubscriptionEntity { @Column(name = "has_advanced_reports", columnDefinition = "boolean default false") private boolean hasAdvancedReports = false; + public SubscriptionEntity(String name, String planCode) { + this.name = name; + this.planCode = planCode; + } + public Long getId() { return id; } diff --git a/src/main/java/com/outfitlab/project/infrastructure/model/UserEntity.java b/src/main/java/com/outfitlab/project/infrastructure/model/UserEntity.java index 4aa320f..42f3b2d 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/model/UserEntity.java +++ b/src/main/java/com/outfitlab/project/infrastructure/model/UserEntity.java @@ -63,6 +63,9 @@ public class UserEntity implements UserDetails { public UserEntity() { } + public UserEntity(String email) { + this.email = email; + } public UserEntity(String name, String lastName, String email, String satulation, String secondName, Integer years, String password) { diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/ColorRepositoryImpl.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/ColorRepositoryImpl.java index a6d9b9c..816a8e9 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/ColorRepositoryImpl.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/ColorRepositoryImpl.java @@ -21,7 +21,7 @@ public ColorRepositoryImpl(ColorJpaRepository colorJpaRepository) { @Override public List findAllColores() { List colors = this.colorJpaRepository.findAll(); - if (colors.isEmpty()) throw new ColorNotFoundException("No encontramos ocaciones."); + if (colors.isEmpty()) throw new ColorNotFoundException("No encontramos ocasiones."); return colors.stream().map(ColorEntity::convertEntityToModel) .toList(); } diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/GarmentRepositoryImpl.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/GarmentRepositoryImpl.java index cda1ca7..1d024ec 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/GarmentRepositoryImpl.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/GarmentRepositoryImpl.java @@ -1,7 +1,6 @@ package com.outfitlab.project.infrastructure.repositories; import com.outfitlab.project.domain.exceptions.GarmentNotFoundException; -import com.outfitlab.project.domain.interfaces.repositories.ClimaRepository; import com.outfitlab.project.domain.interfaces.repositories.GarmentRepository; import com.outfitlab.project.domain.model.ClimaModel; import com.outfitlab.project.domain.model.ColorModel; @@ -20,14 +19,14 @@ public class GarmentRepositoryImpl implements GarmentRepository { - private final int PAGE_SIZE = 20; + private final int PAGE_SIZE = 10; private final GarmentJpaRepository garmentJpaRepository; private final BrandJpaRepository brandJpaRepository; private final ColorJpaRepository colorJpaRepository; private final ClimaJpaRepository climaJpaRepository; - private final OcacionJpaRepository ocacionJpaRepository; + private final OcasionJpaRepository ocacionJpaRepository; - public GarmentRepositoryImpl(GarmentJpaRepository garmentJpaRepository, BrandJpaRepository brandJpaRepository, ColorJpaRepository colorJpaRepository, ClimaJpaRepository climaJpaRepository, OcacionJpaRepository ocacionJpaRepository) { + public GarmentRepositoryImpl(GarmentJpaRepository garmentJpaRepository, BrandJpaRepository brandJpaRepository, ColorJpaRepository colorJpaRepository, ClimaJpaRepository climaJpaRepository, OcasionJpaRepository ocacionJpaRepository) { this.garmentJpaRepository = garmentJpaRepository; this.brandJpaRepository = brandJpaRepository; this.colorJpaRepository = colorJpaRepository; diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/GeminiClientImpl.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/GeminiClientImpl.java index 2433e6a..020dfa4 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/GeminiClientImpl.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/GeminiClientImpl.java @@ -104,7 +104,7 @@ private String buildRequestBody(String peticionUsuario) throws com.fasterxml.jac return objectMapper.writeValueAsString(directRequest); } - private String extractJsonContent(String apiResponseBody) throws Exception { + protected String extractJsonContent(String apiResponseBody) throws Exception { Map responseMap = objectMapper.readValue(apiResponseBody, Map.class); List> candidates = (List>) responseMap.get("candidates"); diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/OcacionRepositoryImpl.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/OcasionRepositoryImpl.java similarity index 60% rename from src/main/java/com/outfitlab/project/infrastructure/repositories/OcacionRepositoryImpl.java rename to src/main/java/com/outfitlab/project/infrastructure/repositories/OcasionRepositoryImpl.java index bffcb9a..585cff1 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/OcacionRepositoryImpl.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/OcasionRepositoryImpl.java @@ -1,26 +1,25 @@ package com.outfitlab.project.infrastructure.repositories; -import com.outfitlab.project.domain.exceptions.ClimaNotFoundException; import com.outfitlab.project.domain.exceptions.OcasionNotFoundException; import com.outfitlab.project.domain.interfaces.repositories.OcacionRepository; import com.outfitlab.project.domain.model.OcasionModel; -import com.outfitlab.project.infrastructure.model.ClimaEntity; import com.outfitlab.project.infrastructure.model.OcasionEntity; -import com.outfitlab.project.infrastructure.repositories.interfaces.OcacionJpaRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.OcasionJpaRepository; import java.util.List; -public class OcacionRepositoryImpl implements OcacionRepository { +public class OcasionRepositoryImpl implements OcacionRepository { - private final OcacionJpaRepository ocacionJpaRepository; - public OcacionRepositoryImpl(OcacionJpaRepository ocacionJpaRepository) { - this.ocacionJpaRepository = ocacionJpaRepository; + private final OcasionJpaRepository ocasionJpaRepository; + + public OcasionRepositoryImpl(OcasionJpaRepository ocasionJpaRepository) { + this.ocasionJpaRepository = ocasionJpaRepository; } @Override public List findAllOcasiones() { - List ocaciones = this.ocacionJpaRepository.findAll(); + List ocaciones = this.ocasionJpaRepository.findAll(); if (ocaciones.isEmpty()) throw new OcasionNotFoundException("No encontramos ocaciones."); return ocaciones.stream().map(OcasionEntity::convertEntityToModel) .toList(); diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/PrendaOcacionRepositoryImpl.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/PrendaOcacionRepositoryImpl.java deleted file mode 100644 index 0475ae1..0000000 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/PrendaOcacionRepositoryImpl.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.outfitlab.project.infrastructure.repositories; - -import com.outfitlab.project.domain.interfaces.repositories.PrendaOcacionRepository; -import com.outfitlab.project.infrastructure.repositories.interfaces.PrendaOcacionJpaRepository; - -public class PrendaOcacionRepositoryImpl implements PrendaOcacionRepository { - - private final PrendaOcacionJpaRepository prendaOcacionJpaRepository; - - public PrendaOcacionRepositoryImpl(PrendaOcacionJpaRepository prendaOcacionJpaRepository){ - this.prendaOcacionJpaRepository = prendaOcacionJpaRepository; - } - - @Override - public void deleteAllPrendaOcacionByGarment(String garmentCode) { - this.prendaOcacionJpaRepository.deleteAllByGarmentCode(garmentCode); - } -} diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/PrendaOcasionRepositoryImpl.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/PrendaOcasionRepositoryImpl.java new file mode 100644 index 0000000..0e527eb --- /dev/null +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/PrendaOcasionRepositoryImpl.java @@ -0,0 +1,18 @@ +package com.outfitlab.project.infrastructure.repositories; + +import com.outfitlab.project.domain.interfaces.repositories.PrendaOcasionRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.PrendaOcasionJpaRepository; + +public class PrendaOcasionRepositoryImpl implements PrendaOcasionRepository { + + private final PrendaOcasionJpaRepository prendaOcasionJpaRepository; + + public PrendaOcasionRepositoryImpl(PrendaOcasionJpaRepository prendaOcasionJpaRepository){ + this.prendaOcasionJpaRepository = prendaOcasionJpaRepository; + } + + @Override + public void deleteAllPrendaOcasionByGarment(String garmentCode) { + this.prendaOcasionJpaRepository.deleteAllByGarmentCode(garmentCode); + } +} diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/RecomendationRepositoryImpl.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/RecomendationRepositoryImpl.java index 4047fe2..e9c8dfe 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/RecomendationRepositoryImpl.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/RecomendationRepositoryImpl.java @@ -45,6 +45,14 @@ public void createSugerenciasByGarmentCode(String garmentCode, String type, List this.recomendationJpaRepository.saveAll(sugerenciasToCeate); } + @Override + public void deleteRecomendationByGarmentsCode(String garmentCodePrimary, String garmentCodeSecondary, String type) { + if (type.equalsIgnoreCase("inferior")) + this.recomendationJpaRepository.deleteWhenPrimaryIsBottom(garmentCodePrimary, garmentCodeSecondary); + else + this.recomendationJpaRepository.deleteWhenPrimaryIsTop(garmentCodePrimary, garmentCodeSecondary); + } + private List getGarmentRecomendationEntitiesToCreate(String type, List sugerencias, PrendaEntity prendaPrincipal) { List sugerenciasToCeate = new ArrayList<>(); diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/TripoRepositoryImpl.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/TripoRepositoryImpl.java index 2fc77d5..5456965 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/TripoRepositoryImpl.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/TripoRepositoryImpl.java @@ -16,6 +16,7 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; @@ -61,7 +62,13 @@ public Map requestUploadImagenToTripo(String url) throws ErrorRe uploadResult.put("originalFilename", originalFilename); uploadResult.put("fileExtension", getFileExtension(originalFilename)); - ResponseEntity response = generateRequestToUploadImageToTripo(imageFile, originalFilename); + ResponseEntity response; + try { + response = generateRequestToUploadImageToTripo(imageFile, originalFilename); + } catch (HttpStatusCodeException e) { + log.warn("Tripo upload failed. status={} body={}", e.getStatusCode(), e.getResponseBodyAsString()); + throw new ErrorUploadImageToTripoException("Tripo devolvió " + e.getStatusCode() + ": " + e.getResponseBodyAsString()); + } checkIfResponseIsOk(response); uploadResult.put("imageToken", tryGetImageToken(response)); @@ -76,7 +83,13 @@ public String requestGenerateGlbToTripo(Map uploadData) throws E String taskBody = tryGetTaskBody(mapper, bodyMap); HttpEntity taskEntity = new HttpEntity<>(taskBody, taskHeaders); - ResponseEntity taskResponse = restTemplate.postForEntity(taskUrl, taskEntity, String.class); + ResponseEntity taskResponse; + try { + taskResponse = restTemplate.postForEntity(taskUrl, taskEntity, String.class); + } catch (HttpStatusCodeException e) { + log.warn("Tripo task creation failed. status={} body={}", e.getStatusCode(), e.getResponseBodyAsString()); + throw new ErrorGenerateGlbException("Tripo devolvió " + e.getStatusCode() + ": " + e.getResponseBodyAsString()); + } checkIfStatusResponseIsOk(taskResponse); return tryGetTaskIdFromResponse(mapper, taskResponse); @@ -145,7 +158,7 @@ private void checkIfExtensionIsValid(String extension) { } } - private MultipartFile convertImageUrlToMultipartFile(String imageUrl) throws ErrorUploadImageToTripoException { + protected MultipartFile convertImageUrlToMultipartFile(String imageUrl) throws ErrorUploadImageToTripoException { InputStream inputStream = null; try { URL url = new URL(imageUrl); @@ -234,7 +247,7 @@ private Map getHttpBodyImageToModelTripo(Map upl return bodyMap; } - private HttpHeaders getHttpHeaders(MediaType type) { + protected HttpHeaders getHttpHeaders(MediaType type) { HttpHeaders taskHeaders = new HttpHeaders(); taskHeaders.setContentType(type); taskHeaders.setBearerAuth(tripoApiKey); @@ -268,7 +281,7 @@ private static void checkIfResponseIsOk(ResponseEntity response) throws } } - private ResponseEntity generateRequestToUploadImageToTripo(MultipartFile imageFile, String originalFilename) throws ErroBytesException { + protected ResponseEntity generateRequestToUploadImageToTripo(MultipartFile imageFile, String originalFilename) throws ErroBytesException { MultiValueMap body = new LinkedMultiValueMap<>(); body.add("file", tryGetByteArrayResourceFromImage(imageFile, originalFilename)); HttpHeaders headers = getHttpHeaders(MediaType.MULTIPART_FORM_DATA); @@ -288,7 +301,7 @@ private String tryGetImageToken(ResponseEntity response) throws ErrorRea return imageToken; } - private ResponseEntity requestTripoTaskStatus(String taskId, HttpEntity entityWithTaskHeaders) { + protected ResponseEntity requestTripoTaskStatus(String taskId, HttpEntity entityWithTaskHeaders) { return restTemplate.exchange( taskUrl + "/" + taskId, HttpMethod.GET, diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/UploadImageRepositoryImpl.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/UploadImageRepositoryImpl.java index 42fee9f..ae4c8cb 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/UploadImageRepositoryImpl.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/UploadImageRepositoryImpl.java @@ -24,6 +24,13 @@ public class UploadImageRepositoryImpl implements com.outfitlab.project.domain.i @Value("${AWS_BUCKET_NAME}") private String bucketName; + public UploadImageRepositoryImpl(S3Client s3Client, String region, String bucketName) { + this.s3Client = s3Client; + this.region = region; + this.bucketName = bucketName; + } + + @Override public String uploadFile(MultipartFile file, String folder) { try { @@ -73,4 +80,8 @@ private String extractKeyFromUrl(String url) { if (index == -1) {throw new IllegalArgumentException("URL de S3 inválida: " + url);} return url.substring(index + ".amazonaws.com/".length()); } + + + public void setRegion(String region) { this.region = region; } + public void setBucketName(String bucketName) { this.bucketName = bucketName; } } diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/UserGarmentFavoriteRepositoryImpl.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/UserGarmentFavoriteRepositoryImpl.java index 73cac56..bf240f0 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/UserGarmentFavoriteRepositoryImpl.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/UserGarmentFavoriteRepositoryImpl.java @@ -19,7 +19,7 @@ public class UserGarmentFavoriteRepositoryImpl implements UserGarmentFavoriteRepository { - private final int PAGE_SIZE = 20; + private final int PAGE_SIZE = 10; private final UserGarmentFavoriteJpaRepository userGarmentFavoriteJpaRepository; private final UserJpaRepository userJpaRepository; private final GarmentJpaRepository garmentJpaRepository; diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/UserRepositoryImpl.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/UserRepositoryImpl.java index ede41b0..e744c6d 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/UserRepositoryImpl.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/UserRepositoryImpl.java @@ -11,9 +11,7 @@ import com.outfitlab.project.infrastructure.repositories.interfaces.UserJpaRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; - import java.util.List; -import java.util.Optional; import static com.outfitlab.project.domain.enums.Role.*; // ← Usar enums diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/GarmentJpaRepository.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/GarmentJpaRepository.java index 502db87..4682080 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/GarmentJpaRepository.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/GarmentJpaRepository.java @@ -7,7 +7,9 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/OcacionJpaRepository.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/OcasionJpaRepository.java similarity index 85% rename from src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/OcacionJpaRepository.java rename to src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/OcasionJpaRepository.java index b09de9a..575d4df 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/OcacionJpaRepository.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/OcasionJpaRepository.java @@ -5,7 +5,7 @@ import java.util.Optional; -public interface OcacionJpaRepository extends JpaRepository { +public interface OcasionJpaRepository extends JpaRepository { Optional findByNombre(String nombre); Optional findOcasionEntityByNombre(String nombre); diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/PrendaOcacionJpaRepository.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/PrendaOcasionJpaRepository.java similarity index 91% rename from src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/PrendaOcacionJpaRepository.java rename to src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/PrendaOcasionJpaRepository.java index 53cba56..385a7bd 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/PrendaOcacionJpaRepository.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/PrendaOcasionJpaRepository.java @@ -7,7 +7,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; -public interface PrendaOcacionJpaRepository extends JpaRepository { +public interface PrendaOcasionJpaRepository extends JpaRepository { @Transactional @Modifying @Query(""" diff --git a/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/RecomendationJpaRepository.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/RecomendationJpaRepository.java index a905063..8494791 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/RecomendationJpaRepository.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/interfaces/RecomendationJpaRepository.java @@ -23,4 +23,25 @@ public interface RecomendationJpaRepository extends JpaRepository getRecomendations(@PathVariable String garmentCode) { .body(e.getMessage()); } } + + @DeleteMapping("/garment-recomendation/delete") + public ResponseEntity deleteRecomendation(@RequestParam String garmentCodePrimary, @RequestParam String garmentCodeSecondary, @RequestParam String type) { + try { + return ResponseEntity.ok(this.deleteRecomendationByPrimaryAndSecondaryGarmentCode.execute(garmentCodePrimary, garmentCodeSecondary, type)); + } catch (Exception e) { + return ResponseEntity + .status(404) + .body(e.getMessage()); + } + } } diff --git a/src/main/java/com/outfitlab/project/presentation/dto/StatusResponse.java b/src/main/java/com/outfitlab/project/presentation/dto/StatusResponse.java index 12fa8d1..af149d1 100644 --- a/src/main/java/com/outfitlab/project/presentation/dto/StatusResponse.java +++ b/src/main/java/com/outfitlab/project/presentation/dto/StatusResponse.java @@ -1,7 +1,9 @@ package com.outfitlab.project.presentation.dto; -import java.util.List; +import lombok.AllArgsConstructor; +import java.util.List; +@AllArgsConstructor public class StatusResponse { private String id; private String status; diff --git a/src/test/java/com/outfitlab/project/domain/useCases/brand/ActivateBrandTest.java b/src/test/java/com/outfitlab/project/domain/useCases/brand/ActivateBrandTest.java new file mode 100644 index 0000000..d0d3005 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/brand/ActivateBrandTest.java @@ -0,0 +1,75 @@ +package com.outfitlab.project.domain.useCases.brand; + +import com.outfitlab.project.domain.exceptions.BrandsNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.BrandRepository; +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.model.BrandModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class ActivateBrandTest { + + private BrandRepository brandRepository = mock(BrandRepository.class); + private UserRepository userRepository = mock(UserRepository.class); + private ActivateBrand activateBrand; + + private final String BRAND_CODE = "adidas"; + private final String USER_EMAIL = "adidas@corp.com"; + private final String SUCCESS_MESSAGE = "Marca activada con éxito."; + + @BeforeEach + void setUp() { + activateBrand = new ActivateBrand(brandRepository, userRepository); + } + + + @Test + public void shouldActivateUserAndReturnSuccessMessageWhenBrandExists() throws BrandsNotFoundException { + givenBrandExistsAndUserEmailIsAvailable(BRAND_CODE, USER_EMAIL); + + String result = whenExecuteActivateBrand(BRAND_CODE); + + thenResultIsSuccessMessage(result); + thenUserWasActivated(USER_EMAIL); + } + + @Test + public void shouldThrowBrandsNotFoundExceptionWhenBrandDoesNotExist() { + givenBrandDoesNotExist(BRAND_CODE); + + assertThrows(BrandsNotFoundException.class, () -> activateBrand.execute(BRAND_CODE)); + + thenActivationWasNeverCalled(); + } + + + //privadosss + private void givenBrandExistsAndUserEmailIsAvailable(String brandCode, String userEmail) { + when(brandRepository.findByBrandCode(brandCode)).thenReturn(new BrandModel()); + when(userRepository.getEmailUserRelatedToBrandByBrandCode(brandCode)).thenReturn(userEmail); + } + + private void givenBrandDoesNotExist(String brandCode) { + when(brandRepository.findByBrandCode(brandCode)).thenReturn(null); + } + + private String whenExecuteActivateBrand(String brandCode) { + return activateBrand.execute(brandCode); + } + + private void thenResultIsSuccessMessage(String result) { + assertEquals(SUCCESS_MESSAGE, result, "El mensaje de retorno debe ser de activación exitosa."); + } + + private void thenUserWasActivated(String userEmail) { + verify(userRepository, times(1)).getEmailUserRelatedToBrandByBrandCode(BRAND_CODE); + verify(userRepository, times(1)).activateUser(userEmail); + } + + private void thenActivationWasNeverCalled() { + verify(userRepository, never()).activateUser(anyString()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/brand/CreateBrandTest.java b/src/test/java/com/outfitlab/project/domain/useCases/brand/CreateBrandTest.java new file mode 100644 index 0000000..9ff6ac4 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/brand/CreateBrandTest.java @@ -0,0 +1,63 @@ +package com.outfitlab.project.domain.useCases.brand; + +import com.outfitlab.project.domain.interfaces.repositories.BrandRepository; +import com.outfitlab.project.domain.model.BrandModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class CreateBrandTest { + + private BrandRepository brandRepository = mock(BrandRepository.class); + private CreateBrand createBrand; + + private final String BRAND_NAME = "Zara Shop"; + private final String LOGO_URL = "http://logo.com/zara-shop.png"; + private final String URL_SITE = "http://zarashop.com"; + private final String EXPECTED_BRAND_CODE = "zara_shop"; + private final String EXPECTED_RESPONSE = "brand-id-123"; + + @BeforeEach + void setUp() { + createBrand = new CreateBrand(brandRepository); + } + + + @Test + public void shouldCreateBrandModelWithFormattedCodeAndCallRepository() { + givenRepositoryReturnsExpectedResponse(EXPECTED_RESPONSE); + + String result = whenExecuteCreateBrand(BRAND_NAME, LOGO_URL, URL_SITE); + + thenRepositoryWasCalledWithCorrectBrandModel(BRAND_NAME, LOGO_URL, URL_SITE, EXPECTED_BRAND_CODE); + thenResultIsExpected(result, EXPECTED_RESPONSE); + } + + + //privadoss + private void givenRepositoryReturnsExpectedResponse(String response) { + when(brandRepository.createBrand(any(BrandModel.class))).thenReturn(response); + } + + private String whenExecuteCreateBrand(String brandName, String logoUrl, String urlSite) { + return createBrand.execute(brandName, logoUrl, urlSite); + } + + private void thenResultIsExpected(String actualResult, String expectedResponse) { + assertEquals(expectedResponse, actualResult, "El resultado debe ser la respuesta del repositorio."); + } + + private void thenRepositoryWasCalledWithCorrectBrandModel(String expectedName, String expectedLogoUrl, String expectedUrlSite, String expectedCode) { + ArgumentCaptor captor = ArgumentCaptor.forClass(BrandModel.class); + verify(brandRepository, times(1)).createBrand(captor.capture()); + + BrandModel capturedModel = captor.getValue(); + + assertEquals(expectedName, capturedModel.getNombre(), "El nombre de la marca debe coincidir."); + assertEquals(expectedLogoUrl, capturedModel.getLogoUrl(), "La URL del logo debe coincidir."); + assertEquals(expectedUrlSite, capturedModel.getUrlSite(), "La URL del sitio debe coincidir."); + assertEquals(expectedCode, capturedModel.getCodigoMarca(), "El código de marca debe estar formateado."); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/brand/DesactivateBrandTest.java b/src/test/java/com/outfitlab/project/domain/useCases/brand/DesactivateBrandTest.java new file mode 100644 index 0000000..8561161 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/brand/DesactivateBrandTest.java @@ -0,0 +1,74 @@ +package com.outfitlab.project.domain.useCases.brand; + +import com.outfitlab.project.domain.exceptions.BrandsNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.BrandRepository; +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.model.BrandModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class DesactivateBrandTest { + + private BrandRepository brandRepository = mock(BrandRepository.class); + private UserRepository userRepository = mock(UserRepository.class); + private DesactivateBrand desactivateBrand; + + private final String BRAND_CODE = "nike"; + private final String USER_EMAIL = "nike@corp.com"; + private final String SUCCESS_MESSAGE = "Marca desactivada con éxito."; + + @BeforeEach + void setUp() { + desactivateBrand = new DesactivateBrand(brandRepository, userRepository); + } + + + @Test + public void shouldDesactivateUserAndReturnSuccessMessageWhenBrandExists() throws BrandsNotFoundException { + givenBrandExistsAndUserEmailIsAvailable(BRAND_CODE, USER_EMAIL); + + String result = whenExecuteDesactivateBrand(BRAND_CODE); + + thenResultIsSuccessMessage(result); + thenUserWasDesactivated(USER_EMAIL); + } + + @Test + public void shouldThrowBrandsNotFoundExceptionWhenBrandDoesNotExist() { + givenBrandDoesNotExist(BRAND_CODE); + + assertThrows(BrandsNotFoundException.class, () -> desactivateBrand.execute(BRAND_CODE)); + + thenDesactivationWasNeverCalled(); + } + + + //privadoss + private void givenBrandExistsAndUserEmailIsAvailable(String brandCode, String userEmail) { + when(brandRepository.findByBrandCode(brandCode)).thenReturn(new BrandModel()); + when(userRepository.getEmailUserRelatedToBrandByBrandCode(brandCode)).thenReturn(userEmail); + } + + private void givenBrandDoesNotExist(String brandCode) { + when(brandRepository.findByBrandCode(brandCode)).thenReturn(null); + } + + private String whenExecuteDesactivateBrand(String brandCode) { + return desactivateBrand.execute(brandCode); + } + + private void thenResultIsSuccessMessage(String result) { + assertEquals(SUCCESS_MESSAGE, result, "El mensaje de retorno debe ser de desactivación exitosa."); + } + + private void thenUserWasDesactivated(String userEmail) { + verify(userRepository, times(1)).getEmailUserRelatedToBrandByBrandCode(BRAND_CODE); + verify(userRepository, times(1)).desactivateUser(userEmail); + } + + private void thenDesactivationWasNeverCalled() { + verify(userRepository, never()).desactivateUser(anyString()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/brand/GetAllBrandsWithRelatedUsersTest.java b/src/test/java/com/outfitlab/project/domain/useCases/brand/GetAllBrandsWithRelatedUsersTest.java new file mode 100644 index 0000000..4516969 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/brand/GetAllBrandsWithRelatedUsersTest.java @@ -0,0 +1,72 @@ +package com.outfitlab.project.domain.useCases.brand; + +import com.outfitlab.project.domain.exceptions.PageLessThanZeroException; +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.model.dto.UserWithBrandsDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import java.util.Collections; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetAllBrandsWithRelatedUsersTest { + + private UserRepository userRepository = mock(UserRepository.class); + private GetAllBrandsWithRelatedUsers getAllBrandsWithRelatedUsers; + + private final int VALID_PAGE = 5; + private final int INVALID_PAGE = -1; + + @BeforeEach + void setUp() { + getAllBrandsWithRelatedUsers = new GetAllBrandsWithRelatedUsers(userRepository); + } + + + @Test + public void shouldReturnPageOfUsersWithBrandsWhenPageIsValid() { + Page expectedPage = givenRepositoryReturnsPage(VALID_PAGE); + + Page result = whenExecuteGetAllBrands(VALID_PAGE); + + thenReturnedPageMatchesExpected(result, expectedPage); + thenRepositoryWasCalledOnce(VALID_PAGE); + } + + @Test + public void shouldThrowPageLessThanZeroExceptionWhenPageIsNegative() { + assertThrows(PageLessThanZeroException.class, + () -> whenExecuteGetAllBrands(INVALID_PAGE), + "Se esperaba PageLessThanZeroException para una página negativa."); + + thenRepositoryWasNeverCalled(); + } + + + //privadoss + private Page givenRepositoryReturnsPage(int page) { + Page mockPage = new PageImpl<>(Collections.singletonList(new UserWithBrandsDTO())); + + when(userRepository.getAllBrandsWithUserRelated(page)).thenReturn(mockPage); + return mockPage; + } + + private Page whenExecuteGetAllBrands(int page) { + return getAllBrandsWithRelatedUsers.execute(page); + } + + private void thenReturnedPageMatchesExpected(Page actual, Page expected) { + assertNotNull(actual, "La página resultante no debe ser nula."); + assertEquals(expected, actual, "El resultado debe coincidir con la página simulada."); + } + + private void thenRepositoryWasCalledOnce(int page) { + verify(userRepository, times(1)).getAllBrandsWithUserRelated(page); + } + + private void thenRepositoryWasNeverCalled() { + verify(userRepository, never()).getAllBrandsWithUserRelated(anyInt()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/brand/GetNotificationsNewBrandsTest.java b/src/test/java/com/outfitlab/project/domain/useCases/brand/GetNotificationsNewBrandsTest.java new file mode 100644 index 0000000..ac61bb4 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/brand/GetNotificationsNewBrandsTest.java @@ -0,0 +1,73 @@ +package com.outfitlab.project.domain.useCases.brand; + +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.model.dto.UserWithBrandsDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.Collections; +import java.util.List; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetNotificationsNewBrandsTest { + + private UserRepository userRepository = mock(UserRepository.class); + private GetNotificationsNewBrands getNotificationsNewBrands; + + private final int BRAND_COUNT = 3; + + @BeforeEach + void setUp() { + getNotificationsNewBrands = new GetNotificationsNewBrands(userRepository); + } + + + @Test + public void shouldReturnListOfNotApprovedBrandsWhenBrandsExist() { + List expectedList = givenRepositoryReturnsBrands(BRAND_COUNT); + + List result = whenExecuteGetNotifications(); + + thenResultListMatchesExpected(result, expectedList, BRAND_COUNT); + thenRepositoryWasCalledOnce(); + } + + @Test + public void shouldReturnEmptyListWhenNoNotApprovedBrandsExist() { + List expectedEmptyList = givenRepositoryReturnsEmptyList(); + + List result = whenExecuteGetNotifications(); + + thenResultListMatchesExpected(result, expectedEmptyList, 0); + thenRepositoryWasCalledOnce(); + } + + + //privadoss + private List givenRepositoryReturnsBrands(int count) { + List mockList = Collections.nCopies(count, new UserWithBrandsDTO()); + + when(userRepository.getNotApprovedBrands()).thenReturn(mockList); + return mockList; + } + + private List givenRepositoryReturnsEmptyList() { + List emptyList = Collections.emptyList(); + when(userRepository.getNotApprovedBrands()).thenReturn(emptyList); + return emptyList; + } + + private List whenExecuteGetNotifications() { + return getNotificationsNewBrands.execute(); + } + + private void thenResultListMatchesExpected(List actual, List expected, int expectedCount) { + assertNotNull(actual, "La lista resultante no debe ser nula."); + assertEquals(expectedCount, actual.size(), "El tamaño de la lista debe coincidir."); + assertEquals(expected, actual, "El contenido de la lista debe coincidir con la lista simulada."); + } + + private void thenRepositoryWasCalledOnce() { + verify(userRepository, times(1)).getNotApprovedBrands(); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/bucketImages/DeleteImageTest.java b/src/test/java/com/outfitlab/project/domain/useCases/bucketImages/DeleteImageTest.java new file mode 100644 index 0000000..3e6f718 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/bucketImages/DeleteImageTest.java @@ -0,0 +1,37 @@ +package com.outfitlab.project.domain.useCases.bucketImages; + +import com.outfitlab.project.domain.interfaces.repositories.UploadImageRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.*; + +public class DeleteImageTest { + + private UploadImageRepository uploadImageRepository = mock(UploadImageRepository.class); + private DeleteImage deleteImage; + + private final String IMAGE_URL = "https://mybucket.s3.aws.com/images/12345/photo.jpg"; + + @BeforeEach + void setUp() { + deleteImage = new DeleteImage(uploadImageRepository); + } + + + @Test + public void shouldCallRepositoryToDeleteFileWithCorrectUrl() { + whenExecuteDeleteImage(IMAGE_URL); + + thenRepositoryDeleteFileWasCalled(IMAGE_URL); + } + + + //privadoss + private void whenExecuteDeleteImage(String imageUrl) { + deleteImage.execute(imageUrl); + } + + private void thenRepositoryDeleteFileWasCalled(String expectedUrl) { + verify(uploadImageRepository, times(1)).deleteFile(expectedUrl); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/bucketImages/SaveImageTest.java b/src/test/java/com/outfitlab/project/domain/useCases/bucketImages/SaveImageTest.java new file mode 100644 index 0000000..2a7b049 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/bucketImages/SaveImageTest.java @@ -0,0 +1,54 @@ +package com.outfitlab.project.domain.useCases.bucketImages; + +import com.outfitlab.project.domain.interfaces.repositories.UploadImageRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.web.multipart.MultipartFile; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class SaveImageTest { + + private UploadImageRepository uploadImageRepository = mock(UploadImageRepository.class); + private SaveImage saveImage; + + private final String FOLDER_NAME = "models_images"; + private final String EXPECTED_URL = "https://mybucket.s3.aws.com/models_images/image.png"; + private MultipartFile mockImageFile; + + @BeforeEach + void setUp() { + mockImageFile = mock(MultipartFile.class); + saveImage = new SaveImage(uploadImageRepository); + } + + + @Test + public void shouldCallRepositoryToUploadFileAndReturnUrl() { + givenRepositoryReturnsUrl(mockImageFile, FOLDER_NAME, EXPECTED_URL); + + String resultUrl = whenExecuteSaveImage(mockImageFile, FOLDER_NAME); + + thenResultMatchesExpectedUrl(resultUrl, EXPECTED_URL); + thenRepositoryUploadFileWasCalled(mockImageFile, FOLDER_NAME); + } + + + //privadosss + private void givenRepositoryReturnsUrl(MultipartFile file, String folder, String url) { + when(uploadImageRepository.uploadFile(file, folder)).thenReturn(url); + } + + private String whenExecuteSaveImage(MultipartFile imageFile, String folder) { + return saveImage.execute(imageFile, folder); + } + + private void thenResultMatchesExpectedUrl(String actualUrl, String expectedUrl) { + assertNotNull(actualUrl, "La URL devuelta no debe ser nula."); + assertEquals(expectedUrl, actualUrl, "La URL devuelta debe coincidir con la URL simulada."); + } + + private void thenRepositoryUploadFileWasCalled(MultipartFile file, String folder) { + verify(uploadImageRepository, times(1)).uploadFile(file, folder); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/combination/CreateCombinationTest.java b/src/test/java/com/outfitlab/project/domain/useCases/combination/CreateCombinationTest.java new file mode 100644 index 0000000..7e15387 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/combination/CreateCombinationTest.java @@ -0,0 +1,111 @@ +package com.outfitlab.project.domain.useCases.combination; + +import com.outfitlab.project.domain.interfaces.repositories.CombinationRepository; +import com.outfitlab.project.domain.model.CombinationModel; +import com.outfitlab.project.domain.model.PrendaModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class CreateCombinationTest { + + private CombinationRepository combinationRepository = mock(CombinationRepository.class); + private CreateCombination createCombination; + + private PrendaModel mockPrendaSuperior; + private PrendaModel mockPrendaInferior; + private CombinationModel mockSavedModel; + + @BeforeEach + void setUp() { + mockPrendaSuperior = mock(PrendaModel.class); + mockPrendaInferior = mock(PrendaModel.class); + mockSavedModel = mock(CombinationModel.class); + createCombination = new CreateCombination(combinationRepository); + } + + + @Test + public void shouldSuccessfullyCreateAndSaveCombinationModel() { + givenRepositorySavesModelAndReturns(mockSavedModel); + + CombinationModel result = whenExecuteCreateCombination(mockPrendaSuperior, mockPrendaInferior); + + thenResultMatchesSavedModel(result, mockSavedModel); + thenRepositoryWasCalledWithCorrectPrendas(mockPrendaSuperior, mockPrendaInferior); + } + + @Test + public void shouldCreateCombinationModelWhenPrendaInferiorIsNull() { + givenRepositorySavesModelAndReturns(mockSavedModel); + PrendaModel nullPrendaInferior = null; + + CombinationModel result = whenExecuteCreateCombination(mockPrendaSuperior, nullPrendaInferior); + + thenResultMatchesSavedModel(result, mockSavedModel); + thenRepositoryWasCalledWithCorrectPrendas(mockPrendaSuperior, nullPrendaInferior); + } + + @Test + public void shouldCreateCombinationModelWhenPrendaSuperiorIsNull() { + givenRepositorySavesModelAndReturns(mockSavedModel); + PrendaModel nullPrendaSuperior = null; + + CombinationModel result = whenExecuteCreateCombination(nullPrendaSuperior, mockPrendaInferior); + + thenResultMatchesSavedModel(result, mockSavedModel); + thenRepositoryWasCalledWithCorrectPrendas(nullPrendaSuperior, mockPrendaInferior); + } + + @Test + public void shouldCreateCombinationModelWhenBothPrendasAreNull() { + givenRepositorySavesModelAndReturns(mockSavedModel); + + CombinationModel result = whenExecuteCreateCombination(null, null); + + thenResultMatchesSavedModel(result, mockSavedModel); + thenRepositoryWasCalledWithCorrectPrendas(null, null); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFailsToSave() { + givenRepositoryThrowsRuntimeException(); + + assertThrows(RuntimeException.class, + () -> whenExecuteCreateCombination(mockPrendaSuperior, mockPrendaInferior), + "Se esperaba que la excepción de persistencia se propagara."); + + verify(combinationRepository, times(1)).save(any(CombinationModel.class)); + } + + + //privadosss + private void givenRepositorySavesModelAndReturns(CombinationModel savedModel) { + when(combinationRepository.save(any(CombinationModel.class))).thenReturn(savedModel); + } + + private void givenRepositoryThrowsRuntimeException() { + doThrow(new RuntimeException("Simulated DB error")).when(combinationRepository).save(any(CombinationModel.class)); + } + + private CombinationModel whenExecuteCreateCombination(PrendaModel superior, PrendaModel inferior) { + return createCombination.execute(superior, inferior); + } + + private void thenResultMatchesSavedModel(CombinationModel actualResult, CombinationModel expectedSavedModel) { + assertNotNull(actualResult, "El resultado no debe ser nulo."); + assertEquals(expectedSavedModel, actualResult, "El resultado debe ser el modelo devuelto por el repositorio."); + } + + private void thenRepositoryWasCalledWithCorrectPrendas(PrendaModel expectedSuperior, PrendaModel expectedInferior) { + ArgumentCaptor captor = ArgumentCaptor.forClass(CombinationModel.class); + verify(combinationRepository, times(1)).save(captor.capture()); + + CombinationModel capturedModel = captor.getValue(); + + assertEquals(expectedSuperior, capturedModel.getPrendaSuperior(), "El modelo debe contener la prenda superior correcta."); + assertEquals(expectedInferior, capturedModel.getPrendaInferior(), "El modelo debe contener la prenda inferior correcta."); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/combination/DeleteAllCombinationRelatedToGarmentTest.java b/src/test/java/com/outfitlab/project/domain/useCases/combination/DeleteAllCombinationRelatedToGarmentTest.java new file mode 100644 index 0000000..e223667 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/combination/DeleteAllCombinationRelatedToGarmentTest.java @@ -0,0 +1,67 @@ +package com.outfitlab.project.domain.useCases.combination; + +import com.outfitlab.project.domain.interfaces.repositories.CombinationRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +public class DeleteAllCombinationRelatedToGarmentTest { + + private CombinationRepository combinationRepository = mock(CombinationRepository.class); + private DeleteAllCombinationRelatedToGarment deleteAllCombinationRelatedToGarment; + + private final String VALID_GARMENT_CODE = "PANTALON-001"; + private final String EMPTY_GARMENT_CODE = ""; + private final String NULL_GARMENT_CODE = null; + + @BeforeEach + void setUp() { + deleteAllCombinationRelatedToGarment = new DeleteAllCombinationRelatedToGarment(combinationRepository); + } + + + @Test + public void shouldCallRepositoryToDeleteAllCombinationsByValidGarmentCode() { + whenExecuteDeleteAll(VALID_GARMENT_CODE); + + thenRepositoryDeleteAllWasCalled(VALID_GARMENT_CODE, 1); + } + + @Test + public void shouldCallRepositoryWithEmptyStringWhenGarmentCodeIsEmpty() { + whenExecuteDeleteAll(EMPTY_GARMENT_CODE); + + thenRepositoryDeleteAllWasCalled(EMPTY_GARMENT_CODE, 1); + } + + @Test + public void shouldCallRepositoryWithNullWhenGarmentCodeIsNull() { + whenExecuteDeleteAll(NULL_GARMENT_CODE); + + thenRepositoryDeleteAllWasCalled(NULL_GARMENT_CODE, 1); + } + + @Test + public void shouldNotThrowExceptionIfRepositoryThrowsRuntimeException() { + givenRepositoryThrowsRuntimeException(); + + assertThrows(RuntimeException.class, () -> whenExecuteDeleteAll(VALID_GARMENT_CODE)); + + thenRepositoryDeleteAllWasCalled(VALID_GARMENT_CODE, 1); + } + + + //privadoss + private void givenRepositoryThrowsRuntimeException() { + doThrow(new RuntimeException("Simulated DB error")).when(combinationRepository).deleteAllByGarmentcode(anyString()); + } + + private void whenExecuteDeleteAll(String garmentCode) { + deleteAllCombinationRelatedToGarment.execute(garmentCode); + } + + private void thenRepositoryDeleteAllWasCalled(String expectedGarmentCode, int times) { + verify(combinationRepository, times(times)).deleteAllByGarmentcode(expectedGarmentCode); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/combination/GetCombinationByPrendasTest.java b/src/test/java/com/outfitlab/project/domain/useCases/combination/GetCombinationByPrendasTest.java new file mode 100644 index 0000000..bfd8859 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/combination/GetCombinationByPrendasTest.java @@ -0,0 +1,122 @@ +package com.outfitlab.project.domain.useCases.combination; + +import com.outfitlab.project.domain.exceptions.CombinationNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.CombinationRepository; +import com.outfitlab.project.domain.model.CombinationModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.Optional; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetCombinationByPrendasTest { + + private CombinationRepository combinationRepository = mock(CombinationRepository.class); + private GetCombinationByPrendas getCombinationByPrendas; + + private final Long SUPERIOR_ID = 101L; + private final Long INFERIOR_ID = 202L; + private final Long NULL_ID = null; + + private CombinationModel mockCombination; + + @BeforeEach + void setUp() { + mockCombination = mock(CombinationModel.class); + getCombinationByPrendas = new GetCombinationByPrendas(combinationRepository); + } + + + @Test + public void shouldReturnCombinationWhenBothPrendaIdsAreFound() throws CombinationNotFoundException { + givenRepositoryFindsCombination(SUPERIOR_ID, INFERIOR_ID, mockCombination); + + CombinationModel result = whenExecuteGetCombination(SUPERIOR_ID, INFERIOR_ID); + + thenResultMatchesExpectedCombination(result, mockCombination); + thenRepositoryWasCalledOnce(SUPERIOR_ID, INFERIOR_ID); + } + + @Test + public void shouldThrowCombinationNotFoundExceptionWhenCombinationDoesNotExist() { + givenRepositoryFindsNoCombination(SUPERIOR_ID, INFERIOR_ID); + + assertThrows(CombinationNotFoundException.class, + () -> whenExecuteGetCombination(SUPERIOR_ID, INFERIOR_ID), + "Debe lanzar CombinationNotFoundException cuando Optional está vacío."); + + thenRepositoryWasCalledOnce(SUPERIOR_ID, INFERIOR_ID); + } + + @Test + public void shouldThrowCombinationNotFoundExceptionWhenSuperiorIdIsNullAndCombinationDoesNotExist() { + givenRepositoryFindsNoCombination(NULL_ID, INFERIOR_ID); + + assertThrows(CombinationNotFoundException.class, + () -> whenExecuteGetCombination(NULL_ID, INFERIOR_ID), + "Debe fallar si no se encuentra combinación con ID Superior nulo."); + + thenRepositoryWasCalledOnce(NULL_ID, INFERIOR_ID); + } + + @Test + public void shouldThrowCombinationNotFoundExceptionWhenInferiorIdIsNullAndCombinationDoesNotExist() { + givenRepositoryFindsNoCombination(SUPERIOR_ID, NULL_ID); + + assertThrows(CombinationNotFoundException.class, + () -> whenExecuteGetCombination(SUPERIOR_ID, NULL_ID), + "Debe fallar si no se encuentra combinación con ID Inferior nulo."); + + thenRepositoryWasCalledOnce(SUPERIOR_ID, NULL_ID); + } + + @Test + public void shouldThrowCombinationNotFoundExceptionWhenBothIdsAreNullAndCombinationDoesNotExist() { + givenRepositoryFindsNoCombination(NULL_ID, NULL_ID); + + assertThrows(CombinationNotFoundException.class, + () -> whenExecuteGetCombination(NULL_ID, NULL_ID), + "Debe fallar si no se encuentra combinación con ambos IDs nulos."); + + thenRepositoryWasCalledOnce(NULL_ID, NULL_ID); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(SUPERIOR_ID, INFERIOR_ID); + + assertThrows(RuntimeException.class, + () -> whenExecuteGetCombination(SUPERIOR_ID, INFERIOR_ID), + "La excepción de tiempo de ejecución debe ser propagada."); + + thenRepositoryWasCalledOnce(SUPERIOR_ID, INFERIOR_ID); + } + + + //privadosss + private void givenRepositoryFindsCombination(Long superiorId, Long inferiorId, CombinationModel model) { + when(combinationRepository.findByPrendas(superiorId, inferiorId)).thenReturn(Optional.of(model)); + } + + private void givenRepositoryFindsNoCombination(Long superiorId, Long inferiorId) { + when(combinationRepository.findByPrendas(superiorId, inferiorId)).thenReturn(Optional.empty()); + } + + private void givenRepositoryThrowsRuntimeException(Long superiorId, Long inferiorId) { + doThrow(new RuntimeException("Simulated DB error")).when(combinationRepository).findByPrendas(superiorId, inferiorId); + } + + + private CombinationModel whenExecuteGetCombination(Long superiorId, Long inferiorId) throws CombinationNotFoundException { + return getCombinationByPrendas.execute(superiorId, inferiorId); + } + + private void thenResultMatchesExpectedCombination(CombinationModel actual, CombinationModel expected) { + assertNotNull(actual, "El resultado no debe ser nulo."); + assertEquals(expected, actual, "La combinación devuelta debe coincidir con la simulada."); + } + + private void thenRepositoryWasCalledOnce(Long superiorId, Long inferiorId) { + verify(combinationRepository, times(1)).findByPrendas(superiorId, inferiorId); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/combinationAttempt/DeleteAllCombinationAttempsRelatedToCombinationsRelatedToGarmentTest.java b/src/test/java/com/outfitlab/project/domain/useCases/combinationAttempt/DeleteAllCombinationAttempsRelatedToCombinationsRelatedToGarmentTest.java new file mode 100644 index 0000000..762d0b2 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/combinationAttempt/DeleteAllCombinationAttempsRelatedToCombinationsRelatedToGarmentTest.java @@ -0,0 +1,70 @@ +package com.outfitlab.project.domain.useCases.combinationAttempt; + +import com.outfitlab.project.domain.interfaces.repositories.CombinationAttemptRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class DeleteAllCombinationAttempsRelatedToCombinationsRelatedToGarmentTest { + + private CombinationAttemptRepository combinationAttemptRepository = mock(CombinationAttemptRepository.class); + private DeleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment deleteAllAttemps; + + private final String VALID_GARMENT_CODE = "PANTALON-007"; + private final String EMPTY_GARMENT_CODE = ""; + private final String NULL_GARMENT_CODE = null; + + @BeforeEach + void setUp() { + deleteAllAttemps = new DeleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment(combinationAttemptRepository); + } + + + @Test + public void shouldCallRepositoryToDeleteAllAttempsByValidGarmentCode() { + whenExecuteDeleteAll(VALID_GARMENT_CODE); + + thenRepositoryDeleteAllWasCalled(VALID_GARMENT_CODE, 1); + } + + @Test + public void shouldCallRepositoryWithEmptyStringWhenGarmentCodeIsEmpty() { + whenExecuteDeleteAll(EMPTY_GARMENT_CODE); + + thenRepositoryDeleteAllWasCalled(EMPTY_GARMENT_CODE, 1); + } + + @Test + public void shouldCallRepositoryWithNullWhenGarmentCodeIsNull() { + whenExecuteDeleteAll(NULL_GARMENT_CODE); + + thenRepositoryDeleteAllWasCalled(NULL_GARMENT_CODE, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(VALID_GARMENT_CODE); + + assertThrows(RuntimeException.class, () -> whenExecuteDeleteAll(VALID_GARMENT_CODE)); + + thenRepositoryDeleteAllWasCalled(VALID_GARMENT_CODE, 1); + } + + + //privadoss + private void givenRepositoryThrowsRuntimeException(String garmentCode) { + doThrow(new RuntimeException("Simulated cascaded delete error")) + .when(combinationAttemptRepository) + .deleteAllByAttempsReltedToCombinationRelatedToGarments(garmentCode); + } + + private void whenExecuteDeleteAll(String garmentCode) { + deleteAllAttemps.execute(garmentCode); + } + + private void thenRepositoryDeleteAllWasCalled(String expectedGarmentCode, int times) { + verify(combinationAttemptRepository, times(times)) + .deleteAllByAttempsReltedToCombinationRelatedToGarments(expectedGarmentCode); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/combinationAttempt/RegisterCombinationAttemptTest.java b/src/test/java/com/outfitlab/project/domain/useCases/combinationAttempt/RegisterCombinationAttemptTest.java new file mode 100644 index 0000000..3fecabe --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/combinationAttempt/RegisterCombinationAttemptTest.java @@ -0,0 +1,173 @@ +package com.outfitlab.project.domain.useCases.combinationAttempt; + +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.CombinationAttemptRepository; +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.model.CombinationAttemptModel; +import com.outfitlab.project.domain.model.CombinationModel; +import com.outfitlab.project.domain.model.UserModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class RegisterCombinationAttemptTest { + + private CombinationAttemptRepository attemptRepository = mock(CombinationAttemptRepository.class); + private UserRepository userRepository = mock(UserRepository.class); + + private RegisterCombinationAttempt registerCombinationAttempt; + + private static final String VALID_EMAIL = "test@user.com"; + private static final String IMAGE_URL = "http://storage/img/attempt-123.jpg"; + private static final Long EXPECTED_SAVED_ID = 42L; + private static final Long VALID_COMBINATION_ID = 10L; + + private CombinationModel mockCombination; + private UserModel mockUser; + + @BeforeEach + void setUp() throws UserNotFoundException { + mockCombination = mock(CombinationModel.class); + mockUser = mock(UserModel.class); + when(mockCombination.getId()).thenReturn(VALID_COMBINATION_ID); + registerCombinationAttempt = new RegisterCombinationAttempt(attemptRepository, userRepository); + } + + + @Test + public void shouldRegisterAttemptAndReturnSavedIdWhenAllDataIsValid() throws UserNotFoundException { + givenRepositorySavesAttemptAndReturns(EXPECTED_SAVED_ID); + givenUserExists(VALID_EMAIL, mockUser); + + Long resultId = whenExecuteRegisterAttempt(VALID_EMAIL, mockCombination, IMAGE_URL); + + thenResultMatchesExpectedId(resultId, EXPECTED_SAVED_ID); + thenRepositoryWasCalledWithCorrectAttempt(mockUser, mockCombination, IMAGE_URL); + thenUserConsultationWasCalled(VALID_EMAIL, 1); + } + + @Test + public void shouldThrowIllegalArgumentExceptionWhenUserEmailIsNull() { + String nullEmail = null; + + assertThrows(IllegalArgumentException.class, + () -> whenExecuteRegisterAttempt(nullEmail, mockCombination, IMAGE_URL), + "Debe lanzar IllegalArgumentException si el email es null."); + + thenUserConsultationWasCalled(nullEmail, 0); + thenRepositorySaveWasNeverCalled(); + } + + @Test + public void shouldThrowIllegalArgumentExceptionWhenCombinationIsNull() throws UserNotFoundException { + givenUserExists(VALID_EMAIL, mockUser); + + assertThrows(IllegalArgumentException.class, + () -> whenExecuteRegisterAttempt(VALID_EMAIL, null, IMAGE_URL), + "Debe lanzar IllegalArgumentException si la combinación es null."); + + thenUserConsultationWasCalled(VALID_EMAIL, 1); + thenRepositorySaveWasNeverCalled(); + } + + @Test + public void shouldThrowIllegalArgumentExceptionWhenCombinationIdIsNull() throws UserNotFoundException { + CombinationModel invalidCombination = mock(CombinationModel.class); + when(invalidCombination.getId()).thenReturn(null); + givenUserExists(VALID_EMAIL, mockUser); + + assertThrows(IllegalArgumentException.class, + () -> whenExecuteRegisterAttempt(VALID_EMAIL, invalidCombination, IMAGE_URL), + "Debe lanzar IllegalArgumentException si el ID de la combinación es null."); + + thenRepositorySaveWasNeverCalled(); + } + + @Test + public void shouldPropagateUserNotFoundExceptionWhenUserDoesNotExist() { + givenUserDoesNotExist(VALID_EMAIL); + + assertThrows(UserNotFoundException.class, + () -> whenExecuteRegisterAttempt(VALID_EMAIL, mockCombination, IMAGE_URL), + "Debe propagar UserNotFoundException si el usuario no existe."); + + thenUserConsultationWasCalled(VALID_EMAIL, 1); + thenRepositorySaveWasNeverCalled(); + } + + @Test + public void shouldRegisterAttemptSuccessfullyWhenImageUrlIsNull() throws UserNotFoundException { + givenRepositorySavesAttemptAndReturns(EXPECTED_SAVED_ID); + givenUserExists(VALID_EMAIL, mockUser); + String nullImageUrl = null; + + Long resultId = whenExecuteRegisterAttempt(VALID_EMAIL, mockCombination, nullImageUrl); + + thenResultMatchesExpectedId(resultId, EXPECTED_SAVED_ID); + thenRepositoryWasCalledWithCorrectAttempt(mockUser, mockCombination, nullImageUrl); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFailsToSave() throws UserNotFoundException { + givenRepositoryThrowsRuntimeException(); + givenUserExists(VALID_EMAIL, mockUser); + + assertThrows(RuntimeException.class, + () -> whenExecuteRegisterAttempt(VALID_EMAIL, mockCombination, IMAGE_URL), + "Debe propagar RuntimeException si el repositorio falla."); + + thenRepositorySaveWasCalled(1); + } + + + //privadosss + private void givenUserExists(String email, UserModel user) throws UserNotFoundException { + when(userRepository.findUserByEmail(email)).thenReturn(user); + } + + private void givenUserDoesNotExist(String email) { + when(userRepository.findUserByEmail(email)).thenThrow(UserNotFoundException.class); + } + + private void givenRepositorySavesAttemptAndReturns(Long id) { + when(attemptRepository.save(any(CombinationAttemptModel.class))).thenReturn(id); + } + + private void givenRepositoryThrowsRuntimeException() { + doThrow(new RuntimeException("DB Connection Error")).when(attemptRepository).save(any(CombinationAttemptModel.class)); + } + + private Long whenExecuteRegisterAttempt(String userEmail, CombinationModel combination, String imageUrl) { + return registerCombinationAttempt.execute(userEmail, combination, imageUrl); + } + + private void thenResultMatchesExpectedId(Long actualId, Long expectedId) { + assertNotNull(actualId, "El ID devuelto no debe ser nulo."); + assertEquals(expectedId, actualId, "El ID debe coincidir con el ID simulado."); + } + + private void thenUserConsultationWasCalled(String email, int times) { + verify(userRepository, times(times)).findUserByEmail(email); + } + + private void thenRepositorySaveWasCalled(int times) { + verify(attemptRepository, times(times)).save(any(CombinationAttemptModel.class)); + } + + private void thenRepositorySaveWasNeverCalled() { + verify(attemptRepository, never()).save(any(CombinationAttemptModel.class)); + } + + private void thenRepositoryWasCalledWithCorrectAttempt(UserModel expectedUser, CombinationModel expectedCombination, String expectedUrl) { + ArgumentCaptor captor = ArgumentCaptor.forClass(CombinationAttemptModel.class); + verify(attemptRepository, times(1)).save(captor.capture()); + + CombinationAttemptModel capturedAttempt = captor.getValue(); + + assertEquals(expectedUser, capturedAttempt.getUser(), "El modelo debe contener el usuario correcto."); + assertEquals(expectedCombination, capturedAttempt.getCombination(), "El modelo debe contener la combinación correcta."); + assertEquals(expectedUrl, capturedAttempt.getImageUrl(), "La URL de la imagen debe coincidir."); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/combinationFavorite/AddCombinationToFavouriteTest.java b/src/test/java/com/outfitlab/project/domain/useCases/combinationFavorite/AddCombinationToFavouriteTest.java new file mode 100644 index 0000000..f766ee5 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/combinationFavorite/AddCombinationToFavouriteTest.java @@ -0,0 +1,185 @@ +package com.outfitlab.project.domain.useCases.combinationFavorite; + +import com.outfitlab.project.domain.exceptions.FavoritesException; +import com.outfitlab.project.domain.exceptions.PlanLimitExceededException; +import com.outfitlab.project.domain.exceptions.SubscriptionNotFoundException; +import com.outfitlab.project.domain.exceptions.UserCombinationFavoriteAlreadyExistsException; +import com.outfitlab.project.domain.exceptions.UserCombinationFavoriteNotFoundException; +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.UserCombinationFavoriteRepository; +import com.outfitlab.project.domain.model.UserCombinationFavoriteModel; +import com.outfitlab.project.domain.useCases.subscription.CheckUserPlanLimit; +import com.outfitlab.project.domain.useCases.subscription.IncrementUsageCounter; +import com.outfitlab.project.infrastructure.model.UserCombinationFavoriteEntity; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class AddCombinationToFavouriteTest { + + private UserCombinationFavoriteRepository userCombinationFavoriteRepository = mock(UserCombinationFavoriteRepository.class); + private CheckUserPlanLimit checkUserPlanLimit = mock(CheckUserPlanLimit.class); + private IncrementUsageCounter incrementUsageCounter = mock(IncrementUsageCounter.class); + private AddCombinationToFavourite addCombinationToFavourite; + + private final String USER_EMAIL = "user@test.com"; + private final String COMBINATION_URL = "http://url/comb/abc"; + private final String LIMIT_TYPE = "favorites"; + private final String SUCCESS_MESSAGE = "Combinación añadida a favoritos."; + + @BeforeEach + void setUp() throws UserCombinationFavoriteNotFoundException, FavoritesException, UserNotFoundException { + addCombinationToFavourite = new AddCombinationToFavourite( + userCombinationFavoriteRepository, + checkUserPlanLimit, + incrementUsageCounter + ); + givenFavoriteDoesNotExist(); + givenAddToFavoritesIsSuccessful(); + } + + + @Test + public void shouldAddCombinationAndIncrementCounterWhenChecksPass() throws Exception { + String result = whenExecuteAddCombination(COMBINATION_URL, USER_EMAIL); + + thenResultIsSuccessMessage(result); + thenPlanLimitWasChecked(1); + thenFavoriteExistenceWasChecked(1); + thenAddToFavoritesWasCalled(COMBINATION_URL, USER_EMAIL, 1); + thenUsageCounterWasIncremented(1); + } + + @Test + public void shouldThrowPlanLimitExceededExceptionWhenLimitIsReached() throws Exception { + doThrow(PlanLimitExceededException.class).when(checkUserPlanLimit).execute(USER_EMAIL, LIMIT_TYPE); + + assertThrows(PlanLimitExceededException.class, + () -> whenExecuteAddCombination(COMBINATION_URL, USER_EMAIL)); + + thenPlanLimitWasChecked(1); + thenFavoriteExistenceWasChecked(0); + thenAddToFavoritesWasCalled(COMBINATION_URL, USER_EMAIL, 0); + thenUsageCounterWasIncremented(0); + } + + @Test + public void shouldThrowUserCombinationFavoriteAlreadyExistsExceptionWhenAlreadyExists() throws Exception { + givenFavoriteAlreadyExists(); + + assertThrows(UserCombinationFavoriteAlreadyExistsException.class, + () -> whenExecuteAddCombination(COMBINATION_URL, USER_EMAIL)); + + thenPlanLimitWasChecked(1); + thenFavoriteExistenceWasChecked(1); + thenAddToFavoritesWasCalled(COMBINATION_URL, USER_EMAIL, 0); + thenUsageCounterWasIncremented(0); + } + + @Test + public void shouldThrowFavoritesExceptionWhenAddToFavoritesFails() throws Exception { + givenAddToFavoritesFailsWithNull(); + + assertThrows(FavoritesException.class, + () -> whenExecuteAddCombination(COMBINATION_URL, USER_EMAIL)); + + thenPlanLimitWasChecked(1); + thenFavoriteExistenceWasChecked(1); + thenAddToFavoritesWasCalled(COMBINATION_URL, USER_EMAIL, 1); + thenUsageCounterWasIncremented(0); + } + + @Test + public void shouldPropagateSubscriptionNotFoundException() throws Exception { + doThrow(SubscriptionNotFoundException.class).when(checkUserPlanLimit).execute(USER_EMAIL, LIMIT_TYPE); + + assertThrows(SubscriptionNotFoundException.class, + () -> whenExecuteAddCombination(COMBINATION_URL, USER_EMAIL)); + + thenPlanLimitWasChecked(1); + thenFavoriteExistenceWasChecked(0); + thenUsageCounterWasIncremented(0); + } + + @Test + public void shouldPropagateUserNotFoundExceptionWhenAddingToFavorites() throws Exception { + givenAddToFavoritesThrowsUserNotFound(); + + assertThrows(UserNotFoundException.class, + () -> whenExecuteAddCombination(COMBINATION_URL, USER_EMAIL)); + + thenPlanLimitWasChecked(1); + thenFavoriteExistenceWasChecked(1); + thenAddToFavoritesWasCalled(COMBINATION_URL, USER_EMAIL, 1); + thenUsageCounterWasIncremented(0); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenCheckingIfFavoriteExistsFails() throws Exception { + givenFavoriteExistenceThrowsRuntimeException(); + + assertThrows(RuntimeException.class, + () -> whenExecuteAddCombination(COMBINATION_URL, USER_EMAIL)); + + thenPlanLimitWasChecked(1); + thenFavoriteExistenceWasChecked(1); + thenAddToFavoritesWasCalled(COMBINATION_URL, USER_EMAIL, 0); + thenUsageCounterWasIncremented(0); + } + + + //privadoss + private void givenFavoriteDoesNotExist() throws UserCombinationFavoriteNotFoundException { + doThrow(UserCombinationFavoriteNotFoundException.class) + .when(userCombinationFavoriteRepository).findByCombinationUrlAndUserEmail(COMBINATION_URL, USER_EMAIL); + } + + private void givenFavoriteAlreadyExists() throws UserCombinationFavoriteNotFoundException { + doReturn(mock(UserCombinationFavoriteModel.class)) + .when(userCombinationFavoriteRepository).findByCombinationUrlAndUserEmail(COMBINATION_URL, USER_EMAIL); + } + + private void givenFavoriteExistenceThrowsRuntimeException() throws UserCombinationFavoriteNotFoundException { + doThrow(new RuntimeException("DB Connection Error during check")) + .when(userCombinationFavoriteRepository).findByCombinationUrlAndUserEmail(COMBINATION_URL, USER_EMAIL); + } + + private void givenAddToFavoritesIsSuccessful() throws UserNotFoundException { + UserCombinationFavoriteEntity successEntity = mock(UserCombinationFavoriteEntity.class); + when(userCombinationFavoriteRepository.addToFavorite(anyString(), anyString())).thenReturn(successEntity); + } + + private void givenAddToFavoritesFailsWithNull() { + when(userCombinationFavoriteRepository.addToFavorite(anyString(), anyString())).thenReturn(null); + } + + private void givenAddToFavoritesThrowsUserNotFound() throws UserNotFoundException { + doThrow(UserNotFoundException.class) + .when(userCombinationFavoriteRepository).addToFavorite(COMBINATION_URL, USER_EMAIL); + } + + private String whenExecuteAddCombination(String combinationUrl, String userEmail) throws Exception { + return addCombinationToFavourite.execute(combinationUrl, userEmail); + } + + private void thenResultIsSuccessMessage(String result) { + assertEquals(SUCCESS_MESSAGE, result, "El mensaje de retorno debe ser el de éxito."); + } + + private void thenPlanLimitWasChecked(int times) throws Exception { + verify(checkUserPlanLimit, times(times)).execute(USER_EMAIL, LIMIT_TYPE); + } + + private void thenFavoriteExistenceWasChecked(int times) throws UserCombinationFavoriteNotFoundException { + verify(userCombinationFavoriteRepository, times(times)).findByCombinationUrlAndUserEmail(COMBINATION_URL, USER_EMAIL); + } + + private void thenAddToFavoritesWasCalled(String url, String email, int times) throws FavoritesException, UserNotFoundException { + verify(userCombinationFavoriteRepository, times(times)).addToFavorite(url, email); + } + + private void thenUsageCounterWasIncremented(int times) { + verify(incrementUsageCounter, times(times)).execute(USER_EMAIL, LIMIT_TYPE); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/combinationFavorite/DeleteCombinationFromFavoriteTest.java b/src/test/java/com/outfitlab/project/domain/useCases/combinationFavorite/DeleteCombinationFromFavoriteTest.java new file mode 100644 index 0000000..0ce84eb --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/combinationFavorite/DeleteCombinationFromFavoriteTest.java @@ -0,0 +1,151 @@ +package com.outfitlab.project.domain.useCases.combinationFavorite; + +import com.outfitlab.project.domain.exceptions.UserCombinationFavoriteNotFoundException; +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.UserCombinationFavoriteRepository; +import com.outfitlab.project.domain.model.UserCombinationFavoriteModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class DeleteCombinationFromFavoriteTest { + + private UserCombinationFavoriteRepository userCombinationFavoriteRepository = mock(UserCombinationFavoriteRepository.class); + private DeleteCombinationFromFavorite deleteCombinationFromFavorite; + + private final String USER_EMAIL = "user@test.com"; + private final String COMBINATION_URL = "http://url/comb/abc"; + private final String SUCCESS_MESSAGE = "Combinación eliminada de favoritos."; + private final String NULL_URL = null; + private final String EMPTY_EMAIL = ""; + + + @BeforeEach + void setUp() throws UserCombinationFavoriteNotFoundException, UserNotFoundException { + deleteCombinationFromFavorite = new DeleteCombinationFromFavorite(userCombinationFavoriteRepository); + givenFavoriteExists(); + givenDeletionIsSuccessful(); + } + + + @Test + public void shouldDeleteCombinationAndReturnSuccessMessageWhenFavoriteExists() throws Exception { + String result = whenExecuteDelete(COMBINATION_URL, USER_EMAIL); + + thenResultIsSuccessMessage(result); + thenFavoriteExistenceWasChecked(1); + thenDeletionWasCalled(COMBINATION_URL, USER_EMAIL, 1); + } + + @Test + public void shouldThrowUserCombinationFavoriteNotFoundExceptionWhenFavoriteDoesNotExist() throws UserNotFoundException { + givenFavoriteDoesNotExist(); + + assertThrows(UserCombinationFavoriteNotFoundException.class, + () -> whenExecuteDelete(COMBINATION_URL, USER_EMAIL), + "Debe lanzar NotFoundException si no se encuentra la combinación."); + + thenFavoriteExistenceWasChecked(1); + thenDeletionWasCalled(COMBINATION_URL, USER_EMAIL, 0); + } + + @Test + public void shouldPropagateUserNotFoundExceptionWhenDeletingFavorite() throws UserCombinationFavoriteNotFoundException { + givenDeletionThrowsUserNotFound(); + + assertThrows(UserNotFoundException.class, + () -> whenExecuteDelete(COMBINATION_URL, USER_EMAIL), + "Debe propagar UserNotFoundException si el usuario no es válido durante la eliminación."); + + thenFavoriteExistenceWasChecked(1); + thenDeletionWasCalled(COMBINATION_URL, USER_EMAIL, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenCheckingExistenceFails() { + givenExistenceCheckThrowsRuntimeException(); + + assertThrows(RuntimeException.class, + () -> whenExecuteDelete(COMBINATION_URL, USER_EMAIL)); + + thenFavoriteExistenceWasChecked(1); + thenDeletionWasCalled(COMBINATION_URL, USER_EMAIL, 0); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenDeletionFails() throws UserNotFoundException { + givenDeletionThrowsRuntimeException(); + + assertThrows(RuntimeException.class, + () -> whenExecuteDelete(COMBINATION_URL, USER_EMAIL)); + + thenFavoriteExistenceWasChecked(1); + thenDeletionWasCalled(COMBINATION_URL, USER_EMAIL, 1); + } + + @Test + public void shouldAttemptDeletionWhenCombinationUrlIsNull() throws Exception { + whenExecuteDelete(NULL_URL, USER_EMAIL); + + thenFavoriteExistenceWasChecked(1, NULL_URL, USER_EMAIL); + thenDeletionWasCalled(NULL_URL, USER_EMAIL, 1); + } + + @Test + public void shouldAttemptDeletionWhenUserEmailIsEmpty() throws Exception { + whenExecuteDelete(COMBINATION_URL, EMPTY_EMAIL); + + thenFavoriteExistenceWasChecked(1, COMBINATION_URL, EMPTY_EMAIL); + thenDeletionWasCalled(COMBINATION_URL, EMPTY_EMAIL, 1); + } + + + //privadosss + private void givenFavoriteExists() throws UserCombinationFavoriteNotFoundException { + when(userCombinationFavoriteRepository.findByCombinationUrlAndUserEmail(anyString(), anyString())) + .thenReturn(mock(UserCombinationFavoriteModel.class)); + } + + private void givenFavoriteDoesNotExist() throws UserCombinationFavoriteNotFoundException { + doThrow(UserCombinationFavoriteNotFoundException.class) + .when(userCombinationFavoriteRepository).findByCombinationUrlAndUserEmail(COMBINATION_URL, USER_EMAIL); + } + + private void givenDeletionIsSuccessful() throws UserNotFoundException { + doNothing().when(userCombinationFavoriteRepository).deleteFromFavorites(anyString(), anyString()); + } + + private void givenDeletionThrowsUserNotFound() throws UserNotFoundException { + doThrow(UserNotFoundException.class) + .when(userCombinationFavoriteRepository).deleteFromFavorites(COMBINATION_URL, USER_EMAIL); + } + + private void givenExistenceCheckThrowsRuntimeException() { + doThrow(new RuntimeException("DB error on read")).when(userCombinationFavoriteRepository).findByCombinationUrlAndUserEmail(anyString(), anyString()); + } + + private void givenDeletionThrowsRuntimeException() throws UserNotFoundException { + doThrow(new RuntimeException("DB error on delete")).when(userCombinationFavoriteRepository).deleteFromFavorites(COMBINATION_URL, USER_EMAIL); + } + + private String whenExecuteDelete(String combinationUrl, String userEmail) throws UserCombinationFavoriteNotFoundException, UserNotFoundException { + return deleteCombinationFromFavorite.execute(combinationUrl, userEmail); + } + + private void thenResultIsSuccessMessage(String result) { + assertEquals(SUCCESS_MESSAGE, result, "El mensaje de retorno debe ser de eliminación exitosa."); + } + + private void thenFavoriteExistenceWasChecked(int times) throws UserCombinationFavoriteNotFoundException { + verify(userCombinationFavoriteRepository, times(times)).findByCombinationUrlAndUserEmail(COMBINATION_URL, USER_EMAIL); + } + + private void thenFavoriteExistenceWasChecked(int times, String url, String email) throws UserCombinationFavoriteNotFoundException { + verify(userCombinationFavoriteRepository, times(times)).findByCombinationUrlAndUserEmail(url, email); + } + + private void thenDeletionWasCalled(String url, String email, int times) throws UserNotFoundException { + verify(userCombinationFavoriteRepository, times(times)).deleteFromFavorites(url, email); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/combinationFavorite/GetCombinationFavoritesForUserByEmailTest.java b/src/test/java/com/outfitlab/project/domain/useCases/combinationFavorite/GetCombinationFavoritesForUserByEmailTest.java new file mode 100644 index 0000000..ddd8dab --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/combinationFavorite/GetCombinationFavoritesForUserByEmailTest.java @@ -0,0 +1,140 @@ +package com.outfitlab.project.domain.useCases.combinationFavorite; + +import com.outfitlab.project.domain.exceptions.FavoritesException; +import com.outfitlab.project.domain.exceptions.PageLessThanZeroException; +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.UserCombinationFavoriteRepository; +import com.outfitlab.project.domain.model.UserCombinationFavoriteModel; +import com.outfitlab.project.domain.model.dto.PageDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.Collections; +import java.util.List; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetCombinationFavoritesForUserByEmailTest { + + private UserCombinationFavoriteRepository userCombinationFavoriteRepository = mock(UserCombinationFavoriteRepository.class); + private GetCombinationFavoritesForUserByEmail getCombinationFavoritesForUserByEmail; + + private final String VALID_EMAIL = "user@test.com"; + private final int VALID_PAGE = 2; + private final int INVALID_PAGE = -1; + private final String NULL_EMAIL = null; + private final String EMPTY_EMAIL = ""; + + private PageDTO mockPageDTO; + + @BeforeEach + void setUp() throws FavoritesException, UserNotFoundException { + mockPageDTO = createMockPageDTO(3); + getCombinationFavoritesForUserByEmail = new GetCombinationFavoritesForUserByEmail(userCombinationFavoriteRepository); + } + + + @Test + public void shouldReturnFavoritesPageWhenEmailAndPageAreValid() throws Exception { + givenRepositoryReturnsFavoritesPage(VALID_EMAIL, VALID_PAGE, mockPageDTO); + + PageDTO result = whenExecuteGetFavorites(VALID_EMAIL, VALID_PAGE); + + thenResultMatchesExpectedPage(result, mockPageDTO); + thenRepositoryWasCalledOnce(VALID_EMAIL, VALID_PAGE); + } + + @Test + public void shouldThrowPageLessThanZeroExceptionWhenPageIsNegative() { + assertThrows(PageLessThanZeroException.class, + () -> whenExecuteGetFavorites(VALID_EMAIL, INVALID_PAGE), + "Debe lanzar PageLessThanZeroException para página negativa."); + + thenRepositoryWasNeverCalled(); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenUserEmailIsNull() { + givenRepositoryThrowsRuntimeException(NULL_EMAIL, VALID_PAGE); + + assertThrows(RuntimeException.class, + () -> whenExecuteGetFavorites(NULL_EMAIL, VALID_PAGE), + "Debe propagar RuntimeException si el email es null y el repositorio falla."); + + thenRepositoryWasCalledOnce(NULL_EMAIL, VALID_PAGE); + } + + @Test + public void shouldReturnFavoritesPageWhenUserEmailIsEmpty() throws Exception { + givenRepositoryReturnsFavoritesPage(EMPTY_EMAIL, VALID_PAGE, mockPageDTO); + + PageDTO result = whenExecuteGetFavorites(EMPTY_EMAIL, VALID_PAGE); + + thenResultMatchesExpectedPage(result, mockPageDTO); + thenRepositoryWasCalledOnce(EMPTY_EMAIL, VALID_PAGE); + } + + @Test + public void shouldPropagateUserNotFoundException() throws Exception { + givenRepositoryThrowsUserNotFound(VALID_EMAIL, VALID_PAGE); + + assertThrows(UserNotFoundException.class, + () -> whenExecuteGetFavorites(VALID_EMAIL, VALID_PAGE), + "Debe propagar UserNotFoundException."); + + thenRepositoryWasCalledOnce(VALID_EMAIL, VALID_PAGE); + } + + @Test + public void shouldPropagateFavoritesException() throws Exception { + givenRepositoryThrowsFavoritesException(VALID_EMAIL, VALID_PAGE); + + assertThrows(FavoritesException.class, + () -> whenExecuteGetFavorites(VALID_EMAIL, VALID_PAGE), + "Debe propagar FavoritesException."); + + thenRepositoryWasCalledOnce(VALID_EMAIL, VALID_PAGE); + } + + + //privadosss + private PageDTO createMockPageDTO(int size) { + PageDTO pageDTO = new PageDTO<>(); + List content = Collections.nCopies(size, mock(UserCombinationFavoriteModel.class)); + pageDTO.setContent(content); + return pageDTO; + } + + private void givenRepositoryReturnsFavoritesPage(String email, int page, PageDTO response) throws UserNotFoundException, FavoritesException { + when(userCombinationFavoriteRepository.getCombinationFavoritesForUserByEmail(email, page)).thenReturn(response); + } + + private void givenRepositoryThrowsUserNotFound(String email, int page) throws UserNotFoundException, FavoritesException { + doThrow(UserNotFoundException.class).when(userCombinationFavoriteRepository).getCombinationFavoritesForUserByEmail(email, page); + } + + private void givenRepositoryThrowsFavoritesException(String email, int page) throws UserNotFoundException, FavoritesException { + doThrow(FavoritesException.class).when(userCombinationFavoriteRepository).getCombinationFavoritesForUserByEmail(email, page); + } + + private void givenRepositoryThrowsRuntimeException(String email, int page) { + doThrow(RuntimeException.class).when(userCombinationFavoriteRepository).getCombinationFavoritesForUserByEmail(email, page); + } + + private PageDTO whenExecuteGetFavorites(String userEmail, int page) throws PageLessThanZeroException, UserNotFoundException, FavoritesException { + return getCombinationFavoritesForUserByEmail.execute(userEmail, page); + } + + private void thenResultMatchesExpectedPage(PageDTO actual, PageDTO expected) { + assertNotNull(actual, "La página resultante no debe ser nula."); + assertEquals(expected, actual, "El resultado debe coincidir con la página simulada."); + assertEquals(expected.getContent().size(), actual.getContent().size(), "El tamaño del contenido debe coincidir."); + } + + private void thenRepositoryWasCalledOnce(String userEmail, int page) { + verify(userCombinationFavoriteRepository, times(1)).getCombinationFavoritesForUserByEmail(userEmail, page); + } + + private void thenRepositoryWasNeverCalled() { + verify(userCombinationFavoriteRepository, never()).getCombinationFavoritesForUserByEmail(anyString(), anyInt()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/dashboard/GetActividadPorDiasTest.java b/src/test/java/com/outfitlab/project/domain/useCases/dashboard/GetActividadPorDiasTest.java new file mode 100644 index 0000000..9bd912e --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/dashboard/GetActividadPorDiasTest.java @@ -0,0 +1,154 @@ +package com.outfitlab.project.domain.useCases.dashboard; + +import com.outfitlab.project.domain.interfaces.repositories.CombinationAttemptRepository; +import com.outfitlab.project.domain.model.CombinationAttemptModel; +import com.outfitlab.project.domain.model.dto.PrendaDashboardDTO.DiaPrueba; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.IntStream; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetActividadPorDiasTest { + + private CombinationAttemptRepository combinationAttemptRepository = mock(CombinationAttemptRepository.class); + private GetActividadPorDias getActividadPorDias; + + private final int DAYS_TO_CHECK = 30; + + @BeforeEach + void setUp() { + getActividadPorDias = new GetActividadPorDias(combinationAttemptRepository); + } + + + @Test + public void shouldReturnCorrectCountsWhenAttemptsAreSpreadAcrossDays() { + List mockAttempts = givenAttemptsExistOnSpecificDays(1, 1, 3, 15, 2, 30); + givenRepositoryReturnsAttempts(mockAttempts); + + List result = whenExecuteGetActividad(); + + thenRepositoryFindLastNDaysWasCalled(DAYS_TO_CHECK); + + List expected = List.of( + createDiaPrueba(1, 1), + createDiaPrueba(15, 3), + createDiaPrueba(30, 2) + ); + thenDailyCountsAreCorrect(result, expected); + } + + @Test + public void shouldReturn30DaysWithZeroCountsWhenNoAttemptsAreFound() { + givenRepositoryReturnsEmptyAttempts(); + + List result = whenExecuteGetActividad(); + + thenRepositoryFindLastNDaysWasCalled(DAYS_TO_CHECK); + thenAllDaysHaveZeroCount(result); + } + + @Test + public void shouldCountAllAttemptsCorrectlyInSingleDay() { + List mockAttempts = givenAttemptsExistOnSpecificDays(5, 1); + givenRepositoryReturnsAttempts(mockAttempts); + + List result = whenExecuteGetActividad(); + + thenDailyCountsAreCorrect(result, List.of(createDiaPrueba(1, 5))); + } + + @Test + public void shouldCountAttemptsOnBoundaryDaysCorrectly() { + List mockAttempts = givenAttemptsExistOnSpecificDays(2, 1, 4, 30); + givenRepositoryReturnsAttempts(mockAttempts); + + List result = whenExecuteGetActividad(); + + List expected = List.of( + createDiaPrueba(1, 2), + createDiaPrueba(30, 4) + ); + thenDailyCountsAreCorrect(result, expected); + } + + @Test + public void shouldIgnoreAttemptsWithInvalidDayOfMonth() { + List mockAttempts = new ArrayList<>(); + mockAttempts.add(createAttempt(10)); + mockAttempts.add(createAttempt(31)); + + givenRepositoryReturnsAttempts(mockAttempts); + + List result = whenExecuteGetActividad(); + + thenDailyCountsAreCorrect(result, List.of(createDiaPrueba(10, 1))); + } + + + //privadoss + private void givenRepositoryReturnsAttempts(List attempts) { + when(combinationAttemptRepository.findLastNDays(DAYS_TO_CHECK)).thenReturn(attempts); + } + + private void givenRepositoryReturnsEmptyAttempts() { + when(combinationAttemptRepository.findLastNDays(DAYS_TO_CHECK)).thenReturn(Collections.emptyList()); + } + + private CombinationAttemptModel createAttempt(int dayOfMonth) { + CombinationAttemptModel attempt = mock(CombinationAttemptModel.class); + + LocalDateTime date = LocalDateTime.of(2025, 1, dayOfMonth, 10, 0); + when(attempt.getCreatedAt()).thenReturn(date); + return attempt; + } + + private List givenAttemptsExistOnSpecificDays(int... countDayPairs) { + List attempts = new ArrayList<>(); + IntStream.range(0, countDayPairs.length / 2) + .forEach(i -> { + int count = countDayPairs[i * 2]; + int day = countDayPairs[i * 2 + 1]; + + for (int j = 0; j < count; j++) { + attempts.add(createAttempt(day)); + } + }); + return attempts; + } + + private List whenExecuteGetActividad() { + return getActividadPorDias.execute(); + } + + private DiaPrueba createDiaPrueba(int dia, int cantidad) { + return new DiaPrueba(dia, cantidad); + } + + private void thenRepositoryFindLastNDaysWasCalled(int days) { + verify(combinationAttemptRepository, times(1)).findLastNDays(days); + } + + private void thenDailyCountsAreCorrect(List actualResult, List expectedNonZeroDays) { + assertEquals(DAYS_TO_CHECK, actualResult.size(), "La lista debe tener exactamente 30 días."); + + java.util.Map actualCounts = new java.util.HashMap<>(); + actualResult.forEach(dp -> actualCounts.put(dp.dia(), dp.pruebas())); + + expectedNonZeroDays.forEach(expectedDP -> { + assertTrue(actualCounts.containsKey(expectedDP.dia()), "El día " + expectedDP.dia() + " debe existir en el resultado."); + + assertEquals(expectedDP.pruebas(), actualCounts.get(expectedDP.dia()), "El día " + expectedDP.dia() + " debe tener el conteo correcto."); + }); + } + + private void thenAllDaysHaveZeroCount(List actualResult) { + assertEquals(DAYS_TO_CHECK, actualResult.size(), "La lista debe tener 30 días."); + actualResult.forEach(dp -> assertEquals(0, dp.pruebas(), "Todos los días deben tener conteo cero.")); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/dashboard/GetColorConversionTest.java b/src/test/java/com/outfitlab/project/domain/useCases/dashboard/GetColorConversionTest.java new file mode 100644 index 0000000..e6c27a5 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/dashboard/GetColorConversionTest.java @@ -0,0 +1,175 @@ +package com.outfitlab.project.domain.useCases.dashboard; + +import com.outfitlab.project.domain.interfaces.repositories.CombinationAttemptRepository; +import com.outfitlab.project.domain.model.CombinationAttemptModel; +import com.outfitlab.project.domain.model.CombinationModel; +import com.outfitlab.project.domain.model.PrendaModel; +import com.outfitlab.project.domain.model.BrandModel; +import com.outfitlab.project.domain.model.ColorModel; +import com.outfitlab.project.domain.model.dto.PrendaDashboardDTO.ColorConversion; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetColorConversionTest { + + private CombinationAttemptRepository combinationAttemptRepository = mock(CombinationAttemptRepository.class); + private GetColorConversion getColorConversion; + + private final String TARGET_BRAND = "ADIDAS"; + private final String OTHER_BRAND = "NIKE"; + private final String COLOR_RED = "Rojo"; + private final String COLOR_BLUE = "Azul"; + + @BeforeEach + void setUp() { + getColorConversion = new GetColorConversion(combinationAttemptRepository); + } + + + @Test + public void shouldReturnCorrectCountsAndSortByPruebasForTargetBrand() { + List mockAttempts = new ArrayList<>(); + mockAttempts.add(createAttempt(TARGET_BRAND, COLOR_RED, OTHER_BRAND, COLOR_BLUE)); + mockAttempts.add(createAttempt(TARGET_BRAND, COLOR_RED, TARGET_BRAND, COLOR_BLUE)); + mockAttempts.add(createAttempt(OTHER_BRAND, COLOR_BLUE, TARGET_BRAND, COLOR_RED)); + mockAttempts.add(createAttempt(TARGET_BRAND, COLOR_BLUE, OTHER_BRAND, COLOR_RED)); + + givenRepositoryReturnsAttempts(mockAttempts); + + List result = whenExecuteGetColorConversion(TARGET_BRAND); + + thenResultSizeIsCorrect(result, 2); + thenResultIsSortedAndCountedCorrectly(result, COLOR_RED, 3, COLOR_BLUE, 2); + } + + @Test + public void shouldReturnEmptyListWhenNoAttemptsAreFound() { + givenRepositoryReturnsAttempts(Collections.emptyList()); + + List result = whenExecuteGetColorConversion(TARGET_BRAND); + + thenResultSizeIsCorrect(result, 0); + } + + @Test + public void shouldCountColorOncePerAttemptEvenIfUsedTwice() { + List mockAttempts = List.of( + createAttempt(TARGET_BRAND, COLOR_RED, TARGET_BRAND, COLOR_RED) + ); + givenRepositoryReturnsAttempts(mockAttempts); + + List result = whenExecuteGetColorConversion(TARGET_BRAND); + + thenResultSizeIsCorrect(result, 1); + thenResultIsSortedAndCountedCorrectly(result, COLOR_RED, 1); + } + + @Test + public void shouldHandleNullPrendasOrNullColorNamesWithoutNPE() { + List mockAttempts = new ArrayList<>(); + + mockAttempts.add(createAttempt(TARGET_BRAND, COLOR_RED, null, COLOR_BLUE)); + mockAttempts.add(createAttempt(null, COLOR_RED, TARGET_BRAND, COLOR_BLUE)); + mockAttempts.add(createAttempt(null, null, null, null)); + + givenRepositoryReturnsAttempts(mockAttempts); + + List result = whenExecuteGetColorConversion(TARGET_BRAND); + + thenResultSizeIsCorrect(result, 2); + thenResultIsSortedAndCountedCorrectly(result, COLOR_RED, 1, COLOR_BLUE, 1); + } + + @Test + public void shouldReturnEmptyListWhenTargetBrandCodeIsNull() { + givenRepositoryReturnsAttempts(List.of(createAttempt(TARGET_BRAND, COLOR_RED, TARGET_BRAND, COLOR_RED))); + + List result = whenExecuteGetColorConversion(null); + + thenResultSizeIsCorrect(result, 0); + } + + + //privadoss + private void givenRepositoryReturnsAttempts(List attempts) { + when(combinationAttemptRepository.findAll()).thenReturn(attempts); + } + + private CombinationAttemptModel createAttempt(String supBrandCode, String supColorName, + String infBrandCode, String infColorName) { + + final String safeSupBrandCode = supBrandCode != null ? supBrandCode : ""; + final String safeInfBrandCode = infBrandCode != null ? infBrandCode : ""; + final String safeSupColorName = supColorName != null ? supColorName : ""; + final String safeInfColorName = infColorName != null ? infColorName : ""; + + CombinationAttemptModel attempt = mock(CombinationAttemptModel.class); + CombinationModel combination = mock(CombinationModel.class); + + PrendaModel prendaSup = mock(PrendaModel.class); + + BrandModel brandSup = mock(BrandModel.class); + when(brandSup.getCodigoMarca()).thenReturn(safeSupBrandCode); + when(prendaSup.getMarca()).thenReturn(brandSup); + + ColorModel colorSup = mock(ColorModel.class); + when(colorSup.getNombre()).thenReturn(safeSupColorName); + when(prendaSup.getColor()).thenReturn(colorSup); + + PrendaModel prendaInf = mock(PrendaModel.class); + + BrandModel brandInf = mock(BrandModel.class); + when(brandInf.getCodigoMarca()).thenReturn(safeInfBrandCode); + when(prendaInf.getMarca()).thenReturn(brandInf); + + ColorModel colorInf = mock(ColorModel.class); + when(colorInf.getNombre()).thenReturn(safeInfColorName); + when(prendaInf.getColor()).thenReturn(colorInf); + + when(combination.getPrendaSuperior()).thenReturn(prendaSup); + when(combination.getPrendaInferior()).thenReturn(prendaInf); + + when(attempt.getCombination()).thenReturn(combination); + + return attempt; + } + + private List whenExecuteGetColorConversion(String brandCode) { + return getColorConversion.execute(brandCode); + } + + private void thenResultSizeIsCorrect(List result, int size) { + assertNotNull(result); + assertEquals(size, result.size(), "La lista resultante debe tener el tamaño esperado."); + } + + private void thenResultIsSortedAndCountedCorrectly(List result, Object... expectedPairs) { + + if (result.size() > 1) { + assertTrue(result.get(0).pruebas() >= result.get(1).pruebas(), "El resultado debe estar ordenado por 'pruebas' descendente."); + } + + Map actualCounts = new HashMap<>(); + result.forEach(cc -> actualCounts.put(cc.color(), cc.pruebas())); + + for (int i = 0; i < expectedPairs.length; i += 2) { + String colorName = (String) expectedPairs[i]; + int expectedCount = (Integer) expectedPairs[i + 1]; + + assertTrue(actualCounts.containsKey(colorName), "El color '" + colorName + "' debe estar en la lista."); + assertEquals(expectedCount, actualCounts.get(colorName), "El conteo de pruebas para " + colorName + " es incorrecto."); + + result.stream() + .filter(cc -> cc.color().equals(colorName)) + .findFirst() + .ifPresent(cc -> { + assertEquals(0, cc.favoritos(), "El campo 'favoritos' debe ser 0."); + assertEquals(0.0, cc.conversion(), 0.001, "El campo 'conversion' debe ser 0.0."); + }); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/dashboard/GetTopCombosTest.java b/src/test/java/com/outfitlab/project/domain/useCases/dashboard/GetTopCombosTest.java new file mode 100644 index 0000000..2e10610 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/dashboard/GetTopCombosTest.java @@ -0,0 +1,183 @@ +package com.outfitlab.project.domain.useCases.dashboard; + +import com.outfitlab.project.domain.interfaces.repositories.CombinationAttemptRepository; +import com.outfitlab.project.domain.model.CombinationAttemptModel; +import com.outfitlab.project.domain.model.CombinationModel; +import com.outfitlab.project.domain.model.PrendaModel; +import com.outfitlab.project.domain.model.BrandModel; +import com.outfitlab.project.domain.model.UserModel; +import com.outfitlab.project.domain.model.dto.PrendaDashboardDTO.ComboPopular; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetTopCombosTest { + + private CombinationAttemptRepository combinationAttemptRepository = mock(CombinationAttemptRepository.class); + private GetTopCombos getTopCombos; + + private final String TARGET_BRAND = "ADIDAS"; + private final String OTHER_BRAND = "NIKE"; + private final String P_REMERA = "Remera"; + private final String P_JEANS = "Jeans"; + private final String P_BUZO = "Buzo"; + private final String P_SHORTS = "Shorts"; + private final String IMG_SUP = "url/sup"; + private final String IMG_INF = "url/inf"; + private final int TOP_N_LIMIT = 2; + + @BeforeEach + void setUp() { + getTopCombos = new GetTopCombos(combinationAttemptRepository); + } + + + @Test + public void shouldGroupCountAndSortTopNCombosByPruebas() { + List mockAttempts = new ArrayList<>(); + + //combo 1 + mockAttempts.add(createAttempt(P_REMERA, P_JEANS, TARGET_BRAND, TARGET_BRAND, true)); + mockAttempts.add(createAttempt(P_REMERA, P_JEANS, TARGET_BRAND, OTHER_BRAND, true)); + mockAttempts.add(createAttempt(P_REMERA, P_JEANS, OTHER_BRAND, TARGET_BRAND, false)); + + //combo 2 + mockAttempts.add(createAttempt(P_BUZO, P_SHORTS, TARGET_BRAND, TARGET_BRAND, true)); + + //combo 3 + mockAttempts.add(createAttempt(P_REMERA, P_SHORTS, TARGET_BRAND, TARGET_BRAND, true)); + mockAttempts.add(createAttempt(P_REMERA, P_SHORTS, TARGET_BRAND, TARGET_BRAND, true)); + mockAttempts.add(createAttempt(P_REMERA, P_SHORTS, TARGET_BRAND, TARGET_BRAND, true)); + mockAttempts.add(createAttempt(P_REMERA, P_SHORTS, TARGET_BRAND, OTHER_BRAND, true)); + + //combo ignorafo + mockAttempts.add(createAttempt(P_BUZO, P_JEANS, OTHER_BRAND, OTHER_BRAND, true)); + mockAttempts.add(createAttempt(P_BUZO, P_JEANS, OTHER_BRAND, OTHER_BRAND, true)); + + givenRepositoryReturnsAttempts(mockAttempts); + + List result = whenExecuteGetTopCombos(TOP_N_LIMIT, TARGET_BRAND); + + thenResultSizeIsCorrect(result, TOP_N_LIMIT); + thenResultIsCorrectlySorted(result, P_REMERA, P_SHORTS, P_REMERA, P_JEANS); + thenComboHasCorrectCounts(result.get(0), P_REMERA, P_SHORTS, 4, 4); + } + + @Test + public void shouldReturnEmptyListWhenNoAttemptsAreFound() { + givenRepositoryReturnsAttempts(Collections.emptyList()); + + List result = whenExecuteGetTopCombos(TOP_N_LIMIT, TARGET_BRAND); + + thenResultSizeIsCorrect(result, 0); + } + + @Test + public void shouldReturnEmptyListWhenOnlyOtherBrandsArePresent() { + List mockAttempts = List.of( + createAttempt(P_REMERA, P_JEANS, OTHER_BRAND, OTHER_BRAND, true) + ); + givenRepositoryReturnsAttempts(mockAttempts); + + List result = whenExecuteGetTopCombos(TOP_N_LIMIT, TARGET_BRAND); + + thenResultSizeIsCorrect(result, 0); + } + + @Test + public void shouldIgnoreComboIfBothBrandsAreNull() { + List mockAttempts = List.of( + createAttempt(P_REMERA, P_JEANS, null, null, true) + ); + givenRepositoryReturnsAttempts(mockAttempts); + + List result = whenExecuteGetTopCombos(TOP_N_LIMIT, TARGET_BRAND); + + thenResultSizeIsCorrect(result, 0); + } + + @Test + public void shouldCountThumbsAsZeroWhenUserModelIsNull() { + List mockAttempts = List.of( + createAttempt(P_REMERA, P_JEANS, TARGET_BRAND, OTHER_BRAND, true), + createAttempt(P_REMERA, P_JEANS, TARGET_BRAND, OTHER_BRAND, false) + ); + givenRepositoryReturnsAttempts(mockAttempts); + + List result = whenExecuteGetTopCombos(TOP_N_LIMIT, TARGET_BRAND); + + thenResultSizeIsCorrect(result, 1); + thenComboHasCorrectCounts(result.get(0), P_REMERA, P_JEANS, 2, 1); + } + + + //privadoss + private void givenRepositoryReturnsAttempts(List attempts) { + when(combinationAttemptRepository.findAll()).thenReturn(attempts); + } + + private CombinationAttemptModel createAttempt(String supName, String infName, + String supBrandCode, String infBrandCode, + boolean userExists) { + + final String safeSupBrandCode = supBrandCode != null ? supBrandCode : ""; + final String safeInfBrandCode = infBrandCode != null ? infBrandCode : ""; + + CombinationAttemptModel attempt = mock(CombinationAttemptModel.class); + CombinationModel combination = mock(CombinationModel.class); + PrendaModel prendaSup = mock(PrendaModel.class); + PrendaModel prendaInf = mock(PrendaModel.class); + + BrandModel mockBrandSup = mock(BrandModel.class); + when(mockBrandSup.getCodigoMarca()).thenReturn(safeSupBrandCode); + when(prendaSup.getMarca()).thenReturn(mockBrandSup); + + BrandModel mockBrandInf = mock(BrandModel.class); + when(mockBrandInf.getCodigoMarca()).thenReturn(safeInfBrandCode); + when(prendaInf.getMarca()).thenReturn(mockBrandInf); + + when(prendaSup.getNombre()).thenReturn(supName); + when(prendaInf.getNombre()).thenReturn(infName); + when(prendaSup.getImagenUrl()).thenReturn(IMG_SUP); + when(prendaInf.getImagenUrl()).thenReturn(IMG_INF); + + when(combination.getPrendaSuperior()).thenReturn(prendaSup); + when(combination.getPrendaInferior()).thenReturn(prendaInf); + + UserModel mockUser = userExists ? mock(UserModel.class) : null; + when(attempt.getUser()).thenReturn(mockUser); + + when(attempt.getCombination()).thenReturn(combination); + + return attempt; + } + + private List whenExecuteGetTopCombos(int topN, String brandCode) { + return getTopCombos.execute(topN, brandCode); + } + + private void thenResultSizeIsCorrect(List result, int size) { + assertNotNull(result); + assertEquals(size, result.size(), "La lista resultante debe tener el tamaño TOP_N esperado."); + } + + private void thenResultIsCorrectlySorted(List result, String top1Sup, String top1Inf, String top2Sup, String top2Inf) { + assertEquals(top1Sup, result.get(0).getSuperior(), "El Top 1 debe tener la prenda superior correcta."); + assertEquals(top1Inf, result.get(0).getInferior(), "El Top 1 debe tener la prenda inferior correcta."); + + assertTrue(result.get(0).getPruebas() >= result.get(1).getPruebas(), "El Top 1 debe tener más pruebas que el Top 2."); + + assertEquals(top2Sup, result.get(1).getSuperior(), "El Top 2 debe tener la prenda superior correcta."); + assertEquals(top2Inf, result.get(1).getInferior(), "El Top 2 debe tener la prenda inferior correcta."); + } + + private void thenComboHasCorrectCounts(ComboPopular combo, String supName, String infName, int expectedPruebas, int expectedThumbs) { + assertEquals(supName, combo.getSuperior()); + assertEquals(infName, combo.getInferior()); + assertEquals(expectedPruebas, combo.getPruebas(), "El conteo de Pruebas es incorrecto."); + assertEquals(expectedThumbs, combo.getThumbs(), "El conteo de Thumbs (Likes) es incorrecto."); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/dashboard/GetTopPrendasTest.java b/src/test/java/com/outfitlab/project/domain/useCases/dashboard/GetTopPrendasTest.java new file mode 100644 index 0000000..3232636 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/dashboard/GetTopPrendasTest.java @@ -0,0 +1,160 @@ +package com.outfitlab.project.domain.useCases.dashboard; + +import com.outfitlab.project.domain.interfaces.repositories.CombinationAttemptRepository; +import com.outfitlab.project.domain.interfaces.repositories.GarmentRepository; +import com.outfitlab.project.domain.model.CombinationAttemptModel; +import com.outfitlab.project.domain.model.PrendaModel; +import com.outfitlab.project.domain.model.BrandModel; +import com.outfitlab.project.domain.model.ColorModel; +import com.outfitlab.project.domain.model.dto.PrendaDashboardDTO.TopPrenda; +import com.outfitlab.project.domain.model.dto.PrendaDashboardDTO.DailyPrueba; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetTopPrendasTest { + + private GarmentRepository prendaRepository = mock(GarmentRepository.class); + private CombinationAttemptRepository combinationAttemptRepository = mock(CombinationAttemptRepository.class); + private GetTopPrendas getTopPrendas; + + private final LocalDate FIXED_TODAY = LocalDate.of(2025, 11, 27); + private final LocalDateTime FIXED_NOW = FIXED_TODAY.atStartOfDay(); + + private final String TARGET_BRAND = "ADIDAS"; + private final String OTHER_BRAND = "NIKE"; + private final int TOP_N_LIMIT = 2; + + @BeforeEach + void setUp() { + getTopPrendas = new GetTopPrendas(prendaRepository, combinationAttemptRepository); + } + + + @Test + public void shouldReturnEmptyListWhenNoPrendasMatchBrandCode() { + List allPrendas = List.of(createPrenda(1L, "T-Shirt", OTHER_BRAND)); + givenRepositoryReturnsAllPrendas(allPrendas); + + try (MockedStatic mockedStatic = mockStatic(LocalDate.class)) { + mockedStatic.when(LocalDate::now).thenReturn(FIXED_TODAY); + + List result = whenExecuteGetTopPrendas(TOP_N_LIMIT, TARGET_BRAND); + + thenResultSizeIsCorrect(result, 0); + } + } + + @Test + public void shouldReturnPrendasWithZeroPruebasWhenNoAttemptsExist() { + List allPrendas = List.of(createPrenda(1L, "T-Shirt", TARGET_BRAND)); + givenRepositoryReturnsAllPrendas(allPrendas); + givenAttemptsExistForPrenda(1L, 0); + + try (MockedStatic mockedStatic = mockStatic(LocalDate.class)) { + mockedStatic.when(LocalDate::now).thenReturn(FIXED_TODAY); + + List result = whenExecuteGetTopPrendas(TOP_N_LIMIT, TARGET_BRAND); + + thenResultSizeIsCorrect(result, 1); + assertEquals(0, result.get(0).pruebas(), "El conteo total debe ser cero."); + thenDailyCountsAreCorrect(result.get(0).daily(), 0, 0); + } + } + + @Test + public void shouldPropagateNPEWhenBrandModelIsNull() { + List allPrendas = List.of(createPrenda(1L, "T-Shirt", null)); + givenRepositoryReturnsAllPrendas(allPrendas); + + try (MockedStatic mockedStatic = mockStatic(LocalDate.class)) { + mockedStatic.when(LocalDate::now).thenReturn(FIXED_TODAY); + + assertThrows(NullPointerException.class, + () -> whenExecuteGetTopPrendas(TOP_N_LIMIT, TARGET_BRAND), + "Debe fallar con NPE si la marca es null y se intenta acceder a su código."); + } + } + + + //privadoss + private void givenRepositoryReturnsAllPrendas(List prendas) { + when(prendaRepository.findAll()).thenReturn(prendas); + } + + private void givenAttemptsExistForPrenda(Long prendaId, int totalAttempts, int... countDayPairs) { + List attempts = new ArrayList<>(); + + for (int i = 0; i < countDayPairs.length; i += 2) { + int count = countDayPairs[i]; + int daysAgo = countDayPairs[i + 1]; + + LocalDateTime date = FIXED_NOW.minusDays(daysAgo); + + for (int j = 0; j < count; j++) { + CombinationAttemptModel attempt = mock(CombinationAttemptModel.class); + when(attempt.getCreatedAt()).thenReturn(date); + attempts.add(attempt); + } + } + + if (attempts.size() != totalAttempts) { + throw new IllegalArgumentException("El número de intentos simulados no coincide con el totalAttempts."); + } + + when(combinationAttemptRepository.findAllByPrenda(prendaId)).thenReturn(attempts); + } + + private PrendaModel createPrenda(Long id, String name, String brandCode) { + PrendaModel prenda = mock(PrendaModel.class); + + BrandModel brand = null; + if (brandCode != null) { + brand = mock(BrandModel.class); + when(brand.getCodigoMarca()).thenReturn(brandCode); + } + + ColorModel color = mock(ColorModel.class); + when(color.getNombre()).thenReturn("ColorName"); + + when(prenda.getId()).thenReturn(id); + when(prenda.getNombre()).thenReturn(name); + when(prenda.getMarca()).thenReturn(brand); + when(prenda.getColor()).thenReturn(color); + when(prenda.getImagenUrl()).thenReturn("url/" + id); + + return prenda; + } + + private List whenExecuteGetTopPrendas(int topN, String brandCode) { + return getTopPrendas.execute(topN, brandCode); + } + + private void thenResultSizeIsCorrect(List result, int size) { + assertNotNull(result); + assertEquals(size, result.size(), "El tamaño del resultado debe ser el esperado."); + } + + private void thenResultIsSortedAndContentIsCorrect(List result, Long top1Id, int top1Pruebas, Long top2Id, int top2Pruebas) { + assertTrue(result.get(0).pruebas() >= result.get(1).pruebas(), "El Top 1 debe tener más pruebas que el Top 2."); + + assertEquals(top1Id, result.get(0).id(), "El ID del Top 1 es incorrecto."); + assertEquals(top1Pruebas, result.get(0).pruebas(), "El conteo de pruebas del Top 1 es incorrecto."); + + assertEquals(top2Id, result.get(1).id(), "El ID del Top 2 es incorrecto."); + assertEquals(top2Pruebas, result.get(1).pruebas(), "El conteo de pruebas del Top 2 es incorrecto."); + } + + private void thenDailyCountsAreCorrect(@NotNull List daily, int todayCount, int yesterdayCount) { + assertEquals(30, daily.size(), "La lista daily debe tener 30 días."); + assertEquals(todayCount, daily.get(29).pruebas(), "El conteo de hoy debe ser el esperado."); + assertEquals(yesterdayCount, daily.get(28).pruebas(), "El conteo de ayer debe ser el esperado."); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/garment/AddGarmentToFavoriteTest.java b/src/test/java/com/outfitlab/project/domain/useCases/garment/AddGarmentToFavoriteTest.java index 0622582..6f51b43 100644 --- a/src/test/java/com/outfitlab/project/domain/useCases/garment/AddGarmentToFavoriteTest.java +++ b/src/test/java/com/outfitlab/project/domain/useCases/garment/AddGarmentToFavoriteTest.java @@ -11,64 +11,132 @@ public class AddGarmentToFavoriteTest { - private UserGarmentFavoriteRepository userGarmentFavoriteRepository = mock( - UserGarmentFavoriteRepositoryImpl.class); + private UserGarmentFavoriteRepository userGarmentFavoriteRepository = mock(UserGarmentFavoriteRepositoryImpl.class); + private AddGarmentToFavorite addGarmentToFavorite = new AddGarmentToFavorite(userGarmentFavoriteRepository); @Test - public void givenValidGarmentAndUserWhenExecuteThenAddToFavoritesSuccessfully() - throws Exception, UserGarmentFavoriteNotFoundException, FavoritesException { - String garmentCode = "G001"; - String userEmail = "test@aaaa.com"; - UserGarmentFavoriteEntity fav = new UserGarmentFavoriteEntity(); + public void givenValidGarmentAndUserWhenExecuteThenAddToFavoritesSuccessfully(){ - when(userGarmentFavoriteRepository.findByGarmentCodeAndUserEmail(garmentCode, userEmail)) - .thenThrow(new UserGarmentFavoriteNotFoundException("No está en favoritos")); - when(userGarmentFavoriteRepository.addToFavorite(garmentCode, userEmail)).thenReturn(fav); + String garmentCode = givenGarmentCode(); + String userEmail = givenUserEmail(); + UserGarmentFavoriteEntity fav = givenFavoriteEntity(); - String result = addGarmentToFavorite.execute(garmentCode, userEmail); + givenFavoriteNotExisting(garmentCode, userEmail); + givenAddFavoriteReturns(garmentCode, userEmail, fav); - assertNotNull(result); - assertEquals("Prenda añadida a favoritos.", result); + String result = whenExecuteAddFavorite(garmentCode, userEmail); + + thenFavoriteAddedSuccessfully(result); } + @Test - public void givenExistingFavoriteWhenExecuteThenThrowUserGarmentFavoriteAlreadyExistsException() - throws UserGarmentFavoriteNotFoundException { - String garmentCode = "G001"; - String userEmail = "test@aaaa.com"; + public void givenExistingFavoriteWhenExecuteThenThrowUserGarmentFavoriteAlreadyExistsException() { - when(userGarmentFavoriteRepository.findByGarmentCodeAndUserEmail(garmentCode, userEmail)) - .thenReturn(null); + String garmentCode = givenGarmentCode(); + String userEmail = givenUserEmail(); + givenFavoriteAlreadyExists(garmentCode, userEmail); - assertThrows(UserGarmentFavoriteAlreadyExistsException.class, - () -> addGarmentToFavorite.execute(garmentCode, userEmail)); + thenThrowAlreadyExists(garmentCode, userEmail); } @Test - public void givenGarmentOrUserNotFoundWhenExecuteThenThrowCorrespondingException() - throws UserNotFoundException, UserGarmentFavoriteNotFoundException, GarmentNotFoundException { - String garmentCode = "G001"; - String userEmail = "test@aaaa.com"; + public void givenGarmentOrUserNotFoundWhenExecuteThenThrowCorrespondingException(){ - when(userGarmentFavoriteRepository.findByGarmentCodeAndUserEmail(garmentCode, userEmail)) - .thenThrow(new UserGarmentFavoriteNotFoundException("No está en favoritos")); - when(userGarmentFavoriteRepository.addToFavorite(garmentCode, userEmail)) - .thenThrow(new UserNotFoundException("Usuario no encontrado")); + String garmentCode = givenGarmentCode(); + String userEmail = givenUserEmail(); - assertThrows(UserNotFoundException.class, () -> addGarmentToFavorite.execute(garmentCode, userEmail)); + givenFavoriteNotExisting(garmentCode, userEmail); + givenAddFavoriteThrowsUserNotFound(garmentCode, userEmail); + + thenThrowUserNotFound(garmentCode, userEmail); } + @Test - public void givenFavoriteAdditionFailsWhenExecuteThenThrowFavoritesException() - throws Exception, UserGarmentFavoriteNotFoundException { - String garmentCode = "G001"; - String userEmail = "test@aaa.com"; + public void givenFavoriteAdditionFailsWhenExecuteThenThrowFavoritesException() { + + String garmentCode = givenGarmentCode(); + String userEmail = givenUserEmail(); - when(userGarmentFavoriteRepository.findByGarmentCodeAndUserEmail(garmentCode, userEmail)) - .thenThrow(new UserGarmentFavoriteNotFoundException("No está en favoritos")); - when(userGarmentFavoriteRepository.addToFavorite(garmentCode, userEmail)).thenReturn(null); + givenFavoriteNotExisting(garmentCode, userEmail); + givenAddFavoriteReturnsNull(garmentCode, userEmail); + + thenThrowFavoritesException(garmentCode, userEmail); + } + + +// privados --------------- + private UserGarmentFavoriteEntity givenFavoriteEntity() { + return new UserGarmentFavoriteEntity(); + } + + private void givenFavoriteNotExisting(String garment, String user) + throws UserGarmentFavoriteNotFoundException { + when(userGarmentFavoriteRepository.findByGarmentCodeAndUserEmail(garment, user)) + .thenThrow(new UserGarmentFavoriteNotFoundException("No está en favoritos")); + } + + private void givenAddFavoriteReturns(String garment, String user, UserGarmentFavoriteEntity entity) + throws FavoritesException { + when(userGarmentFavoriteRepository.addToFavorite(garment, user)) + .thenReturn(entity); + } + + private String whenExecuteAddFavorite(String garment, String user) throws FavoritesException { + return addGarmentToFavorite.execute(garment, user); + } + + private void thenFavoriteAddedSuccessfully(String result) { + assertNotNull(result); + assertEquals("Prenda añadida a favoritos.", result); + } + + private void givenFavoriteAlreadyExists(String garment, String user) + throws UserGarmentFavoriteNotFoundException { + when(userGarmentFavoriteRepository.findByGarmentCodeAndUserEmail(garment, user)) + .thenReturn(null); + } + + private void thenThrowAlreadyExists(String garment, String user) { + assertThrows( + UserGarmentFavoriteAlreadyExistsException.class, + () -> addGarmentToFavorite.execute(garment, user) + ); + } + + private void givenAddFavoriteThrowsUserNotFound(String garment, String user) + throws UserNotFoundException, FavoritesException { + when(userGarmentFavoriteRepository.addToFavorite(garment, user)) + .thenThrow(new UserNotFoundException("Usuario no encontrado")); + } + + private void thenThrowUserNotFound(String garment, String user) { + assertThrows( + UserNotFoundException.class, + () -> addGarmentToFavorite.execute(garment, user) + ); + } + + private void givenAddFavoriteReturnsNull(String garment, String user) + throws FavoritesException { + when(userGarmentFavoriteRepository.addToFavorite(garment, user)) + .thenReturn(null); + } + + private void thenThrowFavoritesException(String garment, String user) { + assertThrows( + FavoritesException.class, + () -> addGarmentToFavorite.execute(garment, user) + ); + } + + private String givenGarmentCode() { + return "G001"; + } - assertThrows(FavoritesException.class, () -> addGarmentToFavorite.execute(garmentCode, userEmail)); + private String givenUserEmail() { + return "test@aaaa.com"; } } diff --git a/src/test/java/com/outfitlab/project/domain/useCases/garment/CreateGarmentTest.java b/src/test/java/com/outfitlab/project/domain/useCases/garment/CreateGarmentTest.java new file mode 100644 index 0000000..a88b120 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/garment/CreateGarmentTest.java @@ -0,0 +1,82 @@ +package com.outfitlab.project.domain.useCases.garment; + +import com.outfitlab.project.domain.interfaces.repositories.GarmentRepository; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +class CreateGarmentTest { + + private final GarmentRepository garmentRepository = mock(GarmentRepository.class); + private final CreateGarment createGarment = new CreateGarment(garmentRepository); + + @Test + void givenValidDataWhenExecuteThenCallsRepositoryCorrectly() { + String name = "Camisa Azul"; + + whenExecute(name, "superior", "azul", "BR001", "url", "calido", List.of("casual"), "hombre"); + + thenRepositoryWasCalledWith( + name, + "camisa_azul", + "superior", + "azul", + "BR001", + "url", + "calido", + List.of("casual"), + "hombre" + ); + } + + @Test + void givenAccentedNameWhenExecuteThenFormatIsCorrect() { + String name = "Pañuelo Óptimo"; + + whenExecute(name, "accesorio", "rojo", "BR002", "img", "templado", List.of(), "unisex"); + + thenRepositoryWasCalledWith( + name, + "panuelo_optimo", + "accesorio", + "rojo", + "BR002", + "img", + "templado", + List.of(), + "unisex" + ); + } + + @Test + void givenRepositoryThrowsRuntimeWhenExecuteThenPropagateSameException() { + RuntimeException expected = new RuntimeException("falló el repo"); + givenRepositoryThrows(expected); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> whenExecute("Camisa", "superior", "azul", "BR", "img", "calido", List.of(), "hombre")); + + assertEquals("falló el repo", thrown.getMessage()); + } + + +//privados ----- + private void whenExecute(String name, String type, String color, String brand, String img, String clima, List ocasiones, String genero) { + createGarment.execute(name, type, color, brand, img, clima, ocasiones, genero); + } + + private void thenRepositoryWasCalledWith(String name, String expectedCode, String type, + String color, String brand, String image, String clima, List ocasiones, String genero) { + verify(garmentRepository, times(1)).createGarment(eq(name), eq(expectedCode), eq(type), eq(color), + eq(brand), eq(image), eq(clima), eq(ocasiones), eq(genero)); + } + + private void givenRepositoryThrows(RuntimeException exception) { + doThrow(exception) + .when(garmentRepository) + .createGarment(any(), any(), any(), any(), any(), any(), any(), any(), any()); + } +} diff --git a/src/test/java/com/outfitlab/project/domain/useCases/garment/DeleteAllFavoritesRelatedToGarmentTest.java b/src/test/java/com/outfitlab/project/domain/useCases/garment/DeleteAllFavoritesRelatedToGarmentTest.java new file mode 100644 index 0000000..b3214d9 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/garment/DeleteAllFavoritesRelatedToGarmentTest.java @@ -0,0 +1,20 @@ +package com.outfitlab.project.domain.useCases.garment; + +import com.outfitlab.project.domain.interfaces.repositories.UserGarmentFavoriteRepository; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.*; + +public class DeleteAllFavoritesRelatedToGarmentTest { + + private UserGarmentFavoriteRepository repo = mock(UserGarmentFavoriteRepository.class); + + @Test + void executeShouldCallRepositoryWithCorrectGarmentCode() { + DeleteAllFavoritesRelatedToGarment useCase = new DeleteAllFavoritesRelatedToGarment(repo); + + useCase.execute("ABC123"); + + verify(repo, times(1)).deleteFavoritesRelatedToGarment("ABC123"); + } +} diff --git a/src/test/java/com/outfitlab/project/domain/useCases/garment/DeleteGarmentFromFavoriteTest.java b/src/test/java/com/outfitlab/project/domain/useCases/garment/DeleteGarmentFromFavoriteTest.java index 2196dc6..d2f775b 100644 --- a/src/test/java/com/outfitlab/project/domain/useCases/garment/DeleteGarmentFromFavoriteTest.java +++ b/src/test/java/com/outfitlab/project/domain/useCases/garment/DeleteGarmentFromFavoriteTest.java @@ -11,69 +11,135 @@ public class DeleteGarmentFromFavoriteTest { - private UserGarmentFavoriteRepository userGarmentFavoriteRepository = mock(UserGarmentFavoriteRepositoryImpl.class); - private DeleteGarmentFromFavorite deleteGarmentFromFavorite = new DeleteGarmentFromFavorite(userGarmentFavoriteRepository); + private UserGarmentFavoriteRepository userGarmentFavoriteRepository = + mock(UserGarmentFavoriteRepositoryImpl.class); + + private DeleteGarmentFromFavorite deleteGarmentFromFavorite = + new DeleteGarmentFromFavorite(userGarmentFavoriteRepository); @Test - public void givenExistingFavoriteWhenExecuteThenDeleteSuccessfully() throws Exception, UserGarmentFavoriteNotFoundException { - String garmentCode = "G001"; - String userEmail = "user@sasas.com"; - UserGarmentFavoriteModel fav = new UserGarmentFavoriteModel(); + public void givenExistingFavoriteWhenExecuteThenDeleteSuccessfully() throws Exception { - when(userGarmentFavoriteRepository.findByGarmentCodeAndUserEmail(garmentCode, userEmail)) - .thenReturn(fav); + String garmentCode = givenGarmentCode(); + String userEmail = givenUserEmail(); + UserGarmentFavoriteModel fav = givenFavoriteModel(); - doNothing().when(userGarmentFavoriteRepository).deleteFromFavorites(garmentCode, userEmail); + givenFavoriteExists(garmentCode, userEmail, fav); + givenDeleteSuccess(garmentCode, userEmail); - String result = deleteGarmentFromFavorite.execute(garmentCode, userEmail); + String result = whenExecuteDeleteFavorite(garmentCode, userEmail); - assertNotNull(result); - assertEquals("Prenda eliminada de favoritos.", result); + thenFavoriteDeletedSuccessfully(result); } @Test - public void givenNonexistentFavoriteWhenExecuteThenThrowUserGarmentFavoriteNotFoundException() throws Exception, UserGarmentFavoriteNotFoundException { - String garmentCode = "G001"; - String userEmail = "user@asasa.com"; + public void givenNonexistentFavoriteWhenExecuteThenThrowUserGarmentFavoriteNotFoundException(){ + String garmentCode = givenGarmentCode(); + String userEmail = givenUserEmail(); - when(userGarmentFavoriteRepository.findByGarmentCodeAndUserEmail(garmentCode, userEmail)) - .thenThrow(new UserGarmentFavoriteNotFoundException("No está en favoritos")); + givenFavoriteDoesNotExist(garmentCode, userEmail); - assertThrows(UserGarmentFavoriteNotFoundException.class, () -> - deleteGarmentFromFavorite.execute(garmentCode, userEmail) - ); + thenThrowFavoriteNotFound(garmentCode, userEmail); } @Test - public void givenUserNotFoundWhenExecuteThenThrowUserNotFoundException() throws Exception, UserGarmentFavoriteNotFoundException { - String garmentCode = "G001"; - String userEmail = "user@asas.com"; - UserGarmentFavoriteModel fav = new UserGarmentFavoriteModel(); + public void givenUserNotFoundWhenExecuteThenThrowUserNotFoundException() { - when(userGarmentFavoriteRepository.findByGarmentCodeAndUserEmail(garmentCode, userEmail)) - .thenReturn(fav); - doThrow(new UserNotFoundException("Usuario no encontrado")) - .when(userGarmentFavoriteRepository).deleteFromFavorites(garmentCode, userEmail); + String garmentCode = givenGarmentCode(); + String userEmail = givenUserEmail(); + UserGarmentFavoriteModel fav = givenFavoriteModel(); - assertThrows(UserNotFoundException.class, () -> - deleteGarmentFromFavorite.execute(garmentCode, userEmail) - ); + givenFavoriteExists(garmentCode, userEmail, fav); + givenDeleteThrowsUserNotFound(garmentCode, userEmail); + + thenThrowUserNotFound(garmentCode, userEmail); } @Test - public void givenGarmentNotFoundWhenExecuteThenThrowGarmentNotFoundException() throws Exception, UserGarmentFavoriteNotFoundException { - String garmentCode = "G001"; - String userEmail = "user@asasa.com"; - UserGarmentFavoriteModel fav = new UserGarmentFavoriteModel(); + public void givenGarmentNotFoundWhenExecuteThenThrowGarmentNotFoundException() + throws Exception { - when(userGarmentFavoriteRepository.findByGarmentCodeAndUserEmail(garmentCode, userEmail)) - .thenReturn(fav); + String garmentCode = givenGarmentCode(); + String userEmail = givenUserEmail(); + UserGarmentFavoriteModel fav = givenFavoriteModel(); + + givenFavoriteExists(garmentCode, userEmail, fav); + givenDeleteThrowsGarmentNotFound(garmentCode, userEmail); + + thenThrowGarmentNotFound(garmentCode, userEmail); + } + + + //privadoss ---------------------- + private void givenDeleteThrowsGarmentNotFound(String garment, String user) + throws Exception { doThrow(new GarmentNotFoundException("Prenda no encontrada")) - .when(userGarmentFavoriteRepository).deleteFromFavorites(garmentCode, userEmail); + .when(userGarmentFavoriteRepository).deleteFromFavorites(garment, user); + } - assertThrows(GarmentNotFoundException.class, () -> - deleteGarmentFromFavorite.execute(garmentCode, userEmail) + private void thenThrowGarmentNotFound(String garment, String user) { + assertThrows( + GarmentNotFoundException.class, + () -> deleteGarmentFromFavorite.execute(garment, user) ); } -} + private String givenGarmentCode() { + return "G001"; + } + + private UserGarmentFavoriteModel givenFavoriteModel() { + return new UserGarmentFavoriteModel(); + } + private String givenUserEmail() { + return "user@sasas.com"; + } + + + private void givenFavoriteExists(String garment, String user, UserGarmentFavoriteModel fav) + throws UserGarmentFavoriteNotFoundException { + when(userGarmentFavoriteRepository.findByGarmentCodeAndUserEmail(garment, user)) + .thenReturn(fav); + } + + private void givenDeleteSuccess(String garment, String user) throws Exception { + doNothing().when(userGarmentFavoriteRepository) + .deleteFromFavorites(garment, user); + } + + private String whenExecuteDeleteFavorite(String garment, String user) throws Exception { + return deleteGarmentFromFavorite.execute(garment, user); + } + + private void thenFavoriteDeletedSuccessfully(String result) { + assertNotNull(result); + assertEquals("Prenda eliminada de favoritos.", result); + } + + + private void givenFavoriteDoesNotExist(String garment, String user) + throws UserGarmentFavoriteNotFoundException { + when(userGarmentFavoriteRepository.findByGarmentCodeAndUserEmail(garment, user)) + .thenThrow(new UserGarmentFavoriteNotFoundException("No está en favoritos")); + } + + private void thenThrowFavoriteNotFound(String garment, String user) { + assertThrows( + UserGarmentFavoriteNotFoundException.class, + () -> deleteGarmentFromFavorite.execute(garment, user) + ); + } + + private void givenDeleteThrowsUserNotFound(String garment, String user){ + doThrow(new UserNotFoundException("Usuario no encontrado")) + .when(userGarmentFavoriteRepository).deleteFromFavorites(garment, user); + } + + private void thenThrowUserNotFound(String garment, String user) { + assertThrows( + UserNotFoundException.class, + () -> deleteGarmentFromFavorite.execute(garment, user) + ); + } + +} diff --git a/src/test/java/com/outfitlab/project/domain/useCases/garment/DeleteGarmentTest.java b/src/test/java/com/outfitlab/project/domain/useCases/garment/DeleteGarmentTest.java new file mode 100644 index 0000000..f237741 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/garment/DeleteGarmentTest.java @@ -0,0 +1,113 @@ +package com.outfitlab.project.domain.useCases.garment; + +import com.outfitlab.project.domain.exceptions.BrandsNotFoundException; +import com.outfitlab.project.domain.exceptions.DeleteGarmentException; +import com.outfitlab.project.domain.interfaces.repositories.BrandRepository; +import com.outfitlab.project.domain.interfaces.repositories.GarmentRepository; +import com.outfitlab.project.domain.model.BrandModel; +import com.outfitlab.project.domain.model.PrendaModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +class DeleteGarmentTest { + + private GarmentRepository garmentRepository; + private BrandRepository brandRepository; + private DeleteGarment deleteGarment; + + @BeforeEach + void setup() { + garmentRepository = mock(GarmentRepository.class); + brandRepository = mock(BrandRepository.class); + deleteGarment = new DeleteGarment(garmentRepository, brandRepository); + } + + @Test + void shouldThrowBrandsNotFoundExceptionWhenBrandDoesNotExist() { + String brandCode = "BR01"; + PrendaModel garment = givenGarment("X1", brandCode); + + whenFindByBrandCodeReturnNull(brandCode); + + whenTryToDeleteThenThrowsBrandNotFoundException(garment, brandCode); + thenDeleteNeverCalled(); + } + + @Test + void shouldThrowDeleteGarmentExceptionWhenGarmentBelongsToAnotherBrand() { + String brandCode = "BR01"; + givenBrandExists(brandCode); + + PrendaModel garment = givenGarment("X1", "OTRA"); // marca distint + + whenTryToDeleteGarmentThenThrowDeleteGarmentException(garment, brandCode); + thenDeleteNeverCalled(); + } + + private void whenTryToDeleteGarmentThenThrowDeleteGarmentException(PrendaModel garment, String brandCode) { + assertThrows( + DeleteGarmentException.class, + () -> deleteGarment.execute(garment, brandCode) + ); + } + + + @Test + void shouldDeleteGarmentWhenBrandExistsAndGarmentBelongsToBrand() { + String brandCode = "BR01"; + String garmentCode = "X1"; + givenBrandExists(brandCode); + PrendaModel garment = givenGarment(garmentCode, brandCode); + + whenDeleteGarment(garment, brandCode); + + thenGarmentDeletedOnce(garmentCode); + } + + private void whenDeleteGarment(PrendaModel garment, String brandCode) { + deleteGarment.execute(garment, brandCode); + } + + + // privadoss + private PrendaModel givenGarment(String garmentCode, String brandCode) { + BrandModel marca = new BrandModel(); + marca.setCodigoMarca(brandCode); + + PrendaModel prenda = new PrendaModel(); + prenda.setGarmentCode(garmentCode); + prenda.setMarca(marca); + + return prenda; + } + + private void givenBrandExists(String brandCode) { + BrandModel marca = new BrandModel(); + marca.setCodigoMarca(brandCode); + when(brandRepository.findByBrandCode(brandCode)).thenReturn(marca); + } + + private void thenDeleteNeverCalled() { + verify(garmentRepository, never()).deleteGarment(anyString()); + } + + private void thenGarmentDeletedOnce(String expectedCode) { + verify(garmentRepository, times(1)).deleteGarment(expectedCode); + } + + + private void whenFindByBrandCodeReturnNull(String brandCode) { + when(brandRepository.findByBrandCode(brandCode)).thenReturn(null); + } + + private void whenTryToDeleteThenThrowsBrandNotFoundException(PrendaModel garment, String brandCode) { + assertThrows( + BrandsNotFoundException.class, + () -> deleteGarment.execute(garment, brandCode) + ); + } +} + diff --git a/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentByCodeTest.java b/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentByCodeTest.java new file mode 100644 index 0000000..226d1b0 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentByCodeTest.java @@ -0,0 +1,64 @@ +package com.outfitlab.project.domain.useCases.garment; + +import com.outfitlab.project.domain.interfaces.repositories.GarmentRepository; +import com.outfitlab.project.domain.model.PrendaModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class GetGarmentByCodeTest { + + private GarmentRepository garmentRepository; + private GetGarmentByCode getGarmentByCode; + + @BeforeEach + void setup() { + garmentRepository = mock(GarmentRepository.class); + getGarmentByCode = new GetGarmentByCode(garmentRepository); + } + + @Test + void shouldReturnGarmentWhenExists() { + PrendaModel expectedGarment = givenGarment("X1"); + when(garmentRepository.findByGarmentCode("X1")).thenReturn(expectedGarment); + + PrendaModel result = whenGetExecute("X1"); + + thenGarmentFound(result, expectedGarment); + } + + @Test + void shouldReturnNullWhenGarmentDoesNotExist() { + when(garmentRepository.findByGarmentCode("NO_EXISTE")).thenReturn(null); + + PrendaModel result = whenGetExecute("NO_EXISTE"); + + thenGarmentNotFound(result); + } + + + +//privados ------------------------------ + private PrendaModel givenGarment(String code) { + PrendaModel prenda = new PrendaModel(); + prenda.setGarmentCode(code); + return prenda; + } + + private void thenGarmentFound(PrendaModel result, PrendaModel expected) { + assertNotNull(result); + assertEquals(expected, result); + verify(garmentRepository, times(1)).findByGarmentCode(expected.getGarmentCode()); + } + + private void thenGarmentNotFound(PrendaModel result) { + assertNull(result); + verify(garmentRepository, times(1)).findByGarmentCode(anyString()); + } + + private PrendaModel whenGetExecute(String code) { + return getGarmentByCode.execute(code); + } +} diff --git a/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentRecomendationByTextTest.java b/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentRecomendationByTextTest.java new file mode 100644 index 0000000..0f975c5 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentRecomendationByTextTest.java @@ -0,0 +1,56 @@ +package com.outfitlab.project.domain.useCases.garment; + +import com.outfitlab.project.domain.interfaces.port.GeminiClient; +import com.outfitlab.project.domain.interfaces.repositories.GarmentRepository; +import com.outfitlab.project.domain.model.dto.ConjuntoDTO; +import com.outfitlab.project.domain.model.dto.GeminiRecommendationDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class GetGarmentRecomendationByTextTest { + + private GeminiClient geminiClient; + private GarmentRepository garmentRepository; + private GetGarmentRecomendationByText useCase; + + @BeforeEach + void setUp() { + geminiClient = mock(GeminiClient.class); + garmentRepository = mock(GarmentRepository.class); + useCase = new GetGarmentRecomendationByText(geminiClient, garmentRepository); + } + + @Test + void debeRetornarMensajeCuandoNoHayPrendasAptas() { + givenGeminiClientParameters("frio", "fiesta"); + givenNoPrendasAptas(); + + List resultado = whenEjecutaUseCase(); + + thenDebeRetornarMensajeSinPrendas(resultado); + } + private void givenGeminiClientParameters(String clima, String ocasion) { + GeminiRecommendationDTO dto = new GeminiRecommendationDTO(clima, ocasion); + when(geminiClient.extractParameters(anyString())).thenReturn(dto); + } + + private void givenNoPrendasAptas() { + when(garmentRepository.findByClimaAndOcasion(anyString(), anyString())) + .thenReturn(List.of()); + } + + private List whenEjecutaUseCase() { + return useCase.execute("hola", "usuario1"); + } + + private void thenDebeRetornarMensajeSinPrendas(List resultado) { + assertEquals(1, resultado.size()); + assertEquals("Lo siento, no tengo prendas que coincidan con la búsqueda.", resultado.get(0).getNombre()); + assertTrue(resultado.get(0).getPrendas().isEmpty()); + } +} diff --git a/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentRecomendationTest.java b/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentRecomendationTest.java index ba05657..36314aa 100644 --- a/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentRecomendationTest.java +++ b/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentRecomendationTest.java @@ -13,49 +13,103 @@ class GetGarmentRecomendationTest { - private GarmentRecomendationRepository garmentRecomendationRepository = mock(RecomendationRepositoryImpl.class); - private GetGarmentRecomendation getGarmentRecomendation = new GetGarmentRecomendation(garmentRecomendationRepository); + private final GarmentRecomendationRepository garmentRecomendationRepository = mock(RecomendationRepositoryImpl.class); + + private final GetGarmentRecomendation getGarmentRecomendation = new GetGarmentRecomendation(garmentRecomendationRepository); @Test - public void givenValidGarmentCodeWhenRecommendationsExistThenReturnListSuccessfully() throws GarmentNotFoundException { - String garmentCode = "G001"; - List recomendations = List.of(new GarmentRecomendationModel(), new GarmentRecomendationModel()); + public void givenValidGarmentCodeWhenRecommendationsExistThenReturnListSuccessfully() + throws GarmentNotFoundException { - when(garmentRecomendationRepository.findRecomendationsByGarmentCode(garmentCode)) - .thenReturn(recomendations); + String garmentCode = givenGarmentCode("G001"); + List expectedList = givenRecommendationListOfSize(2); - List result = getGarmentRecomendation.execute(garmentCode); + mockRepositoryReturning(garmentCode, expectedList); - assertNotNull(result); - assertEquals(2, result.size()); - verify(garmentRecomendationRepository, times(1)).findRecomendationsByGarmentCode(garmentCode); + List result = whenExecutingWith(garmentCode); + + thenResultNotNull(result); + thenResultHasSize(result, 2); + thenRepositoryCalledOnce(garmentCode); } @Test - public void givenValidGarmentCodeWhenNoRecommendationsExistThenReturnEmptyList() throws GarmentNotFoundException { - String garmentCode = "G002"; + public void givenValidGarmentCodeWhenNoRecommendationsExistThenReturnEmptyList() + throws GarmentNotFoundException { - when(garmentRecomendationRepository.findRecomendationsByGarmentCode(garmentCode)) - .thenReturn(List.of()); + String garmentCode = givenGarmentCode("G002"); - List result = getGarmentRecomendation.execute(garmentCode); + mockRepositoryReturningEmpty(garmentCode); - assertNotNull(result); - assertTrue(result.isEmpty()); - verify(garmentRecomendationRepository, times(1)).findRecomendationsByGarmentCode(garmentCode); + List result = whenExecutingWith(garmentCode); + + thenResultNotNull(result); + thenResultIsEmpty(result); + thenRepositoryCalledOnce(garmentCode); } @Test - public void givenInvalidGarmentCodeWhenExecuteThenThrowGarmentNotFoundException() throws GarmentNotFoundException { - String garmentCode = "cualquiera"; + public void givenInvalidGarmentCodeWhenExecuteThenThrowGarmentNotFoundException() + throws GarmentNotFoundException { - when(garmentRecomendationRepository.findRecomendationsByGarmentCode(garmentCode)) - .thenThrow(new GarmentNotFoundException("Prenda no encontrada")); + String garmentCode = givenGarmentCode("INVALID"); + + mockRepositoryThrowingNotFound(garmentCode); assertThrows(GarmentNotFoundException.class, () -> - getGarmentRecomendation.execute(garmentCode) + whenExecutingWith(garmentCode) ); - verify(garmentRecomendationRepository, times(1)).findRecomendationsByGarmentCode(garmentCode); + thenRepositoryCalledOnce(garmentCode); + } + + // privadosss + private String givenGarmentCode(String code) { + return code; + } + + private List givenRecommendationListOfSize(int size) { + return java.util.stream.Stream + .generate(GarmentRecomendationModel::new) + .limit(size) + .toList(); + } + + private void mockRepositoryReturning(String garmentCode, List result) { + when(garmentRecomendationRepository.findRecomendationsByGarmentCode(garmentCode)) + .thenReturn(result); + } + + private void mockRepositoryReturningEmpty(String garmentCode) { + when(garmentRecomendationRepository.findRecomendationsByGarmentCode(garmentCode)) + .thenReturn(List.of()); + } + + private void mockRepositoryThrowingNotFound(String garmentCode) { + when(garmentRecomendationRepository.findRecomendationsByGarmentCode(garmentCode)) + .thenThrow(new GarmentNotFoundException("Prenda no encontrada")); + } + + private List whenExecutingWith(String garmentCode) + throws GarmentNotFoundException { + + return getGarmentRecomendation.execute(garmentCode); + } + + private void thenResultNotNull(Object result) { + assertNotNull(result); + } + + private void thenResultHasSize(List list, int size) { + assertEquals(size, list.size()); + } + + private void thenResultIsEmpty(List list) { + assertTrue(list.isEmpty()); + } + + private void thenRepositoryCalledOnce(String garmentCode) { + verify(garmentRecomendationRepository, times(1)) + .findRecomendationsByGarmentCode(garmentCode); } } diff --git a/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentsByTypeTest.java b/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentsByTypeTest.java index 4972aeb..24e298c 100644 --- a/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentsByTypeTest.java +++ b/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentsByTypeTest.java @@ -15,49 +15,98 @@ class GetGarmentsByTypeTest { - private GarmentRepository garmentRepository = mock(GarmentRepositoryImpl.class); - private GetGarmentsByType getGarmentsByType = new GetGarmentsByType(garmentRepository); + private final GarmentRepository garmentRepository = mock(GarmentRepositoryImpl.class); + private final GetGarmentsByType getGarmentsByType = new GetGarmentsByType(garmentRepository); @Test public void givenValidTypeWithGarmentsWhenExecuteThenReturnPageSuccessfully() throws GarmentNotFoundException { - String type = "superior"; - int page = 0; - Page pageResponse = new PageImpl<>(List.of(new PrendaModel(), new PrendaModel())); + String type = givenType("superior"); + int page = givenPage(0); + Page pageResponse = givenPageWithElements(2); - when(garmentRepository.getGarmentsByType(type.toLowerCase(), page)).thenReturn(pageResponse); + mockRepositoryReturning(type, page, pageResponse); - Page result = getGarmentsByType.execute(type, page); + Page result = whenExecute(type, page); - assertNotNull(result); - assertEquals(2, result.getContent().size()); - verify(garmentRepository, times(1)).getGarmentsByType("superior", page); + thenResultNotNull(result); + thenResultHasSize(result, 2); + thenRepositoryCalledOnce(type, page); } @Test public void givenValidTypeWithNoGarmentsWhenExecuteThenThrowGarmentNotFoundException() { - String type = "inferior"; - int page = 1; - Page emptyPage = new PageImpl<>(List.of()); + String type = givenType("inferior"); + int page = givenPage(1); + Page emptyPage = givenEmptyPage(); - when(garmentRepository.getGarmentsByType(type.toLowerCase(), page)).thenReturn(emptyPage); + mockRepositoryReturning(type, page, emptyPage); - GarmentNotFoundException exception = assertThrows(GarmentNotFoundException.class, () -> getGarmentsByType.execute(type, page)); + GarmentNotFoundException ex = assertThrows(GarmentNotFoundException.class, + () -> whenExecute(type, page)); - assertTrue(exception.getMessage().contains("No encontramos prendas de tipo: " + type)); - verify(garmentRepository, times(1)).getGarmentsByType("inferior", page); + thenMessageContains(ex, "No encontramos prendas de tipo: " + type); + thenRepositoryCalledOnce(type, page); } @Test public void givenEmptyPageWhenExecuteThenThrowGarmentNotFoundException() { - String type = "accesorio"; - int page = 2; - Page emptyPage = new PageImpl<>(List.of()); + String type = givenType("accesorio"); + int page = givenPage(2); + Page emptyPage = givenEmptyPage(); - when(garmentRepository.getGarmentsByType(type.toLowerCase(), page)).thenReturn(emptyPage); + mockRepositoryReturning(type, page, emptyPage); - assertThrows(GarmentNotFoundException.class, () -> getGarmentsByType.execute(type, page)); - verify(garmentRepository, times(1)).getGarmentsByType("accesorio", page); + assertThrows(GarmentNotFoundException.class, () -> whenExecute(type, page)); + thenRepositoryCalledOnce(type, page); } -} + + // privados --- + private String givenType(String type) { + return type; + } + + private int givenPage(int page) { + return page; + } + + private Page givenPageWithElements(int count) { + return new PageImpl<>( + java.util.stream.Stream.generate(PrendaModel::new).limit(count).toList() + ); + } + + private Page givenEmptyPage() { + return new PageImpl<>(List.of()); + } + + private void mockRepositoryReturning(String type, int page, Page result) { + when(garmentRepository.getGarmentsByType(type.toLowerCase(), page)).thenReturn(result); + } + + private Page whenExecute(String type, int page) throws GarmentNotFoundException { + return getGarmentsByType.execute(type, page); + } + + private void thenResultNotNull(Object result) { + assertNotNull(result); + } + + private void thenResultHasSize(Page page, int size) { + assertEquals(size, page.getContent().size()); + } + + private void thenResultIsEmpty(Page page) { + assertTrue(page.isEmpty()); + } + + private void thenMessageContains(Exception ex, String expected) { + assertTrue(ex.getMessage().contains(expected)); + } + + private void thenRepositoryCalledOnce(String type, int page) { + verify(garmentRepository, times(1)) + .getGarmentsByType(type.toLowerCase(), page); + } +} diff --git a/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentsFavoritesForUserByEmailTest.java b/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentsFavoritesForUserByEmailTest.java index d571561..a02ccaf 100644 --- a/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentsFavoritesForUserByEmailTest.java +++ b/src/test/java/com/outfitlab/project/domain/useCases/garment/GetGarmentsFavoritesForUserByEmailTest.java @@ -14,53 +14,91 @@ public class GetGarmentsFavoritesForUserByEmailTest { - private UserGarmentFavoriteRepository userGarmentFavoriteRepository = mock(UserGarmentFavoriteRepositoryImpl.class); - private GetGarmentsFavoritesForUserByEmail getGarmentsFavoritesForUserByEmail = new GetGarmentsFavoritesForUserByEmail(userGarmentFavoriteRepository); + private final UserGarmentFavoriteRepository userGarmentFavoriteRepository = mock(UserGarmentFavoriteRepositoryImpl.class); + private final GetGarmentsFavoritesForUserByEmail getGarmentsFavoritesForUserByEmail = new GetGarmentsFavoritesForUserByEmail(userGarmentFavoriteRepository); @Test - public void givenValidUserAndPageWhenExecuteThenReturnFavoritesPage() throws Exception, FavoritesException { + public void givenValidUserAndPageWhenExecuteThenReturnFavoritesPage() throws Exception { String userEmail = "user@example.com"; int page = 1; PageDTO expectedPage = new PageDTO<>(); - when(userGarmentFavoriteRepository.getGarmentsFavoritesForUserByEmail(userEmail, page)).thenReturn(expectedPage); + mockRepositoryReturnPage(userEmail, page, expectedPage); PageDTO result = getGarmentsFavoritesForUserByEmail.execute(userEmail, page); - assertNotNull(result); - assertEquals(expectedPage, result); - verify(userGarmentFavoriteRepository, times(1)).getGarmentsFavoritesForUserByEmail(userEmail, page); + thenReturnFavoritePage(expectedPage, result, userEmail, page); } @Test - public void givenNegativePageNumberWhenExecuteThenThrowPageLessThanZeroException() throws UserNotFoundException, FavoritesException { + public void givenNegativePageNumberWhenExecuteThenThrowPageLessThanZeroException() { String userEmail = "user@example.com"; int page = -1; - assertThrows(PageLessThanZeroException.class, () -> getGarmentsFavoritesForUserByEmail.execute(userEmail, page)); + whenThrowsPageLessThanZeroException(userEmail, page); + verify(userGarmentFavoriteRepository, never()).getGarmentsFavoritesForUserByEmail(anyString(), anyInt()); } @Test - public void givenNonexistentUserWhenExecuteThenThrowUserNotFoundException() throws Exception, FavoritesException { + public void givenNonexistentUserWhenExecuteThenThrowUserNotFoundException() throws Exception { String userEmail = "notfound@example.com"; int page = 0; - when(userGarmentFavoriteRepository.getGarmentsFavoritesForUserByEmail(userEmail, page)).thenThrow(new UserNotFoundException("Usuario no encontrado")); + mockRepositoryThrow(userEmail, page, new UserNotFoundException("Usuario no encontrado")); - assertThrows(UserNotFoundException.class, () -> getGarmentsFavoritesForUserByEmail.execute(userEmail, page)); - verify(userGarmentFavoriteRepository, times(1)).getGarmentsFavoritesForUserByEmail(userEmail, page); + whenThrowsUserNotFoundException(userEmail, page); + + verifyRepositoryCalledOnce(userEmail, page); } @Test - public void givenRepositoryFailureWhenExecuteThenThrowFavoritesException() throws Exception, FavoritesException { + public void givenRepositoryFailureWhenExecuteThenThrowFavoritesException() throws Exception { String userEmail = "user@example.com"; int page = 2; - when(userGarmentFavoriteRepository.getGarmentsFavoritesForUserByEmail(userEmail, page)).thenThrow(new FavoritesException("Error al obtener favoritos")); + mockRepositoryThrow(userEmail, page, new FavoritesException("Error al obtener favoritos")); + whenThrowsFavoritesException(userEmail, page); - assertThrows(FavoritesException.class, () -> getGarmentsFavoritesForUserByEmail.execute(userEmail, page)); - verify(userGarmentFavoriteRepository, times(1)).getGarmentsFavoritesForUserByEmail(userEmail, page); + verifyRepositoryCalledOnce(userEmail, page); } -} + + +// privadoss + private void whenThrowsUserNotFoundException(String userEmail, int page) { + assertThrows(UserNotFoundException.class, + () -> getGarmentsFavoritesForUserByEmail.execute(userEmail, page)); + } + + private void whenThrowsFavoritesException(String userEmail, int page) { + assertThrows(FavoritesException.class, + () -> getGarmentsFavoritesForUserByEmail.execute(userEmail, page)); + } + + private void whenThrowsPageLessThanZeroException(String userEmail, int page) { + assertThrows(PageLessThanZeroException.class, () -> getGarmentsFavoritesForUserByEmail.execute(userEmail, page)); + } + + private void mockRepositoryReturnPage(String email, int page, PageDTO response) + throws Exception { + when(userGarmentFavoriteRepository.getGarmentsFavoritesForUserByEmail(email, page)) + .thenReturn(response); + } + + private void thenReturnFavoritePage(PageDTO expectedPage, PageDTO result, String userEmail, int page) { + assertEquals(expectedPage, result); + verifyRepositoryCalledOnce(userEmail, page); + } + + private void mockRepositoryThrow(String email, int page, Exception exception) + throws Exception { + when(userGarmentFavoriteRepository.getGarmentsFavoritesForUserByEmail(email, page)) + .thenThrow(exception); + } + + private void verifyRepositoryCalledOnce(String email, int page) { + verify(userGarmentFavoriteRepository, times(1)) + .getGarmentsFavoritesForUserByEmail(email, page); + } +} diff --git a/src/test/java/com/outfitlab/project/domain/useCases/garment/UpdateGarmentTest.java b/src/test/java/com/outfitlab/project/domain/useCases/garment/UpdateGarmentTest.java new file mode 100644 index 0000000..9a8c45f --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/garment/UpdateGarmentTest.java @@ -0,0 +1,123 @@ +package com.outfitlab.project.domain.useCases.garment; + +import com.outfitlab.project.domain.exceptions.BrandsNotFoundException; +import com.outfitlab.project.domain.exceptions.UpdateGarmentException; +import com.outfitlab.project.domain.interfaces.repositories.BrandRepository; +import com.outfitlab.project.domain.interfaces.repositories.GarmentRepository; +import com.outfitlab.project.domain.model.PrendaModel; +import com.outfitlab.project.domain.model.BrandModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +class UpdateGarmentTest { + + private GarmentRepository garmentRepository; + private BrandRepository brandRepository; + private UpdateGarment useCase; + + @BeforeEach + void setUp() { + garmentRepository = mock(GarmentRepository.class); + brandRepository = mock(BrandRepository.class); + useCase = new UpdateGarment(garmentRepository, brandRepository); + } + + @Test + void debeActualizarCorrectamenteLaPrenda() { + PrendaModel garment = givenGarmentDeLaMismaMarca(); + givenMarcaExistente(); + givenRepositorioDevuelvePrenda(garment); + + whenEjecutaUseCase(); + + thenDebeLlamarseUpdateGarment(); + } + + @Test + void debeLanzarExcepcionCuandoLaMarcaNoExiste() { + givenMarcaNoExiste(); + assertThrows(BrandsNotFoundException.class, this::whenEjecutaUseCase); + } + + @Test + void debeLanzarExcepcionCuandoLaPrendaEsDeOtraMarca() { + PrendaModel garment = givenGarmentDeOtraMarca(); + givenMarcaExistente(); + givenRepositorioDevuelvePrenda(garment); + + assertThrows(UpdateGarmentException.class, this::whenEjecutaUseCase); + } + +//privados ---- + private void givenMarcaExistente() { + BrandModel marca = mock(BrandModel.class); + when(brandRepository.findByBrandCode(eq("BRAND123"))).thenReturn(marca); + } + + private void givenMarcaNoExiste() { + when(brandRepository.findByBrandCode(eq("BRAND123"))).thenReturn(null); + } + + private void givenRepositorioDevuelvePrenda(PrendaModel garment) { + when(garmentRepository.findByGarmentCode(eq("ABC123"))) + .thenReturn(garment); + } + + private PrendaModel givenGarmentDeLaMismaMarca() { + BrandModel marca = mock(BrandModel.class); + when(marca.getCodigoMarca()).thenReturn("BRAND123"); + + PrendaModel garment = mock(PrendaModel.class); + when(garment.getMarca()).thenReturn(marca); + when(garment.getGarmentCode()).thenReturn("ABC123"); + + return garment; + } + + private PrendaModel givenGarmentDeOtraMarca() { + BrandModel marca = mock(BrandModel.class); + when(marca.getCodigoMarca()).thenReturn("OTRA"); + + PrendaModel garment = mock(PrendaModel.class); + when(garment.getMarca()).thenReturn(marca); + when(garment.getGarmentCode()).thenReturn("ABC123"); + + return garment; + } + + private void whenEjecutaUseCase() { + useCase.execute( + "Nombre", + "tipo", + "color", + "evento", + "ABC123", + "BRAND123", + "img.png", + "clima", + List.of("formal"), + "genero" + ); + } + + private void thenDebeLlamarseUpdateGarment() { + verify(garmentRepository, times(1)) + .updateGarment( + eq("Nombre"), + eq("tipo"), + eq("color"), + eq("evento"), + eq("ABC123"), + eq("img.png"), + anyString(), // formatGarmentCode() -> validado + eq("clima"), + eq(List.of("formal")), + eq("genero") + ); + } +} diff --git a/src/test/java/com/outfitlab/project/domain/useCases/gmail/SubscribeUserTest.java b/src/test/java/com/outfitlab/project/domain/useCases/gmail/SubscribeUserTest.java new file mode 100644 index 0000000..14198ce --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/gmail/SubscribeUserTest.java @@ -0,0 +1,130 @@ +package com.outfitlab.project.domain.useCases.gmail; + +import com.outfitlab.project.domain.interfaces.gateways.GmailGateway; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class SubscribeUserTest { + + private GmailGateway gmailGateway = mock(GmailGateway.class); + private SubscribeUser subscribeUser; + + private final String VALID_EMAIL = "nuevo.usuario@test.com"; + private final String EXPECTED_SUBJECT = "¡Bienvenido a OutfitLab! 🥳"; + + @BeforeEach + void setUp() { + subscribeUser = new SubscribeUser(gmailGateway); + } + + + @Test + public void shouldSendWelcomeEmailToValidUser() { + whenExecuteSubscribeUser(VALID_EMAIL); + + thenWelcomeEmailWasSent(VALID_EMAIL); + } + + @Test + public void shouldThrowIllegalArgumentExceptionWhenEmailIsNull() { + String nullEmail = null; + + assertThrows(IllegalArgumentException.class, + () -> whenExecuteSubscribeUser(nullEmail), + "Debe fallar si el email es nulo."); + + thenEmailWasNeverSent(); + } + + @Test + public void shouldThrowIllegalArgumentExceptionWhenEmailIsEmpty() { + String emptyEmail = ""; + + assertThrows(IllegalArgumentException.class, + () -> whenExecuteSubscribeUser(emptyEmail), + "Debe fallar si el email está vacío."); + + thenEmailWasNeverSent(); + } + + @Test + public void shouldThrowIllegalArgumentExceptionWhenEmailIsBlank() { + String blankEmail = " "; + + assertThrows(IllegalArgumentException.class, + () -> whenExecuteSubscribeUser(blankEmail), + "Debe fallar si el email contiene solo espacios."); + + thenEmailWasNeverSent(); + } + + @Test + public void shouldThrowIllegalArgumentExceptionWhenEmailDoesNotContainAtSymbol() { + String invalidEmail = "usuario.test.com"; + + assertThrows(IllegalArgumentException.class, + () -> whenExecuteSubscribeUser(invalidEmail), + "Debe fallar si el email no contiene '@'."); + + thenEmailWasNeverSent(); + } + + @Test + public void shouldSendEmailWhenEmailIsOnlyAtSymbolAsValidationAllowsIt() { + String email = "@"; + + whenExecuteSubscribeUser(email); + + thenWelcomeEmailWasSent(email); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenGmailGatewayFails() { + givenGmailGatewayThrowsRuntimeException(); + + assertThrows(RuntimeException.class, + () -> whenExecuteSubscribeUser(VALID_EMAIL), + "Debe propagar la excepción de Runtime si el gateway falla."); + + thenEmailWasSent(VALID_EMAIL, 1); + } + + + //privaadoss + private void givenGmailGatewayThrowsRuntimeException() { + doThrow(new RuntimeException("Simulated network failure")) + .when(gmailGateway).sendEmail(anyString(), anyString(), anyString()); + } + + private void whenExecuteSubscribeUser(String email) { + subscribeUser.execute(email); + } + + private void thenWelcomeEmailWasSent(String expectedEmail) { + ArgumentCaptor emailCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor subjectCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor bodyCaptor = ArgumentCaptor.forClass(String.class); + + verify(gmailGateway, times(1)).sendEmail(emailCaptor.capture(), subjectCaptor.capture(), bodyCaptor.capture()); + + assertEquals(expectedEmail, emailCaptor.getValue(), "El email del destinatario es incorrecto."); + assertEquals(EXPECTED_SUBJECT, subjectCaptor.getValue(), "El asunto del correo es incorrecto."); + + String actualBody = bodyCaptor.getValue(); + assertNotNull(actualBody, "El cuerpo del correo no debe ser nulo."); + assertTrue(actualBody.contains("¡Gracias por suscribirte a OutfitLab!"), "El cuerpo debe contener el mensaje de bienvenida."); + assertTrue(actualBody.contains(""), "El cuerpo debe contener formato HTML."); + } + + private void thenEmailWasSent(String expectedEmail, int times) { + verify(gmailGateway, times(times)).sendEmail(eq(expectedEmail), anyString(), anyString()); + } + + private void thenEmailWasNeverSent() { + verify(gmailGateway, never()).sendEmail(anyString(), anyString(), anyString()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/recomendations/CreateSugerenciasByGarmentsCodeTest.java b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/CreateSugerenciasByGarmentsCodeTest.java new file mode 100644 index 0000000..cd1af8c --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/CreateSugerenciasByGarmentsCodeTest.java @@ -0,0 +1,75 @@ +package com.outfitlab.project.domain.useCases.recomendations; + +import com.outfitlab.project.domain.interfaces.repositories.GarmentRecomendationRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +public class CreateSugerenciasByGarmentsCodeTest { + + private GarmentRecomendationRepository garmentRecomendationRepository = mock(GarmentRecomendationRepository.class); + private CreateSugerenciasByGarmentsCode createSugerenciasByGarmentsCode; + + private final String VALID_GARMENT_CODE = "JEAN_AZUL"; + private final String VALID_TYPE = "top"; + private final List VALID_SUGERENCIAS = List.of("G002", "G003"); + private final String EMPTY_CODE = ""; + private final List EMPTY_SUGERENCIAS = Collections.emptyList(); + + @BeforeEach + void setUp() { + createSugerenciasByGarmentsCode = new CreateSugerenciasByGarmentsCode(garmentRecomendationRepository); + } + + + @Test + public void shouldCallRepositoryWithAllValidParameters() { + whenExecuteCreateSugerencias(VALID_GARMENT_CODE, VALID_TYPE, VALID_SUGERENCIAS); + + thenRepositoryWasCalled(VALID_GARMENT_CODE, VALID_TYPE, VALID_SUGERENCIAS, 1); + } + + @Test + public void shouldCallRepositoryWhenSugerenciasListIsEmpty() { + whenExecuteCreateSugerencias(VALID_GARMENT_CODE, VALID_TYPE, EMPTY_SUGERENCIAS); + + thenRepositoryWasCalled(VALID_GARMENT_CODE, VALID_TYPE, EMPTY_SUGERENCIAS, 1); + } + + @Test + public void shouldCallRepositoryWhenGarmentCodeIsEmpty() { + whenExecuteCreateSugerencias(EMPTY_CODE, VALID_TYPE, VALID_SUGERENCIAS); + + thenRepositoryWasCalled(EMPTY_CODE, VALID_TYPE, VALID_SUGERENCIAS, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(); + + assertThrows(RuntimeException.class, + () -> whenExecuteCreateSugerencias(VALID_GARMENT_CODE, VALID_TYPE, VALID_SUGERENCIAS), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryWasCalled(VALID_GARMENT_CODE, VALID_TYPE, VALID_SUGERENCIAS, 1); + } + + + //privadoss + private void givenRepositoryThrowsRuntimeException() { + doThrow(new RuntimeException("DB Connection error")) + .when(garmentRecomendationRepository).createSugerenciasByGarmentCode(anyString(), anyString(), anyList()); + } + + private void whenExecuteCreateSugerencias(String code, String type, List sugerencias) { + createSugerenciasByGarmentsCode.execute(code, type, sugerencias); + } + + private void thenRepositoryWasCalled(String expectedCode, String expectedType, List expectedSugerencias, int times) { + verify(garmentRecomendationRepository, times(times)).createSugerenciasByGarmentCode(expectedCode, expectedType, expectedSugerencias); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/recomendations/DeleteAllPrendaOcacionRelatedToGarmentTest.java b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/DeleteAllPrendaOcacionRelatedToGarmentTest.java new file mode 100644 index 0000000..bf20603 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/DeleteAllPrendaOcacionRelatedToGarmentTest.java @@ -0,0 +1,73 @@ +package com.outfitlab.project.domain.useCases.recomendations; + +import com.outfitlab.project.domain.interfaces.repositories.PrendaOcasionRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class DeleteAllPrendaOcacionRelatedToGarmentTest { + + private PrendaOcasionRepository prendaOcasionRepository = mock(PrendaOcasionRepository.class); + private DeleteAllPrendaOcacionRelatedToGarment deleteAllPrendaOcacionRelatedToGarment; + + private final String VALID_GARMENT_CODE = "REMERA-001"; + private final String EMPTY_GARMENT_CODE = ""; + private final String NULL_GARMENT_CODE = null; + + @BeforeEach + void setUp() { + deleteAllPrendaOcacionRelatedToGarment = new DeleteAllPrendaOcacionRelatedToGarment(prendaOcasionRepository); + } + + @Test + public void shouldCallRepositoryToDeleteAllPrendaOcacionByValidGarmentCode() { + whenExecuteDeleteAll(VALID_GARMENT_CODE); + + thenRepositoryDeleteAllWasCalled(VALID_GARMENT_CODE, 1); + } + + @Test + public void shouldCallRepositoryWhenGarmentCodeIsEmpty() { + whenExecuteDeleteAll(EMPTY_GARMENT_CODE); + + thenRepositoryDeleteAllWasCalled(EMPTY_GARMENT_CODE, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenGarmentCodeIsNull() { + givenRepositoryThrowsRuntimeException(NULL_GARMENT_CODE); + + assertThrows(RuntimeException.class, + () -> whenExecuteDeleteAll(NULL_GARMENT_CODE), + "Se espera que el RuntimeException del repositorio/framework se propague al delegar null."); + + thenRepositoryDeleteAllWasCalled(NULL_GARMENT_CODE, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(VALID_GARMENT_CODE); + + assertThrows(RuntimeException.class, + () -> whenExecuteDeleteAll(VALID_GARMENT_CODE), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryDeleteAllWasCalled(VALID_GARMENT_CODE, 1); + } + + // privadoss + private void givenRepositoryThrowsRuntimeException(String garmentCode) { + doThrow(new RuntimeException("Simulated DB error")) + .when(prendaOcasionRepository).deleteAllPrendaOcasionByGarment(garmentCode); + } + + private void whenExecuteDeleteAll(String garmentCode) { + deleteAllPrendaOcacionRelatedToGarment.execute(garmentCode); + } + + private void thenRepositoryDeleteAllWasCalled(String expectedGarmentCode, int times) { + verify(prendaOcasionRepository, times(times)).deleteAllPrendaOcasionByGarment(expectedGarmentCode); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/recomendations/DeleteGarmentRecomentationsRelatedToGarmentTest.java b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/DeleteGarmentRecomentationsRelatedToGarmentTest.java new file mode 100644 index 0000000..985b98d --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/DeleteGarmentRecomentationsRelatedToGarmentTest.java @@ -0,0 +1,75 @@ +package com.outfitlab.project.domain.useCases.recomendations; + +import com.outfitlab.project.domain.interfaces.repositories.GarmentRecomendationRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class DeleteGarmentRecomentationsRelatedToGarmentTest { + + private GarmentRecomendationRepository garmentRecomendationRepository = mock(GarmentRecomendationRepository.class); + private DeleteGarmentRecomentationsRelatedToGarment deleteGarmentRecomentationsRelatedToGarment; + + private final String VALID_GARMENT_CODE = "PANTALON-001"; + private final String EMPTY_GARMENT_CODE = ""; + private final String NULL_GARMENT_CODE = null; + + @BeforeEach + void setUp() { + deleteGarmentRecomentationsRelatedToGarment = new DeleteGarmentRecomentationsRelatedToGarment(garmentRecomendationRepository); + } + + + @Test + public void shouldCallRepositoryToDeleteRecomendationsByValidGarmentCode() { + whenExecuteDelete(VALID_GARMENT_CODE); + + thenRepositoryDeleteWasCalled(VALID_GARMENT_CODE, 1); + } + + @Test + public void shouldCallRepositoryWhenGarmentCodeIsEmpty() { + whenExecuteDelete(EMPTY_GARMENT_CODE); + + thenRepositoryDeleteWasCalled(EMPTY_GARMENT_CODE, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenGarmentCodeIsNull() { + givenRepositoryThrowsRuntimeException(NULL_GARMENT_CODE); + + assertThrows(RuntimeException.class, + () -> whenExecuteDelete(NULL_GARMENT_CODE), + "Se espera que el RuntimeException del repositorio/framework se propague al delegar null."); + + thenRepositoryDeleteWasCalled(NULL_GARMENT_CODE, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(VALID_GARMENT_CODE); + + assertThrows(RuntimeException.class, + () -> whenExecuteDelete(VALID_GARMENT_CODE), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryDeleteWasCalled(VALID_GARMENT_CODE, 1); + } + + + //privadoss + private void givenRepositoryThrowsRuntimeException(String garmentCode) { + doThrow(new RuntimeException("Simulated DB error")) + .when(garmentRecomendationRepository).deleteRecomendationsByGarmentCode(garmentCode); + } + + private void whenExecuteDelete(String garmentCode) { + deleteGarmentRecomentationsRelatedToGarment.execute(garmentCode); + } + + private void thenRepositoryDeleteWasCalled(String expectedGarmentCode, int times) { + verify(garmentRecomendationRepository, times(times)).deleteRecomendationsByGarmentCode(expectedGarmentCode); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/recomendations/DeleteRecomendationByPrimaryAndSecondaryGarmentCodeTest.java b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/DeleteRecomendationByPrimaryAndSecondaryGarmentCodeTest.java new file mode 100644 index 0000000..5b5c746 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/DeleteRecomendationByPrimaryAndSecondaryGarmentCodeTest.java @@ -0,0 +1,91 @@ +package com.outfitlab.project.domain.useCases.recomendations; + +import com.outfitlab.project.domain.interfaces.repositories.GarmentRecomendationRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +public class DeleteRecomendationByPrimaryAndSecondaryGarmentCodeTest { + + private GarmentRecomendationRepository garmentRecomendationRepository = mock(GarmentRecomendationRepository.class); + private DeleteRecomendationByPrimaryAndSecondaryGarmentCode deleteRecomendationByPrimaryAndSecondaryGarmentCode; + + private final String PRIMARY_CODE = "superior-1"; + private final String SECONDARY_CODE = "inferior-2"; + private final String TYPE_CODE = "matching"; + private final String SUCCESS_MESSAGE = "Sugerencia eliminada con éxito."; + private final String NULL_CODE = null; + private final String EMPTY_CODE = ""; + + @BeforeEach + void setUp() { + deleteRecomendationByPrimaryAndSecondaryGarmentCode = new DeleteRecomendationByPrimaryAndSecondaryGarmentCode(garmentRecomendationRepository); + } + + + @Test + public void shouldCallRepositoryAndDeleteSugerenciaSuccessfully() { + String result = whenExecuteDelete(PRIMARY_CODE, SECONDARY_CODE, TYPE_CODE); + + assertEquals(SUCCESS_MESSAGE, result, "Debe retornar el mensaje de éxito."); + thenRepositoryDeleteWasCalled(PRIMARY_CODE, SECONDARY_CODE, TYPE_CODE, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenPrimaryCodeIsNull() { + givenRepositoryThrowsRuntimeException(NULL_CODE, SECONDARY_CODE, TYPE_CODE); + + assertThrows(RuntimeException.class, + () -> whenExecuteDelete(NULL_CODE, SECONDARY_CODE, TYPE_CODE), + "Se espera que el RuntimeException se propague al delegar el código primario nulo."); + + thenRepositoryDeleteWasCalled(NULL_CODE, SECONDARY_CODE, TYPE_CODE, 1); + } + + @Test + public void shouldCallRepositoryWhenSecondaryCodeIsEmpty() { + whenExecuteDelete(PRIMARY_CODE, EMPTY_CODE, TYPE_CODE); + + thenRepositoryDeleteWasCalled(PRIMARY_CODE, EMPTY_CODE, TYPE_CODE, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenTypeIsNull() { + givenRepositoryThrowsRuntimeException(PRIMARY_CODE, SECONDARY_CODE, NULL_CODE); + + assertThrows(RuntimeException.class, + () -> whenExecuteDelete(PRIMARY_CODE, SECONDARY_CODE, NULL_CODE), + "Se espera que el RuntimeException se propague al delegar el tipo nulo."); + + thenRepositoryDeleteWasCalled(PRIMARY_CODE, SECONDARY_CODE, NULL_CODE, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(PRIMARY_CODE, SECONDARY_CODE, TYPE_CODE); + + assertThrows(RuntimeException.class, + () -> whenExecuteDelete(PRIMARY_CODE, SECONDARY_CODE, TYPE_CODE), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryDeleteWasCalled(PRIMARY_CODE, SECONDARY_CODE, TYPE_CODE, 1); + } + + + //privadossss + private void givenRepositoryThrowsRuntimeException(String primaryCode, String secondaryCode, String type) { + doThrow(new RuntimeException("Simulated DB error")) + .when(garmentRecomendationRepository).deleteRecomendationByGarmentsCode(primaryCode, secondaryCode, type); + } + + private String whenExecuteDelete(String primaryCode, String secondaryCode, String type) { + return deleteRecomendationByPrimaryAndSecondaryGarmentCode.execute(primaryCode, secondaryCode, type); + } + + private void thenRepositoryDeleteWasCalled(String expectedPrimary, String expectedSecondary, String expectedType, int times) { + verify(garmentRecomendationRepository, times(times)).deleteRecomendationByGarmentsCode(expectedPrimary, expectedSecondary, expectedType); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/recomendations/GetAllClimaTest.java b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/GetAllClimaTest.java new file mode 100644 index 0000000..db62326 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/GetAllClimaTest.java @@ -0,0 +1,89 @@ +package com.outfitlab.project.domain.useCases.recomendations; + +import com.outfitlab.project.domain.interfaces.repositories.ClimaRepository; +import com.outfitlab.project.domain.model.ClimaModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetAllClimaTest { + + private ClimaRepository climaRepository = mock(ClimaRepository.class); + private GetAllClima getAllClima; + + private final int CLIMA_COUNT = 3; + + @BeforeEach + void setUp() { + getAllClima = new GetAllClima(climaRepository); + } + + + @Test + public void shouldReturnListOfClimasWhenClimasAreFound() { + List expectedClimas = givenRepositoryReturnsClimas(CLIMA_COUNT); + + List result = whenExecuteGetAllClima(); + + thenResultMatchesExpectedList(result, expectedClimas, CLIMA_COUNT); + thenRepositoryFindAllClimasWasCalled(1); + } + + @Test + public void shouldReturnEmptyListWhenNoClimasAreFound() { + List expectedEmptyList = givenRepositoryReturnsEmptyList(); + + List result = whenExecuteGetAllClima(); + + thenResultMatchesExpectedList(result, expectedEmptyList, 0); + thenRepositoryFindAllClimasWasCalled(1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(); + + assertThrows(RuntimeException.class, + () -> whenExecuteGetAllClima(), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryFindAllClimasWasCalled(1); + } + + + //privadoss + private List givenRepositoryReturnsClimas(int count) { + List mockList = Collections.nCopies(count, mock(ClimaModel.class)); + when(climaRepository.findAllClimas()).thenReturn(mockList); + return mockList; + } + + private List givenRepositoryReturnsEmptyList() { + List emptyList = Collections.emptyList(); + when(climaRepository.findAllClimas()).thenReturn(emptyList); + return emptyList; + } + + private void givenRepositoryThrowsRuntimeException() { + doThrow(new RuntimeException("Simulated DB error")) + .when(climaRepository).findAllClimas(); + } + + private List whenExecuteGetAllClima() { + return getAllClima.execute(); + } + + private void thenResultMatchesExpectedList(List actual, List expected, int expectedCount) { + assertNotNull(actual, "La lista resultante no debe ser nula."); + assertEquals(expectedCount, actual.size(), "El tamaño de la lista debe coincidir."); + assertEquals(expected, actual, "El contenido de la lista debe coincidir con la lista simulada."); + } + + private void thenRepositoryFindAllClimasWasCalled(int times) { + verify(climaRepository, times(times)).findAllClimas(); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/recomendations/GetAllColorsTest.java b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/GetAllColorsTest.java new file mode 100644 index 0000000..b6db181 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/GetAllColorsTest.java @@ -0,0 +1,89 @@ +package com.outfitlab.project.domain.useCases.recomendations; + +import com.outfitlab.project.domain.interfaces.repositories.ColorRepository; +import com.outfitlab.project.domain.model.ColorModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetAllColorsTest { + + private ColorRepository colorRepository = mock(ColorRepository.class); + private GetAllColors getAllColors; + + private final int COLOR_COUNT = 5; + + @BeforeEach + void setUp() { + getAllColors = new GetAllColors(colorRepository); + } + + + @Test + public void shouldReturnListOfColorsWhenColorsAreFound() { + List expectedColors = givenRepositoryReturnsColors(COLOR_COUNT); + + List result = whenExecuteGetAllColors(); + + thenResultMatchesExpectedList(result, expectedColors, COLOR_COUNT); + thenRepositoryFindAllColoresWasCalled(1); + } + + @Test + public void shouldReturnEmptyListWhenNoColorsAreFound() { + List expectedEmptyList = givenRepositoryReturnsEmptyList(); + + List result = whenExecuteGetAllColors(); + + thenResultMatchesExpectedList(result, expectedEmptyList, 0); + thenRepositoryFindAllColoresWasCalled(1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(); + + assertThrows(RuntimeException.class, + () -> whenExecuteGetAllColors(), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryFindAllColoresWasCalled(1); + } + + + //privadosss + private List givenRepositoryReturnsColors(int count) { + List mockList = Collections.nCopies(count, mock(ColorModel.class)); + when(colorRepository.findAllColores()).thenReturn(mockList); + return mockList; + } + + private List givenRepositoryReturnsEmptyList() { + List emptyList = Collections.emptyList(); + when(colorRepository.findAllColores()).thenReturn(emptyList); + return emptyList; + } + + private void givenRepositoryThrowsRuntimeException() { + doThrow(new RuntimeException("Simulated DB error")) + .when(colorRepository).findAllColores(); + } + + private List whenExecuteGetAllColors() { + return getAllColors.execute(); + } + + private void thenResultMatchesExpectedList(List actual, List expected, int expectedCount) { + assertNotNull(actual, "La lista resultante no debe ser nula."); + assertEquals(expectedCount, actual.size(), "El tamaño de la lista debe coincidir."); + assertEquals(expected, actual, "El contenido de la lista debe coincidir con la lista simulada."); + } + + private void thenRepositoryFindAllColoresWasCalled(int times) { + verify(colorRepository, times(times)).findAllColores(); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/recomendations/GetAllOcacionTest.java b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/GetAllOcacionTest.java new file mode 100644 index 0000000..5b79fc6 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/GetAllOcacionTest.java @@ -0,0 +1,89 @@ +package com.outfitlab.project.domain.useCases.recomendations; + +import com.outfitlab.project.domain.interfaces.repositories.OcacionRepository; +import com.outfitlab.project.domain.model.OcasionModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetAllOcacionTest { + + private OcacionRepository ocacionRepository = mock(OcacionRepository.class); + private GetAllOcacion getAllOcacion; + + private final int OCASION_COUNT = 4; + + @BeforeEach + void setUp() { + getAllOcacion = new GetAllOcacion(ocacionRepository); + } + + + @Test + public void shouldReturnListOfOcasionesWhenOcasionesAreFound() { + List expectedOcasiones = givenRepositoryReturnsOcasiones(OCASION_COUNT); + + List result = whenExecuteGetAllOcasion(); + + thenResultMatchesExpectedList(result, expectedOcasiones, OCASION_COUNT); + thenRepositoryFindAllOcasionesWasCalled(1); + } + + @Test + public void shouldReturnEmptyListWhenNoOcasionesAreFound() { + List expectedEmptyList = givenRepositoryReturnsEmptyList(); + + List result = whenExecuteGetAllOcasion(); + + thenResultMatchesExpectedList(result, expectedEmptyList, 0); + thenRepositoryFindAllOcasionesWasCalled(1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(); + + assertThrows(RuntimeException.class, + () -> whenExecuteGetAllOcasion(), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryFindAllOcasionesWasCalled(1); + } + + + //privadoss + private List givenRepositoryReturnsOcasiones(int count) { + List mockList = Collections.nCopies(count, mock(OcasionModel.class)); + when(ocacionRepository.findAllOcasiones()).thenReturn(mockList); + return mockList; + } + + private List givenRepositoryReturnsEmptyList() { + List emptyList = Collections.emptyList(); + when(ocacionRepository.findAllOcasiones()).thenReturn(emptyList); + return emptyList; + } + + private void givenRepositoryThrowsRuntimeException() { + doThrow(new RuntimeException("Simulated DB error")) + .when(ocacionRepository).findAllOcasiones(); + } + + private List whenExecuteGetAllOcasion() { + return this.getAllOcacion.execute(); + } + + private void thenResultMatchesExpectedList(List actual, List expected, int expectedCount) { + assertNotNull(actual, "La lista resultante no debe ser nula."); + assertEquals(expectedCount, actual.size(), "El tamaño de la lista debe coincidir."); + assertEquals(expected, actual, "El contenido de la lista debe coincidir con la lista simulada."); + } + + private void thenRepositoryFindAllOcasionesWasCalled(int times) { + verify(ocacionRepository, times(times)).findAllOcasiones(); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/subscription/AssignFreePlanToUserTest.java b/src/test/java/com/outfitlab/project/domain/useCases/subscription/AssignFreePlanToUserTest.java new file mode 100644 index 0000000..582af66 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/subscription/AssignFreePlanToUserTest.java @@ -0,0 +1,99 @@ +package com.outfitlab.project.domain.useCases.subscription; + +import com.outfitlab.project.domain.interfaces.repositories.SubscriptionRepository; +import com.outfitlab.project.domain.interfaces.repositories.UserSubscriptionRepository; +import com.outfitlab.project.domain.model.SubscriptionModel; +import com.outfitlab.project.domain.model.UserSubscriptionModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import java.time.LocalDateTime; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class AssignFreePlanToUserTest { + + private UserSubscriptionRepository userSubscriptionRepository = mock(UserSubscriptionRepository.class); + private SubscriptionRepository subscriptionRepository = mock(SubscriptionRepository.class); + + private AssignFreePlanToUser assignFreePlanToUser; + + private final String USER_EMAIL = "new_user@example.com"; + private final String USER_FREE_PLAN_CODE = "user-free-monthly"; + private final String BRAND_FREE_PLAN_CODE = "brand-free-monthly"; + + @BeforeEach + void setUp() { + assignFreePlanToUser = new AssignFreePlanToUser(userSubscriptionRepository, subscriptionRepository); + } + + + @Test + public void shouldAssignUserFreePlanWhenUserIsNotBrand() { + boolean isBrand = false; + SubscriptionModel mockFreePlan = givenSubscriptionPlanExists(USER_FREE_PLAN_CODE, "10", "5", "Ilimitado"); + + whenExecuteAssignFreePlanToUser(USER_EMAIL, isBrand); + + thenRepositoryWasCalledWithCorrectPlan(USER_FREE_PLAN_CODE, 10, 5, null); + } + + @Test + public void shouldAssignBrandFreePlanWhenUserIsBrand() { + boolean isBrand = true; + SubscriptionModel mockFreePlan = givenSubscriptionPlanExists(BRAND_FREE_PLAN_CODE, "20", "20", "5"); + + whenExecuteAssignFreePlanToUser(USER_EMAIL, isBrand); + + thenRepositoryWasCalledWithCorrectPlan(BRAND_FREE_PLAN_CODE, 20, 20, 5); + } + + @Test + public void shouldHandleNullOrEmptyFeaturesGracefully() { + boolean isBrand = false; + SubscriptionModel mockFreePlan = givenSubscriptionPlanExists(USER_FREE_PLAN_CODE, null, "", "1"); + + whenExecuteAssignFreePlanToUser(USER_EMAIL, isBrand); + + thenRepositoryWasCalledWithCorrectPlan(USER_FREE_PLAN_CODE, null, null, 1); + } + + + //privadoss + private SubscriptionModel givenSubscriptionPlanExists(String planCode, String feature1, String feature2, String feature3) { + SubscriptionModel mockPlan = mock(SubscriptionModel.class); + + when(subscriptionRepository.getByPlanCode(planCode)).thenReturn(mockPlan); + + when(mockPlan.getPlanCode()).thenReturn(planCode); + when(mockPlan.getFeature1()).thenReturn(feature2); //combinaciones + when(mockPlan.getFeature2()).thenReturn(feature1); //favoritos + when(mockPlan.getFeature3()).thenReturn(feature3); //3d + + return mockPlan; + } + + private void whenExecuteAssignFreePlanToUser(String userEmail, boolean isBrand) { + assignFreePlanToUser.execute(userEmail, isBrand); + } + + private void thenRepositoryWasCalledWithCorrectPlan(String expectedPlanCode, Integer expectedMaxCombinations, Integer expectedMaxFavorites, Integer expectedMaxModels) { + verify(subscriptionRepository, times(1)).getByPlanCode(expectedPlanCode); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(UserSubscriptionModel.class); + verify(userSubscriptionRepository, times(1)).save(argumentCaptor.capture()); + + UserSubscriptionModel capturedSubscription = argumentCaptor.getValue(); + + assertEquals(USER_EMAIL, capturedSubscription.getUserEmail(), "El email del usuario debe coincidir."); + assertEquals(expectedPlanCode, capturedSubscription.getPlanCode(), "El código de plan asignado debe ser el esperado."); + assertEquals("ACTIVE", capturedSubscription.getStatus(), "El estado de la suscripción debe ser ACTIVO."); + + assertEquals(expectedMaxCombinations, capturedSubscription.getMaxCombinations(), "El límite de combinaciones debe ser el esperado."); + assertEquals(expectedMaxFavorites, capturedSubscription.getMaxFavorites(), "El límite de favoritos debe ser el esperado."); + assertEquals(expectedMaxModels, capturedSubscription.getMaxModels(), "El límite de modelos generados debe ser el esperado (null para Ilimitado)."); + + assertNotNull(capturedSubscription.getSubscriptionStart(), "La fecha de inicio de suscripción no debe ser nula."); + assertTrue(capturedSubscription.getSubscriptionStart().isBefore(LocalDateTime.now().plusSeconds(1)), "La fecha de inicio debe ser reciente."); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/subscription/CheckUserPlanLimitTest.java b/src/test/java/com/outfitlab/project/domain/useCases/subscription/CheckUserPlanLimitTest.java new file mode 100644 index 0000000..f0750a5 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/subscription/CheckUserPlanLimitTest.java @@ -0,0 +1,130 @@ +package com.outfitlab.project.domain.useCases.subscription; + +import com.outfitlab.project.domain.exceptions.PlanLimitExceededException; +import com.outfitlab.project.domain.exceptions.SubscriptionNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.UserSubscriptionRepository; +import com.outfitlab.project.domain.model.UserSubscriptionModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class CheckUserPlanLimitTest { + + private UserSubscriptionRepository userSubscriptionRepository = mock(UserSubscriptionRepository.class); + + private CheckUserPlanLimit checkUserPlanLimit; + + private final String USER_EMAIL = "user@plan.com"; + private final String LIMIT_COMBINATIONS = "combinations"; + private final String LIMIT_FAVORITES = "favorites"; + private final String LIMIT_3D_MODELS = "3d_models"; + private final String LIMIT_DOWNLOADS = "downloads"; + private final int MAX_LIMIT = 10; + private final int CURRENT_USAGE_SAFE = MAX_LIMIT - 1; + private final int CURRENT_USAGE_EXCEEDED = MAX_LIMIT + 1; + + @BeforeEach + void setUp() { + checkUserPlanLimit = new CheckUserPlanLimit(userSubscriptionRepository); + } + + + @Test + public void shouldNotThrowExceptionWhenCombinationsLimitIsNotReached() throws PlanLimitExceededException, SubscriptionNotFoundException { + givenSubscriptionExists(CURRENT_USAGE_SAFE, MAX_LIMIT, 0, MAX_LIMIT, 0, MAX_LIMIT); + + whenExecuteCheckLimit(LIMIT_COMBINATIONS); + + thenRepositoryWasCalledOnce(USER_EMAIL); + } + + @Test + public void shouldNotThrowExceptionWhenFavoritesLimitIsNotReached() throws PlanLimitExceededException, SubscriptionNotFoundException { + givenSubscriptionExists(0, MAX_LIMIT, CURRENT_USAGE_SAFE, MAX_LIMIT, 0, MAX_LIMIT); + + whenExecuteCheckLimit(LIMIT_FAVORITES); + + thenRepositoryWasCalledOnce(USER_EMAIL); + } + + @Test + public void shouldNotThrowExceptionWhenCombinationsLimitIsUnlimitedNull() throws PlanLimitExceededException, SubscriptionNotFoundException { + givenSubscriptionExists(CURRENT_USAGE_EXCEEDED, null, 0, MAX_LIMIT, 0, MAX_LIMIT); + whenExecuteCheckLimit(LIMIT_COMBINATIONS); + } + + @Test + public void shouldNotThrowExceptionWhenDownloadsLimitIsUnlimitedNegativeOne() throws PlanLimitExceededException, SubscriptionNotFoundException { + givenSubscriptionExists(0, MAX_LIMIT, 0, MAX_LIMIT, CURRENT_USAGE_EXCEEDED, -1); + whenExecuteCheckLimit(LIMIT_DOWNLOADS); + } + + @Test + public void shouldThrowPlanLimitExceededExceptionWhenModelsLimitIsExceeded() throws SubscriptionNotFoundException { + givenSubscriptionExists(0, MAX_LIMIT, CURRENT_USAGE_EXCEEDED, MAX_LIMIT, 0, MAX_LIMIT); + + assertThrows(PlanLimitExceededException.class, + () -> whenExecuteCheckLimit(LIMIT_3D_MODELS), + "Se esperaba PlanLimitExceededException al exceder modelos 3D."); + + thenRepositoryWasCalledOnce(USER_EMAIL); + } + + @Test + public void shouldThrowPlanLimitExceededExceptionWhenDownloadsLimitIsExactlyReached() throws SubscriptionNotFoundException { + givenSubscriptionExists(0, MAX_LIMIT, 0, MAX_LIMIT, MAX_LIMIT, MAX_LIMIT); + + assertThrows(PlanLimitExceededException.class, + () -> whenExecuteCheckLimit(LIMIT_DOWNLOADS), + "Se esperaba PlanLimitExceededException al alcanzar el límite exacto."); + + thenRepositoryWasCalledOnce(USER_EMAIL); + } + + @Test + public void shouldThrowSubscriptionNotFoundExceptionWhenSubscriptionDoesNotExist() throws SubscriptionNotFoundException { + givenRepositoryThrowsSubscriptionNotFound(); + + assertThrows(SubscriptionNotFoundException.class, + () -> whenExecuteCheckLimit(LIMIT_FAVORITES), + "Se esperaba SubscriptionNotFoundException cuando no hay suscripción."); + + thenRepositoryWasCalledOnce(USER_EMAIL); + } + + + //privadoss + private void givenSubscriptionExists(int combinationsUsed, Integer maxCombinations, + int favoritesCount, Integer maxFavorites, + int downloadsCount, Integer maxDownloads) throws SubscriptionNotFoundException { + + UserSubscriptionModel mockSubscription = mock(UserSubscriptionModel.class); + + when(mockSubscription.getCombinationsUsed()).thenReturn(combinationsUsed); + when(mockSubscription.getMaxCombinations()).thenReturn(maxCombinations); + + when(mockSubscription.getFavoritesCount()).thenReturn(favoritesCount); + when(mockSubscription.getMaxFavorites()).thenReturn(maxFavorites); + + when(mockSubscription.getModelsGenerated()).thenReturn(favoritesCount); + when(mockSubscription.getMaxModels()).thenReturn(maxFavorites); + + when(mockSubscription.getDownloadsCount()).thenReturn(downloadsCount); + when(mockSubscription.getMaxDownloads()).thenReturn(maxDownloads); + + when(userSubscriptionRepository.findByUserEmail(USER_EMAIL)).thenReturn(mockSubscription); + } + + private void givenRepositoryThrowsSubscriptionNotFound() throws SubscriptionNotFoundException { + when(userSubscriptionRepository.findByUserEmail(USER_EMAIL)).thenThrow(SubscriptionNotFoundException.class); + } + + private void whenExecuteCheckLimit(String limitType) throws PlanLimitExceededException, SubscriptionNotFoundException { + checkUserPlanLimit.execute(USER_EMAIL, limitType); + } + + private void thenRepositoryWasCalledOnce(String userEmail) throws SubscriptionNotFoundException { + verify(userSubscriptionRepository, times(1)).findByUserEmail(userEmail); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/subscription/CreateMercadoPagoPreferenceTest.java b/src/test/java/com/outfitlab/project/domain/useCases/subscription/CreateMercadoPagoPreferenceTest.java index 6c0b096..6833c99 100644 --- a/src/test/java/com/outfitlab/project/domain/useCases/subscription/CreateMercadoPagoPreferenceTest.java +++ b/src/test/java/com/outfitlab/project/domain/useCases/subscription/CreateMercadoPagoPreferenceTest.java @@ -1,31 +1,62 @@ package com.outfitlab.project.domain.useCases.subscription; import com.mercadopago.exceptions.MPApiException; -import com.mercadopago.exceptions.MPException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.*; +@ExtendWith(MockitoExtension.class) public class CreateMercadoPagoPreferenceTest { private CreateMercadoPagoPreference createPreferenceUseCase; private final String PLAN_ID = "premium-demo-1"; private final String USER_EMAIL = "test@example.com"; - private final BigDecimal PRICE = new BigDecimal("100.00"); + private final BigDecimal VALID_PRICE = new BigDecimal("25000.00"); private final String CURRENCY = "ARS"; + private final String WEBHOOK_URL = "http://localhost:8080"; + private final String FRONTEND_URL = "http://localhost:5173"; @BeforeEach void setUp() { - String webhookBaseUrl = "http://localhost:8080"; - String frontendBaseUrl = "http://localhost:5173"; - createPreferenceUseCase = new CreateMercadoPagoPreference(webhookBaseUrl, frontendBaseUrl); + createPreferenceUseCase = new CreateMercadoPagoPreference(WEBHOOK_URL, FRONTEND_URL); + } + + + @Test + public void shouldThrowMPApiExceptionWhenPriceIsNull() { + BigDecimal invalidPrice = null; + + //when y then + thenExecutionThrowsMPApiException(PLAN_ID, USER_EMAIL, invalidPrice, CURRENCY); + } + + @Test + public void shouldThrowMPApiExceptionWhenPriceIsZeroOrNegative() { + BigDecimal zeroPrice = BigDecimal.ZERO; + BigDecimal negativePrice = new BigDecimal("-1.00"); + + thenExecutionThrowsMPApiException(PLAN_ID, USER_EMAIL, zeroPrice, CURRENCY); + thenExecutionThrowsMPApiException(PLAN_ID, USER_EMAIL, negativePrice, CURRENCY); } @Test - public void givenNullPriceWhenExecuteThenThrowMPApiException() { + public void shouldThrowMPExceptionOrMPApiExceptionWhenUserEmailIsNull() { + String invalidEmail = null; + + assertThrows(MPApiException.class, + () -> createPreferenceUseCase.execute(PLAN_ID, invalidEmail, VALID_PRICE, CURRENCY), + "Se esperaba una MPApiException cuando el email del usuario es nulo."); + } + - assertThrows(MPApiException.class, () -> createPreferenceUseCase.execute(PLAN_ID, USER_EMAIL, null, CURRENCY)); + //privado + private void thenExecutionThrowsMPApiException(String planId, String email, BigDecimal price, String currency) { + assertThrows(MPApiException.class, + () -> createPreferenceUseCase.execute(planId, email, price, currency), + "Se esperaba una MPApiException."); } } \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/subscription/GetAllSubscriptionTest.java b/src/test/java/com/outfitlab/project/domain/useCases/subscription/GetAllSubscriptionTest.java new file mode 100644 index 0000000..ac05cf5 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/subscription/GetAllSubscriptionTest.java @@ -0,0 +1,126 @@ +package com.outfitlab.project.domain.useCases.subscription; + +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.SubscriptionRepository; +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.model.BrandModel; +import com.outfitlab.project.domain.model.SubscriptionModel; +import com.outfitlab.project.domain.model.UserModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.Collections; +import java.util.List; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetAllSubscriptionTest { + + private SubscriptionRepository subscriptionRepository = mock(SubscriptionRepository.class); + private UserRepository userRepository = mock(UserRepository.class); + private GetAllSubscription getAllSubscription; + + private final String USER_EMAIL = "normal@user.com"; + private final String BRAND_EMAIL = "brand@user.com"; + private final String PLAN_TYPE_USER = "USER"; + private final String PLAN_TYPE_BRAND = "BRAND"; + + @BeforeEach + void setUp() { + getAllSubscription = new GetAllSubscription(subscriptionRepository, userRepository); + } + + + @Test + public void shouldReturnUserPlansWhenEmailIsNull() { + String nullEmail = null; + givenRepositoryReturnsPlans(PLAN_TYPE_USER, 3); + + List result = whenExecuteGetAllSubscription(nullEmail); + + thenResultContainsPlans(result, PLAN_TYPE_USER, 3); + thenUserConsultationWasNeverCalled(); + } + + @Test + public void shouldReturnUserPlansWhenEmailIsEmpty() { + String emptyEmail = ""; + givenRepositoryReturnsPlans(PLAN_TYPE_USER, 5); + + List result = whenExecuteGetAllSubscription(emptyEmail); + + thenResultContainsPlans(result, PLAN_TYPE_USER, 5); + thenUserConsultationWasNeverCalled(); + } + + @Test + public void shouldReturnUserPlansWhenUserIsFoundAndIsNotBrand() throws UserNotFoundException { + givenUserExists(USER_EMAIL, false); + givenRepositoryReturnsPlans(PLAN_TYPE_USER, 2); + + List result = whenExecuteGetAllSubscription(USER_EMAIL); + + thenResultContainsPlans(result, PLAN_TYPE_USER, 2); + thenUserConsultationWasCalled(USER_EMAIL); + } + + @Test + public void shouldReturnBrandPlansWhenUserIsFoundAndIsBrand() throws UserNotFoundException { + givenUserExists(BRAND_EMAIL, true); + givenRepositoryReturnsPlans(PLAN_TYPE_BRAND, 4); + + List result = whenExecuteGetAllSubscription(BRAND_EMAIL); + + thenResultContainsPlans(result, PLAN_TYPE_BRAND, 4); + thenUserConsultationWasCalled(BRAND_EMAIL); + } + + @Test + public void shouldReturnUserPlansWhenUserIsNotFound() throws UserNotFoundException { + givenUserDoesNotExist(USER_EMAIL); + givenRepositoryReturnsPlans(PLAN_TYPE_USER, 1); + + List result = whenExecuteGetAllSubscription(USER_EMAIL); + + thenResultContainsPlans(result, PLAN_TYPE_USER, 1); + thenUserConsultationWasCalled(USER_EMAIL); + } + + + //privadoss + private void givenUserExists(String email, boolean isBrand) throws UserNotFoundException { + UserModel user = mock(UserModel.class); + + BrandModel brandObjectMock = isBrand ? mock(BrandModel.class) : null; + + when(user.getBrand()).thenReturn(brandObjectMock); + when(userRepository.findUserByEmail(email)).thenReturn(user); + } + + private void givenUserDoesNotExist(String email) throws UserNotFoundException { + when(userRepository.findUserByEmail(email)).thenThrow(new UserNotFoundException("Usuario no encontrado")); + } + + private void givenRepositoryReturnsPlans(String planType, int count) { + List mockPlans = Collections.nCopies(count, new SubscriptionModel()); + when(subscriptionRepository.findByPlanType(planType)).thenReturn(mockPlans); + } + + private List whenExecuteGetAllSubscription(String userEmail) { + return getAllSubscription.execute(userEmail); + } + + private void thenResultContainsPlans(List result, String expectedPlanType, int expectedCount) { + assertNotNull(result, "El resultado no debe ser nulo."); + assertEquals(expectedCount, result.size(), "El número de planes devuelto debe ser el esperado."); + + verify(subscriptionRepository, times(1)).findByPlanType(expectedPlanType); + } + + private void thenUserConsultationWasCalled(String email) throws UserNotFoundException { + verify(userRepository, times(1)).findUserByEmail(email); + } + + private void thenUserConsultationWasNeverCalled() { + verify(userRepository, never()).findUserByEmail(anyString()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/subscription/IncrementUsageCounterTest.java b/src/test/java/com/outfitlab/project/domain/useCases/subscription/IncrementUsageCounterTest.java new file mode 100644 index 0000000..359443d --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/subscription/IncrementUsageCounterTest.java @@ -0,0 +1,54 @@ +package com.outfitlab.project.domain.useCases.subscription; + +import com.outfitlab.project.domain.interfaces.repositories.UserSubscriptionRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.*; + +public class IncrementUsageCounterTest { + + private UserSubscriptionRepository userSubscriptionRepository = mock(UserSubscriptionRepository.class); + private IncrementUsageCounter incrementUsageCounter; + + private final String USER_EMAIL = "test@user.com"; + private final String COUNTER_COMBINATIONS = "combinations"; + private final String COUNTER_MODELS = "3d_models"; + private final String COUNTER_FAVORITES = "favorites"; + + @BeforeEach + void setUp() { + incrementUsageCounter = new IncrementUsageCounter(userSubscriptionRepository); + } + + + @Test + public void shouldCallRepositoryToIncrementCombinationsCounter() { + whenExecuteIncrementCounter(USER_EMAIL, COUNTER_COMBINATIONS); + + thenRepositoryIncrementCounterWasCalled(USER_EMAIL, COUNTER_COMBINATIONS); + } + + @Test + public void shouldCallRepositoryToIncrementModelsCounter() { + whenExecuteIncrementCounter(USER_EMAIL, COUNTER_MODELS); + + thenRepositoryIncrementCounterWasCalled(USER_EMAIL, COUNTER_MODELS); + } + + @Test + public void shouldCallRepositoryToIncrementFavoritesCounter() { + whenExecuteIncrementCounter(USER_EMAIL, COUNTER_FAVORITES); + + thenRepositoryIncrementCounterWasCalled(USER_EMAIL, COUNTER_FAVORITES); + } + + + //privadoss + private void whenExecuteIncrementCounter(String userEmail, String counterType) { + incrementUsageCounter.execute(userEmail, counterType); + } + + private void thenRepositoryIncrementCounterWasCalled(String userEmail, String counterType) { + verify(userSubscriptionRepository, times(1)).incrementCounter(userEmail, counterType); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/subscription/ProcessPaymentNotificationTest.java b/src/test/java/com/outfitlab/project/domain/useCases/subscription/ProcessPaymentNotificationTest.java index 38590b4..c24ae7d 100644 --- a/src/test/java/com/outfitlab/project/domain/useCases/subscription/ProcessPaymentNotificationTest.java +++ b/src/test/java/com/outfitlab/project/domain/useCases/subscription/ProcessPaymentNotificationTest.java @@ -1,11 +1,16 @@ package com.outfitlab.project.domain.useCases.subscription; -import com.mercadopago.net.MPResponse; import com.mercadopago.exceptions.MPApiException; import com.mercadopago.exceptions.MPException; +import com.mercadopago.net.MPResponse; import com.mercadopago.resources.payment.Payment; -import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.exceptions.SubscriptionNotFoundException; import com.outfitlab.project.domain.interfaces.gateways.MercadoPagoPaymentGateway; +import com.outfitlab.project.domain.interfaces.repositories.SubscriptionRepository; +import com.outfitlab.project.domain.interfaces.repositories.UserSubscriptionRepository; +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.model.SubscriptionModel; +import com.outfitlab.project.domain.model.UserSubscriptionModel; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -16,63 +21,106 @@ public class ProcessPaymentNotificationTest { private UserRepository userRepository = mock(UserRepository.class); private MercadoPagoPaymentGateway paymentGateway = mock(MercadoPagoPaymentGateway.class); - private com.outfitlab.project.domain.interfaces.repositories.UserSubscriptionRepository userSubscriptionRepository = mock(com.outfitlab.project.domain.interfaces.repositories.UserSubscriptionRepository.class); - private com.outfitlab.project.domain.interfaces.repositories.SubscriptionRepository subscriptionRepository = mock(com.outfitlab.project.domain.interfaces.repositories.SubscriptionRepository.class); + private UserSubscriptionRepository userSubscriptionRepository = mock(UserSubscriptionRepository.class); + private SubscriptionRepository subscriptionRepository = mock(SubscriptionRepository.class); + private ProcessPaymentNotification processNotificationUseCase; private final Long PAYMENT_ID = 12345L; private final String EXTERNAL_REFERENCE = "user-id-5678"; + private final String PLAN_CODE = "premium"; + private final String PAYMENT_STATUS_APPROVED = "approved"; + private final String PAYMENT_STATUS_REJECTED = "rejected"; @BeforeEach void setUp() { - processNotificationUseCase = new ProcessPaymentNotification(userRepository, paymentGateway, userSubscriptionRepository, subscriptionRepository); + processNotificationUseCase = new ProcessPaymentNotification( + userRepository, + paymentGateway, + userSubscriptionRepository, + subscriptionRepository + ); } + @Test - public void givenApprovedPaymentWhenExecuteThenActivateUserPremium() throws MPException, MPApiException, com.outfitlab.project.domain.exceptions.SubscriptionNotFoundException { - Payment mockPayment = mock(Payment.class); - when(mockPayment.getStatus()).thenReturn("approved"); - when(mockPayment.getExternalReference()).thenReturn(EXTERNAL_REFERENCE); - // Mock description to contain plan info if needed, or just ensure it doesn't crash - when(mockPayment.getDescription()).thenReturn("Plan Premium"); - - when(paymentGateway.getPaymentDetails(PAYMENT_ID)).thenReturn(mockPayment); - - // Mock subscription behavior - com.outfitlab.project.domain.model.SubscriptionModel mockPlan = new com.outfitlab.project.domain.model.SubscriptionModel(); - mockPlan.setPlanCode("premium"); - mockPlan.setFeature2("Unlimited"); // For limit parsing - when(subscriptionRepository.getByPlanCode(anyString())).thenReturn(mockPlan); - - com.outfitlab.project.domain.model.UserSubscriptionModel mockUserSub = new com.outfitlab.project.domain.model.UserSubscriptionModel(); - when(userSubscriptionRepository.findByUserEmail(anyString())).thenReturn(mockUserSub); + public void shouldActivateUserPremiumWhenPaymentIsApproved() throws MPException, MPApiException, SubscriptionNotFoundException { + givenApprovedPaymentDetails(PAYMENT_ID, EXTERNAL_REFERENCE, "Plan Premium"); + givenSubscriptionModelsExist(PLAN_CODE); - processNotificationUseCase.execute(PAYMENT_ID); + whenExecuteProcessNotification(PAYMENT_ID); - verify(paymentGateway, times(1)).getPaymentDetails(PAYMENT_ID); + thenPaymentDetailsAreFetched(PAYMENT_ID); } @Test - public void givenRejectedPaymentWhenExecuteThenDoNotActivateUserPremium() throws MPException, MPApiException, com.outfitlab.project.domain.exceptions.SubscriptionNotFoundException { - Payment mockPayment = mock(Payment.class); - when(mockPayment.getStatus()).thenReturn("rejected"); - when(mockPayment.getExternalReference()).thenReturn(EXTERNAL_REFERENCE); - when(paymentGateway.getPaymentDetails(PAYMENT_ID)).thenReturn(mockPayment); + public void shouldNotUpdateSubscriptionWhenPaymentIsRejected() throws MPException, MPApiException, SubscriptionNotFoundException { + givenRejectedPaymentDetails(PAYMENT_ID, EXTERNAL_REFERENCE); - processNotificationUseCase.execute(PAYMENT_ID); + whenExecuteProcessNotification(PAYMENT_ID); - verify(paymentGateway, times(1)).getPaymentDetails(PAYMENT_ID); - verify(userSubscriptionRepository, never()).update(any()); + thenPaymentDetailsAreFetched(PAYMENT_ID); + thenUpdateWasNeverCalled(); } @Test - public void givenMPApiExceptionWhenExecuteThenPropagateException() throws MPException, MPApiException, com.outfitlab.project.domain.exceptions.SubscriptionNotFoundException { + public void shouldPropagateMPApiExceptionWhenFetchingPaymentDetailsFails() throws MPException, MPApiException { + givenPaymentGatewayThrowsMPApiException(PAYMENT_ID); + + assertThrows(MPApiException.class, () -> whenExecuteProcessNotification(PAYMENT_ID)); + + thenPaymentDetailsAreFetched(PAYMENT_ID); + thenUpdateWasNeverCalled(); + } + + + //privadoss + private void givenApprovedPaymentDetails(Long paymentId, String externalReference, String planDescription) throws MPException, MPApiException { + Payment mockPayment = mock(Payment.class); + + when(mockPayment.getStatus()).thenReturn(PAYMENT_STATUS_APPROVED); + when(mockPayment.getExternalReference()).thenReturn(externalReference); + when(mockPayment.getDescription()).thenReturn(planDescription); + + when(paymentGateway.getPaymentDetails(paymentId)).thenReturn(mockPayment); + } + + private void givenRejectedPaymentDetails(Long paymentId, String externalReference) throws MPException, MPApiException { + Payment mockPayment = mock(Payment.class); + when(mockPayment.getStatus()).thenReturn(PAYMENT_STATUS_REJECTED); + when(mockPayment.getExternalReference()).thenReturn(externalReference); + + when(paymentGateway.getPaymentDetails(paymentId)).thenReturn(mockPayment); + } + + private void givenSubscriptionModelsExist(String planCode) throws SubscriptionNotFoundException { + SubscriptionModel mockPlan = mock(SubscriptionModel.class); + when(mockPlan.getPlanCode()).thenReturn(planCode); + when(mockPlan.getFeature2()).thenReturn("Unlimited"); + + when(subscriptionRepository.getByPlanCode(anyString())).thenReturn(mockPlan); + + UserSubscriptionModel mockUserSub = mock(UserSubscriptionModel.class); + when(userSubscriptionRepository.findByUserEmail(anyString())).thenReturn(mockUserSub); + } + + private void givenPaymentGatewayThrowsMPApiException(Long paymentId) throws MPException, MPApiException { MPResponse mockResponse = mock(MPResponse.class); doThrow(new MPApiException("Error de API simulado", mockResponse)) - .when(paymentGateway).getPaymentDetails(PAYMENT_ID); + .when(paymentGateway).getPaymentDetails(paymentId); + } - assertThrows(MPApiException.class, () -> processNotificationUseCase.execute(PAYMENT_ID)); - verify(paymentGateway, times(1)).getPaymentDetails(PAYMENT_ID); + private void whenExecuteProcessNotification(Long paymentId) throws MPException, MPApiException, SubscriptionNotFoundException { + processNotificationUseCase.execute(paymentId); + } + + + private void thenPaymentDetailsAreFetched(Long paymentId) throws MPException, MPApiException { + verify(paymentGateway, times(1)).getPaymentDetails(paymentId); + } + + private void thenUpdateWasNeverCalled() { + verify(userSubscriptionRepository, never()).update(any()); } } \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/tripo/CheckTaskStatusTest.java b/src/test/java/com/outfitlab/project/domain/useCases/tripo/CheckTaskStatusTest.java index e54625c..6391f5f 100644 --- a/src/test/java/com/outfitlab/project/domain/useCases/tripo/CheckTaskStatusTest.java +++ b/src/test/java/com/outfitlab/project/domain/useCases/tripo/CheckTaskStatusTest.java @@ -1,8 +1,12 @@ package com.outfitlab.project.domain.useCases.tripo; -import com.outfitlab.project.domain.exceptions.*; +import com.outfitlab.project.domain.exceptions.ErrorGenerateGlbException; +import com.outfitlab.project.domain.exceptions.ErrorGlbGenerateTimeExpiredException; +import com.outfitlab.project.domain.exceptions.ErrorReadJsonException; +import com.outfitlab.project.domain.exceptions.ErrorWhenSleepException; import com.outfitlab.project.domain.interfaces.repositories.TripoRepository; import com.outfitlab.project.infrastructure.repositories.TripoRepositoryImpl; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -11,59 +15,81 @@ public class CheckTaskStatusTest { private TripoRepository tripoRepository = mock(TripoRepositoryImpl.class); - private CheckTaskStatus checkTaskStatus = new CheckTaskStatus(tripoRepository); + private CheckTaskStatus checkTaskStatus; + private final String VALID_TASK_ID = "12345"; + private final String STATUS_COMPLETED = "COMPLETED"; + + @BeforeEach + void setUp() { + checkTaskStatus = new CheckTaskStatus(tripoRepository); + } + @Test - public void givenValidTaskIdWhenExecuteThenReturnStatus() throws ErrorGlbGenerateTimeExpiredException, ErrorGenerateGlbException, ErrorWhenSleepException, ErrorReadJsonException { - String taskId = "12345"; - String expectedStatus = "COMPLETED"; + public void shouldReturnCompletedStatusWhenTaskIsSuccessful() throws Exception { + givenRepositoryReturnsStatus(VALID_TASK_ID, STATUS_COMPLETED); - when(tripoRepository.requestStatusGlbTripo(taskId)).thenReturn(expectedStatus); + String result = whenExecuteCheckTaskStatus(VALID_TASK_ID); - String result = checkTaskStatus.execute(taskId); - assertEquals(expectedStatus, result); - verify(tripoRepository, times(1)).requestStatusGlbTripo(taskId); + thenReturnedStatusMatchesExpected(result, STATUS_COMPLETED); + thenRepositoryWasCalledOnce(VALID_TASK_ID); } @Test - public void givenRepositoryThrowTimeExpiredExceptionWhenExecuteThenPropagate() throws ErrorGlbGenerateTimeExpiredException, ErrorGenerateGlbException, ErrorWhenSleepException, ErrorReadJsonException { - String taskId = "12345"; - - when(tripoRepository.requestStatusGlbTripo(taskId)) - .thenThrow(new ErrorGlbGenerateTimeExpiredException("Tiempo expirado")); + public void shouldPropagateTimeExpiredExceptionWhenGlbGenerationTimeExpires() { + givenRepositoryThrowsException(VALID_TASK_ID, new ErrorGlbGenerateTimeExpiredException("Tiempo expirado")); - assertThrows(ErrorGlbGenerateTimeExpiredException.class, () -> checkTaskStatus.execute(taskId)); - verify(tripoRepository, times(1)).requestStatusGlbTripo(taskId); + thenExecutionThrowsException(VALID_TASK_ID, ErrorGlbGenerateTimeExpiredException.class); } @Test - public void givenRepositoryWhenExecuteThenThrowSleepException() throws ErrorGlbGenerateTimeExpiredException, ErrorGenerateGlbException, ErrorWhenSleepException, ErrorReadJsonException { - String taskId = "12345"; + public void shouldPropagateErrorWhenSleepExceptionWhenThreadWaitingFails() { + givenRepositoryThrowsException(VALID_TASK_ID, new ErrorWhenSleepException("Error al esperar el hilo")); - when(tripoRepository.requestStatusGlbTripo(taskId)).thenThrow(new ErrorWhenSleepException("Error al esperar el hilo")); + thenExecutionThrowsException(VALID_TASK_ID, ErrorWhenSleepException.class); + } - assertThrows(ErrorWhenSleepException.class, () -> checkTaskStatus.execute(taskId)); - verify(tripoRepository, times(1)).requestStatusGlbTripo(taskId); + @Test + public void shouldPropagateErrorReadJsonExceptionWhenApiResponseIsInvalid() { + givenRepositoryThrowsException(VALID_TASK_ID, new ErrorReadJsonException("Error leyendo JSON")); + + thenExecutionThrowsException(VALID_TASK_ID, ErrorReadJsonException.class); } @Test - public void givenRepositoryThrowReadJsonExceptionWhenExecuteThenPropagate() throws ErrorGlbGenerateTimeExpiredException, ErrorGenerateGlbException, ErrorWhenSleepException, ErrorReadJsonException { - String taskId = "12345"; + public void shouldPropagateErrorGenerateGlbExceptionWhenGenerationFails() { + givenRepositoryThrowsException(VALID_TASK_ID, new ErrorGenerateGlbException("Error generando GLB")); + + thenExecutionThrowsException(VALID_TASK_ID, ErrorGenerateGlbException.class); + } - when(tripoRepository.requestStatusGlbTripo(taskId)).thenThrow(new ErrorReadJsonException("Error leyendo JSON")); - assertThrows(ErrorReadJsonException.class, () -> checkTaskStatus.execute(taskId)); - verify(tripoRepository, times(1)).requestStatusGlbTripo(taskId); + private void givenRepositoryReturnsStatus(String taskId, String status) throws ErrorGenerateGlbException, ErrorGlbGenerateTimeExpiredException, ErrorWhenSleepException, ErrorReadJsonException { + when(tripoRepository.requestStatusGlbTripo(taskId)).thenReturn(status); } - @Test - public void givenRepositoryThrowGenerateGlbExceptionWhenExecuteThenPropagate() throws ErrorGlbGenerateTimeExpiredException, ErrorGenerateGlbException, ErrorWhenSleepException, ErrorReadJsonException { - String taskId = "12345"; + private void givenRepositoryThrowsException(String taskId, Exception exception) { + try { + doThrow(exception).when(tripoRepository).requestStatusGlbTripo(taskId); + } catch (Exception e) { + } + } - when(tripoRepository.requestStatusGlbTripo(taskId)).thenThrow(new ErrorGenerateGlbException("Error generando GLB")); + private String whenExecuteCheckTaskStatus(String taskId) throws ErrorGlbGenerateTimeExpiredException, ErrorGenerateGlbException, ErrorWhenSleepException, ErrorReadJsonException { + return checkTaskStatus.execute(taskId); + } - assertThrows(ErrorGenerateGlbException.class, () -> checkTaskStatus.execute(taskId)); - verify(tripoRepository, times(1)).requestStatusGlbTripo(taskId); + private void thenReturnedStatusMatchesExpected(String result, String expectedStatus) { + assertEquals(expectedStatus, result); + } + + private void thenExecutionThrowsException(String taskId, Class expectedException) { + assertThrows(expectedException, () -> checkTaskStatus.execute(taskId)); + + thenRepositoryWasCalledOnce(taskId); } -} + private void thenRepositoryWasCalledOnce(String taskId) { + verify(tripoRepository, times(1)).requestStatusGlbTripo(taskId); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/tripo/FindTripoModelByTaskidTest.java b/src/test/java/com/outfitlab/project/domain/useCases/tripo/FindTripoModelByTaskidTest.java index 218bf72..540e0f9 100644 --- a/src/test/java/com/outfitlab/project/domain/useCases/tripo/FindTripoModelByTaskidTest.java +++ b/src/test/java/com/outfitlab/project/domain/useCases/tripo/FindTripoModelByTaskidTest.java @@ -3,6 +3,7 @@ import com.outfitlab.project.domain.interfaces.repositories.TripoRepository; import com.outfitlab.project.domain.model.TripoModel; import com.outfitlab.project.infrastructure.repositories.TripoRepositoryImpl; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -11,32 +12,66 @@ public class FindTripoModelByTaskidTest { private TripoRepository tripoRepository = mock(TripoRepositoryImpl.class); - private FindTripoModelByTaskid findTripoModelByTaskid = new FindTripoModelByTaskid(tripoRepository); + private FindTripoModelByTaskid findTripoModelByTaskid; + private final String VALID_TASK_ID = "TASK-001"; + private final String INVALID_TASK_ID = "TASK-002"; + + @BeforeEach + void setUp() { + findTripoModelByTaskid = new FindTripoModelByTaskid(tripoRepository); + } + + + @Test + public void shouldReturnTripoModelWhenTaskIsFound() { + TripoModel expectedModel = givenRepositoryReturnsModel(VALID_TASK_ID); + + TripoModel result = whenExecuteFindModel(VALID_TASK_ID); + + thenReturnedModelIsCorrect(result, expectedModel, VALID_TASK_ID); + } @Test - public void givenValidTaskIdWhenExecuteThenReturnTripoModel() { - String taskId = "TASK-001"; + public void shouldReturnNullWhenTaskIsNotFound() { + givenRepositoryReturnsNull(INVALID_TASK_ID); + + TripoModel result = whenExecuteFindModel(INVALID_TASK_ID); + + thenReturnedModelIsNull(result); + thenRepositoryWasCalledOnce(INVALID_TASK_ID); + } + + + //privadoss + private TripoModel givenRepositoryReturnsModel(String taskId) { TripoModel model = new TripoModel(); model.setTaskId(taskId); when(tripoRepository.buscarPorTaskId(taskId)).thenReturn(model); + return model; + } + + private void givenRepositoryReturnsNull(String taskId) { + when(tripoRepository.buscarPorTaskId(taskId)).thenReturn(null); + } - TripoModel result = findTripoModelByTaskid.execute(taskId); - assertNotNull(result); - assertEquals(taskId, result.getTaskId()); - verify(tripoRepository, times(1)).buscarPorTaskId(taskId); + private TripoModel whenExecuteFindModel(String taskId) { + return findTripoModelByTaskid.execute(taskId); } - @Test - public void givenInavlidTaskIdWhenExecuteThenReturnNull() { - String taskId = "TASK-002"; + private void thenReturnedModelIsCorrect(TripoModel result, TripoModel expectedModel, String expectedTaskId) { + assertNotNull(result, "El resultado no debe ser nulo."); + assertEquals(expectedModel, result, "El modelo devuelto debe coincidir con el modelo simulado."); + assertEquals(expectedTaskId, result.getTaskId(), "El ID de tarea debe coincidir."); + thenRepositoryWasCalledOnce(expectedTaskId); + } - when(tripoRepository.buscarPorTaskId(taskId)).thenReturn(null); - TripoModel result = findTripoModelByTaskid.execute(taskId); + private void thenReturnedModelIsNull(TripoModel result) { + assertNull(result, "El resultado debe ser nulo."); + } - assertNull(result); + private void thenRepositoryWasCalledOnce(String taskId) { verify(tripoRepository, times(1)).buscarPorTaskId(taskId); } -} - +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/tripo/GenerateImageToModelTrippoTest.java b/src/test/java/com/outfitlab/project/domain/useCases/tripo/GenerateImageToModelTrippoTest.java index 1923752..277495a 100644 --- a/src/test/java/com/outfitlab/project/domain/useCases/tripo/GenerateImageToModelTrippoTest.java +++ b/src/test/java/com/outfitlab/project/domain/useCases/tripo/GenerateImageToModelTrippoTest.java @@ -4,6 +4,7 @@ import com.outfitlab.project.domain.exceptions.ErrorReadJsonException; import com.outfitlab.project.domain.interfaces.repositories.TripoRepository; import com.outfitlab.project.infrastructure.repositories.TripoRepositoryImpl; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.HashMap; @@ -15,45 +16,75 @@ public class GenerateImageToModelTrippoTest { private TripoRepository tripoRepository = mock(TripoRepositoryImpl.class); - private GenerateImageToModelTrippo generateImageToModelTrippo = new GenerateImageToModelTrippo(tripoRepository); + private GenerateImageToModelTrippo generateImageToModelTrippo; + + private final String IMAGE_URL = "https://testeo.com/image.png"; + private final String SUCCESS_TASK_ID = "GLB_TASK_ID_14125"; + private final String KEY_IMAGE_URL = "imageUrl"; + private Map validUploadData; + + @BeforeEach + void setUp() { + generateImageToModelTrippo = new GenerateImageToModelTrippo(tripoRepository); + validUploadData = new HashMap<>(); + validUploadData.put(KEY_IMAGE_URL, IMAGE_URL); + } + @Test - public void givenValidUploadDataWhenExecuteThenReturnSuccessResponse() throws ErrorGenerateGlbException, ErrorReadJsonException { - Map uploadData = new HashMap<>(); - uploadData.put("imageUrl", "https://testeo.com/image.png"); - String idResult = "GLB_TASK_ID_14125"; + public void shouldReturnTaskIdWhenGlbGenerationIsSuccessful() throws Exception { + givenRepositoryReturnsTaskId(validUploadData, SUCCESS_TASK_ID); - when(tripoRepository.requestGenerateGlbToTripo(uploadData)).thenReturn(idResult); + String result = whenExecuteGenerateGlb(validUploadData); + + thenReturnedResultIsCorrect(result, SUCCESS_TASK_ID); + thenRepositoryWasCalledOnce(validUploadData); + } - String result = generateImageToModelTrippo.execute(uploadData); + @Test + public void shouldPropagateErrorGenerateGlbExceptionWhenTripoFails() { + givenRepositoryThrowsException(validUploadData, new ErrorGenerateGlbException("Error al generar GLB")); - assertNotNull(result); - assertEquals(idResult, result); - verify(tripoRepository, times(1)).requestGenerateGlbToTripo(uploadData); + thenExecutionThrowsException(validUploadData, ErrorGenerateGlbException.class); } @Test - public void givenValidDataWhenGenerateGlbThenThrowErrorGenerateGlbException() throws ErrorGenerateGlbException, ErrorReadJsonException { - Map uploadData = new HashMap<>(); - uploadData.put("imageUrl", "https://testeo.com/image.png"); + public void shouldPropagateErrorReadJsonExceptionWhenApiResponseIsInvalid() { + givenRepositoryThrowsException(validUploadData, new ErrorReadJsonException("Error al leer JSON")); + + thenExecutionThrowsException(validUploadData, ErrorReadJsonException.class); + } - when(tripoRepository.requestGenerateGlbToTripo(uploadData)) - .thenThrow(new ErrorGenerateGlbException("Error al generar GLB")); - assertThrows(ErrorGenerateGlbException.class, () -> generateImageToModelTrippo.execute(uploadData)); - verify(tripoRepository, times(1)).requestGenerateGlbToTripo(uploadData); + //privadoss + private void givenRepositoryReturnsTaskId(Map data, String taskId) throws ErrorGenerateGlbException, ErrorReadJsonException { + when(tripoRepository.requestGenerateGlbToTripo(data)).thenReturn(taskId); + } + + private void givenRepositoryThrowsException(Map data, Exception exception) { + try { + doThrow(exception).when(tripoRepository).requestGenerateGlbToTripo(data); + } catch (ErrorGenerateGlbException | ErrorReadJsonException e) { + } } - @Test - public void givenValidDataWhenGenerateGlbThenThrowErrorReadJsonException() throws ErrorGenerateGlbException, ErrorReadJsonException { - Map uploadData = new HashMap<>(); - uploadData.put("imageUrl", "https://testeo.com/image.png"); - when(tripoRepository.requestGenerateGlbToTripo(uploadData)) - .thenThrow(new ErrorReadJsonException("Error al leer JSON")); + private String whenExecuteGenerateGlb(Map data) throws ErrorGenerateGlbException, ErrorReadJsonException { + return generateImageToModelTrippo.execute(data); + } - assertThrows(ErrorReadJsonException.class, () -> generateImageToModelTrippo.execute(uploadData)); - verify(tripoRepository, times(1)).requestGenerateGlbToTripo(uploadData); + private void thenReturnedResultIsCorrect(String result, String expectedTaskId) { + assertNotNull(result, "El resultado no debe ser nulo."); + assertEquals(expectedTaskId, result, "El ID de tarea devuelto debe coincidir con el esperado."); } -} + private void thenExecutionThrowsException(Map data, Class expectedException) { + assertThrows(expectedException, () -> generateImageToModelTrippo.execute(data)); + + thenRepositoryWasCalledOnce(data); + } + + private void thenRepositoryWasCalledOnce(Map data) { + verify(tripoRepository, times(1)).requestGenerateGlbToTripo(data); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/tripo/SaveImageTest.java b/src/test/java/com/outfitlab/project/domain/useCases/tripo/SaveImageTest.java index a80ad75..25f442e 100644 --- a/src/test/java/com/outfitlab/project/domain/useCases/tripo/SaveImageTest.java +++ b/src/test/java/com/outfitlab/project/domain/useCases/tripo/SaveImageTest.java @@ -3,28 +3,71 @@ import com.outfitlab.project.domain.interfaces.repositories.UploadImageRepository; import com.outfitlab.project.domain.useCases.bucketImages.SaveImage; import com.outfitlab.project.infrastructure.repositories.UploadImageRepositoryImpl; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.web.multipart.MultipartFile; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -class SaveImageTest { +public class SaveImageTest { private UploadImageRepository uploadImageRepository = mock(UploadImageRepositoryImpl.class); - private SaveImage saveImage = new SaveImage(uploadImageRepository); + private SaveImage saveImage; + + private final String FOLDER_NAME = "models_images"; + private final String EXPECTED_URL = "https://aws-bucket.s3.amazonaws.com/models_images/img123.png"; + private MultipartFile mockFile; + + @BeforeEach + void setUp() { + saveImage = new SaveImage(uploadImageRepository); + mockFile = mock(MultipartFile.class); + } + + + @Test + public void shouldReturnUrlWhenImageFileIsSuccessfullyUploaded() { + givenRepositoryReturnsUrl(mockFile, FOLDER_NAME, EXPECTED_URL); + + String result = whenExecuteSaveImage(mockFile, FOLDER_NAME); + + thenReturnedUrlIsCorrect(result, EXPECTED_URL); + thenRepositoryWasCalledOnce(mockFile, FOLDER_NAME); + } @Test - public void givenValidImageFileWhenExecuteThenReturnUrl() { - MultipartFile mockFile = mock(MultipartFile.class); - String expectedUrl = "https://aws-bucket.s3.amazonaws.com/models_images/img123.png"; + public void shouldPropagateRuntimeExceptionWhenUploadFails() { + givenRepositoryThrowsRuntimeException(mockFile, FOLDER_NAME); + + assertThrows(RuntimeException.class, + () -> whenExecuteSaveImage(mockFile, FOLDER_NAME), + "Se espera que la excepción de Runtime se propague si el repositorio falla."); - when(uploadImageRepository.uploadFile(mockFile, "models_images")).thenReturn(expectedUrl); - String result = saveImage.execute(mockFile, "models_images"); + thenRepositoryWasCalledOnce(mockFile, FOLDER_NAME); + } + + + //privadoss + private void givenRepositoryReturnsUrl(MultipartFile file, String folder, String url) { + when(uploadImageRepository.uploadFile(file, folder)).thenReturn(url); + } + + private void givenRepositoryThrowsRuntimeException(MultipartFile file, String folder) { + doThrow(new RuntimeException("Simulated upload failure")).when(uploadImageRepository).uploadFile(file, folder); + } - assertNotNull(result); - assertEquals(expectedUrl, result); - verify(uploadImageRepository, times(1)).uploadFile(mockFile, "models_images"); + private String whenExecuteSaveImage(MultipartFile file, String folder) { + return saveImage.execute(file, folder); } -} + + private void thenReturnedUrlIsCorrect(String result, String expectedUrl) { + assertNotNull(result, "El resultado no debe ser nulo."); + assertEquals(expectedUrl, result, "La URL devuelta debe coincidir con la esperada."); + } + + private void thenRepositoryWasCalledOnce(MultipartFile file, String folder) { + verify(uploadImageRepository, times(1)).uploadFile(file, folder); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/tripo/UpdateTripoModelTest.java b/src/test/java/com/outfitlab/project/domain/useCases/tripo/UpdateTripoModelTest.java index da0558b..3509760 100644 --- a/src/test/java/com/outfitlab/project/domain/useCases/tripo/UpdateTripoModelTest.java +++ b/src/test/java/com/outfitlab/project/domain/useCases/tripo/UpdateTripoModelTest.java @@ -4,38 +4,75 @@ import com.outfitlab.project.domain.interfaces.repositories.TripoRepository; import com.outfitlab.project.domain.model.TripoModel; import com.outfitlab.project.infrastructure.repositories.TripoRepositoryImpl; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -class UpdateTripoModelTest { +public class UpdateTripoModelTest { private TripoRepository tripoRepository = mock(TripoRepositoryImpl.class); - private UpdateTripoModel updateTripoModel = new UpdateTripoModel(tripoRepository); + private UpdateTripoModel updateTripoModel; + + private TripoModel mockModel; + private TripoModel mockUpdatedModel; + + @BeforeEach + void setUp() { + updateTripoModel = new UpdateTripoModel(tripoRepository); + mockModel = new TripoModel(); + mockUpdatedModel = new TripoModel(); + } + + + @Test + public void shouldReturnUpdatedModelWhenModelIsValid() throws ErrorTripoEntityNotFoundException { + givenRepositoryReturnsUpdatedModel(mockModel, mockUpdatedModel); + + TripoModel result = whenExecuteUpdate(mockModel); + + thenReturnedModelIsCorrect(result, mockUpdatedModel, mockModel); + } @Test - public void givenValidModelWhenExecuteThenReturnUpdatedModel() throws ErrorTripoEntityNotFoundException { - TripoModel model = new TripoModel(); - TripoModel updatedModel = new TripoModel(); + public void shouldThrowErrorTripoEntityNotFoundExceptionWhenModelIsInvalid() { + givenRepositoryThrowsNotFoundException(mockModel); + thenExecutionThrowsNotFoundException(mockModel); + } + + + private void givenRepositoryReturnsUpdatedModel(TripoModel model, TripoModel updatedModel) throws ErrorTripoEntityNotFoundException { when(tripoRepository.update(model)).thenReturn(updatedModel); + } + + private void givenRepositoryThrowsNotFoundException(TripoModel model) { + try { + when(tripoRepository.update(model)).thenThrow(new ErrorTripoEntityNotFoundException("No se encontró la entidad")); + } catch (ErrorTripoEntityNotFoundException e) { + } + } - TripoModel result = updateTripoModel.execute(model); - assertNotNull(result); - assertEquals(updatedModel, result); - verify(tripoRepository, times(1)).update(model); + private TripoModel whenExecuteUpdate(TripoModel model) throws ErrorTripoEntityNotFoundException { + return updateTripoModel.execute(model); } - @Test - public void givenInvalidTripoEntityWhenUpdateTripoModelThenThrowErrorTripoEntityNotFoundException() throws ErrorTripoEntityNotFoundException { - TripoModel model = new TripoModel(); - when(tripoRepository.update(model)).thenThrow(new ErrorTripoEntityNotFoundException("No se encontró la entidad")); + private void thenReturnedModelIsCorrect(TripoModel result, TripoModel expectedUpdatedModel, TripoModel modelUsed) { + assertNotNull(result, "El resultado no debe ser nulo."); + assertEquals(expectedUpdatedModel, result, "El modelo devuelto debe ser el actualizado."); + thenRepositoryUpdateWasCalled(modelUsed, 1); + } + + private void thenExecutionThrowsNotFoundException(TripoModel modelUsed) { + assertThrows(ErrorTripoEntityNotFoundException.class, () -> updateTripoModel.execute(modelUsed)); - assertThrows(ErrorTripoEntityNotFoundException.class, () -> updateTripoModel.execute(model)); - verify(tripoRepository, times(1)).update(model); + thenRepositoryUpdateWasCalled(modelUsed, 1); } -} + private void thenRepositoryUpdateWasCalled(TripoModel model, int times) { + verify(tripoRepository, times(times)).update(model); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/tripo/UploadImageToTripoTest.java b/src/test/java/com/outfitlab/project/domain/useCases/tripo/UploadImageToTripoTest.java index 10634c4..e62c525 100644 --- a/src/test/java/com/outfitlab/project/domain/useCases/tripo/UploadImageToTripoTest.java +++ b/src/test/java/com/outfitlab/project/domain/useCases/tripo/UploadImageToTripoTest.java @@ -1,8 +1,12 @@ package com.outfitlab.project.domain.useCases.tripo; -import com.outfitlab.project.domain.exceptions.*; +import com.outfitlab.project.domain.exceptions.ErroBytesException; +import com.outfitlab.project.domain.exceptions.ErrorReadJsonException; +import com.outfitlab.project.domain.exceptions.ErrorUploadImageToTripoException; +import com.outfitlab.project.domain.exceptions.FileEmptyException; import com.outfitlab.project.domain.interfaces.repositories.TripoRepository; import com.outfitlab.project.infrastructure.repositories.TripoRepositoryImpl; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.Map; @@ -13,46 +17,92 @@ public class UploadImageToTripoTest { private TripoRepository tripoRepository = mock(TripoRepositoryImpl.class); - private UploadImageToTripo uploadImageToTripo = new UploadImageToTripo(tripoRepository); + private UploadImageToTripo uploadImageToTripo; + + private final String VALID_URL = "https://image.com/fotoOk.jpg"; + private final String STATUS_OK = "ok"; + private final Map SUCCESS_RESPONSE = Map.of("status", STATUS_OK); + private final String URL_ERROR_BYTES = "https://image.com/cualquieraErrorBytes.jpg"; + private final String URL_ERROR_JSON = "https://image.com/cualquieraErrorJson.jpg"; + private final String URL_ERROR_UPLOAD = "https://image.com/errorAlsubirla.jpg"; + private final String URL_ERROR_EMPTY = "https://image.com/errorArchivoVacio.jpg"; + + + @BeforeEach + void setUp() { + uploadImageToTripo = new UploadImageToTripo(tripoRepository); + } + @Test - public void givenValidUrlWhenExecuteThenReturnResultMap() throws FileEmptyException, ErrorReadJsonException, ErroBytesException, ErrorUploadImageToTripoException { - String url = "https://image.com/fotoOk.jpg"; - Map expectedResponse = Map.of("status", "ok"); + public void shouldReturnSuccessStatusMapWhenUploadIsSuccessful() throws Exception { + givenRepositoryReturnsResponse(VALID_URL, SUCCESS_RESPONSE); - when(tripoRepository.requestUploadImagenToTripo(url)).thenReturn(expectedResponse); - Map result = uploadImageToTripo.execute(url); + Map result = whenExecuteUploadImage(VALID_URL); - assertNotNull(result); - assertEquals("ok", result.get("status")); - verify(tripoRepository, times(1)).requestUploadImagenToTripo(url); + thenReturnedMapIsSuccessful(result, STATUS_OK); + thenRepositoryWasCalledOnce(VALID_URL); } @Test - public void givenWrongImageWhenUploadImageToTripoThenThrowErroBytesException() throws FileEmptyException, ErrorReadJsonException, ErroBytesException, ErrorUploadImageToTripoException { - String url = "https://image.com/cualquieraErrorBytes.jpg"; - when(tripoRepository.requestUploadImagenToTripo(url)).thenThrow(new ErroBytesException("Error de bytes")); - assertThrows(ErroBytesException.class, () -> uploadImageToTripo.execute(url)); + public void shouldPropagateErroBytesExceptionWhenFileIsCorrupted() { + givenRepositoryThrowsException(URL_ERROR_BYTES, new ErroBytesException("Error de bytes")); + + thenExecutionThrowsException(URL_ERROR_BYTES, ErroBytesException.class); } @Test - public void givenValidResponseWhenUploadImageToTrippoAndReadTheJsomThenThrowErrorReadJsonException() throws FileEmptyException, ErrorReadJsonException, ErroBytesException, ErrorUploadImageToTripoException { - String url = "https://image.com/cualquieraErrorJson.jpg"; - when(tripoRepository.requestUploadImagenToTripo(url)).thenThrow(new ErrorReadJsonException("Error al leer JSON")); - assertThrows(ErrorReadJsonException.class, () -> uploadImageToTripo.execute(url)); + public void shouldPropagateErrorReadJsonExceptionWhenResponseIsMalformed() { + givenRepositoryThrowsException(URL_ERROR_JSON, new ErrorReadJsonException("Error al leer JSON")); + + thenExecutionThrowsException(URL_ERROR_JSON, ErrorReadJsonException.class); } @Test - public void givenValidImageWhenUploadImageToTripoThenThrowsErrorUploadImageToTripoException() throws FileEmptyException, ErrorReadJsonException, ErroBytesException, ErrorUploadImageToTripoException { - String url = "https://image.com/errorAlsubirla.jpg"; - when(tripoRepository.requestUploadImagenToTripo(url)).thenThrow(new ErrorUploadImageToTripoException("Error al subir imagen a Tripo")); - assertThrows(ErrorUploadImageToTripoException.class, () -> uploadImageToTripo.execute(url)); + public void shouldPropagateErrorUploadImageToTripoExceptionWhenTripoUploadFails() { + givenRepositoryThrowsException(URL_ERROR_UPLOAD, new ErrorUploadImageToTripoException("Error al subir imagen a Tripo")); + + thenExecutionThrowsException(URL_ERROR_UPLOAD, ErrorUploadImageToTripoException.class); } @Test - public void givenEmptyImageeWhenUploadImageToTripoThenThrowFileEmptyException() throws FileEmptyException, ErrorReadJsonException, ErroBytesException, ErrorUploadImageToTripoException { - String url = "https://image.com/errorArchivoVacio.jpg"; - when(tripoRepository.requestUploadImagenToTripo(url)).thenThrow(new FileEmptyException("Archivo vacío")); - assertThrows(FileEmptyException.class, () -> uploadImageToTripo.execute(url)); + public void shouldPropagateFileEmptyExceptionWhenFileIsReportedEmpty() { + givenRepositoryThrowsException(URL_ERROR_EMPTY, new FileEmptyException("Archivo vacío")); + + thenExecutionThrowsException(URL_ERROR_EMPTY, FileEmptyException.class); + } + + + //privadosss + private void givenRepositoryReturnsResponse(String url, Map response) throws FileEmptyException, ErrorReadJsonException, ErroBytesException, ErrorUploadImageToTripoException { + when(tripoRepository.requestUploadImagenToTripo(url)).thenReturn(response); + } + + private void givenRepositoryThrowsException(String url, Exception exception) { + try { + doThrow(exception).when(tripoRepository).requestUploadImagenToTripo(url); + } catch (FileEmptyException | ErrorReadJsonException | ErroBytesException | ErrorUploadImageToTripoException e) { + } + } + + private Map whenExecuteUploadImage(String url) throws FileEmptyException, ErrorReadJsonException, ErroBytesException, ErrorUploadImageToTripoException { + return uploadImageToTripo.execute(url); + } + + + private void thenReturnedMapIsSuccessful(Map result, String expectedStatus) { + assertNotNull(result, "El resultado no debe ser nulo."); + assertTrue(result.containsKey("status"), "El mapa debe contener la clave 'status'."); + assertEquals(expectedStatus, result.get("status"), "El estado debe ser 'ok'."); + } + + private void thenExecutionThrowsException(String url, Class expectedException) { + assertThrows(expectedException, () -> uploadImageToTripo.execute(url)); + + thenRepositoryWasCalledOnce(url); + } + + private void thenRepositoryWasCalledOnce(String url) { + verify(tripoRepository, times(1)).requestUploadImagenToTripo(url); } -} +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/user/ActivateUserTest.java b/src/test/java/com/outfitlab/project/domain/useCases/user/ActivateUserTest.java new file mode 100644 index 0000000..6e1d438 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/user/ActivateUserTest.java @@ -0,0 +1,79 @@ +package com.outfitlab.project.domain.useCases.user; + +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +public class ActivateUserTest { + + private UserRepository userRepository = mock(UserRepository.class); + private ActivateUser activateUser; + + private final String VALID_EMAIL = "test@user.com"; + private final String EMPTY_EMAIL = ""; + private final String NULL_EMAIL = null; + private final String SUCCESS_MESSAGE = "Usuario activado con éxito."; + + @BeforeEach + void setUp() { + activateUser = new ActivateUser(userRepository); + } + + + @Test + public void shouldCallRepositoryToActivateUserAndReturnSuccessMessage() { + String result = whenExecuteActivateUser(VALID_EMAIL); + + assertEquals(SUCCESS_MESSAGE, result, "Debe retornar el mensaje de éxito."); + thenRepositoryActivateWasCalled(VALID_EMAIL, 1); + } + + @Test + public void shouldCallRepositoryWithEmptyStringWhenEmailIsEmpty() { + String result = whenExecuteActivateUser(EMPTY_EMAIL); + + assertEquals(SUCCESS_MESSAGE, result, "Debe retornar el mensaje de éxito."); + thenRepositoryActivateWasCalled(EMPTY_EMAIL, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenEmailIsNull() { + givenRepositoryThrowsRuntimeException(NULL_EMAIL); + + assertThrows(RuntimeException.class, + () -> whenExecuteActivateUser(NULL_EMAIL), + "Se espera que el RuntimeException se propague al delegar el email nulo."); + + thenRepositoryActivateWasCalled(NULL_EMAIL, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(VALID_EMAIL); + + assertThrows(RuntimeException.class, + () -> whenExecuteActivateUser(VALID_EMAIL), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryActivateWasCalled(VALID_EMAIL, 1); + } + + + //privadoss + private void givenRepositoryThrowsRuntimeException(String email) { + doThrow(new RuntimeException("Simulated DB error")) + .when(userRepository).activateUser(email); + } + + private String whenExecuteActivateUser(String email) { + return activateUser.execute(email); + } + + private void thenRepositoryActivateWasCalled(String expectedEmail, int times) { + verify(userRepository, times(times)).activateUser(expectedEmail); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/user/ConvertToAdminTest.java b/src/test/java/com/outfitlab/project/domain/useCases/user/ConvertToAdminTest.java new file mode 100644 index 0000000..89d22ec --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/user/ConvertToAdminTest.java @@ -0,0 +1,79 @@ +package com.outfitlab.project.domain.useCases.user; + +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +public class ConvertToAdminTest { + + private UserRepository userRepository = mock(UserRepository.class); + private ConvertToAdmin convertToAdmin; + + private final String VALID_EMAIL = "user.to.admin@test.com"; + private final String EMPTY_EMAIL = ""; + private final String NULL_EMAIL = null; + private final String SUCCESS_MESSAGE = "El usuario se ha convertido a Administrador con éxito."; + + @BeforeEach + void setUp() { + convertToAdmin = new ConvertToAdmin(userRepository); + } + + + @Test + public void shouldCallRepositoryToConvertUserToAdminAndReturnSuccessMessage() { + String result = whenExecuteConvertToAdmin(VALID_EMAIL); + + assertEquals(SUCCESS_MESSAGE, result, "Debe retornar el mensaje de éxito."); + thenRepositoryConvertWasCalled(VALID_EMAIL, 1); + } + + @Test + public void shouldCallRepositoryWithEmptyStringWhenEmailIsEmpty() { + String result = whenExecuteConvertToAdmin(EMPTY_EMAIL); + + assertEquals(SUCCESS_MESSAGE, result, "Debe retornar el mensaje de éxito."); + thenRepositoryConvertWasCalled(EMPTY_EMAIL, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenEmailIsNull() { + givenRepositoryThrowsRuntimeException(NULL_EMAIL); + + assertThrows(RuntimeException.class, + () -> whenExecuteConvertToAdmin(NULL_EMAIL), + "Se espera que el RuntimeException se propague al delegar el email nulo."); + + thenRepositoryConvertWasCalled(NULL_EMAIL, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(VALID_EMAIL); + + assertThrows(RuntimeException.class, + () -> whenExecuteConvertToAdmin(VALID_EMAIL), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryConvertWasCalled(VALID_EMAIL, 1); + } + + + //privados + private void givenRepositoryThrowsRuntimeException(String email) { + doThrow(new RuntimeException("Simulated DB error")) + .when(userRepository).convertToAdmin(email); + } + + private String whenExecuteConvertToAdmin(String email) { + return convertToAdmin.execute(email); + } + + private void thenRepositoryConvertWasCalled(String expectedEmail, int times) { + verify(userRepository, times(times)).convertToAdmin(expectedEmail); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/user/ConvertToUserTest.java b/src/test/java/com/outfitlab/project/domain/useCases/user/ConvertToUserTest.java new file mode 100644 index 0000000..83f83c2 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/user/ConvertToUserTest.java @@ -0,0 +1,79 @@ +package com.outfitlab.project.domain.useCases.user; + +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +public class ConvertToUserTest { + + private UserRepository userRepository = mock(UserRepository.class); + private ConvertToUser convertToUser; + + private final String VALID_EMAIL = "admin.to.user@test.com"; + private final String EMPTY_EMAIL = ""; + private final String NULL_EMAIL = null; + private final String SUCCESS_MESSAGE = "El administrador se ha convertido a Usuario con éxito."; + + @BeforeEach + void setUp() { + convertToUser = new ConvertToUser(userRepository); + } + + + @Test + public void shouldCallRepositoryToConvertAdminToUserAndReturnSuccessMessage() { + String result = whenExecuteConvertToUser(VALID_EMAIL); + + assertEquals(SUCCESS_MESSAGE, result, "Debe retornar el mensaje de éxito."); + thenRepositoryConvertWasCalled(VALID_EMAIL, 1); + } + + @Test + public void shouldCallRepositoryWithEmptyStringWhenEmailIsEmpty() { + String result = whenExecuteConvertToUser(EMPTY_EMAIL); + + assertEquals(SUCCESS_MESSAGE, result, "Debe retornar el mensaje de éxito."); + thenRepositoryConvertWasCalled(EMPTY_EMAIL, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenEmailIsNull() { + givenRepositoryThrowsRuntimeException(NULL_EMAIL); + + assertThrows(RuntimeException.class, + () -> whenExecuteConvertToUser(NULL_EMAIL), + "Se espera que el RuntimeException se propague al delegar el email nulo."); + + thenRepositoryConvertWasCalled(NULL_EMAIL, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(VALID_EMAIL); + + assertThrows(RuntimeException.class, + () -> whenExecuteConvertToUser(VALID_EMAIL), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryConvertWasCalled(VALID_EMAIL, 1); + } + + + //privadoss + private void givenRepositoryThrowsRuntimeException(String email) { + doThrow(new RuntimeException("Simulated DB error")) + .when(userRepository).convertToUser(email); + } + + private String whenExecuteConvertToUser(String email) { + return convertToUser.execute(email); + } + + private void thenRepositoryConvertWasCalled(String expectedEmail, int times) { + verify(userRepository, times(times)).convertToUser(expectedEmail); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/user/DesactivateUserTest.java b/src/test/java/com/outfitlab/project/domain/useCases/user/DesactivateUserTest.java new file mode 100644 index 0000000..5f845ce --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/user/DesactivateUserTest.java @@ -0,0 +1,79 @@ +package com.outfitlab.project.domain.useCases.user; + +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +public class DesactivateUserTest { + + private UserRepository userRepository = mock(UserRepository.class); + private DesactivateUser desactivateUser; + + private final String VALID_EMAIL = "user.to.desactivate@test.com"; + private final String EMPTY_EMAIL = ""; + private final String NULL_EMAIL = null; + private final String SUCCESS_MESSAGE = "Usuario desactivado con éxito."; + + @BeforeEach + void setUp() { + desactivateUser = new DesactivateUser(userRepository); + } + + + @Test + public void shouldCallRepositoryToDesactivateUserAndReturnSuccessMessage() { + String result = whenExecuteDesactivateUser(VALID_EMAIL); + + assertEquals(SUCCESS_MESSAGE, result, "Debe retornar el mensaje de éxito."); + thenRepositoryDesactivateWasCalled(VALID_EMAIL, 1); + } + + @Test + public void shouldCallRepositoryWithEmptyStringWhenEmailIsEmpty() { + String result = whenExecuteDesactivateUser(EMPTY_EMAIL); + + assertEquals(SUCCESS_MESSAGE, result, "Debe retornar el mensaje de éxito."); + thenRepositoryDesactivateWasCalled(EMPTY_EMAIL, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenEmailIsNull() { + givenRepositoryThrowsRuntimeException(NULL_EMAIL); + + assertThrows(RuntimeException.class, + () -> whenExecuteDesactivateUser(NULL_EMAIL), + "Se espera que el RuntimeException se propague al delegar el email nulo."); + + thenRepositoryDesactivateWasCalled(NULL_EMAIL, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(VALID_EMAIL); + + assertThrows(RuntimeException.class, + () -> whenExecuteDesactivateUser(VALID_EMAIL), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryDesactivateWasCalled(VALID_EMAIL, 1); + } + + + //privadoss + private void givenRepositoryThrowsRuntimeException(String email) { + doThrow(new RuntimeException("Simulated DB error")) + .when(userRepository).desactivateUser(email); + } + + private String whenExecuteDesactivateUser(String email) { + return desactivateUser.execute(email); + } + + private void thenRepositoryDesactivateWasCalled(String expectedEmail, int times) { + verify(userRepository, times(times)).desactivateUser(expectedEmail); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/user/GetAllUsersTest.java b/src/test/java/com/outfitlab/project/domain/useCases/user/GetAllUsersTest.java new file mode 100644 index 0000000..767ae4e --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/user/GetAllUsersTest.java @@ -0,0 +1,97 @@ +package com.outfitlab.project.domain.useCases.user; + +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.model.UserModel; +import com.outfitlab.project.presentation.dto.UserDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.Collections; +import java.util.List; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetAllUsersTest { + + private UserRepository userRepository = mock(UserRepository.class); + private GetAllUsers getAllUsers; + + private final int USER_COUNT = 3; + + @BeforeEach + void setUp() { + getAllUsers = new GetAllUsers(userRepository); + } + + + @Test + public void shouldReturnListOfUsersWhenUsersAreFound() { + List mockModels = givenRepositoryReturnsUserModels(USER_COUNT); + List expectedDTOs = createExpectedUserDTOs(USER_COUNT); + + givenRepositoryReturnsUserModels(mockModels); + + List result = whenExecuteGetAllUsers(); + + thenResultMatchesExpectedList(result, expectedDTOs, USER_COUNT); + thenRepositoryFindAllWasCalled(1); + } + + @Test + public void shouldReturnEmptyListWhenNoUsersAreFound() { + givenRepositoryReturnsUserModels(Collections.emptyList()); + + List result = whenExecuteGetAllUsers(); + + thenResultMatchesExpectedList(result, Collections.emptyList(), 0); + thenRepositoryFindAllWasCalled(1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(); + + assertThrows(RuntimeException.class, + () -> whenExecuteGetAllUsers(), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryFindAllWasCalled(1); + } + + + //privadosss + private void givenRepositoryReturnsUserModels(List models) { + when(userRepository.findAllWithRoleUserAndAdmin()).thenReturn(models); + } + + private void givenRepositoryThrowsRuntimeException() { + doThrow(new RuntimeException("Simulated DB error")) + .when(userRepository).findAllWithRoleUserAndAdmin(); + } + + private List givenRepositoryReturnsUserModels(int count) { + return IntStream.range(0, count) + .mapToObj(i -> mock(UserModel.class)) + .toList(); + } + + private List createExpectedUserDTOs(int count) { + return IntStream.range(0, count) + .mapToObj(i -> mock(UserDTO.class)) + .toList(); + } + + private List whenExecuteGetAllUsers() { + return getAllUsers.execute(); + } + + private void thenResultMatchesExpectedList(List actual, List expected, int expectedCount) { + assertNotNull(actual, "La lista resultante no debe ser nula."); + assertEquals(expectedCount, actual.size(), "El tamaño de la lista de DTOs debe coincidir."); + } + + private void thenRepositoryFindAllWasCalled(int times) { + verify(userRepository, times(times)).findAllWithRoleUserAndAdmin(); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/user/GetUserByEmailTest.java b/src/test/java/com/outfitlab/project/domain/useCases/user/GetUserByEmailTest.java new file mode 100644 index 0000000..cbd324a --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/user/GetUserByEmailTest.java @@ -0,0 +1,112 @@ +package com.outfitlab.project.domain.useCases.user; + +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.model.UserModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class GetUserByEmailTest { + + private UserRepository userRepository = mock(UserRepository.class); + private GetUserByEmail getUserByEmail; + + private final String VALID_EMAIL = "user@found.com"; + private final String NOT_FOUND_EMAIL = "user@notfound.com"; + private final String NULL_EMAIL = null; + private final String EMPTY_EMAIL = ""; + private UserModel mockUserModel; + + @BeforeEach + void setUp() { + mockUserModel = mock(UserModel.class); + when(mockUserModel.getEmail()).thenReturn(VALID_EMAIL); + getUserByEmail = new GetUserByEmail(userRepository); + } + + + @Test + public void shouldReturnUserModelWhenUserIsFound() throws UserNotFoundException { + givenRepositoryFindsUser(VALID_EMAIL, mockUserModel); + + UserModel result = whenExecuteGetUser(VALID_EMAIL); + + thenResultMatchesExpectedModel(result, mockUserModel, VALID_EMAIL); + thenRepositoryFindUserByEmailWasCalled(VALID_EMAIL, 1); + } + + @Test + public void shouldPropagateUserNotFoundExceptionWhenUserIsNotFound() throws UserNotFoundException { + givenRepositoryThrowsUserNotFound(NOT_FOUND_EMAIL); + + assertThrows(UserNotFoundException.class, + () -> whenExecuteGetUser(NOT_FOUND_EMAIL), + "Se espera que UserNotFoundException se propague."); + + thenRepositoryFindUserByEmailWasCalled(NOT_FOUND_EMAIL, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenEmailIsNull() throws UserNotFoundException { + givenRepositoryThrowsRuntimeException(NULL_EMAIL); + + assertThrows(RuntimeException.class, + () -> whenExecuteGetUser(NULL_EMAIL), + "Se espera que el RuntimeException se propague al delegar email nulo."); + + thenRepositoryFindUserByEmailWasCalled(NULL_EMAIL, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenEmailIsEmpty() throws UserNotFoundException { + givenRepositoryThrowsRuntimeException(EMPTY_EMAIL); + + assertThrows(RuntimeException.class, + () -> whenExecuteGetUser(EMPTY_EMAIL), + "Se espera que el RuntimeException se propague al delegar email vacío."); + + thenRepositoryFindUserByEmailWasCalled(EMPTY_EMAIL, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() throws UserNotFoundException { + givenRepositoryThrowsRuntimeException(VALID_EMAIL); + + assertThrows(RuntimeException.class, + () -> whenExecuteGetUser(VALID_EMAIL), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryFindUserByEmailWasCalled(VALID_EMAIL, 1); + } + + + //privados + private void givenRepositoryFindsUser(String email, UserModel model) throws UserNotFoundException { + when(userRepository.findUserByEmail(email)).thenReturn(model); + } + + private void givenRepositoryThrowsUserNotFound(String email) throws UserNotFoundException { + doThrow(new UserNotFoundException("Usuario no encontrado")).when(userRepository).findUserByEmail(email); + } + + private void givenRepositoryThrowsRuntimeException(String email) throws UserNotFoundException { + doThrow(new RuntimeException("Simulated DB error")).when(userRepository).findUserByEmail(email); + } + + private UserModel whenExecuteGetUser(String email) throws UserNotFoundException { + return getUserByEmail.execute(email); + } + + private void thenResultMatchesExpectedModel(UserModel actual, UserModel expected, String expectedEmail) { + assertNotNull(actual, "El modelo de usuario devuelto no debe ser nulo."); + assertEquals(expected, actual, "El modelo devuelto debe coincidir con el modelo simulado."); + assertEquals(expectedEmail, actual.getEmail(), "El email del usuario debe coincidir."); + } + + private void thenRepositoryFindUserByEmailWasCalled(String email, int times) throws UserNotFoundException { + verify(userRepository, times(times)).findUserByEmail(email); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/user/LoginUserTest.java b/src/test/java/com/outfitlab/project/domain/useCases/user/LoginUserTest.java new file mode 100644 index 0000000..58f92d8 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/user/LoginUserTest.java @@ -0,0 +1,185 @@ +package com.outfitlab.project.domain.useCases.user; + +import com.outfitlab.project.domain.exceptions.NullFieldsException; +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.model.dto.LoginDTO; +import com.outfitlab.project.infrastructure.config.security.AuthResponse; +import com.outfitlab.project.infrastructure.config.security.jwt.JwtService; +import com.outfitlab.project.infrastructure.repositories.interfaces.TokenRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.UserJpaRepository; +import com.outfitlab.project.infrastructure.model.UserEntity; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.crypto.password.PasswordEncoder; +import java.util.Collections; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class LoginUserTest { + + private UserRepository userRepository = mock(UserRepository.class); + private UserJpaRepository userJpaRepository = mock(UserJpaRepository.class); + private TokenRepository tokenRepository = mock(TokenRepository.class); + private JwtService jwtService = mock(JwtService.class); + private PasswordEncoder passwordEncoder = mock(PasswordEncoder.class); + private AuthenticationManager authManager = mock(AuthenticationManager.class); + private Authentication mockAuthentication = mock(Authentication.class); + private LoginUser loginUser; + + private final String FIXED_EMAIL = "test@user.com"; + private final String FIXED_PASSWORD = "password123"; + private final String ACCESS_TOKEN = "access.token.jwt"; + private final String REFRESH_TOKEN = "refresh.token.jwt"; + private final Long USER_ID = 1L; + private LoginDTO validLoginDTO; + private UserEntity mockUserEntity; + + @BeforeEach + void setUp() { + loginUser = new LoginUser(userRepository, passwordEncoder, authManager, tokenRepository, jwtService, userJpaRepository); + validLoginDTO = new LoginDTO(FIXED_EMAIL, FIXED_PASSWORD); + mockUserEntity = mock(UserEntity.class); + when(mockUserEntity.getEmail()).thenReturn(FIXED_EMAIL); + when(mockUserEntity.isVerified()).thenReturn(true); + when(mockUserEntity.getId()).thenReturn(USER_ID); + } + + + @Test + public void shouldReturnAuthResponseWithTokensWhenLoginIsSuccessful() { + givenAuthenticationSucceeds(mockUserEntity, true); + givenTokensAreGenerated(); + givenRepositoryHandlesTokenRevocation(mockUserEntity, 0); + + ResponseEntity response = whenExecuteLogin(validLoginDTO); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + + assertEquals(ACCESS_TOKEN, response.getBody().getAccess_token()); + + thenNewAccessTokenWasSaved(1); + thenTokenRevocationWasCalled(USER_ID, 1); + } + + @Test + public void shouldThrowNullFieldsExceptionWhenEmailIsBlank() { + LoginDTO blankEmailDTO = new LoginDTO(" ", FIXED_PASSWORD); + + assertThrows(NullFieldsException.class, () -> whenExecuteLogin(blankEmailDTO)); + thenAuthenticationWasNeverCalled(); + } + + @Test + public void shouldThrowNullFieldsExceptionWhenPasswordIsBlank() { + LoginDTO blankPasswordDTO = new LoginDTO(FIXED_EMAIL, " "); + + assertThrows(NullFieldsException.class, () -> whenExecuteLogin(blankPasswordDTO)); + thenAuthenticationWasNeverCalled(); + } + + @Test + public void shouldThrowUserNotFoundExceptionWhenAuthenticationFails() { + givenAuthenticationFails(); + + assertThrows(UserNotFoundException.class, () -> whenExecuteLogin(validLoginDTO)); + thenAuthenticationWasCalled(validLoginDTO, 1); + thenUserEntityLookupsWereNeverCalled(); + } + + @Test + public void shouldThrowUserNotFoundExceptionWhenUserIsNotVerified() { + givenAuthenticationSucceeds(mockUserEntity, false); + + assertThrows(UserNotFoundException.class, () -> whenExecuteLogin(validLoginDTO)); + thenAuthenticationWasCalled(validLoginDTO, 1); + thenTokenRevocationWasNeverCalled(); + } + + @Test + public void shouldCallRevocationProcessBeforeSavingNewToken() { + givenAuthenticationSucceeds(mockUserEntity, true); + givenTokensAreGenerated(); + + when(tokenRepository.allValidTokensByUser(USER_ID)).thenReturn(Collections.emptyList()); + whenExecuteLogin(validLoginDTO); + + thenTokenRevocationWasCalled(USER_ID, 1); + } + + + @Test + public void shouldSaveNewAccessTokenAfterRevocation() { + givenAuthenticationSucceeds(mockUserEntity, true); + givenTokensAreGenerated(); + givenRepositoryHandlesTokenRevocation(mockUserEntity, 0); + + whenExecuteLogin(validLoginDTO); + + thenNewAccessTokenWasSaved(1); + } + + + //privadosss + private void givenAuthenticationSucceeds(UserEntity user, boolean isVerified) { + when(authManager.authenticate(any(UsernamePasswordAuthenticationToken.class))).thenReturn(mockAuthentication); + when(userJpaRepository.findByEmail(FIXED_EMAIL)).thenReturn(user); + when(user.isVerified()).thenReturn(isVerified); + when(userJpaRepository.getByEmail(FIXED_EMAIL)).thenReturn(Optional.of(user)); + } + + private void givenAuthenticationFails() { + when(authManager.authenticate(any(UsernamePasswordAuthenticationToken.class))) + .thenThrow(mock(AuthenticationException.class)); + } + + private void givenTokensAreGenerated() { + when(jwtService.generateToken(any(UserEntity.class))).thenReturn(ACCESS_TOKEN); + when(jwtService.generateRefreshToken(any(UserEntity.class))).thenReturn(REFRESH_TOKEN); + } + + private void givenRepositoryHandlesTokenRevocation(UserEntity user, int tokenCount) { + if (tokenCount == 0) { + when(tokenRepository.allValidTokensByUser(user.getId())).thenReturn(Collections.emptyList()); + } + } + + private ResponseEntity whenExecuteLogin(LoginDTO loginDTO) { + return loginUser.execute(loginDTO); + } + + private void thenAuthenticationWasCalled(LoginDTO dto, int times) { + verify(authManager, times(times)).authenticate( + new UsernamePasswordAuthenticationToken(dto.getEmail(), dto.getPassword())); + } + + private void thenAuthenticationWasNeverCalled() { + verify(authManager, never()).authenticate(any()); + } + + private void thenUserEntityLookupsWereNeverCalled() { + verify(userJpaRepository, never()).findByEmail(anyString()); + verify(userJpaRepository, never()).getByEmail(anyString()); + } + + private void thenTokenRevocationWasCalled(Long userId, int times) { + verify(tokenRepository, times(times)).allValidTokensByUser(userId); + } + + private void thenTokenRevocationWasNeverCalled() { + verify(tokenRepository, never()).allValidTokensByUser(anyLong()); + } + + private void thenNewAccessTokenWasSaved(int times) { + verify(tokenRepository, times(times)).save(any()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/user/RefreshTokenTest.java b/src/test/java/com/outfitlab/project/domain/useCases/user/RefreshTokenTest.java new file mode 100644 index 0000000..ccae6b8 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/user/RefreshTokenTest.java @@ -0,0 +1,201 @@ +package com.outfitlab.project.domain.useCases.user; + +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.infrastructure.config.security.AuthResponse; +import com.outfitlab.project.infrastructure.config.security.jwt.JwtService; +import com.outfitlab.project.infrastructure.repositories.interfaces.TokenRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.UserJpaRepository; +import com.outfitlab.project.infrastructure.model.UserEntity; +import io.jsonwebtoken.ExpiredJwtException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import java.util.Collections; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class RefreshTokenTest { + + private JwtService jwtService = mock(JwtService.class); + private UserJpaRepository userJpaRepository = mock(UserJpaRepository.class); + private TokenRepository tokenRepository = mock(TokenRepository.class); + private RefreshToken refreshTokenUseCase; + + private final String VALID_REFRESH_TOKEN = "valid.refresh.token"; + private final String INVALID_REFRESH_TOKEN = "expired.or.invalid.token"; + private final String NEW_ACCESS_TOKEN = "new.access.token"; + private final String NEW_REFRESH_TOKEN = "new.refresh.token"; + private final String FIXED_EMAIL = "user@test.com"; + private final Long USER_ID = 1L; + private UserEntity mockUserEntity; + + @BeforeEach + void setUp() { + refreshTokenUseCase = new RefreshToken(jwtService, userJpaRepository, tokenRepository); + mockUserEntity = mock(UserEntity.class); + when(mockUserEntity.getEmail()).thenReturn(FIXED_EMAIL); + when(mockUserEntity.getId()).thenReturn(USER_ID); + } + + + @Test + public void shouldReturnNewTokensWhenRefreshTokenIsValid() throws Exception { + when(jwtService.isTokenExpired(VALID_REFRESH_TOKEN)).thenReturn(false); + when(jwtService.extractUsername(VALID_REFRESH_TOKEN)).thenReturn(FIXED_EMAIL); + when(userJpaRepository.getByEmail(FIXED_EMAIL)).thenReturn(Optional.of(mockUserEntity)); + when(jwtService.isTokenValid(VALID_REFRESH_TOKEN, mockUserEntity)).thenReturn(true); + + givenTokensAreGenerated(); + givenRepositoryHandlesTokenRevocation(mockUserEntity, 0); + + ResponseEntity response = whenExecuteRefresh(VALID_REFRESH_TOKEN); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(NEW_ACCESS_TOKEN, response.getBody().getAccess_token()); + + thenTokenRevocationWasCalled(USER_ID, 1); + thenNewAccessTokenWasSaved(mockUserEntity, NEW_ACCESS_TOKEN, 1); + } + + @Test + public void shouldThrowUserNotFoundExceptionWhenRefreshTokenIsNull() { + String nullToken = null; + + assertThrows(UserNotFoundException.class, + () -> whenExecuteRefresh(nullToken), + "Debe fallar si el token es nulo."); + + thenJwtServiceWasNeverCalled(); + thenTokenRevocationWasNeverCalled(); + } + + @Test + public void shouldThrowUserNotFoundExceptionWhenRefreshTokenIsBlank() { + String blankToken = " "; + + assertThrows(UserNotFoundException.class, + () -> whenExecuteRefresh(blankToken), + "Debe fallar si el token está en blanco."); + + thenJwtServiceWasNeverCalled(); + thenTokenRevocationWasNeverCalled(); + } + + @Test + public void shouldThrowUserNotFoundExceptionWhenTokenIsExpiredByCheck() { + when(jwtService.isTokenExpired(INVALID_REFRESH_TOKEN)).thenReturn(true); + + assertThrows(UserNotFoundException.class, + () -> whenExecuteRefresh(INVALID_REFRESH_TOKEN), + "Debe fallar si isTokenExpired() retorna true."); + + thenTokenExtractionWasNeverCalled(); + thenTokenRevocationWasNeverCalled(); + } + + @Test + public void shouldThrowUserNotFoundExceptionWhenUserExtractedFromTokenDoesNotExist() { + when(jwtService.isTokenExpired(INVALID_REFRESH_TOKEN)).thenReturn(false); + when(jwtService.extractUsername(INVALID_REFRESH_TOKEN)).thenReturn(FIXED_EMAIL); + when(userJpaRepository.getByEmail(FIXED_EMAIL)).thenReturn(Optional.empty()); // Fallo de búsqueda + + assertThrows(UserNotFoundException.class, + () -> whenExecuteRefresh(INVALID_REFRESH_TOKEN), + "Debe fallar si userJpaRepository no encuentra el usuario."); + + thenUserExtractionWasCalled(1); + thenTokenRevocationWasNeverCalled(); + } + + @Test + public void shouldThrowUserNotFoundExceptionWhenTokenIsNotValidForUser() { + when(jwtService.isTokenExpired(INVALID_REFRESH_TOKEN)).thenReturn(false); + when(jwtService.extractUsername(INVALID_REFRESH_TOKEN)).thenReturn(FIXED_EMAIL); + when(userJpaRepository.getByEmail(FIXED_EMAIL)).thenReturn(Optional.of(mockUserEntity)); + when(jwtService.isTokenValid(INVALID_REFRESH_TOKEN, mockUserEntity)).thenReturn(false); // Fallo de validez + + assertThrows(UserNotFoundException.class, + () -> whenExecuteRefresh(INVALID_REFRESH_TOKEN), + "Debe fallar si isTokenValid() retorna false."); + + thenTokenRevocationWasNeverCalled(); + } + + @Test + public void shouldThrowUserNotFoundExceptionWhenExpiredJwtExceptionIsThrown() { + doThrow(ExpiredJwtException.class).when(jwtService).isTokenExpired(INVALID_REFRESH_TOKEN); + + assertThrows(UserNotFoundException.class, + () -> whenExecuteRefresh(INVALID_REFRESH_TOKEN)); + } + + @Test + public void shouldSkipRevocationWhenNoExistingTokensAreFound() throws Exception { + when(jwtService.isTokenExpired(VALID_REFRESH_TOKEN)).thenReturn(false); + when(jwtService.extractUsername(VALID_REFRESH_TOKEN)).thenReturn(FIXED_EMAIL); + when(userJpaRepository.getByEmail(FIXED_EMAIL)).thenReturn(Optional.of(mockUserEntity)); + when(jwtService.isTokenValid(VALID_REFRESH_TOKEN, mockUserEntity)).thenReturn(true); + + givenTokensAreGenerated(); + + when(tokenRepository.allValidTokensByUser(USER_ID)).thenReturn(Collections.emptyList()); + whenExecuteRefresh(VALID_REFRESH_TOKEN); + + verify(tokenRepository, never()).saveAll(any()); + thenNewAccessTokenWasSaved(mockUserEntity, NEW_ACCESS_TOKEN, 1); + } + + + //privados + private void givenRepositoryHandlesTokenRevocation(UserEntity user, int tokenCount) { + if (tokenCount == 0) { + when(tokenRepository.allValidTokensByUser(user.getId())).thenReturn(Collections.emptyList()); + } + } + + private void givenUserExists(String email, UserEntity user) { + when(userJpaRepository.getByEmail(email)).thenReturn(Optional.of(user)); + } + + private void givenUserDoesNotExist(String email) { + when(userJpaRepository.getByEmail(email)).thenReturn(Optional.empty()); + } + + private void givenTokensAreGenerated() { + when(jwtService.generateToken(any(UserEntity.class))).thenReturn(NEW_ACCESS_TOKEN); + when(jwtService.generateRefreshToken(any(UserEntity.class))).thenReturn(NEW_REFRESH_TOKEN); + } + + private ResponseEntity whenExecuteRefresh(String refreshToken) { + return refreshTokenUseCase.execute(refreshToken); + } + + private void thenTokenRevocationWasCalled(Long userId, int times) { + verify(tokenRepository, times(times)).allValidTokensByUser(userId); + } + + private void thenTokenExtractionWasNeverCalled() { + verify(jwtService, never()).extractUsername(anyString()); + } + + private void thenUserExtractionWasCalled(int times) { + verify(jwtService, times(times)).extractUsername(anyString()); + verify(userJpaRepository, times(times)).getByEmail(FIXED_EMAIL); + } + + private void thenNewAccessTokenWasSaved(UserEntity user, String token, int times) { + verify(tokenRepository, times(times)).save(any()); + } + + private void thenTokenRevocationWasNeverCalled() { + verify(tokenRepository, never()).allValidTokensByUser(anyLong()); + } + + private void thenJwtServiceWasNeverCalled() { + verify(jwtService, never()).isTokenExpired(anyString()); + verify(jwtService, never()).extractUsername(anyString()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/user/RegisterUserTest.java b/src/test/java/com/outfitlab/project/domain/useCases/user/RegisterUserTest.java new file mode 100644 index 0000000..20bf52f --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/user/RegisterUserTest.java @@ -0,0 +1,91 @@ +package com.outfitlab.project.domain.useCases.user; + +import com.outfitlab.project.domain.exceptions.UserAlreadyExistsException; +import com.outfitlab.project.domain.interfaces.gateways.GmailGateway; +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.model.dto.RegisterDTO; +import com.outfitlab.project.infrastructure.config.security.jwt.JwtService; +import com.outfitlab.project.infrastructure.model.UserEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.TokenRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.UserJpaRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.crypto.password.PasswordEncoder; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class RegisterUserTest { + + private UserRepository userRepository = mock(UserRepository.class); + private UserJpaRepository userJpaRepository = mock(UserJpaRepository.class); + private TokenRepository tokenRepository = mock(TokenRepository.class); + private JwtService jwtService = mock(JwtService.class); + private PasswordEncoder passwordEncoder = mock(PasswordEncoder.class); + private AuthenticationManager authManager = mock(AuthenticationManager.class); + private GmailGateway gmailGateway = mock(GmailGateway.class); + private RegisterUser registerUser; + + private static final String BASE_URL = "http://localhost:8080"; + private static final String FIXED_EMAIL = "new.user@test.com"; + private static final String RAW_PASSWORD = "contraseña123"; + private static final String HASHED_PASSWORD = "contraseña-hasheada"; + private static final String ACCESS_TOKEN = "access.token.jwt"; + private static final String REFRESH_TOKEN = "refresh.token.jwt"; + private static final String NAME = "Ailin"; + private static final String LAST_NAME = "Vara"; + + + private RegisterDTO validRequest; + private UserEntity mockSavedUserEntity; + + @BeforeEach + void setUp() { + registerUser = new RegisterUser( + userRepository, passwordEncoder, authManager, tokenRepository, jwtService, userJpaRepository, + gmailGateway, BASE_URL + ); + validRequest = mock(RegisterDTO.class); + when(validRequest.getName()).thenReturn(NAME); + when(validRequest.getLastName()).thenReturn(LAST_NAME); + when(validRequest.getEmail()).thenReturn(FIXED_EMAIL); + when(validRequest.getPassword()).thenReturn(RAW_PASSWORD); + mockSavedUserEntity = mock(UserEntity.class); + when(mockSavedUserEntity.getEmail()).thenReturn(FIXED_EMAIL); + when(mockSavedUserEntity.getPassword()).thenReturn(HASHED_PASSWORD); + } + + + @Test + public void shouldThrowUserAlreadyExistsExceptionWhenUserAlreadyExists() { + givenUserAlreadyExists(); + + assertThrows(UserAlreadyExistsException.class, + () -> registerUser.execute(validRequest), + "Debe fallar si el email ya está registrado."); + + thenCheckIfUserExistsWasCalled(1); + thenPasswordEncoderWasNeverCalled(); + thenEmailWasNeverSent(); + } + + + //privadoss + private void givenUserAlreadyExists() { + when(userJpaRepository.getByEmail(FIXED_EMAIL)).thenReturn(Optional.of(mockSavedUserEntity)); + } + + private void thenCheckIfUserExistsWasCalled(int times) { + verify(userJpaRepository, times(times)).getByEmail(FIXED_EMAIL); + } + + private void thenPasswordEncoderWasNeverCalled() { + verify(passwordEncoder, never()).encode(anyString()); + } + + private void thenEmailWasNeverSent() { + verify(gmailGateway, never()).sendEmail(anyString(), anyString(), anyString()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/user/UpdateBrandUserTest.java b/src/test/java/com/outfitlab/project/domain/useCases/user/UpdateBrandUserTest.java new file mode 100644 index 0000000..64a0d0c --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/user/UpdateBrandUserTest.java @@ -0,0 +1,76 @@ +package com.outfitlab.project.domain.useCases.user; + +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class UpdateBrandUserTest { + + private UserRepository userRepository = mock(UserRepository.class); + private UpdateBrandUser updateBrandUser; + + private final String VALID_EMAIL = "brand.user@test.com"; + private final String VALID_BRAND_CODE = "NEW-BRAND-123"; + private final String NULL_EMAIL = null; + private final String EMPTY_CODE = ""; + + @BeforeEach + void setUp() { + updateBrandUser = new UpdateBrandUser(userRepository); + } + + + @Test + public void shouldCallRepositoryToUpdateBrandUserWithValidData() { + whenExecuteUpdateBrandUser(VALID_EMAIL, VALID_BRAND_CODE); + + thenRepositoryUpdateWasCalled(VALID_EMAIL, VALID_BRAND_CODE, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenUserEmailIsNull() { + givenRepositoryThrowsRuntimeException(NULL_EMAIL, VALID_BRAND_CODE); + + assertThrows(RuntimeException.class, + () -> whenExecuteUpdateBrandUser(NULL_EMAIL, VALID_BRAND_CODE), + "Se espera que el RuntimeException se propague al delegar un email nulo."); + + thenRepositoryUpdateWasCalled(NULL_EMAIL, VALID_BRAND_CODE, 1); + } + + @Test + public void shouldCallRepositoryWhenBrandCodeIsEmpty() { + whenExecuteUpdateBrandUser(VALID_EMAIL, EMPTY_CODE); + + thenRepositoryUpdateWasCalled(VALID_EMAIL, EMPTY_CODE, 1); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryFails() { + givenRepositoryThrowsRuntimeException(VALID_EMAIL, VALID_BRAND_CODE); + + assertThrows(RuntimeException.class, + () -> whenExecuteUpdateBrandUser(VALID_EMAIL, VALID_BRAND_CODE), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenRepositoryUpdateWasCalled(VALID_EMAIL, VALID_BRAND_CODE, 1); + } + + + //privadosss + private void givenRepositoryThrowsRuntimeException(String email, String brandCode) { + doThrow(new RuntimeException("Simulated DB error")) + .when(userRepository).updateBrandUser(email, brandCode); + } + + private void whenExecuteUpdateBrandUser(String userEmail, String brandCode) { + updateBrandUser.execute(userEmail, brandCode); + } + + private void thenRepositoryUpdateWasCalled(String expectedEmail, String expectedBrandCode, int times) { + verify(userRepository, times(times)).updateBrandUser(expectedEmail, expectedBrandCode); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/user/UpdateUserTest.java b/src/test/java/com/outfitlab/project/domain/useCases/user/UpdateUserTest.java new file mode 100644 index 0000000..67da5bd --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/user/UpdateUserTest.java @@ -0,0 +1,169 @@ +package com.outfitlab.project.domain.useCases.user; + +import com.outfitlab.project.domain.exceptions.PasswordException; +import com.outfitlab.project.domain.exceptions.PasswordIsNotTheSame; +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.model.UserModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.password.PasswordEncoder; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class UpdateUserTest { + + private UserRepository userRepository = mock(UserRepository.class); + private PasswordEncoder passwordEncoder = mock(PasswordEncoder.class); + private UpdateUser updateUser; + + private final String OLD_EMAIL = "old@user.com"; + private final String NEW_EMAIL = "new@user.com"; + private final String NAME = "Ailin"; + private final String LAST_NAME = "Vara"; + private final String RAW_PASS = "contraseña123"; + private final String DIFFERENT_PASS = "contraseña-diferente"; + private final String HASHED_PASS = "contraseña-hasheada_xyz"; + private final String IMAGE_URL = "http://image.com/new.jpg"; + private UserModel mockUpdatedModel; + private UserModel mockExistingUserModel; + + @BeforeEach + void setUp() throws UserNotFoundException { + updateUser = new UpdateUser(userRepository, passwordEncoder); + mockUpdatedModel = mock(UserModel.class); + mockExistingUserModel = mock(UserModel.class); + when(userRepository.findUserByEmail(OLD_EMAIL)).thenReturn(mockExistingUserModel); + doThrow(UserNotFoundException.class).when(userRepository).findUserByEmail(NEW_EMAIL); + } + + + @Test + public void shouldUpdateUserDataWithoutChangingEmailOrPassword() throws Exception { + givenRepositoryReturnsUpdatedModel(OLD_EMAIL, mockUpdatedModel); + + UserModel result = whenExecuteUpdate(OLD_EMAIL, NAME, LAST_NAME, OLD_EMAIL, null, null, IMAGE_URL); + + thenResultMatchesExpectedModel(result, mockUpdatedModel); + thenNewEmailCheckWasSkipped(NEW_EMAIL, 0); + thenRepositoryUpdateWasCalled(OLD_EMAIL, NAME, LAST_NAME, OLD_EMAIL, "", IMAGE_URL, 1); + thenPasswordEncoderWasNeverCalled(); + } + + @Test + public void shouldUpdateAllFieldsIncludingEmailAndHashPassword() throws Exception { + givenRepositoryReturnsUpdatedModel(OLD_EMAIL, mockUpdatedModel); + givenPasswordEncoderReturnsHashedPassword(RAW_PASS, HASHED_PASS); + + UserModel result = whenExecuteUpdate(OLD_EMAIL, NAME, LAST_NAME, NEW_EMAIL, RAW_PASS, RAW_PASS, IMAGE_URL); + + thenResultMatchesExpectedModel(result, mockUpdatedModel); + thenNewEmailCheckWasPerformed(NEW_EMAIL, 1); + thenPasswordEncoderWasCalled(RAW_PASS, 1); + thenRepositoryUpdateWasCalled(OLD_EMAIL, NAME, LAST_NAME, NEW_EMAIL, HASHED_PASS, IMAGE_URL, 1); + } + + @Test + public void shouldThrowUserNotFoundExceptionWhenOldUserDoesNotExist() throws UserNotFoundException { + doThrow(UserNotFoundException.class).when(userRepository).findUserByEmail(OLD_EMAIL); + + assertThrows(UserNotFoundException.class, + () -> whenExecuteUpdate(OLD_EMAIL, NAME, LAST_NAME, OLD_EMAIL, null, null, IMAGE_URL), + "Debe fallar si el usuario original no se encuentra."); + + thenUserExistenceCheckWasCalled(OLD_EMAIL, 1); + thenRepositoryUpdateWasNeverCalled(); + } + + @Test + public void shouldThrowPasswordExceptionWhenOnlyPasswordIsSent() { + assertThrows(PasswordException.class, + () -> whenExecuteUpdate(OLD_EMAIL, NAME, LAST_NAME, OLD_EMAIL, RAW_PASS, null, IMAGE_URL), + "Debe fallar si falta la confirmación."); + + thenRepositoryUpdateWasNeverCalled(); + } + + @Test + public void shouldThrowPasswordExceptionWhenOnlyConfirmPasswordIsSent() { + assertThrows(PasswordException.class, + () -> whenExecuteUpdate(OLD_EMAIL, NAME, LAST_NAME, OLD_EMAIL, null, RAW_PASS, IMAGE_URL), + "Debe fallar si falta la contraseña."); + + thenRepositoryUpdateWasNeverCalled(); + } + + @Test + public void shouldThrowPasswordIsNotTheSameExceptionWhenPasswordsDoNotMatch() { + assertThrows(PasswordIsNotTheSame.class, + () -> whenExecuteUpdate(OLD_EMAIL, NAME, LAST_NAME, OLD_EMAIL, RAW_PASS, DIFFERENT_PASS, IMAGE_URL), + "Debe fallar si las contraseñas son diferentes."); + + thenRepositoryUpdateWasNeverCalled(); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenRepositoryUpdateFails() throws Exception { + givenRepositoryThrowsRuntimeException(OLD_EMAIL); + + assertThrows(RuntimeException.class, + () -> whenExecuteUpdate(OLD_EMAIL, NAME, LAST_NAME, OLD_EMAIL, null, null, IMAGE_URL), + "Se espera que el RuntimeException se propague si el repositorio falla."); + + thenUserExistenceCheckWasCalled(OLD_EMAIL, 1); + thenRepositoryUpdateWasCalled(OLD_EMAIL, NAME, LAST_NAME, OLD_EMAIL, "", IMAGE_URL, 1); + } + + + //privadoss + private void givenRepositoryReturnsUpdatedModel(String oldEmail, UserModel updatedModel) { + when(userRepository.updateUser(eq(oldEmail), anyString(), anyString(), anyString(), anyString(), anyString())).thenReturn(updatedModel); + } + + private void givenRepositoryThrowsRuntimeException(String oldEmail) { + doThrow(new RuntimeException("Simulated DB error")) + .when(userRepository).updateUser(eq(oldEmail), anyString(), anyString(), anyString(), anyString(), anyString()); + } + + private void givenPasswordEncoderReturnsHashedPassword(String rawPass, String hashedPass) { + when(passwordEncoder.encode(rawPass)).thenReturn(hashedPass); + } + + private UserModel whenExecuteUpdate(String oldEmail, String name, String lastname, String newEmail, String password, String confirmPassword, String newImageUrl) { + return updateUser.execute(oldEmail, name, lastname, newEmail, password, confirmPassword, newImageUrl); + } + + private void thenResultMatchesExpectedModel(UserModel actual, UserModel expected) { + assertNotNull(actual, "El modelo de usuario devuelto no debe ser nulo."); + assertEquals(expected, actual, "El modelo devuelto debe coincidir con el modelo simulado."); + } + + private void thenNewEmailCheckWasPerformed(String newEmail, int times) { + verify(userRepository, times(times)).findUserByEmail(newEmail); + } + + private void thenNewEmailCheckWasSkipped(String newEmail, int times) { + verify(userRepository, times(times)).findUserByEmail(newEmail); + } + + private void thenUserExistenceCheckWasCalled(String email, int times) { + verify(userRepository, times(times)).findUserByEmail(email); + } + + private void thenPasswordEncoderWasCalled(String rawPass, int times) { + verify(passwordEncoder, times(times)).encode(rawPass); + } + + private void thenPasswordEncoderWasNeverCalled() { + verify(passwordEncoder, never()).encode(anyString()); + } + + private void thenRepositoryUpdateWasCalled(String oldEmail, String name, String lastname, String newEmail, String hashedPassword, String newImageUrl, int times) { + verify(userRepository, times(times)).updateUser(oldEmail, name, lastname, newEmail, hashedPassword, newImageUrl); + } + + private void thenRepositoryUpdateWasNeverCalled() { + verify(userRepository, never()).updateUser(anyString(), anyString(), anyString(), anyString(), anyString(), anyString()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/user/UserProfileTest.java b/src/test/java/com/outfitlab/project/domain/useCases/user/UserProfileTest.java new file mode 100644 index 0000000..157bc0f --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/user/UserProfileTest.java @@ -0,0 +1,139 @@ +package com.outfitlab.project.domain.useCases.user; + +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.model.UserModel; +import com.outfitlab.project.infrastructure.model.UserEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.UserJpaRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class UserProfileTest { + + private UserJpaRepository userJpaRepository = mock(UserJpaRepository.class); + private UserProfile userProfile; + + private final String USER_EMAIL = "test@profile.com"; + private UserModel mockUserModel; + private UserEntity mockUserEntity; + private Authentication mockAuthentication; + private UserDetails mockUserDetails; + + @BeforeEach + void setUp() { + userProfile = new UserProfile(userJpaRepository); + mockUserEntity = mock(UserEntity.class); + when(mockUserEntity.getEmail()).thenReturn(USER_EMAIL); + mockUserModel = mock(UserModel.class); + when(mockUserModel.getEmail()).thenReturn(USER_EMAIL); + mockUserDetails = mock(UserDetails.class); + when(mockUserDetails.getUsername()).thenReturn(USER_EMAIL); + mockAuthentication = mock(Authentication.class); + } + + + @Test + public void shouldReturnUserModelWhenUserIsAuthenticatedAndFound() throws Exception { + givenRepositoryFindsUser(USER_EMAIL, mockUserEntity); + + try (MockedStatic mockedContext = mockStatic(SecurityContextHolder.class)) { + givenSecurityContextSetup(mockedContext, true, mockUserDetails); + + UserModel result = userProfile.execute(); + + assertNotNull(result, "El modelo de usuario no debe ser nulo."); + assertEquals(USER_EMAIL, result.getEmail(), "El email del perfil devuelto debe coincidir."); + thenRepositoryWasCalledOnce(USER_EMAIL); + } + } + + @Test + public void shouldThrowUserNotFoundExceptionWhenAuthenticationIsNull() throws Exception { + try (MockedStatic mockedContext = mockStatic(SecurityContextHolder.class)) { + givenSecurityContextSetup(mockedContext, false, null); // Authentication es null + + assertThrows(UserNotFoundException.class, + () -> userProfile.execute(), + "Debe fallar si no hay Authentication en el contexto."); + + thenRepositoryWasNeverCalled(); + } + } + + @Test + public void shouldThrowUserNotFoundExceptionWhenUserIsNotAuthenticated() throws Exception { + try (MockedStatic mockedContext = mockStatic(SecurityContextHolder.class)) { + givenSecurityContextSetup(mockedContext, false, mockUserDetails); // No autenticado + + assertThrows(UserNotFoundException.class, + () -> userProfile.execute(), + "Debe fallar si el usuario no está marcado como autenticado."); + + thenRepositoryWasNeverCalled(); + } + } + + @Test + public void shouldThrowUserNotFoundExceptionWhenUserIsNotInRepository() throws Exception { + givenRepositoryDoesNotFindUser(USER_EMAIL); + + try (MockedStatic mockedContext = mockStatic(SecurityContextHolder.class)) { + givenSecurityContextSetup(mockedContext, true, mockUserDetails); + + assertThrows(UserNotFoundException.class, + () -> userProfile.execute(), + "Debe fallar si el repositorio devuelve Optional.empty()."); + + thenRepositoryWasCalledOnce(USER_EMAIL); + } + } + + + //privadossss + private void givenSecurityContextSetup(MockedStatic mockedContext, boolean isAuthenticated, UserDetails userDetails) { + + SecurityContext securityContext = mock(SecurityContext.class); + + if (userDetails != null) { + when(mockAuthentication.isAuthenticated()).thenReturn(isAuthenticated); + when(mockAuthentication.getPrincipal()).thenReturn(userDetails); + when(securityContext.getAuthentication()).thenReturn(mockAuthentication); + } else { + when(securityContext.getAuthentication()).thenReturn(null); + } + + mockedContext.when(SecurityContextHolder::getContext).thenReturn(securityContext); + + try (MockedStatic mockedEntity = mockStatic(UserEntity.class)) { + mockedEntity.when(() -> UserEntity.convertEntityToModel(mockUserEntity)).thenReturn(mockUserModel); + } + } + + private void givenRepositoryFindsUser(String email, UserEntity entity) { + when(userJpaRepository.getByEmail(email)).thenReturn(Optional.of(entity)); + } + + private void givenRepositoryDoesNotFindUser(String email) { + when(userJpaRepository.getByEmail(email)).thenReturn(Optional.empty()); + } + + private UserModel whenExecuteUserProfile() { + return userProfile.execute(); + } + + private void thenRepositoryWasCalledOnce(String email) { + verify(userJpaRepository, times(1)).getByEmail(email); + } + + private void thenRepositoryWasNeverCalled() { + verify(userJpaRepository, never()).getByEmail(anyString()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/domain/useCases/user/VerifyEmailTest.java b/src/test/java/com/outfitlab/project/domain/useCases/user/VerifyEmailTest.java new file mode 100644 index 0000000..bd07af3 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/user/VerifyEmailTest.java @@ -0,0 +1,109 @@ +package com.outfitlab.project.domain.useCases.user; + +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.UserRepository; +import com.outfitlab.project.domain.model.UserModel; +import com.outfitlab.project.infrastructure.model.UserEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.UserJpaRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +public class VerifyEmailTest { + + private UserRepository userRepository = mock(UserRepository.class); + private UserJpaRepository userJpaRepository = mock(UserJpaRepository.class); + private VerifyEmail verifyEmail; + + private final String VALID_TOKEN = "valid-verification-token"; + private final String NOT_FOUND_TOKEN = "invalid-token"; + private final String USER_EMAIL = "user@verified.com"; + private UserModel mockUserModel; + private UserEntity mockUserEntity; + + @BeforeEach + void setUp() throws UserNotFoundException { + verifyEmail = new VerifyEmail(userRepository, userJpaRepository); + mockUserModel = mock(UserModel.class); + when(mockUserModel.getEmail()).thenReturn(USER_EMAIL); + mockUserEntity = mock(UserEntity.class); + when(mockUserEntity.getEmail()).thenReturn(USER_EMAIL); + when(userRepository.findUserByVerificationToken(VALID_TOKEN)).thenReturn(mockUserModel); + when(userJpaRepository.findByEmail(USER_EMAIL)).thenReturn(mockUserEntity); + } + + + @Test + public void shouldSetUserVerifiedAndClearTokenWhenTokenIsValid() throws UserNotFoundException { + whenExecuteVerifyEmail(VALID_TOKEN); + + thenUserWasFoundByToken(VALID_TOKEN, 1); + thenUserEntityWasUpdatedAndSaved(); + } + + @Test + public void shouldThrowUserNotFoundExceptionWhenTokenIsNotFound() throws UserNotFoundException { + doThrow(UserNotFoundException.class).when(userRepository).findUserByVerificationToken(NOT_FOUND_TOKEN); + + assertThrows(UserNotFoundException.class, + () -> whenExecuteVerifyEmail(NOT_FOUND_TOKEN), + "Debe fallar si el token no encuentra un usuario."); + + thenUserWasFoundByToken(NOT_FOUND_TOKEN, 1); + thenUserEntityWasNeverSaved(); + } + + @Test + public void shouldPropagateRuntimeExceptionIfUserEntityCannotBeFoundByEmail() { + when(userJpaRepository.findByEmail(USER_EMAIL)).thenReturn(null); + + assertThrows(RuntimeException.class, + () -> whenExecuteVerifyEmail(VALID_TOKEN), + "Debe propagar un fallo si UserEntity no se encuentra para el email."); + + thenUserWasFoundByToken(VALID_TOKEN, 1); + thenRepositorySaveWasNeverCalled(); + } + + @Test + public void shouldPropagateRuntimeExceptionWhenSavingUserEntityFails() throws UserNotFoundException { + doThrow(new RuntimeException("DB Save Error")).when(userJpaRepository).save(mockUserEntity); + + assertThrows(RuntimeException.class, + () -> whenExecuteVerifyEmail(VALID_TOKEN), + "Debe propagar RuntimeException si la persistencia falla."); + + thenUserEntityWasUpdated(1); + } + + + //privadoss + private void whenExecuteVerifyEmail(String token) throws UserNotFoundException { + verifyEmail.execute(token); + } + + private void thenUserWasFoundByToken(String token, int times) throws UserNotFoundException { + verify(userRepository, times(times)).findUserByVerificationToken(token); + } + + private void thenUserEntityWasUpdatedAndSaved() { + verify(mockUserEntity, times(1)).setVerified(true); + verify(mockUserEntity, times(1)).setVerificationToken(null); + verify(userJpaRepository, times(1)).save(mockUserEntity); + } + + private void thenUserEntityWasUpdated(int times) { + verify(mockUserEntity, times(times)).setVerified(true); + verify(mockUserEntity, times(times)).setVerificationToken(null); + } + + private void thenUserEntityWasNeverSaved() { + verify(userJpaRepository, never()).save(any(UserEntity.class)); + } + + private void thenRepositorySaveWasNeverCalled() { + verify(userJpaRepository, never()).save(any(UserEntity.class)); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/BrandRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/BrandRepositoryImplTest.java index e02e1fb..c26164a 100644 --- a/src/test/java/com/outfitlab/project/infrastructure/repositories/BrandRepositoryImplTest.java +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/BrandRepositoryImplTest.java @@ -1,42 +1,131 @@ package com.outfitlab.project.infrastructure.repositories; -import com.outfitlab.project.domain.interfaces.repositories.BrandRepository; +import com.outfitlab.project.domain.exceptions.BrandsNotFoundException; +import com.outfitlab.project.domain.model.BrandModel; import com.outfitlab.project.infrastructure.model.MarcaEntity; import com.outfitlab.project.infrastructure.repositories.interfaces.BrandJpaRepository; + import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; +import org.mockito.InjectMocks; +import org.mockito.Mock; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; import org.springframework.test.context.ActiveProfiles; -import static org.junit.jupiter.api.Assertions.*; +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + + +import static org.mockito.Mockito.*; + + @SpringBootTest @ActiveProfiles("test") // IMPORTANTISIMO GENTE ESTE ACTIVATE PROFILE PARA NO AFECTAR LA BDD DE RENDER, PRESTAR ATENCIÓN class BrandRepositoryImplTest { - @Autowired + @Mock private BrandJpaRepository brandJpaRepository; - @Autowired - private BrandRepository brandRepository; + @InjectMocks + private BrandRepositoryImpl repository; @Test - @Transactional - @Rollback - void testFindByBrandCode() { - MarcaEntity entity = new MarcaEntity(); - entity.setCodigoMarca("raviolito"); - entity.setNombre("Raviolito"); - entity.setLogoUrl("https://raviolito-logo.png"); - brandJpaRepository.save(entity); + void shouldReturnBrandModelWhenBrandCodeExists() { + givenExistingBrand("NIKE", "Nike"); - var result = brandRepository.findByBrandCode("raviolito"); + BrandModel result = whenFindByBrandCode("NIKE"); - assertNotNull(result); - assertEquals("raviolito", result.getCodigoMarca()); - assertEquals("Raviolito", result.getNombre()); + thenBrandShouldBe(result, "NIKE", "Nike"); + verify(brandJpaRepository).findByCodigoMarca("NIKE"); + } + + @Test + void shouldThrowExceptionWhenBrandCodeDoesNotExist() { + givenNonExistingBrand("XYZ"); + + assertThatThrownBy(() -> whenFindByBrandCode("XYZ")) + .isInstanceOf(BrandsNotFoundException.class) + .hasMessageContaining("No encontramos la marca."); + + verify(brandJpaRepository).findByCodigoMarca("XYZ"); + } + + @Test + void shouldReturnBrandsPageWhenGettingAllBrands() { + givenPageBrands("ADIDAS", "Adidas"); + + Page result = whenGetAllBrands(0); + + thenFirstBrandShouldBe(result, "ADIDAS"); + verify(brandJpaRepository).findAll(PageRequest.of(0, 10)); + } + + @Test + void shouldCreateBrandAndReturnCode() { + BrandModel model = givenBrandModel("PUMA", "Puma"); + + givenSavedBrand("PUMA", "Puma"); + + String result = whenCreateBrand(model); + + thenBrandCodeShouldBe(result, "PUMA"); + verify(brandJpaRepository).save(any(MarcaEntity.class)); + } + + private void givenExistingBrand(String code, String name) { + MarcaEntity entity = new MarcaEntity(code, name, "logo.png", "site.com"); + when(brandJpaRepository.findByCodigoMarca(code)).thenReturn(entity); + } + + private void givenNonExistingBrand(String code) { + when(brandJpaRepository.findByCodigoMarca(code)).thenReturn(null); + } + + private void givenPageBrands(String code, String name) { + MarcaEntity entity = new MarcaEntity(code, name, "logo2.png", "site.com"); + Page page = new PageImpl<>(List.of(entity)); + when(brandJpaRepository.findAll(PageRequest.of(0, 10))).thenReturn(page); + } + + private BrandModel givenBrandModel(String code, String name) { + return new BrandModel(code, name, "logo.png", "site.com"); + } + + private void givenSavedBrand(String code, String name) { + MarcaEntity saved = new MarcaEntity(code, name, "logo.png", "site.com"); + when(brandJpaRepository.save(any(MarcaEntity.class))).thenReturn(saved); + } + + private BrandModel whenFindByBrandCode(String code) { + return repository.findByBrandCode(code); + } + + private Page whenGetAllBrands(int page) { + return repository.getAllBrands(page); + } + + private String whenCreateBrand(BrandModel model) { + return repository.createBrand(model); + } + + private void thenBrandShouldBe(BrandModel result, String code, String name) { + assertThat(result).isNotNull(); + assertThat(result.getCodigoMarca()).isEqualTo(code); + assertThat(result.getNombre()).isEqualTo(name); + } + + private void thenFirstBrandShouldBe(Page page, String expectedCode) { + assertThat(page).isNotEmpty(); + assertThat(page.getContent().get(0).getCodigoMarca()).isEqualTo(expectedCode); + } + + private void thenBrandCodeShouldBe(String result, String expectedCode) { + assertThat(result).isEqualTo(expectedCode); } } + diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/ClimaRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/ClimaRepositoryImplTest.java new file mode 100644 index 0000000..224b7d8 --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/ClimaRepositoryImplTest.java @@ -0,0 +1,68 @@ +package com.outfitlab.project.infrastructure.repositories; + +import static org.assertj.core.api.Assertions.*; + +import com.outfitlab.project.domain.exceptions.ClimaNotFoundException; +import com.outfitlab.project.domain.model.ClimaModel; +import com.outfitlab.project.infrastructure.model.ClimaEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.ClimaJpaRepository; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +import static org.mockito.Mockito.*; +@SpringBootTest +@ActiveProfiles("test") +class ClimaRepositoryImplTest { + @Mock + private ClimaJpaRepository climaJpaRepository; + + @InjectMocks + private ClimaRepositoryImpl repository; + + @Test + void shouldReturnClimasWhenTheyExist() { + givenExistingClimas("FRIO"); + + var result = whenFindAllClimas(); + + thenFirstClimaShouldBe(result, "FRIO"); + verify(climaJpaRepository).findAll(); + } + + @Test + void shouldThrowExceptionWhenClimasListIsEmpty() { + givenEmptyClimas(); + + assertThatThrownBy(this::whenFindAllClimas) + .isInstanceOf(ClimaNotFoundException.class) + .hasMessageContaining("No encontramos climas."); + + verify(climaJpaRepository).findAll(); + } + + private void givenExistingClimas(String nombre) { + ClimaEntity clima = new ClimaEntity(); + clima.setId(1L); + clima.setNombre(nombre); + + when(climaJpaRepository.findAll()).thenReturn(List.of(clima)); + } + + private void givenEmptyClimas() { + when(climaJpaRepository.findAll()).thenReturn(List.of()); + } + + private List whenFindAllClimas() { + return repository.findAllClimas(); + } + + private void thenFirstClimaShouldBe(List result, String nombreEsperado) { + assertThat(result).isNotEmpty(); + assertThat(result.get(0).getNombre()).isEqualTo(nombreEsperado); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/ColorRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/ColorRepositoryImplTest.java new file mode 100644 index 0000000..eb76930 --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/ColorRepositoryImplTest.java @@ -0,0 +1,66 @@ +package com.outfitlab.project.infrastructure.repositories; + +import com.outfitlab.project.domain.exceptions.ColorNotFoundException; +import com.outfitlab.project.domain.model.ColorModel; +import com.outfitlab.project.infrastructure.model.ColorEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.ColorJpaRepository; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; +@SpringBootTest +@ActiveProfiles("test") +class ColorRepositoryImplTest { + + @Mock + private ColorJpaRepository colorJpaRepository; + + @InjectMocks + private ColorRepositoryImpl repository; + + @Test + void shouldReturnColorsWhenTheyExist() { + givenExistingColors("ROJO"); + + var result = whenFindAllColores(); + + thenFirstColorShouldBe(result, "ROJO"); + verify(colorJpaRepository).findAll(); + } + + @Test + void shouldThrowExceptionWhenColorsListIsEmpty() { + givenEmptyColors(); + + assertThatThrownBy(this::whenFindAllColores) + .isInstanceOf(ColorNotFoundException.class) + .hasMessageContaining("No encontramos ocasiones."); + + verify(colorJpaRepository).findAll(); + } + private void givenExistingColors(String nombreColor) { + ColorEntity entity = new ColorEntity(); + entity.setId(1L); + entity.setNombre(nombreColor); + + when(colorJpaRepository.findAll()).thenReturn(List.of(entity)); + } + + private void givenEmptyColors() { + when(colorJpaRepository.findAll()).thenReturn(List.of()); + } + + private List whenFindAllColores() { + return repository.findAllColores(); + } + + private void thenFirstColorShouldBe(List result, String nombreEsperado) { + assertThat(result).isNotEmpty(); + assertThat(result.get(0).getNombre()).isEqualTo(nombreEsperado); + } +} diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/CombinationAttemptRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/CombinationAttemptRepositoryImplTest.java new file mode 100644 index 0000000..31dbf4d --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/CombinationAttemptRepositoryImplTest.java @@ -0,0 +1,275 @@ +package com.outfitlab.project.infrastructure.repositories; + +import com.outfitlab.project.domain.model.*; +import com.outfitlab.project.infrastructure.model.*; +import com.outfitlab.project.infrastructure.repositories.interfaces.CombinationAttemptJpaRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.CombinationJpaRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.UserJpaRepository; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; +@SpringBootTest +@ActiveProfiles("test") +class CombinationAttemptRepositoryImplTest { + @Mock + private CombinationAttemptJpaRepository combinationAttemptJpaRepository; + @Mock + private UserJpaRepository userJpaRepository; + @Mock + private CombinationJpaRepository combinationJpaRepository; + @InjectMocks + private CombinationAttemptRepositoryImpl repository; + + + @Test + void shouldSaveWhenUserExistsAndCombinationExistsById() { + CombinationAttemptModel model = givenModelWithUserAndCombinationId(1L, 10L); + + givenExistingUser(1L); + givenExistingCombination(10L); + givenSavedAttempt(99L); + + Long result = whenSave(model); + + thenAttemptIdShouldBe(result, 99L); + } + + @Test + void shouldSaveWhenCombinationFoundByPrendas() { + CombinationAttemptModel model = givenModelWithPrendas(1L, 2L); + + givenExistingUser(1L); + givenCombinationFoundByPrendas(1L, 2L, 50L); + givenSavedAttempt(77L); + + Long result = whenSave(model); + + thenAttemptIdShouldBe(result, 77L); + } + + @Test + void shouldSaveWhenCombinationNotFoundAndCreateNewOne() { + CombinationAttemptModel model = givenModelWithPrendas(1L, 2L); + + givenExistingUser(1L); + givenCombinationNotFoundByPrendas(); + givenCreatedCombination(200L); + givenSavedAttempt(88L); + + Long result = whenSave(model); + + thenAttemptIdShouldBe(result, 88L); + } + + @Test + void shouldThrowWhenUserDoesNotExist() { + CombinationAttemptModel model = givenModelWithUserAndCombinationId(999L, 10L); + + givenUserNotFound(999L); + + assertThatThrownBy(() -> whenSave(model)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("User not found"); + } + + @Test + void shouldThrowWhenCombinationIdDoesNotExist() { + CombinationAttemptModel model = givenModelWithUserAndCombinationId(1L, 500L); + + givenExistingUser(1L); + givenCombinationIdNotFound(500L); + + assertThatThrownBy(() -> whenSave(model)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Combination not found"); + } + + @Test + void shouldReturnAttemptsWhenFindAllByPrenda() { + givenAttemptsByPrenda(1L, 2L, 10L); + + var result = whenFindAllByPrenda(10L); + + thenResultShouldNotBeEmpty(result); + } + + @Test + void shouldReturnAttemptsFromLastNDays() { + givenAttemptsFromLastDays(); + + var result = whenFindLastNDays(5); + + thenResultShouldNotBeEmpty(result); + } + + @Test + void shouldReturnAllAttempts() { + givenAllAttempts(); + + var result = whenFindAll(); + + thenResultShouldNotBeEmpty(result); + } + + @Test + void shouldCallDeleteAllAttemptsByGarmentCode() { + whenDeleteAllByGarment("PANT001"); + + verify(combinationAttemptJpaRepository).deleteAllAttemptsByGarmentCode("PANT001"); + } + + private CombinationAttemptModel givenModelWithUserAndCombinationId(Long userId, Long combId) { + UserModel user = new UserModel(userId, "test@mail.com"); + + CombinationModel combination = new CombinationModel(null, null); + combination.setId(combId); + + return new CombinationAttemptModel(user, combination, LocalDateTime.now(), "url"); + } + + private BrandModel givenMarca(String codigo) { + BrandModel marca = new BrandModel(); + marca.setCodigoMarca(codigo); + marca.setNombre("MarcaTest"); + return marca; + } + + private CombinationAttemptModel givenModelWithPrendas(Long supId, Long infId) { + UserModel user = new UserModel(1L, "user@mail.com"); + BrandModel marcaSup = givenMarca("MARCA-SUP"); + BrandModel marcaInf = givenMarca("MARCA-INF"); + + PrendaModel sup = new PrendaModel(supId, "Sup", "img", marcaSup); + PrendaModel inf = new PrendaModel(infId, "Inf", "img", marcaInf); + + CombinationModel comb = new CombinationModel(sup, inf); + + return new CombinationAttemptModel(user, comb, LocalDateTime.now(), "url"); + } + private void givenExistingUser(Long id) { + UserEntity user = new UserEntity(); + user.setId(id); + when(userJpaRepository.findById(id)).thenReturn(Optional.of(user)); + } + + private void givenUserNotFound(Long id) { + when(userJpaRepository.findById(id)).thenReturn(Optional.empty()); + } + + private void givenExistingCombination(Long id) { + CombinationEntity comb = new CombinationEntity(); + comb.setId(id); + when(combinationJpaRepository.findById(id)).thenReturn(Optional.of(comb)); + } + + private void givenCombinationIdNotFound(Long id) { + when(combinationJpaRepository.findById(id)).thenReturn(Optional.empty()); + } + + private void givenCombinationFoundByPrendas(Long supId, Long infId, Long combId) { + CombinationEntity comb = new CombinationEntity(); + comb.setId(combId); + + when(combinationJpaRepository.findByPrendas(supId, infId)) + .thenReturn(Optional.of(comb)); + } + + private void givenCombinationNotFoundByPrendas() { + when(combinationJpaRepository.findByPrendas(any(), any())).thenReturn(Optional.empty()); + } + + private void givenCreatedCombination(Long id) { + CombinationEntity comb = new CombinationEntity(); + comb.setId(id); + + when(combinationJpaRepository.save(any())).thenReturn(comb); + } + + private void givenSavedAttempt(Long id) { + CombinationAttemptEntity attempt = new CombinationAttemptEntity(); + attempt.setId(id); + when(combinationAttemptJpaRepository.save(any())).thenReturn(attempt); + } + + private void givenAttemptsByPrenda(Long supId, Long infId, Long attemptId) { + CombinationAttemptEntity attempt = minimalAttempt(attemptId); + when(combinationAttemptJpaRepository + .findByCombination_PrendaSuperior_IdOrCombination_PrendaInferior_Id(any(), any())) + .thenReturn(List.of(attempt)); + } + + private void givenAttemptsFromLastDays() { + when(combinationAttemptJpaRepository.findByCreatedAtAfter(any())) + .thenReturn(List.of(minimalAttempt(1L))); + } + + private void givenAllAttempts() { + when(combinationAttemptJpaRepository.findAll()) + .thenReturn(List.of(minimalAttempt(1L))); + } + + private Long whenSave(CombinationAttemptModel model) { + return repository.save(model); + } + + private List whenFindAllByPrenda(Long id) { + return repository.findAllByPrenda(id); + } + + private List whenFindLastNDays(int days) { + return repository.findLastNDays(days); + } + + private List whenFindAll() { + return repository.findAll(); + } + + private void whenDeleteAllByGarment(String garmentCode) { + repository.deleteAllByAttempsReltedToCombinationRelatedToGarments(garmentCode); + } + + private void thenAttemptIdShouldBe(Long actual, Long expected) { + assertThat(actual).isEqualTo(expected); + } + + private void thenResultShouldNotBeEmpty(List list) { + assertThat(list).isNotEmpty(); + } + + private CombinationAttemptEntity minimalAttempt(Long id) { + CombinationEntity combination = new CombinationEntity(); + combination.setId(10L); + + PrendaEntity sup = new PrendaEntity(); + sup.setId(1L); + sup.setNombre("Sup"); + sup.setColor(new ColorEntity("Rojo", 1)); + sup.setMarca(new MarcaEntity("ADIDAS", "Adidas", "img", "site")); + + PrendaEntity inf = new PrendaEntity(); + inf.setId(2L); + inf.setNombre("Inf"); + inf.setColor(new ColorEntity("Azul", 2)); + inf.setMarca(new MarcaEntity("NIKE", "Nike", "img", "site")); + + combination.setPrendaSuperior(sup); + combination.setPrendaInferior(inf); + + CombinationAttemptEntity attempt = new CombinationAttemptEntity(); + attempt.setId(id); + attempt.setCombination(combination); + attempt.setCreatedAt(LocalDateTime.now()); + + return attempt; + } + +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/CombinationRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/CombinationRepositoryImplTest.java new file mode 100644 index 0000000..b422d00 --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/CombinationRepositoryImplTest.java @@ -0,0 +1,164 @@ +package com.outfitlab.project.infrastructure.repositories; + +import com.outfitlab.project.domain.model.BrandModel; +import com.outfitlab.project.domain.model.CombinationModel; +import com.outfitlab.project.domain.model.PrendaModel; +import com.outfitlab.project.infrastructure.model.CombinationEntity; +import com.outfitlab.project.infrastructure.model.MarcaEntity; +import com.outfitlab.project.infrastructure.model.PrendaEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.CombinationJpaRepository; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; +@SpringBootTest +@ActiveProfiles("test") +class CombinationRepositoryImplTest { + @Mock + private CombinationJpaRepository jpaRepository; + + @InjectMocks + private CombinationRepositoryImpl repository; + + @Test + void shouldFindById() { + givenExistingCombination(10L); + + Optional result = whenFindById(10L); + + thenResultShouldBePresent(result); + } + + @Test + void shouldReturnEmptyWhenIdNotFound() { + givenCombinationNotFoundById(50L); + + Optional result = whenFindById(50L); + + thenResultShouldBeEmpty(result); + } + + @Test + void shouldFindByPrendas() { + givenExistingCombinationByPrendas(1L, 2L, 100L); + + Optional result = whenFindByPrendas(1L, 2L); + + thenResultShouldBePresent(result); + } + + @Test + void shouldReturnEmptyWhenPrendasNotFound() { + givenCombinationNotFoundByPrendas(); + + Optional result = whenFindByPrendas(1L, 2L); + + thenResultShouldBeEmpty(result); + } + + @Test + void shouldSave() { + CombinationModel model = givenCombinationModel(5L, 6L); + givenSavedCombination(200L); + + CombinationModel saved = whenSave(model); + + thenCombinationIdShouldBe(saved, 200L); + } + + @Test + void shouldDeleteAllByGarmentCode() { + whenDeleteAllByGarment("X123"); + + thenDeleteShouldBeCalled("X123"); + } + + private void givenExistingCombination(Long id) { + CombinationEntity entity = minimalCombinationEntity(id); + when(jpaRepository.findById(id)).thenReturn(Optional.of(entity)); + } + + private void givenCombinationNotFoundById(Long id) { + when(jpaRepository.findById(id)).thenReturn(Optional.empty()); + } + + private void givenExistingCombinationByPrendas(Long supId, Long infId, Long combId) { + CombinationEntity entity = minimalCombinationEntity(combId); + when(jpaRepository.findByPrendas(supId, infId)).thenReturn(Optional.of(entity)); + } + + private void givenCombinationNotFoundByPrendas() { + when(jpaRepository.findByPrendas(any(), any())).thenReturn(Optional.empty()); + } + + private void givenSavedCombination(Long id) { + CombinationEntity saved = minimalCombinationEntity(id); + when(jpaRepository.save(any())).thenReturn(saved); + } + + private Optional whenFindById(Long id) { + return repository.findById(id); + } + + private Optional whenFindByPrendas(Long supId, Long infId) { + return repository.findByPrendas(supId, infId); + } + + private CombinationModel whenSave(CombinationModel model) { + return repository.save(model); + } + + private void whenDeleteAllByGarment(String code) { + repository.deleteAllByGarmentcode(code); + } + + private void thenResultShouldBePresent(Optional result) { + assertThat(result).isPresent(); + } + + private void thenResultShouldBeEmpty(Optional result) { + assertThat(result).isEmpty(); + } + + private void thenCombinationIdShouldBe(CombinationModel model, Long expected) { + assertThat(model.getId()).isEqualTo(expected); + } + + private void thenDeleteShouldBeCalled(String code) { + verify(jpaRepository).deleteAllByGarmentCode(code); + } + + private CombinationEntity minimalCombinationEntity(Long id) { + PrendaEntity sup = new PrendaEntity(); + sup.setId(1L); + sup.setMarca(new MarcaEntity("A", "MarcaA")); + + PrendaEntity inf = new PrendaEntity(); + inf.setId(2L); + inf.setMarca(new MarcaEntity("B", "MarcaB")); + + CombinationEntity entity = new CombinationEntity(sup, inf); + entity.setId(id); + return entity; + } + + private BrandModel givenMarca(String code) { + BrandModel m = new BrandModel(); + m.setCodigoMarca(code); + m.setNombre("MarcaTest"); + return m; + } + + private CombinationModel givenCombinationModel(Long supId, Long infId) { + PrendaModel sup = new PrendaModel(supId, "Sup", "img", givenMarca("MS")); + PrendaModel inf = new PrendaModel(infId, "Inf", "img", givenMarca("MI")); + + return new CombinationModel(sup, inf); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/FashnRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/FashnRepositoryImplTest.java new file mode 100644 index 0000000..2ccfd66 --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/FashnRepositoryImplTest.java @@ -0,0 +1,169 @@ +package com.outfitlab.project.infrastructure.repositories; + +import static org.junit.jupiter.api.Assertions.*; + +import com.outfitlab.project.domain.exceptions.FashnApiException; +import com.outfitlab.project.domain.exceptions.PredictionFailedException; +import com.outfitlab.project.domain.exceptions.PredictionTimeoutException; +import com.outfitlab.project.presentation.dto.FashnResponse; +import com.outfitlab.project.presentation.dto.StatusResponse; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.*; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; +@SpringBootTest +@ActiveProfiles("test") +class FashnRepositoryImplTest { + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private FashnRepositoryImpl repository; + + @Test + void shouldReturnIdWhenCombineOk() throws Exception { + HttpEntity> expectedEntity = givenHttpEntityForCombine(); + givenFashnRunResponse("ABC123"); + + String result = whenCombine("garment.jpg", "tops", "MAN"); + + thenShouldReturn(result, "ABC123"); + } + + @Test + void shouldThrowExceptionWhenRunReturnsNonOK() throws Exception { + HttpEntity> expectedEntity = givenHttpEntityForCombine(); + givenRunReturnsStatus(HttpStatus.BAD_REQUEST); + + assertThrows(FashnApiException.class, + () -> whenCombine("img.jpg", "tops", "MAN")); + } + + @Test + void shouldReturnImageUrlWhenPollingCompletes() throws Exception { + givenStatusSequenceCompleted("https://final-image.com/result.jpg"); + + String result = whenPollStatus("XYZ999"); + + thenShouldReturn(result, "https://final-image.com/result.jpg"); + } + + @Test + void shouldThrowTimeoutWhenPollingExceedsAttempts() { + givenStatusSequenceNeverCompletes(); + + assertThrows(PredictionTimeoutException.class, + () -> whenPollStatus("XYZ999")); + } + + @Test + void shouldThrowPredictionFailedWhenStatusFailed() { + givenStatusFailed(); + + assertThrows(PredictionFailedException.class, + () -> whenPollStatus("XYZ999")); + } + + private HttpEntity> givenHttpEntityForCombine() throws Exception { + UserDetails mockUser = mock(UserDetails.class); + return new HttpEntity<>(Map.of(), new HttpHeaders()); + } + + private void givenFashnRunResponse(String id) { + FashnResponse resp = new FashnResponse(); + resp.setId(id); + + ResponseEntity entity = + new ResponseEntity<>(resp, HttpStatus.OK); + + when(restTemplate.exchange( + contains("/run"), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(FashnResponse.class) + )).thenReturn(entity); + } + + private void givenRunReturnsStatus(HttpStatus status) { + ResponseEntity entity = + new ResponseEntity<>(null, status); + + when(restTemplate.exchange( + contains("/run"), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(FashnResponse.class) + )).thenReturn(entity); + } + + private void givenStatusSequenceCompleted(String finalUrl) { + StatusResponse first = new StatusResponse("XYZ999", "processing", null, null); + StatusResponse second = new StatusResponse("XYZ999", "completed", List.of(finalUrl), null); + + when(restTemplate.exchange( + contains("/status"), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(StatusResponse.class), + anyString() + )).thenReturn( + new ResponseEntity<>(first, HttpStatus.OK), + new ResponseEntity<>(second, HttpStatus.OK) + ); + } + + private void givenStatusSequenceNeverCompletes() { + StatusResponse ongoing = new StatusResponse("ABC123","processing", null, null); + + ResponseEntity res = + new ResponseEntity<>(ongoing, HttpStatus.OK); + + when(restTemplate.exchange( + contains("/status"), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(StatusResponse.class), + anyString() + )).thenReturn(res); + } + + private void givenStatusFailed() { + StatusResponse failed = new StatusResponse("ABC123", "failed", null, null); + + ResponseEntity entity = + new ResponseEntity<>(failed, HttpStatus.OK); + + when(restTemplate.exchange( + contains("/status"), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(StatusResponse.class), + anyString() + )).thenReturn(entity); + } + + private String whenCombine(String garment, String category, String avatar) throws Exception { + UserDetails mockUser = mock(UserDetails.class); + return repository.combine(garment, category, avatar, mockUser); + } + + private String whenPollStatus(String id) throws Exception { + return repository.pollStatus(id); + } + + private void thenShouldReturn(String actual, String expected) { + assertThat(actual).isEqualTo(expected); + } + +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/GarmentRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/GarmentRepositoryImplTest.java new file mode 100644 index 0000000..2fa3578 --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/GarmentRepositoryImplTest.java @@ -0,0 +1,218 @@ +package com.outfitlab.project.infrastructure.repositories; + +import com.outfitlab.project.domain.exceptions.GarmentNotFoundException; +import com.outfitlab.project.domain.model.PrendaModel; +import com.outfitlab.project.domain.model.dto.PageDTO; +import com.outfitlab.project.infrastructure.model.*; +import com.outfitlab.project.infrastructure.repositories.interfaces.*; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Optional; + +import static org.mockito.Mockito.verify; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +@SpringBootTest +@ActiveProfiles("test") +class GarmentRepositoryImplTest { + @Mock + private GarmentJpaRepository garmentJpaRepository; + @Mock + private BrandJpaRepository brandJpaRepository; + @Mock + private ColorJpaRepository colorJpaRepository; + @Mock + private ClimaJpaRepository climaJpaRepository; + @Mock + private OcasionJpaRepository ocasionJpaRepository; + + @InjectMocks + private GarmentRepositoryImpl repository; + + @Test + void shouldFindByBrandCodeAndTipo() { + givenGarmentsPage("brand1", "superior", 0); + + PageDTO result = whenFindByBrandCodeAndTipo("brand1", "superior", 0); + + thenPageDTOShouldBeCorrect(result, 1); + } + + @Test + void shouldThrowWhenGarmentCodeNotFound() { + givenGarmentNotFound("G123"); + + assertThatThrownBy(() -> whenFindByGarmentCode("G123")) + .isInstanceOf(GarmentNotFoundException.class) + .hasMessageContaining("No encontramos la prenda"); + } + + @Test + void shouldFindGarmentByGarmentCode() { + givenExistingGarment("G123"); + + PrendaModel model = whenFindByGarmentCode("G123"); + + thenGarmentCodeShouldBe(model, "G123"); + } + + @Test + void shouldCreateGarmentSuccessfully() { + givenBrandExists("B1"); + givenColorExists("Red"); + givenClimaExists("Frio"); + givenOcasionExists("Casual"); + + whenCreateGarment("Prenda1", "G123", "superior", "Red", "B1", "img.png", "Frio", + List.of("Casual"), "Hombre"); + + thenGarmentShouldBeSaved(); + } + + @Test + void shouldUpdateGarmentSuccessfully() { + givenExistingGarmentEntity("G123"); + givenColorExists("Blue"); + givenClimaExists("Calor"); + givenOcasionExists("Formal"); + + whenUpdateGarment("Prenda2", "inferior", "Blue", "Formal", "G123", "newImg.png", "G124", "Calor", + List.of("Formal"), "Mujer"); + + thenGarmentShouldBeSaved(); + } + + @Test + void shouldDeleteGarmentSuccessfully() { + String code = "G123"; + + whenDeleteGarment(code); + + thenDeleteByGarmentCodeShouldBeCalled(code); + } + + private void givenGarmentsPage(String brandCode, String tipo, int page) { + // Marca + MarcaEntity brand = new MarcaEntity(); + brand.setCodigoMarca(brandCode); + brand.setNombre("Brand Name"); + + // Color + ColorEntity color = new ColorEntity(); + color.setNombre("Rojo"); + color.setValor(1); + + // Clima + ClimaEntity clima = new ClimaEntity(); + clima.setNombre("Frio"); + + // Prenda + PrendaEntity entity = new PrendaEntity(); + entity.setGarmentCode("G1"); + entity.setNombre("Prenda Test"); + entity.setMarca(brand); + entity.setColor(color); + entity.setClimaAdecuado(clima); + entity.setTipo(tipo); + + Page pageResult = new PageImpl<>(List.of(entity), PageRequest.of(page, 10), 1); + + when(garmentJpaRepository.findByMarca_CodigoMarcaAndTipo( + eq(brandCode), + eq(tipo.toLowerCase()), + any(PageRequest.class) + )).thenReturn(pageResult); + } + + private void givenGarmentNotFound(String garmentCode) { + when(garmentJpaRepository.findByGarmentCode(garmentCode)).thenReturn(null); + } + + private void givenExistingGarment(String garmentCode) { + PrendaEntity entity = new PrendaEntity(); + entity.setGarmentCode(garmentCode); + when(garmentJpaRepository.findByGarmentCode(garmentCode)).thenReturn(entity); + } + + private void givenExistingGarmentEntity(String garmentCode) { + PrendaEntity entity = new PrendaEntity(); + entity.setGarmentCode(garmentCode); + when(garmentJpaRepository.findByGarmentCode(garmentCode)).thenReturn(entity); + } + + private void givenBrandExists(String brandCode) { + MarcaEntity brand = new MarcaEntity(brandCode, "BrandName"); + when(brandJpaRepository.findByCodigoMarca(brandCode)).thenReturn(brand); + } + + private void givenColorExists(String colorNombre) { + ColorEntity color = new ColorEntity(); + color.setNombre(colorNombre); + when(colorJpaRepository.findColorEntityByNombre(colorNombre)).thenReturn(Optional.of(color)); + } + + private void givenClimaExists(String climaNombre) { + ClimaEntity clima = new ClimaEntity(); + clima.setNombre(climaNombre); + when(climaJpaRepository.findClimaEntityByNombre(climaNombre)).thenReturn(Optional.of(clima)); + } + + private void givenOcasionExists(String ocasionNombre) { + OcasionEntity ocasion = new OcasionEntity(); + ocasion.setNombre(ocasionNombre); + when(ocasionJpaRepository.findOcasionEntityByNombre(ocasionNombre)).thenReturn(Optional.of(ocasion)); + } + + private PageDTO whenFindByBrandCodeAndTipo(String brandCode, String tipo, int page) { + return repository.findByBrandCodeAndTipo(brandCode, tipo, page); + } + + private PrendaModel whenFindByGarmentCode(String code) { + return repository.findByGarmentCode(code); + } + + private void whenCreateGarment(String name, String garmentCode, String type, String colorNombre, + String brandCode, String imageUrl, String climaNombre, List ocasiones, String genero) { + repository.createGarment(name, garmentCode, type, colorNombre, brandCode, imageUrl, climaNombre, ocasiones, genero); + } + + private void whenUpdateGarment(String name, String type, String colorNombre, String event, String garmentCode, + String imageUrl, String newGarmentCode, String climaNombre, List ocasiones, String genero) { + repository.updateGarment(name, type, colorNombre, event, garmentCode, imageUrl, newGarmentCode, climaNombre, ocasiones, genero); + } + + private void whenDeleteGarment(String garmentCode) { + repository.deleteGarment(garmentCode); + } + + private void thenPageDTOShouldBeCorrect(PageDTO dto, int expectedSize) { + assertThat(dto.getContent()).hasSize(expectedSize); + assertThat(dto.getPage()).isZero(); + assertThat(dto.getSize()).isEqualTo(10); + assertThat(dto.getTotalElements()).isEqualTo(1); + } + + private void thenGarmentCodeShouldBe(PrendaModel model, String code) { + assertThat(model.getGarmentCode()).isEqualTo(code); + } + + private void thenGarmentShouldBeSaved() { + verify(garmentJpaRepository).save(any(PrendaEntity.class)); + } + + private void thenDeleteByGarmentCodeShouldBeCalled(String code) { + verify(garmentJpaRepository).deleteByGarmentCode(code); + } + + +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/OcasionRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/OcasionRepositoryImplTest.java new file mode 100644 index 0000000..bfa9b86 --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/OcasionRepositoryImplTest.java @@ -0,0 +1,67 @@ +package com.outfitlab.project.infrastructure.repositories; + +import com.outfitlab.project.domain.exceptions.OcasionNotFoundException; +import com.outfitlab.project.domain.model.OcasionModel; +import com.outfitlab.project.infrastructure.model.OcasionEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.OcasionJpaRepository; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.when; + +@SpringBootTest +@ActiveProfiles("test") +class OcasionRepositoryImplTest { + + @Mock + private OcasionJpaRepository ocasionJpaRepository; + + @InjectMocks + private OcasionRepositoryImpl repository; + + @Test + void shouldReturnAllOcasiones() { + givenExistingOcaciones(1); + + List result = whenFindAll(); + + thenListSizeShouldBe(result, 1); + } + + @Test + void shouldThrowExceptionWhenNoOcasionesFound() { + givenNoOcaciones(); + + assertThrows(OcasionNotFoundException.class, this::whenFindAll); + } + + private void givenExistingOcaciones(int qty) { + List entities = new ArrayList<>(); + for (int i = 0; i < qty; i++) { + OcasionEntity e = new OcasionEntity(); + e.setNombre("Nombre " + i); + entities.add(e); + } + when(ocasionJpaRepository.findAll()).thenReturn(entities); + } + + private void givenNoOcaciones() { + when(ocasionJpaRepository.findAll()).thenReturn(List.of()); + } + + private List whenFindAll() { + return repository.findAllOcasiones(); + } + + private void thenListSizeShouldBe(List list, int expected) { + assertEquals(expected, list.size()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/PrendaOcasionRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/PrendaOcasionRepositoryImplTest.java new file mode 100644 index 0000000..c4a8cbc --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/PrendaOcasionRepositoryImplTest.java @@ -0,0 +1,41 @@ +package com.outfitlab.project.infrastructure.repositories; +import com.outfitlab.project.infrastructure.repositories.interfaces.PrendaOcasionJpaRepository; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import static org.mockito.Mockito.verify; + +@SpringBootTest +@ActiveProfiles("test") +class PrendaOcasionRepositoryImplTest { + + @Mock + private PrendaOcasionJpaRepository prendaOcasionJpaRepository; + + @InjectMocks + private PrendaOcasionRepositoryImpl repository; + + @Test + public void shouldDeleteAllPrendaOcacionByGarment() { + String garmentCode = givenGarmentCode(); + + whenDeleteByGarmentCode(garmentCode); + + thenRepositoryShouldCallDeleteMethod(garmentCode); + } + + private String givenGarmentCode() { + return "GARM123"; + } + + private void whenDeleteByGarmentCode(String garmentCode) { + repository.deleteAllPrendaOcasionByGarment(garmentCode); + } + + private void thenRepositoryShouldCallDeleteMethod(String garmentCode) { + verify(prendaOcasionJpaRepository).deleteAllByGarmentCode(garmentCode); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/RecomendationRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/RecomendationRepositoryImplTest.java new file mode 100644 index 0000000..9ca8acd --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/RecomendationRepositoryImplTest.java @@ -0,0 +1,183 @@ +package com.outfitlab.project.infrastructure.repositories; + + +import com.outfitlab.project.domain.exceptions.GarmentNotFoundException; +import com.outfitlab.project.domain.model.GarmentRecomendationModel; +import com.outfitlab.project.infrastructure.model.GarmentRecomendationEntity; +import com.outfitlab.project.infrastructure.model.PrendaEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.GarmentJpaRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.RecomendationJpaRepository; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; +@SpringBootTest +@ActiveProfiles("test") +class RecomendationRepositoryImplTest { + @Mock + private RecomendationJpaRepository recomendationJpaRepository; + + @Mock + private GarmentJpaRepository garmentJpaRepository; + + @InjectMocks + private RecomendationRepositoryImpl repository; + + @Test + void shouldFindRecomendationsByGarmentCode() throws GarmentNotFoundException { + PrendaEntity garment = givenPrendaEntity("G1", "SUPERIOR"); + List entities = givenRecomendationEntities(garment, 2); + givenFindByTopGarment(garment, entities); + + List result = whenFindRecomendations("G1"); + + thenResultShouldHaveSize(result, 2); + } + + @Test + void shouldThrowWhenGarmentNotFound() { + givenGarmentNotFound("INVALID"); + + assertThatThrownBy(() -> whenFindRecomendations("INVALID")) + .isInstanceOf(GarmentNotFoundException.class) + .hasMessageContaining("No encontramos la prenda con el código"); + } + + @Test + void shouldDeleteRecomendationsByGarmentCode() { + whenDeleteRecomendations("G001"); + + thenDeleteShouldBeCalled("G001"); + } + + @Test + void shouldCreateSugerenciasByGarmentCode() throws GarmentNotFoundException { + PrendaEntity principal = givenPrendaEntity("G001", "SUPERIOR"); + List sugerencias = List.of("G002", "G003"); + givenGarmentsExist(sugerencias); + givenSavedRecomendations(); + + whenCreateSugerencias("G001", "inferior", sugerencias); + + thenSaveAllShouldBeCalled(); + } + + @Test + void shouldThrowWhenInvalidTypeInCreateSugerencias() { + PrendaEntity principal = givenPrendaEntity("G001", "SUPERIOR"); + List sugerencias = List.of("G002"); + + givenGarmentsExist(sugerencias); + + assertThatThrownBy(() -> whenCreateSugerencias("G001", "INVALID", sugerencias)) + .isInstanceOf(GarmentNotFoundException.class) + .hasMessageContaining("Tipo de prenda inválido"); + } + + @Test + void shouldDeleteRecomendationByGarmentsCodeWhenTypeIsInferior() { + whenDeleteRecomendation("G001", "G002", "inferior"); + + thenDeleteWhenPrimaryIsBottomCalled("G001", "G002"); + } + + @Test + void shouldDeleteRecomendationByGarmentsCodeWhenTypeIsSuperior() { + whenDeleteRecomendation("G001", "G002", "superior"); + + thenDeleteWhenPrimaryIsTopCalled("G001", "G002"); + } + + private PrendaEntity givenPrendaEntity(String code, String tipo) { + PrendaEntity p = new PrendaEntity(); + p.setGarmentCode(code); + p.setTipo(tipo); + when(garmentJpaRepository.findByGarmentCode(code)).thenReturn(p); + return p; + } + + private void givenGarmentNotFound(String code) { + when(garmentJpaRepository.findByGarmentCode(code)).thenReturn(null); + } + + private List givenRecomendationEntities(PrendaEntity garment, int count) { + List list = new ArrayList<>(); + for (int i = 0; i < count; i++) { + GarmentRecomendationEntity entity = new GarmentRecomendationEntity(); + + // Asumimos que si la prenda principal es SUPERIOR, ponemos la inferior ficticia + PrendaEntity other = new PrendaEntity(); + other.setGarmentCode("DUMMY" + i); + other.setTipo(garment.getTipo().equalsIgnoreCase("SUPERIOR") ? "INFERIOR" : "SUPERIOR"); + other.setMarca(new com.outfitlab.project.infrastructure.model.MarcaEntity("X"+i, "MarcaDummy"+i)); + + if (garment.getTipo().equalsIgnoreCase("SUPERIOR")) { + entity.setTopGarment(garment); + entity.setBottomGarment(other); + } else { + entity.setBottomGarment(garment); + entity.setTopGarment(other); + } + + list.add(entity); + } + return list; + } + + private void givenFindByTopGarment(PrendaEntity garment, List result) { + when(recomendationJpaRepository.findByTopGarment(garment)).thenReturn(result); + } + + private void givenGarmentsExist(List codes) { + for (String code : codes) { + givenPrendaEntity(code, "INFERIOR"); + } + } + + private void givenSavedRecomendations() { + when(recomendationJpaRepository.saveAll(anyList())).thenReturn(new ArrayList<>()); + } + + private List whenFindRecomendations(String code) throws GarmentNotFoundException { + return repository.findRecomendationsByGarmentCode(code); + } + + private void whenDeleteRecomendations(String code) { + repository.deleteRecomendationsByGarmentCode(code); + } + + private void whenCreateSugerencias(String code, String type, List sugerencias) throws GarmentNotFoundException { + repository.createSugerenciasByGarmentCode(code, type, sugerencias); + } + + private void whenDeleteRecomendation(String primary, String secondary, String type) { + repository.deleteRecomendationByGarmentsCode(primary, secondary, type); + } + + private void thenResultShouldHaveSize(List list, int size) { + assertThat(list).hasSize(size); + } + + private void thenDeleteShouldBeCalled(String code) { + verify(recomendationJpaRepository).deleteAllByGarmentCode(code); + } + + private void thenSaveAllShouldBeCalled() { + verify(recomendationJpaRepository).saveAll(anyList()); + } + + private void thenDeleteWhenPrimaryIsBottomCalled(String primary, String secondary) { + verify(recomendationJpaRepository).deleteWhenPrimaryIsBottom(primary, secondary); + } + + private void thenDeleteWhenPrimaryIsTopCalled(String primary, String secondary) { + verify(recomendationJpaRepository).deleteWhenPrimaryIsTop(primary, secondary); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/SubscriptionRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/SubscriptionRepositoryImplTest.java new file mode 100644 index 0000000..4d15deb --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/SubscriptionRepositoryImplTest.java @@ -0,0 +1,134 @@ +package com.outfitlab.project.infrastructure.repositories; + +import com.outfitlab.project.domain.model.SubscriptionModel; +import com.outfitlab.project.infrastructure.model.SubscriptionEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.SubscriptionJpaRepository; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; + +@SpringBootTest +@ActiveProfiles("test") +class SubscriptionRepositoryImplTest { + @Mock + private SubscriptionJpaRepository jpaRepository; + + @InjectMocks + private SubscriptionRepositoryImpl repository; + + @Test + public void shouldGetAllSubscriptions() { + // given + List entities = givenSubscriptionEntities(); + givenJpaFindAllReturns(entities); + + // when + List result = whenGetAllSubscriptions(); + + // then + thenResultShouldMatchEntities(result, entities); + } + + @Test + public void shouldGetSubscriptionByPlanCode() { + // given + String planCode = "PREMIUM"; + SubscriptionEntity entity = givenSubscriptionEntity(planCode); + givenJpaFindByPlanCodeReturns(planCode, entity); + + // when + SubscriptionModel result = whenGetByPlanCode(planCode); + + // then + thenModelShouldMatchEntity(result, entity); + } + + @Test + public void shouldThrowExceptionWhenPlanCodeNotFound() { + // given + String planCode = "INVALID"; + givenJpaFindByPlanCodeReturnsEmpty(planCode); + + // then + thenShouldThrowPlanNotFound(planCode); + } + @Test + public void shouldFindSubscriptionsByPlanType() { + // given + String planType = "MONTHLY"; + List entities = givenSubscriptionEntities(); + givenJpaFindByPlanTypeReturns(planType, entities); + + List result = whenFindByPlanType(planType); + + thenResultShouldMatchEntities(result, entities); + } + + private List givenSubscriptionEntities() { + return List.of( + new SubscriptionEntity("ESTANDAR", "ABC123"), + new SubscriptionEntity("PRO", "DEF456") + ); + } + + private SubscriptionEntity givenSubscriptionEntity(String planCode) { + return new SubscriptionEntity(planCode, "ABC123"); + } + + private void givenJpaFindAllReturns(List entities) { + when(jpaRepository.findAll()).thenReturn(entities); + } + + private void givenJpaFindByPlanCodeReturns(String planCode, SubscriptionEntity entity) { + when(jpaRepository.findByPlanCode(planCode)).thenReturn(Optional.of(entity)); + } + + private void givenJpaFindByPlanCodeReturnsEmpty(String planCode) { + when(jpaRepository.findByPlanCode(planCode)).thenReturn(Optional.empty()); + } + + private void givenJpaFindByPlanTypeReturns(String planType, List entities) { + when(jpaRepository.findByPlanType(planType)).thenReturn(entities); + } + + private List whenGetAllSubscriptions() { + return repository.getAllSubscriptions(); + } + + private SubscriptionModel whenGetByPlanCode(String planCode) { + return repository.getByPlanCode(planCode); + } + + private List whenFindByPlanType(String planType) { + return repository.findByPlanType(planType); + } + private void thenShouldThrowPlanNotFound(String planCode) { + assertThatThrownBy(() -> whenGetByPlanCode(planCode)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Plan no encontrado: " + planCode); + } + + private void thenResultShouldMatchEntities(List result, + List entities) { + assertEquals(entities.size(), result.size()); + + for (int i = 0; i < entities.size(); i++) { + thenModelShouldMatchEntity(result.get(i), entities.get(i)); + } + } + + private void thenModelShouldMatchEntity(SubscriptionModel model, SubscriptionEntity entity) { + assertEquals(entity.getPlanCode(), model.getPlanCode()); + assertEquals(entity.getPlanType(), model.getPlanType()); + assertEquals(entity.getPrice(), model.getPrice()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/TripoRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/TripoRepositoryImplTest.java new file mode 100644 index 0000000..a69aafd --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/TripoRepositoryImplTest.java @@ -0,0 +1,182 @@ +package com.outfitlab.project.infrastructure.repositories; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.outfitlab.project.domain.model.TripoModel; +import com.outfitlab.project.infrastructure.model.TripoEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.TripoJpaRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.multipart.MultipartFile; + +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.*; + +import static org.mockito.Mockito.*; + +@SpringBootTest +@ActiveProfiles("test") +class TripoRepositoryImplTest { + + @Mock + private ObjectMapper mapper; + @Mock + private RestTemplate restTemplate; + @Mock + private TripoJpaRepository tripoJpaRepository; + + @Spy + @InjectMocks + private TripoRepositoryImpl repository; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(repository, "tripoApiKey", "test-api-key"); + } + + private MultipartFile mockMultipartFile(String name) { + return new MockMultipartFile(name, name, "image/png", "dummy".getBytes()); + } + + private ResponseEntity mockResponse(String body) { + return ResponseEntity.ok(body); + } + + @Test + void shouldReturnTripoModelWhenFindByTaskId() { + TripoEntity entity = givenTripoEntity("task123"); + TripoModel result = whenBuscarPorTaskId("task123"); + + thenTripoModelShouldHaveTaskId(result, "task123"); + } + + @Test + void shouldReturnTaskIdWhenRequestGenerateGlbToTripo() throws Exception { + Map uploadData = new HashMap<>(); + uploadData.put("imageToken", "token123"); + uploadData.put("fileExtension", "png"); + + ResponseEntity response = givenTaskResponse("{\"data\":{\"task_id\":\"task123\"}}"); + + String taskId = whenRequestGenerateGlbToTripo(uploadData, response); + + thenTaskIdShouldBe(taskId, "task123"); + } + + @Test + void shouldSaveTripoModel() { + TripoModel model = givenTripoModel("task123"); + TripoEntity entity = TripoEntity.convertToEntity(model); + doReturn(entity).when(tripoJpaRepository).save(any(TripoEntity.class)); + + TripoModel result = whenSaveTripoModel(model); + + thenTripoModelShouldHaveTaskId(result, "task123"); + } + + @Test + void shouldUpdateTripoModel() throws Exception { + TripoModel model = givenTripoModel("task123"); + TripoEntity entity = TripoEntity.convertToEntity(model); + doReturn(entity).when(tripoJpaRepository).findByTaskId("task123"); + doReturn(entity).when(tripoJpaRepository).save(any(TripoEntity.class)); + + TripoModel result = whenUpdateTripoModel(model); + + thenTripoModelShouldHaveTaskId(result, "task123"); + } + + @Test + void shouldReturnGlbUrlWhenStatusIsSuccess() throws Exception { + String taskId = "task123"; + String jsonResponse = "{\"data\":{\"status\":\"success\",\"result\":{\"pbr_model\":{\"url\":\"https://glb.com/model.glb\"}}}}"; + ResponseEntity response = new ResponseEntity<>(jsonResponse, HttpStatus.OK); + + doReturn(response).when(repository).requestTripoTaskStatus(eq(taskId), any(HttpEntity.class)); + + String result = whenRequestStatusGlbTripo(taskId); + + thenGlbUrlShouldBe(result, "https://glb.com/model.glb"); + } + + private TripoModel whenUpdateTripoModel(TripoModel model) throws Exception { + return repository.update(model); + } + + private TripoEntity givenTripoEntity(String taskId) { + TripoEntity entity = new TripoEntity(); + entity.setTaskId(taskId); + doReturn(entity).when(tripoJpaRepository).findByTaskId(taskId); + return entity; + } + + private TripoModel whenBuscarPorTaskId(String taskId) { + return repository.buscarPorTaskId(taskId); + } + + private TripoModel givenTripoModel(String taskId) { + TripoModel model = new TripoModel(); + model.setTaskId(taskId); + return model; + } + + private TripoModel whenSaveTripoModel(TripoModel model) { + return repository.save(model); + } + + private void thenTripoModelShouldHaveTaskId(TripoModel model, String expectedTaskId) { + assertThat(model.getTaskId()).isEqualTo(expectedTaskId); + } + + private ResponseEntity givenUploadResponse(String body) { + doReturn(new ResponseEntity<>(body, HttpStatus.OK)) + .when(repository) + .generateRequestToUploadImageToTripo(any(MultipartFile.class), anyString()); + return new ResponseEntity<>(body, HttpStatus.OK); + } + + private Map whenRequestUploadImagenToTripo(String url, ResponseEntity response) throws Exception { + return repository.requestUploadImagenToTripo(url); + } + + private void thenUploadDataShouldContain(Map data, String token, String filename, String extension) { + assertThat(data.get("imageToken")).isEqualTo(token); + assertThat(data.get("originalFilename")).isEqualTo(filename); + assertThat(data.get("fileExtension")).isEqualTo(extension); + } + + private ResponseEntity givenTaskResponse(String body) { + ResponseEntity response = new ResponseEntity<>(body, HttpStatus.OK); + doReturn(response).when(restTemplate).postForEntity(anyString(), any(HttpEntity.class), eq(String.class)); + return response; + } + + private String whenRequestGenerateGlbToTripo(Map uploadData, ResponseEntity response) throws Exception { + return repository.requestGenerateGlbToTripo(uploadData); + } + + private void thenTaskIdShouldBe(String actual, String expected) { + assertThat(actual).isEqualTo(expected); + } + + private String whenRequestStatusGlbTripo(String taskId) throws Exception { + return repository.requestStatusGlbTripo(taskId); + } + + private void thenGlbUrlShouldBe(String actual, String expected) { + assertThat(actual).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/UploadImageRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/UploadImageRepositoryImplTest.java new file mode 100644 index 0000000..53f4154 --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/UploadImageRepositoryImplTest.java @@ -0,0 +1,109 @@ +package com.outfitlab.project.infrastructure.repositories; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.ActiveProfiles; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.verify; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +@SpringBootTest +@ActiveProfiles("test") +class UploadImageRepositoryImplTest { + + @Mock + private S3Client s3Client; + + private UploadImageRepositoryImpl repository; + + @BeforeEach + void setUp() { + repository = new UploadImageRepositoryImpl(s3Client, "test-bucket", "sa-east-1"); + } + + @Test + void shouldUploadFileSuccessfully() throws Exception { + MockMultipartFile file = givenMultipartFile("test.txt", "Hello world".getBytes()); + + String url = whenUploadFile(file, "folder"); + + thenPutObjectShouldBeCalled(); + thenUrlShouldBeCorrect(url, "folder"); + } + + @Test + void shouldThrowWhenUploadFails() throws Exception { + MockMultipartFile file = givenMultipartFile("fail.txt", "data".getBytes()); + doThrow(new RuntimeException("AWS error")) + .when(s3Client).putObject(any(PutObjectRequest.class), any(RequestBody.class)); + + assertThatThrownBy(() -> whenUploadFile(file, "folder")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Error al subir archivo a S3"); + } + + @Test + void shouldDeleteFileSuccessfully() { + String key = "folder/file.txt"; + String url = repository.getFileUrl(key); + + whenDeleteFile(url); + + thenDeleteObjectShouldBeCalled(key); + } + + @Test + void shouldThrowWhenDeleteFails() { + doThrow(new RuntimeException("AWS delete error")) + .when(s3Client).deleteObject(any(DeleteObjectRequest.class)); + + assertThatThrownBy(() -> repository.deleteFile(repository.getFileUrl("fail.txt"))) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Error al eliminar archivo de S3"); + } + + @Test + void shouldGetFileUrlCorrectly() { + String key = "folder/file.txt"; + + String url = repository.getFileUrl(key); + + assertThat(url) + .isEqualTo("https://sa-east-1.s3.test-bucket.amazonaws.com/folder/file.txt"); + } + + private MockMultipartFile givenMultipartFile(String filename, byte[] content) { + return new MockMultipartFile("file", filename, "text/plain", content); + } + + private String whenUploadFile(MockMultipartFile file, String folder) { + return repository.uploadFile(file, folder); + } + + private void whenDeleteFile(String url) { + repository.deleteFile(url); + } + + private void thenPutObjectShouldBeCalled() { + verify(s3Client, times(1)) + .putObject(any(PutObjectRequest.class), any(RequestBody.class)); + } + + private void thenUrlShouldBeCorrect(String url, String folder) { + assertThat(url).startsWith("https://sa-east-1.s3.test-bucket.amazonaws.com/" + folder + "/"); + assertThat(url).endsWith(".txt"); + } + + private void thenDeleteObjectShouldBeCalled(String key) { + verify(s3Client).deleteObject(argThat((DeleteObjectRequest req) -> req.key().equals(key))); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/UserCombinationFavoriteRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/UserCombinationFavoriteRepositoryImplTest.java new file mode 100644 index 0000000..e3e579d --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/UserCombinationFavoriteRepositoryImplTest.java @@ -0,0 +1,193 @@ +package com.outfitlab.project.infrastructure.repositories; + +import com.outfitlab.project.domain.exceptions.FavoritesException; +import com.outfitlab.project.domain.exceptions.UserCombinationFavoriteNotFoundException; +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.model.UserCombinationFavoriteModel; +import com.outfitlab.project.domain.model.dto.PageDTO; +import com.outfitlab.project.infrastructure.model.UserCombinationFavoriteEntity; +import com.outfitlab.project.infrastructure.model.UserEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.UserCombinationFavoriteJpaRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.UserJpaRepository; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +import static org.mockito.Mockito.*; + +@SpringBootTest +@ActiveProfiles("test") +class UserCombinationFavoriteRepositoryImplTest { + @Mock + private UserCombinationFavoriteJpaRepository favoriteJpaRepository; + + @Mock + private UserJpaRepository userJpaRepository; + + @InjectMocks + private UserCombinationFavoriteRepositoryImpl repository; + + private static final String EMAIL = "test@mail.com"; + private static final String URL = "https://combination.com/1"; + + @Test + void shouldReturnFavoriteWhenExists() throws Exception { + UserCombinationFavoriteEntity entity = givenFavoriteEntity(); + givenFavoriteExists(URL, EMAIL, entity); + + UserCombinationFavoriteModel result = whenFindByCombinationAndEmail(); + + thenFavoriteShouldMatch(result, URL); + } + + @Test + void shouldThrowExceptionWhenFavoriteDoesNotExist() { + givenFavoriteDoesNotExist(URL, EMAIL); + + assertThatThrownBy(() -> whenFindByCombinationAndEmail()) + .isInstanceOf(UserCombinationFavoriteNotFoundException.class) + .hasMessageContaining(URL); + } + + @Test + void shouldAddToFavoritesWhenUserExists() throws Exception { + UserEntity user = givenUser(); + givenUserExists(user); + UserCombinationFavoriteEntity saved = givenSavedFavorite(user); + + givenFavoriteSaved(saved); + + UserCombinationFavoriteEntity result = whenAddToFavorites(); + + assertThat(result.getCombinationUrl()).isEqualTo(URL); + } + + @Test + void shouldThrowExceptionWhenAddingFavoriteForNonExistingUser() { + givenUserDoesNotExist(); + + assertThatThrownBy(() -> whenAddToFavorites()) + .isInstanceOf(UserNotFoundException.class) + .hasMessageContaining(EMAIL); + } + + @Test + void shouldDeleteFavoriteWhenUserExists() throws Exception { + UserEntity user = givenUser(); + givenUserExists(user); + + UserCombinationFavoriteEntity favorite = givenFavoriteEntity(); + givenFavoriteExists(URL, EMAIL, favorite); + + whenDeleteFavorite(); + + verify(favoriteJpaRepository).delete(favorite); + } + + @Test + void shouldThrowExceptionWhenDeletingFavoriteWithNonExistingUser() { + givenUserDoesNotExist(); + + assertThatThrownBy(this::whenDeleteFavorite) + .isInstanceOf(UserNotFoundException.class); + } + + @Test + void shouldReturnPageDTOWhenUserHasFavorites() throws Exception { + UserEntity user = givenUser(); + givenUserExists(user); + + Page page = givenFavoritePage(List.of(givenFavoriteEntity())); + + givenFavoritePageExists(page); + + PageDTO result = whenGetFavoritesPage(); + + assertThat(result.getContent()).hasSize(1); + } + + @Test + void shouldThrowExceptionWhenUserHasNoFavorites() throws Exception { + UserEntity user = givenUser(); + givenUserExists(user); + + givenFavoritePageExists(givenFavoritePage(List.of())); // empty page + + assertThatThrownBy(this::whenGetFavoritesPage) + .isInstanceOf(FavoritesException.class); + } + + private UserCombinationFavoriteEntity givenFavoriteEntity() { + return new UserCombinationFavoriteEntity(new UserEntity(), URL); + } + + private UserCombinationFavoriteEntity givenSavedFavorite(UserEntity user) { + return new UserCombinationFavoriteEntity(user, URL); + } + + private UserEntity givenUser() { + UserEntity user = new UserEntity(); + user.setEmail(EMAIL); + return user; + } + + private void givenUserExists(UserEntity user) { + when(userJpaRepository.findByEmail(EMAIL)).thenReturn(user); + } + + private void givenUserDoesNotExist() { + when(userJpaRepository.findByEmail(EMAIL)).thenReturn(null); + } + + private void givenFavoriteExists(String url, String email, UserCombinationFavoriteEntity entity) { + when(favoriteJpaRepository.findBycombinationUrlAndUser_Email(url, email)) + .thenReturn(entity); + } + + private void givenFavoriteDoesNotExist(String url, String email) { + when(favoriteJpaRepository.findBycombinationUrlAndUser_Email(url, email)) + .thenReturn(null); + } + + private void givenFavoriteSaved(UserCombinationFavoriteEntity saved) { + when(favoriteJpaRepository.save(any())).thenReturn(saved); + } + + private Page givenFavoritePage(List list) { + return new PageImpl<>(list, PageRequest.of(0, 10), list.size()); + } + + private void givenFavoritePageExists(Page page) { + when(favoriteJpaRepository.findByUser_Email(eq(EMAIL), any(PageRequest.class))) + .thenReturn(page); + } + + private UserCombinationFavoriteModel whenFindByCombinationAndEmail() throws Exception { + return repository.findByCombinationUrlAndUserEmail(URL, EMAIL); + } + + private UserCombinationFavoriteEntity whenAddToFavorites() throws Exception { + return repository.addToFavorite(URL, EMAIL); + } + + private void whenDeleteFavorite() throws Exception { + repository.deleteFromFavorites(URL, EMAIL); + } + + private PageDTO whenGetFavoritesPage() throws Exception { + return repository.getCombinationFavoritesForUserByEmail(EMAIL, 0); + } + + private void thenFavoriteShouldMatch(UserCombinationFavoriteModel model, String url) { + assertThat(model.getCombinationUrl()).isEqualTo(url); + } +} diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/UserGarmentFavoriteRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/UserGarmentFavoriteRepositoryImplTest.java new file mode 100644 index 0000000..36e25e9 --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/UserGarmentFavoriteRepositoryImplTest.java @@ -0,0 +1,290 @@ +package com.outfitlab.project.infrastructure.repositories; + + +import com.outfitlab.project.domain.exceptions.FavoritesException; +import com.outfitlab.project.domain.exceptions.GarmentNotFoundException; +import com.outfitlab.project.domain.exceptions.UserGarmentFavoriteNotFoundException; +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.model.PrendaModel; +import com.outfitlab.project.domain.model.UserGarmentFavoriteModel; +import com.outfitlab.project.domain.model.dto.PageDTO; +import com.outfitlab.project.infrastructure.model.PrendaEntity; +import com.outfitlab.project.infrastructure.model.UserEntity; +import com.outfitlab.project.infrastructure.model.UserGarmentFavoriteEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.GarmentJpaRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.UserGarmentFavoriteJpaRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.UserJpaRepository; +import static org.mockito.BDDMockito.given; + +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +import static org.mockito.Mockito.*; + +@SpringBootTest +@ActiveProfiles("test") +class UserGarmentFavoriteRepositoryImplTest { + + @Mock + private UserGarmentFavoriteJpaRepository favoriteJpa; + + @Mock + private UserJpaRepository userJpa; + + @Mock + private GarmentJpaRepository garmentJpa; + + @InjectMocks + private UserGarmentFavoriteRepositoryImpl repository; + + @Test + void shouldReturnFavoriteWhenFavoriteExists() throws Exception { + String email = "user@mail.com"; + String code = "P001"; + + UserEntity user = givenUser(email); + PrendaEntity garment = givenGarment(code); + UserGarmentFavoriteEntity fav = givenFavorite(user, garment); + + givenFindFavoriteReturns(code, email, fav); + + UserGarmentFavoriteModel result = + repository.findByGarmentCodeAndUserEmail(code, email); + + assertThat(result.getGarment().getGarmentCode()).isEqualTo(code); + } + + @Test + void shouldThrowExceptionWhenFavoriteNotFound() { + String email = "user@mail.com"; + String code = "X999"; + + givenFindFavoriteReturns(code, email, null); + + assertThatThrownBy(() -> + whenFindByGarmentCodeAndUserEmail(code, email) + ) + .isInstanceOf(UserGarmentFavoriteNotFoundException.class) + .hasMessageContaining(code); + } + + @Test + void shouldAddFavoriteWhenUserAndGarmentExist() throws Exception { + String email = "user@mail.com"; + String code = "P123"; + + UserEntity user = givenUser(email); + PrendaEntity garment = givenGarment(code); + UserGarmentFavoriteEntity fav = givenFavorite(user, garment); + + givenUserExists(email, user); + givenGarmentExists(code, garment); + givenSaveReturns(fav); + + UserGarmentFavoriteEntity result = + repository.addToFavorite(code, email); + + assertThat(result.getGarment().getGarmentCode()).isEqualTo(code); + } + + @Test + void shouldThrowUserNotFoundWhenAddingFavorite() { + String email = "no@mail.com"; + String code = "P111"; + + givenUserExists(email, null); + + assertThatThrownBy(() -> + whenAddToFavorite(code, email) + ) + .isInstanceOf(UserNotFoundException.class) + .hasMessageContaining(email); + } + + @Test + void shouldThrowGarmentNotFoundWhenAddingFavorite() { + String email = "user@mail.com"; + String code = "P404"; + + givenUserExists(email, givenUser(email)); + givenGarmentExists(code, null); + + assertThatThrownBy(() -> + whenAddToFavorite(code, email) + ) + .isInstanceOf(GarmentNotFoundException.class) + .hasMessageContaining(code); + } + + @Test + void shouldDeleteFavoriteWhenExists() throws Exception { + String email = "user@mail.com"; + String code = "P001"; + + UserEntity user = givenUser(email); + PrendaEntity garment = givenGarment(code); + UserGarmentFavoriteEntity fav = givenFavorite(user, garment); + + givenUserExists(email, user); + givenGarmentExists(code, garment); + given(favoriteJpa.findByGarmentAndUser(garment, user)).willReturn(fav); + + whenDeleteFromFavorites(code, email); + + verify(favoriteJpa).delete(fav); + } + + @Test + void shouldThrowUserNotFoundWhenDeletingFavorite() { + String email = "x@mail.com"; + String code = "P001"; + + givenUserExists(email, null); + + assertThatThrownBy(() -> + whenDeleteFromFavorites(code, email) + ) + .isInstanceOf(UserNotFoundException.class) + .hasMessageContaining(email); + } + + @Test + void shouldThrowGarmentNotFoundWhenDeletingFavorite() { + String email = "user@mail.com"; + String code = "PXYZ"; + + givenUserExists(email, givenUser(email)); + givenGarmentExists(code, null); + + assertThatThrownBy(() -> + whenDeleteFromFavorites(code, email) + ) + .isInstanceOf(GarmentNotFoundException.class) + .hasMessageContaining(code); + } + + @Test + void shouldReturnFavoritesPageWhenExists() throws Exception { + String email = "user@mail.com"; + + UserEntity user = givenUser(email); + givenUserExists(email, user); + + PrendaEntity garment = givenGarment("P1"); + UserGarmentFavoriteEntity fav = givenFavorite(user, garment); + + Page page = + new PageImpl<>(List.of(fav)); + + givenFavoritesPage(email, page); + + PageDTO dto = + repository.getGarmentsFavoritesForUserByEmail(email, 0); + + assertThat(dto.getContent()).hasSize(1); + } + + @Test + void shouldThrowFavoritesExceptionWhenNoFavorites() { + String email = "user@mail.com"; + + givenUserExists(email, givenUser(email)); + + Page emptyPage = + new PageImpl<>(List.of()); + + givenFavoritesPage(email, emptyPage); + + assertThatThrownBy(() -> + whenGetFavorites(email, 0) + ) + .isInstanceOf(FavoritesException.class); + } + + @Test + void shouldThrowUserNotFoundWhenGettingFavorites() { + String email = "x@mail.com"; + givenUserExists(email, null); + + assertThatThrownBy(() -> + whenGetFavorites(email, 0) + ) + .isInstanceOf(UserNotFoundException.class); + } + + @Test + void shouldDeleteFavoritesRelatedToGarment() { + String code = "P001"; + + whenDeleteFavoritesRelated(code); + + verify(favoriteJpa).deleteAllByGarmentCode(code); + } + + private UserEntity givenUser(String email) { + return new UserEntity("user@mail.com"); + } + + private PrendaEntity givenGarment(String code) { + PrendaEntity p = new PrendaEntity(); + p.setGarmentCode(code); + return p; + } + + private UserGarmentFavoriteEntity givenFavorite(UserEntity u, PrendaEntity g) { + return new UserGarmentFavoriteEntity(u, g); + } + + private void givenUserExists(String email, UserEntity user) { + given(userJpa.findByEmail(email)).willReturn(user); + } + + private void givenGarmentExists(String code, PrendaEntity garment) { + given(garmentJpa.findByGarmentCode(code)).willReturn(garment); + } + + private void givenFindFavoriteReturns(String code, String email, UserGarmentFavoriteEntity fav) { + given(favoriteJpa.findByGarment_GarmentCodeAndUser_Email(code, email)).willReturn(fav); + } + + private void givenFavoritesPage(String email, Page page) { + given(favoriteJpa.findFavoritesByUserEmail(eq(email), any())).willReturn(page); + } + + private void givenSaveReturns(UserGarmentFavoriteEntity fav) { + given(favoriteJpa.save(any())).willReturn(fav); + } + + private void whenFindByGarmentCodeAndUserEmail(String code, String email) + throws UserGarmentFavoriteNotFoundException { + repository.findByGarmentCodeAndUserEmail(code, email); + } + + private void whenAddToFavorite(String code, String email) + throws Exception { + repository.addToFavorite(code, email); + } + + private void whenDeleteFromFavorites(String code, String email) + throws Exception { + repository.deleteFromFavorites(code, email); + } + + private void whenGetFavorites(String email, int page) + throws Exception { + repository.getGarmentsFavoritesForUserByEmail(email, page); + } + + private void whenDeleteFavoritesRelated(String code) { + repository.deleteFavoritesRelatedToGarment(code); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/UserRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/UserRepositoryImplTest.java new file mode 100644 index 0000000..3855592 --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/UserRepositoryImplTest.java @@ -0,0 +1,562 @@ +package com.outfitlab.project.infrastructure.repositories; + +import com.outfitlab.project.domain.enums.Role; +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.model.UserModel; +import com.outfitlab.project.infrastructure.model.MarcaEntity; +import com.outfitlab.project.infrastructure.model.UserEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.BrandJpaRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.UserJpaRepository; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.*; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Optional; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@SpringBootTest +@ActiveProfiles("test") +class UserRepositoryImplTest { + @Mock + private UserJpaRepository userJpaRepository; + @Mock + private BrandJpaRepository brandJpaRepository; + @InjectMocks + private UserRepositoryImpl userRepository; + + @Test + void shouldReturnUserByEmail() { + givenExistingUserWithEmail("test@mail.com"); + + UserModel result = whenFindUserByEmail("test@mail.com"); + + thenReturnUserWithEmail(result, "test@mail.com"); + } + + @Test + void shouldThrowExceptionWhenEmailDoesNotExist() { + String email = "test15@mail.com"; + + whenFindingByEmail(email, null); + + thenExceptionShouldBeThrown(email); + verify(userJpaRepository).findByEmail(email); + verify(userJpaRepository, never()).save(any()); + } + + @Test + void shouldReturnUserModelWhenTokenIsValid() { + String token = "abc123"; + givenUserWithVerificationToken(token, "test@mail.com", 1L); + + UserModel result = whenFindUserByVerificationToken(token); + + thenUserShouldHaveEmail(result, "test@mail.com"); + verify(userJpaRepository).findByVerificationToken(token); + } + + + @Test + void shouldThrowExceptionWhenTokenIsInvalid() { + String token = "def456"; + givenNoUserWithToken(token); + + thenExpectUserNotFoundByToken(token); + + verify(userJpaRepository).findByVerificationToken(token); + } + + + @Test + void shouldReturnUserModelWhenIdExists() { + Long id = 1L; + givenUserWithId(id, "test@mail.com"); + + UserModel result = whenFindById(id); + + thenUserShouldHaveId(result, id); + thenUserShouldHaveEmail(result, "test@mail.com"); + verify(userJpaRepository).findById(id); + } + + + @Test + void shouldThrowExceptionWhenIdDoesNotExist() { + Long id = 99L; + givenNoUserWithId(id); + + thenExpectUserNotFoundById(id); + + verify(userJpaRepository).findById(id); + } + + @Test + void shouldPersistAndReturnUserModelWhenSavingUser() { + UserModel model = givenUserModel("save@mail.com", "Juan"); + UserEntity savedEntity = givenSavedEntity(model, 1L); + + whenSavingUserEntity(savedEntity); + UserModel result = whenSavingUser(model); + + thenUserEmailShouldBe(result, "save@mail.com"); + thenUserIdShouldBeNull(result); + verify(userJpaRepository).save(any(UserEntity.class)); + } + + @Test + void shouldReturnUserListWhenUsersExist() { + List entities = givenUserEntityList(); + + whenFindingAllUsers(entities); + List result = whenCallingFindAll(); + + thenResultShouldHaveSize(result, 2); + thenUserEmailShouldBe(result.get(0), "u1@mail.com"); + thenUserEmailShouldBe(result.get(1), "u2@mail.com"); + verify(userJpaRepository).findAll(); + } + + @Test + void shouldDeactivateUserWhenEmailExists() { + String email = "test@mail.com"; + UserEntity entity = givenActiveUserEntity(email); + + whenFindingByEmail(email, entity); + whenDeactivatingUser(email); + + thenUserShouldBeDeactivated(entity); + verify(userJpaRepository).findByEmail(email); + verify(userJpaRepository).save(entity); + } + + @Test + void shouldThrowExceptionWhenDeactivatingNonExistingUser() { + String email = "test17@mail.com"; + + whenFindingByEmail(email, null); + + thenDesactivateShouldThrowException(email); + verify(userJpaRepository).findByEmail(email); + verify(userJpaRepository, never()).save(any()); + } + + @Test + void shouldActivateUserWhenEmailExists() { + String email = "test@mail.com"; + UserEntity entity = givenInactiveAndUnapprovedUser(email); + + whenFindingByEmail(email, entity); + whenActivatingUser(email); + + thenUserShouldBeActive(entity); + thenUserBrandShouldBeApproved(entity); + verify(userJpaRepository).findByEmail(email); + verify(userJpaRepository).save(entity); + } + + @Test + void shouldThrowExceptionWhenActivatingNonExistingUser() { + String email = "test17@mail.com"; + + whenFindingByEmail(email, null); + + thenActivateShouldThrowException(email); + verify(userJpaRepository).findByEmail(email); + verify(userJpaRepository, never()).save(any()); + } + + @Test + void shouldConvertUserToAdminWhenEmailExists() { + String email = "test@mail.com"; + UserEntity entity = givenUserWithRole(email, Role.USER); + + whenFindingByEmail(email, entity); + whenConvertingToAdmin(email); + + thenRoleShouldBe(entity, Role.ADMIN); + verify(userJpaRepository).save(entity); + } + + @Test + void shouldConvertAdminToUserWhenEmailExists() { + String email = "test@mail.com"; + UserEntity entity = givenUserWithRole(email, Role.ADMIN); + + whenFindingByEmail(email, entity); + whenConvertingToUser(email); + + thenRoleShouldBe(entity, Role.USER); + verify(userJpaRepository).save(entity); + } + + @Test + void shouldUpdateBrandUserCorrectly() { + String email = "user@mail.com"; + String brandCode = "TESTBRAND"; + UserEntity user = givenUser(email); + MarcaEntity brand = givenBrand(brandCode); + + whenFindingUserByEmail(email, user); + whenFindingBrandByCode(brandCode, brand); + whenSavingUser(user); + + whenUpdateBrandUser(email, brandCode); + + thenUserShouldHaveBrand(user, brand); + thenUserShouldHaveRole(user, Role.BRAND); + thenUserShouldBeUnapproved(user); + verify(userJpaRepository).save(user); + } + + @Test + void shouldReturnEmailWhenBrandUserExists() { + String brandCode = "TESTBRAND"; + UserEntity user = givenUser("brand@mail.com"); + + whenFindingUserByBrandCode(brandCode, user); + + String email = whenGetEmailUserRelated(brandCode); + + thenEmailShouldBe(email, "brand@mail.com"); + verify(userJpaRepository).findByBrand_CodigoMarca(brandCode); + } + + @Test + void shouldUpdateUserCorrectlyWhenValidDataProvided() { + UserEntity entity = givenUserWithFullData(); + + whenFindingUserByEmail("old@mail.com", entity); + whenSavingUser(entity); + + UserModel result = whenUpdateUser( + "old@mail.com", + "newName", + "newLName", + "new@mail.com", + "newpass", + "new.png" + ); + + thenUserShouldHaveUpdatedFields(entity, "newName", "newLName", "new@mail.com", "newpass", "new.png"); + thenEmailShouldBe(result.getEmail(), "new@mail.com"); + verify(userJpaRepository).save(entity); + } + + @Test + void shouldNotModifyPasswordWhenEmptyPasswordProvided() { + UserEntity entity = givenUserWithOldPassword(); + + whenFindingUserByEmail("old@mail.com", entity); + whenSavingUser(entity); + + whenUpdateUser( + "old@mail.com", + "newName", + "newLNAme", + "new@mail.com", + "", + "image.png" + ); + + thenPasswordShouldBe(entity, "oldpass"); + thenEmailShouldBe(entity.getEmail(), "new@mail.com"); + } + + private void givenExistingUserWithEmail(String email) { + UserEntity entity = new UserEntity(); + entity.setEmail(email); + when(userJpaRepository.findByEmail(email)).thenReturn(entity); + } + + private void givenUserDoesNotExist(String email) { + when(userJpaRepository.findByEmail(email)).thenReturn(null); + } + + private void givenUserWithVerificationToken(String token, String email, Long id) { + UserEntity entity = new UserEntity(); + entity.setId(id); + entity.setEmail(email); + entity.setVerificationToken(token); + + when(userJpaRepository.findByVerificationToken(token)).thenReturn(entity); + } + + private void givenNoUserWithToken(String token) { + when(userJpaRepository.findByVerificationToken(token)).thenReturn(null); + } + + private void givenUserWithId(Long id, String email) { + UserEntity entity = new UserEntity(); + entity.setId(id); + entity.setEmail(email); + + when(userJpaRepository.findById(id)).thenReturn(Optional.of(entity)); + } + + private void givenNoUserWithId(Long id) { + when(userJpaRepository.findById(id)).thenReturn(Optional.empty()); + } + + private UserModel whenFindUserByEmail(String email) { + return userRepository.findUserByEmail(email); + } + + private UserModel whenFindUserByVerificationToken(String token) { + return userRepository.findUserByVerificationToken(token); + } + + private UserModel whenFindById(Long id) { + return userRepository.findById(id); + } + + + private void thenReturnUserWithEmail(UserModel result, String expectedEmail) { + assertNotNull(result); + assertEquals(expectedEmail, result.getEmail()); + } + + private void thenExpectUserNotFound(String email) { + assertThrows( + UserNotFoundException.class, + () -> userRepository.findUserByEmail(email) + ); + } + + private void thenUserShouldHaveEmail(UserModel result, String expectedEmail) { + assertThat(result).isNotNull(); + assertThat(result.getEmail()).isEqualTo(expectedEmail); + } + + private void thenUserShouldHaveId(UserModel result, Long expectedId) { + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(expectedId); + } + + private void thenExpectUserNotFoundByToken(String token) { + assertThatThrownBy(() -> userRepository.findUserByVerificationToken(token)) + .isInstanceOf(UserNotFoundException.class); + } + + private void thenExpectUserNotFoundById(Long id) { + assertThatThrownBy(() -> userRepository.findById(id)) + .isInstanceOf(UserNotFoundException.class); + } + + private UserModel givenUserModel(String email, String name) { + UserModel m = new UserModel(); + m.setEmail(email); + m.setName(name); + return m; + } + + private UserEntity givenSavedEntity(UserModel model, Long id) { + UserEntity e = new UserEntity(model); + e.setId(id); + return e; + } + + private List givenUserEntityList() { + UserEntity u1 = new UserEntity(); + u1.setId(1L); + u1.setEmail("u1@mail.com"); + + UserEntity u2 = new UserEntity(); + u2.setId(2L); + u2.setEmail("u2@mail.com"); + + return List.of(u1, u2); + } + + private UserEntity givenActiveUserEntity(String email) { + UserEntity e = new UserEntity(); + e.setEmail(email); + e.setStatus(true); + return e; + } + + private void whenSavingUserEntity(UserEntity saved) { + when(userJpaRepository.save(any(UserEntity.class))).thenReturn(saved); + } + + private UserModel whenSavingUser(UserModel model) { + return userRepository.saveUser(model); + } + + private void whenFindingAllUsers(List entities) { + when(userJpaRepository.findAll()).thenReturn(entities); + } + + private List whenCallingFindAll() { + return userRepository.findAll(); + } + + private void whenFindingByEmail(String email, UserEntity entity) { + when(userJpaRepository.findByEmail(email)).thenReturn(entity); + } + + private void whenDeactivatingUser(String email) { + userRepository.desactivateUser(email); + } + + private void thenUserEmailShouldBe(UserModel result, String expected) { + assertThat(result.getEmail()).isEqualTo(expected); + } + + private void thenUserIdShouldBeNull(UserModel result) { + assertThat(result.getId()).isNull(); + } + + private void thenResultShouldHaveSize(List list, int size) { + assertThat(list).hasSize(size); + } + + private void thenUserShouldBeDeactivated(UserEntity entity) { + assertThat(entity.isStatus()).isFalse(); + } + + private void thenExceptionShouldBeThrown(String email) { + assertThatThrownBy(() -> userRepository.findUserByEmail(email)) + .isInstanceOf(UserNotFoundException.class); + } + + + private UserEntity givenInactiveAndUnapprovedUser(String email) { + UserEntity e = new UserEntity(); + e.setEmail(email); + e.setStatus(false); + e.setBrandApproved(false); + return e; + } + + private UserEntity givenUserWithRole(String email, Role role) { + UserEntity e = new UserEntity(); + e.setEmail(email); + e.setRole(role); + return e; + } + + private void whenActivatingUser(String email) { + userRepository.activateUser(email); + } + + private void whenConvertingToAdmin(String email) { + userRepository.convertToAdmin(email); + } + + private void whenConvertingToUser(String email) { + userRepository.convertToUser(email); + } + + private void thenUserShouldBeActive(UserEntity entity) { + assertThat(entity.isStatus()).isTrue(); + } + + private void thenUserBrandShouldBeApproved(UserEntity entity) { + assertThat(entity.isBrandApproved()).isTrue(); + } + + private void thenDesactivateShouldThrowException(String email) { + assertThatThrownBy(() -> userRepository.desactivateUser(email)) + .isInstanceOf(UserNotFoundException.class); + } + private void thenActivateShouldThrowException(String email) { + assertThatThrownBy(() -> userRepository.activateUser(email)) + .isInstanceOf(UserNotFoundException.class); + } + + private void thenRoleShouldBe(UserEntity entity, Role expected) { + assertThat(entity.getRole()).isEqualTo(expected); + } + + private UserEntity givenUser(String email) { + UserEntity u = new UserEntity(); + u.setEmail(email); + return u; + } + + private MarcaEntity givenBrand(String code) { + MarcaEntity b = new MarcaEntity(); + b.setCodigoMarca(code); + return b; + } + + private UserEntity givenUserWithFullData() { + UserEntity e = new UserEntity(); + e.setEmail("old@mail.com"); + e.setPassword("oldpass"); + e.setUserImageUrl("old.png"); + return e; + } + + private UserEntity givenUserWithOldPassword() { + UserEntity e = new UserEntity(); + e.setEmail("old@mail.com"); + e.setPassword("oldpass"); + return e; + } + + private void whenFindingUserByEmail(String email, UserEntity user) { + when(userJpaRepository.findByEmail(email)).thenReturn(user); + } + + private void whenFindingBrandByCode(String code, MarcaEntity brand) { + when(brandJpaRepository.findByCodigoMarca(code)).thenReturn(brand); + } + + private void whenFindingUserByBrandCode(String code, UserEntity user) { + when(userJpaRepository.findByBrand_CodigoMarca(code)).thenReturn(user); + } + + private void whenSavingUser(UserEntity user) { + when(userJpaRepository.save(user)).thenReturn(user); + } + + private void whenUpdateBrandUser(String email, String code) { + userRepository.updateBrandUser(email, code); + } + + private String whenGetEmailUserRelated(String brandCode) { + return userRepository.getEmailUserRelatedToBrandByBrandCode(brandCode); + } + + private UserModel whenUpdateUser(String email, String n, String ln, String newEmail, String pass, String img) { + return userRepository.updateUser(email, n, ln, newEmail, pass, img); + } + + private void thenUserShouldHaveBrand(UserEntity user, MarcaEntity brand) { + assertThat(user.getBrand()).isEqualTo(brand); + } + + private void thenUserShouldHaveRole(UserEntity user, Role role) { + assertThat(user.getRole()).isEqualTo(role); + } + + private void thenUserShouldBeUnapproved(UserEntity user) { + assertThat(user.isBrandApproved()).isFalse(); + } + + private void thenEmailShouldBe(String actual, String expected) { + assertThat(actual).isEqualTo(expected); + } + + private void thenUserShouldHaveUpdatedFields(UserEntity e, String n, String ln, String email, String pass, String img) { + assertThat(e.getName()).isEqualTo(n); + assertThat(e.getLastName()).isEqualTo(ln); + assertThat(e.getEmail()).isEqualTo(email); + assertThat(e.getPassword()).isEqualTo(pass); + assertThat(e.getUserImageUrl()).isEqualTo(img); + } + + private void thenPasswordShouldBe(UserEntity e, String expected) { + assertThat(e.getPassword()).isEqualTo(expected); + } + +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/infrastructure/repositories/UserSubscriptionRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/UserSubscriptionRepositoryImplTest.java new file mode 100644 index 0000000..629eb39 --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/UserSubscriptionRepositoryImplTest.java @@ -0,0 +1,368 @@ +package com.outfitlab.project.infrastructure.repositories; + +import com.outfitlab.project.domain.exceptions.SubscriptionNotFoundException; +import com.outfitlab.project.domain.model.UserSubscriptionModel; +import com.outfitlab.project.infrastructure.model.SubscriptionEntity; +import com.outfitlab.project.infrastructure.model.UserEntity; +import com.outfitlab.project.infrastructure.model.UserSubscriptionEntity; +import com.outfitlab.project.infrastructure.repositories.interfaces.SubscriptionJpaRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.UserJpaRepository; +import com.outfitlab.project.infrastructure.repositories.interfaces.UserSubscriptionJpaRepository; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.*; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.mockito.Mockito.*; + +@SpringBootTest +@ActiveProfiles("test") +class UserSubscriptionRepositoryImplTest { + + @Mock + private UserJpaRepository userJpaRepository; + @Mock + private UserSubscriptionJpaRepository userSubscriptionJpaRepository; + @Mock + private SubscriptionJpaRepository subscriptionJpaRepository; + @InjectMocks + private UserSubscriptionRepositoryImpl repository; + + @Test + void shouldReturnSubscriptionModelWhenEmailExists() throws SubscriptionNotFoundException { + String email = "test@mail.com"; + UserSubscriptionEntity entity = givenExistingSubscriptionReturningEntity(email, "BASIC", 10); + + whenFindingSubscriptionByUserEmail(email, entity); + + UserSubscriptionModel result = whenFindByUserEmail(email); + + thenSubscriptionShouldHaveCombinationsUsed(result, 10); + verify(userSubscriptionJpaRepository).findByUserEmail(email); + } + + @Test + void shouldThrowExceptionWhenSubscriptionNotFound() { + String email = "missing@mail.com"; + + whenFindingSubscriptionByUserEmailEmpty(email); + + assertThatThrownBy(() -> whenFindByUserEmail(email)) + .isInstanceOf(SubscriptionNotFoundException.class) + .hasMessageContaining(email); + + verify(userSubscriptionJpaRepository).findByUserEmail(email); + } + + + @Test + void shouldSaveSubscriptionCorrectly() { + String email = "user@mail.com"; + String planCode = "PREMIUM"; + + givenExistingUser(email, 5L); + givenExistingPlan(planCode); + givenSavedSubscription(3); + + UserSubscriptionModel request = givenSubscriptionModel(email, planCode, 3); + UserSubscriptionModel result = whenSave(request); + + thenSubscriptionHasCombinations(result, 3); + verify(userSubscriptionJpaRepository).save(any(UserSubscriptionEntity.class)); + } + + @Test + void shouldThrowExceptionWhenUserNotFoundOnSave() { + String email = "fail@mail.com"; + givenNoUser(email); + + UserSubscriptionModel request = givenSubscriptionModel(email, null, 0); + + assertThatThrownBy(() -> whenSave(request)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Usuario no encontrado"); + } + + @Test + void shouldThrowExceptionWhenPlanNotFoundOnSave() { + String email = "user@mail.com"; + String planCode = "INVALID_PLAN"; + + givenExistingUser(email, 1L); + givenNoPlan(planCode); + + UserSubscriptionModel request = givenSubscriptionModel(email, planCode, 0); + + assertThatThrownBy(() -> whenSave(request)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Plan no encontrado"); + } + + @Test + void shouldUpdateSubscriptionCorrectly() { + String email = "test@mail.com"; + + givenExistingSubscription(email, "ESTANDAR", 1); + givenExistingPlan("ESTANDAR"); + givenExistingPlan("PRO"); + + UserSubscriptionModel request = givenSubscriptionModel(email, "PRO", 7); + UserSubscriptionModel result = whenUpdate(request); + + thenSubscriptionPlanIs(email, "PRO"); + thenSubscriptionHasCombinations(result, 7); + } + + @Test + void shouldThrowExceptionWhenSubscriptionNotFoundOnUpdate() { + String email = "noexiste@mail.com"; + UserSubscriptionModel model = givenSubscriptionModel(email, null, 0); + + givenNoSubscription(email); + + assertThatThrownBy(() -> whenUpdate(model)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Suscripción no encontrada"); + } + + @Test + void shouldThrowExceptionWhenPlanNotFoundOnUpdate() { + String email = "test@mail.com"; + String planCode = "TURBO"; + + givenExistingSubscription(email, "ANY_PLAN", 0); + givenNoPlan(planCode); + + UserSubscriptionModel model = givenSubscriptionModel(email, planCode, 0); + + assertThatThrownBy(() -> whenUpdate(model)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Plan no encontrado"); + } + + @Test + void shouldIncrementCombinationsCounter() { + String email = "test@mail.com"; + givenExistingUser(email, 1L); + + whenIncrementCounter(email, "combinations"); + + thenIncrementCombinationsWasCalled(1L); + } + @Test + void shouldIncrementModelsCounter() { + String email = "test@mail.com"; + givenExistingUser(email, 1L); + + whenIncrementCounter(email, "3d_models"); + + thenIncrementModelsWasCalled(1L); + } + @Test + void givenEmailAnd3dModelsWhenIncrementCounterThenCallsIncrementModels() { + UserEntity user = new UserEntity(); + user.setId(1L); + + when(userJpaRepository.findByEmail("test@mail.com")).thenReturn(user); + + repository.incrementCounter("test@mail.com", "3d_models"); + + verify(userSubscriptionJpaRepository).incrementModelsByUserId(1L); + } + + @Test + void shouldThrowExceptionWhenUserNotFoundOnCounterIncrement() { + String email = "noexiste@mail.com"; + givenNoUser(email); + + assertThatThrownBy(() -> whenIncrementCounter(email, "combinations")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Usuario no encontrado"); + } + + @Test + void shouldDecrementFavoritesWhenCounterIsFavorites() { + givenUserWithId("test@mail.com", 2L); + + whenDecrementCounter("test@mail.com", "favorites"); + + thenDecrementFavoritesWasCalled(2L); + } + + @Test + void shouldDoNothingWhenCounterIsNotFavorite() { + // No hace falta givenUser porque no lo usa internamente + whenDecrementCounter("test@mail.com", "models"); + + thenDecrementFavoritesWasNeverCalled(); + } + + @Test + void shouldThrowExceptionWhenUserDoesNotExistOnDecrement() { + givenNoUserForEmail("noexiste@mail.com"); + + assertThatThrownBy(() -> whenDecrementCounter("noexiste@mail.com", "favorites")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Usuario no encontrado"); + } + + private void whenFindingSubscriptionByUserEmail(String email, UserSubscriptionEntity entity) { + when(userSubscriptionJpaRepository.findByUserEmail(email)).thenReturn(Optional.of(entity)); + } + + private void whenFindingSubscriptionByUserEmailEmpty(String email) { + when(userSubscriptionJpaRepository.findByUserEmail(email)).thenReturn(Optional.empty()); + } + + private UserSubscriptionModel whenFindByUserEmail(String email) throws SubscriptionNotFoundException { + return repository.findByUserEmail(email); + } + + private void thenSubscriptionShouldHaveCombinationsUsed(UserSubscriptionModel model, int expected) { + assertThat(model).isNotNull(); + assertThat(model.getCombinationsUsed()).isEqualTo(expected); + } + + private UserSubscriptionEntity givenSubscriptionEntity(String email, String planCode, Long userId, int combinations) { + UserEntity user = new UserEntity(); + user.setId(userId); + user.setEmail(email); + + SubscriptionEntity plan = new SubscriptionEntity("ESTANDAR", planCode); + plan.setPlanCode(planCode); + + UserSubscriptionEntity entity = new UserSubscriptionEntity(); + entity.setId(99L); + entity.setUser(user); + entity.setSubscription(plan); + entity.setCombinationsUsed(combinations); + + return entity; + } + + private void givenExistingSubscription(String email, String planCode, int combinations) { + UserSubscriptionEntity entity = givenSubscriptionEntity(email, planCode, 10L, combinations); + when(userSubscriptionJpaRepository.findByUserEmail(email)).thenReturn(Optional.of(entity)); + when(userSubscriptionJpaRepository.save(any(UserSubscriptionEntity.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + } + + private UserSubscriptionEntity givenExistingSubscriptionReturningEntity(String email, String planCode, int combinations) { + UserSubscriptionEntity entity = givenSubscriptionEntity(email, planCode, 10L, combinations); + when(userSubscriptionJpaRepository.findByUserEmail(email)) + .thenReturn(Optional.of(entity)); + return entity; + } + private void givenExistingPlan(String planCode) { + SubscriptionEntity plan = new SubscriptionEntity("ESTANDAR", planCode); + plan.setPlanCode(planCode); + when(subscriptionJpaRepository.findByPlanCode(planCode)).thenReturn(Optional.of(plan)); + } + + private void givenSavedSubscription(int combinations) { + UserSubscriptionEntity entity = givenSubscriptionEntity("user@mail.com", "PREMIUM", 5L, combinations); + when(userSubscriptionJpaRepository.save(any(UserSubscriptionEntity.class))) + .thenReturn(entity); + } + + private void givenNoSubscription(String email) { + when(userSubscriptionJpaRepository.findByUserEmail(email)) + .thenReturn(Optional.empty()); + } + + private void givenExistingUser(String email, Long id) { + UserEntity user = new UserEntity(); + user.setEmail(email); + user.setId(id); + when(userJpaRepository.findByEmail(email)).thenReturn(user); + } + + private void givenNoUser(String email) { + when(userJpaRepository.findByEmail(email)).thenReturn(null); + } + + + private void givenNoPlan(String planCode) { + when(subscriptionJpaRepository.findByPlanCode(planCode)) + .thenReturn(Optional.empty()); + } + + private UserSubscriptionModel givenSubscriptionModel(String email, String planCode, int used) { + UserSubscriptionModel model = new UserSubscriptionModel(); + model.setUserEmail(email); + model.setPlanCode(planCode); + model.setCombinationsUsed(used); + return model; + } + + private UserSubscriptionModel whenSave(UserSubscriptionModel model) { + return repository.save(model); + } + + private UserSubscriptionModel whenUpdate(UserSubscriptionModel model) { + return repository.update(model); + } + + private void thenSubscriptionHasCombinations(UserSubscriptionModel result, int expected) { + assertThat(result).isNotNull(); + assertThat(result.getCombinationsUsed()).isEqualTo(expected); + } + + private void thenSubscriptionPlanIs(String email, String expectedPlan) { + SubscriptionEntity subscription = + userSubscriptionJpaRepository.findByUserEmail(email).get().getSubscription(); + + assertThat(subscription.getPlanCode()).isEqualTo(expectedPlan); + } + + private void givenPlanExists(String planCode) { + SubscriptionEntity plan = new SubscriptionEntity("ESTANDAR", planCode); + plan.setPlanCode(planCode); + when(subscriptionJpaRepository.findByPlanCode(planCode)).thenReturn(Optional.of(plan)); + } + + private void whenIncrementCounter(String email, String counterType) { + repository.incrementCounter(email, counterType); + } + + private void whenDecrementCounter(String email, String counterType) { + repository.decrementCounter(email, counterType); + } + + private void thenIncrementCombinationsWasCalled(Long userId) { + verify(userSubscriptionJpaRepository).incrementCombinationsByUserId(userId); + } + + private void thenIncrementFavoritesWasCalled(Long userId) { + verify(userSubscriptionJpaRepository).incrementFavoritesByUserId(userId); + } + + private void thenIncrementModelsWasCalled(Long userId) { + verify(userSubscriptionJpaRepository).incrementModelsByUserId(userId); + } + + private void thenDecrementFavoritesWasCalled(Long userId) { + verify(userSubscriptionJpaRepository).decrementFavoritesByUserId(userId); + } + + private void givenUserWithId(String email, Long id) { + UserEntity user = new UserEntity(); + user.setEmail(email); + user.setId(id); + + when(userJpaRepository.findByEmail(email)).thenReturn(user); + } + + private void givenNoUserForEmail(String email) { + when(userJpaRepository.findByEmail(email)).thenReturn(null); + } + + private void thenDecrementFavoritesWasNeverCalled() { + verify(userSubscriptionJpaRepository, never()).decrementFavoritesByUserId(anyLong()); + } +} \ No newline at end of file diff --git a/src/test/java/com/outfitlab/project/presentation/BrandControllerTest.java b/src/test/java/com/outfitlab/project/presentation/BrandControllerTest.java index c4196b7..ae25006 100644 --- a/src/test/java/com/outfitlab/project/presentation/BrandControllerTest.java +++ b/src/test/java/com/outfitlab/project/presentation/BrandControllerTest.java @@ -16,18 +16,61 @@ class BrandControllerTest { - private GetAllBrands getAllBrands = mock(GetAllBrands.class);; + private GetAllBrands getAllBrands = mock(GetAllBrands.class); private GetBrandAndGarmentsByBrandCode getBrandAndGarmentsByBrandCode = mock(GetBrandAndGarmentsByBrandCode.class); private ActivateBrand activateBrand = mock(ActivateBrand.class); private DesactivateBrand desactivateBrand = mock(DesactivateBrand.class); private GetAllBrandsWithRelatedUsers getAllBrandsWithRelatedUsers = mock(GetAllBrandsWithRelatedUsers.class); private GetNotificationsNewBrands getNotificationsNewBrands = mock(GetNotificationsNewBrands.class); - private BrandController brandController = new BrandController(getAllBrands, getBrandAndGarmentsByBrandCode, activateBrand, desactivateBrand, getAllBrandsWithRelatedUsers, getNotificationsNewBrands); + + private BrandController controller = new BrandController( + getAllBrands, + getBrandAndGarmentsByBrandCode, + activateBrand, + desactivateBrand, + getAllBrandsWithRelatedUsers, + getNotificationsNewBrands); + + @Test + void givenValidPageWhenGetMarcasThenReturnOk() throws Exception { + givenBrandsExist(); + ResponseEntity response = whenCallGetMarcas(0); + thenResponseOkWithBrands(response); + thenGetAllBrandsCalledOnce(); + } + + @Test + void givenNoBrandsWhenGetMarcasThenReturn404() throws Exception { + givenBrandsNotFound(); + ResponseEntity response = whenCallGetMarcas(0); + thenResponseNotFound(response, "No se encontraron marcas"); + } + + @Test + void givenNegativePageWhenGetMarcasThenReturn404() throws Exception { + givenNegativePageError(); + ResponseEntity response = whenCallGetMarcas(-1); + thenResponseNotFound(response, "Página menor que cero"); + } + + @Test + void givenNonExistingBrandWhenGetBrandGarmentsThenReturn404() throws Exception { + givenBrandNotFoundForGetBrandGarments(); + ResponseEntity response = whenCallGetBrandAndGarments("puma", 0); + thenResponseNotFound(response, "Marca no encontrada"); + } @Test - public void givenValidPageWhenGetMarcasThenReturnsOkWithContent() throws Exception, BrandsNotFoundException { - BrandDTO brand1 = new BrandDTO("nike", "Nike", "https://logos.com/logaso.png"); - BrandDTO brand2 = new BrandDTO("adidas", "Adidas", "https://logos.com/logaso.png"); + void givenNegativePageWhenGetBrandGarmentsThenReturn404() throws Exception { + givenNegativePageErrorForBrandGarments(); + ResponseEntity response = whenCallGetBrandAndGarments("nike", -1); + thenResponseNotFound(response, "Página menor que cero"); + } + +// privadosss ---- + private void givenBrandsExist() throws Exception { + BrandDTO brand1 = new BrandDTO("nike", "Nike", "https://logos.com/logaso.png"); + BrandDTO brand2 = new BrandDTO("adidas", "Adidas", "https://logos.com/logaso.png"); Page pageResult = new PageImpl<>( List.of(brand1, brand2), @@ -36,57 +79,51 @@ public void givenValidPageWhenGetMarcasThenReturnsOkWithContent() throws Excepti ); when(getAllBrands.execute(0)).thenReturn(pageResult); - ResponseEntity response = brandController.getMarcas(0); - - - assertEquals(200, response.getStatusCode().value()); - Map body = (Map) response.getBody(); - assertNotNull(body); - assertEquals(2L, body.get("totalElements")); - assertEquals(0, body.get("page")); - assertNotNull(body.get("content")); - - verify(getAllBrands, times(1)).execute(0); } - @Test - public void givenNoBrandsFoundWhenGetMarcasThenReturns404() throws Exception, BrandsNotFoundException { - when(getAllBrands.execute(0)).thenThrow(new BrandsNotFoundException("No se encontraron marcas")); - - ResponseEntity response = brandController.getMarcas(0); - - assertEquals(404, response.getStatusCode().value()); - assertEquals("No se encontraron marcas", response.getBody()); + private void givenBrandsNotFound() throws Exception { + when(getAllBrands.execute(0)) + .thenThrow(new BrandsNotFoundException("No se encontraron marcas")); } - @Test - public void givenNegativePageWhenGetMarcasThenReturns404() throws Exception, BrandsNotFoundException { - when(getAllBrands.execute(-1)).thenThrow(new PageLessThanZeroException("Página menor que cero")); - - ResponseEntity response = brandController.getMarcas(-1); + private void givenNegativePageError() throws Exception { + when(getAllBrands.execute(-1)) + .thenThrow(new PageLessThanZeroException("Página menor que cero")); + } - assertEquals(404, response.getStatusCode().value()); - assertEquals("Página menor que cero", response.getBody()); + private void givenBrandNotFoundForGetBrandGarments() throws Exception { + when(getBrandAndGarmentsByBrandCode.execute("puma", 0)) + .thenThrow(new BrandsNotFoundException("Marca no encontrada")); } - @Test - public void givenNoExistingBrandWhenGetBrandAndGarmentsByBrandCodeThenReturns404() throws Exception, BrandsNotFoundException { - when(getBrandAndGarmentsByBrandCode.execute("puma", 0)).thenThrow(new BrandsNotFoundException("Marca no encontrada")); + private void givenNegativePageErrorForBrandGarments() throws Exception { + when(getBrandAndGarmentsByBrandCode.execute("nike", -1)) + .thenThrow(new PageLessThanZeroException("Página menor que cero")); + } - ResponseEntity response = brandController.getBrandAndGarmentsByBrandCode("puma", 0); + private ResponseEntity whenCallGetMarcas(int page) { + return controller.getMarcas(page); + } - assertEquals(404, response.getStatusCode().value()); - assertEquals("Marca no encontrada", response.getBody()); + private ResponseEntity whenCallGetBrandAndGarments(String brand, int page) { + return controller.getBrandAndGarmentsByBrandCode(brand, page); } - @Test - public void givenNegativePageWhenGetBrandAndGarmentsByBrandCodeThenReturns404() throws Exception, BrandsNotFoundException { - when(getBrandAndGarmentsByBrandCode.execute("nike", -1)).thenThrow(new PageLessThanZeroException("Página menor que cero")); + private void thenResponseOkWithBrands(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + Map body = (Map) response.getBody(); + assertNotNull(body); + assertEquals(2L, body.get("totalElements")); + assertEquals(0, body.get("page")); + assertNotNull(body.get("content")); + } - ResponseEntity response = brandController.getBrandAndGarmentsByBrandCode("nike", -1); + private void thenGetAllBrandsCalledOnce() { + verify(getAllBrands, times(1)).execute(0); + } + private void thenResponseNotFound(ResponseEntity response, String expectedMessage) { assertEquals(404, response.getStatusCode().value()); - assertEquals("Página menor que cero", response.getBody()); + assertEquals(expectedMessage, response.getBody()); } } - diff --git a/src/test/java/com/outfitlab/project/presentation/CategoryControllerTest.java b/src/test/java/com/outfitlab/project/presentation/CategoryControllerTest.java new file mode 100644 index 0000000..d06e246 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/CategoryControllerTest.java @@ -0,0 +1,81 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.model.ClimaModel; +import com.outfitlab.project.domain.model.ColorModel; +import com.outfitlab.project.domain.model.OcasionModel; +import com.outfitlab.project.domain.model.dto.RecommendationCategoriesDTO; +import com.outfitlab.project.domain.useCases.recomendations.GetAllClima; +import com.outfitlab.project.domain.useCases.recomendations.GetAllColors; +import com.outfitlab.project.domain.useCases.recomendations.GetAllOcacion; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.http.ResponseEntity; + +import java.util.Arrays; +import java.util.List; + +import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CategoryControllerTest { + + private GetAllClima getAllClima; + private GetAllOcacion getAllOcacion; + private GetAllColors getAllColors; + + private CategoryController controller; + + @BeforeEach + void setUp() { + getAllClima = Mockito.mock(GetAllClima.class); + getAllOcacion = Mockito.mock(GetAllOcacion.class); + getAllColors = Mockito.mock(GetAllColors.class); + + controller = new CategoryController(getAllClima, getAllOcacion, getAllColors); + } + + @Test + void shouldReturnRecommendationCategories() { + + List climas = givenClimas(); + List ocasiones = givenOcasiones(); + List colores = givenColores(); + + mockUseCases(climas, ocasiones, colores); + + ResponseEntity response = whenCallingEndpoint(); + + thenResponseIsCorrect(response, climas, ocasiones, colores); + } + + private List givenClimas() { + return Arrays.asList(new ClimaModel(1L, "calido"), new ClimaModel(2L,"frio")); + } + + private List givenOcasiones() { + return Arrays.asList(new OcasionModel(1L,"fiesta"), new OcasionModel(2L,"trabajo")); + } + + private List givenColores() { + return Arrays.asList(new ColorModel(1L, "rojo", 1), new ColorModel(2L, "azul", 1)); + } + + private void mockUseCases(List climas, List ocasiones, List colores) { + when(getAllClima.execute()).thenReturn(climas); + when(getAllOcacion.execute()).thenReturn(ocasiones); + when(getAllColors.execute()).thenReturn(colores); + } + + private ResponseEntity whenCallingEndpoint() { + return controller.getRecommendationCategories(); + } + + private void thenResponseIsCorrect(ResponseEntity response, List climas, List ocasiones, List colores) { + assertEquals(200, response.getStatusCodeValue()); + RecommendationCategoriesDTO body = response.getBody(); + assertEquals(climas, body.getClimas()); + assertEquals(ocasiones, body.getOcasiones()); + assertEquals(colores, body.getColores()); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/CombinationControllerTest.java b/src/test/java/com/outfitlab/project/presentation/CombinationControllerTest.java new file mode 100644 index 0000000..ca1f149 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/CombinationControllerTest.java @@ -0,0 +1,408 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.exceptions.*; +import com.outfitlab.project.domain.model.CombinationModel; +import com.outfitlab.project.domain.model.PrendaModel; +import com.outfitlab.project.domain.model.UserCombinationFavoriteModel; +import com.outfitlab.project.domain.model.dto.PageDTO; +import com.outfitlab.project.domain.useCases.combination.CreateCombination; +import com.outfitlab.project.domain.useCases.combination.GetCombinationByPrendas; +import com.outfitlab.project.domain.useCases.combinationAttempt.RegisterCombinationAttempt; +import com.outfitlab.project.domain.useCases.combinationFavorite.AddCombinationToFavourite; +import com.outfitlab.project.domain.useCases.combinationFavorite.DeleteCombinationFromFavorite; +import com.outfitlab.project.domain.useCases.combinationFavorite.GetCombinationFavoritesForUserByEmail; +import com.outfitlab.project.domain.useCases.garment.GetGarmentByCode; +import com.outfitlab.project.presentation.dto.RegisterAttemptRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class CombinationControllerTest { + + private AddCombinationToFavourite addCombinationToFavourite; + private DeleteCombinationFromFavorite deleteCombinationFromFavorite; + private GetCombinationFavoritesForUserByEmail getCombinationFavoritesForUserByEmail; + private GetGarmentByCode getGarmentByCode; + private GetCombinationByPrendas getCombinationByPrendas; + private CreateCombination createCombination; + private RegisterCombinationAttempt registerCombinationAttempt; + + private CombinationController controller; + + @BeforeEach + void setUp() { + addCombinationToFavourite = mock(AddCombinationToFavourite.class); + deleteCombinationFromFavorite = mock(DeleteCombinationFromFavorite.class); + getCombinationFavoritesForUserByEmail = mock(GetCombinationFavoritesForUserByEmail.class); + getGarmentByCode = mock(GetGarmentByCode.class); + getCombinationByPrendas = mock(GetCombinationByPrendas.class); + createCombination = mock(CreateCombination.class); + registerCombinationAttempt = mock(RegisterCombinationAttempt.class); + + controller = new CombinationController( + addCombinationToFavourite, + deleteCombinationFromFavorite, + getCombinationFavoritesForUserByEmail, + getGarmentByCode, + getCombinationByPrendas, + createCombination, + registerCombinationAttempt); + } + + // ========== registerAttempt Tests ========== + + @Test + void givenValidRequestWithExistingCombinationWhenRegisterAttemptThenReturnOk() throws Exception { + RegisterAttemptRequest request = givenValidRegisterAttemptRequest(); + PrendaModel sup = givenGarmentExists("sup-code", 1L); + PrendaModel inf = givenGarmentExists("inf-code", 2L); + CombinationModel combination = givenCombinationExists(1L, 2L); + givenAttemptRegistered(123L); + + ResponseEntity response = whenCallRegisterAttempt(request); + + thenResponseOkWithMessage(response, "Combinacion registrada con ID: 123"); + thenVerifyGarmentByCodeCalled("sup-code"); + thenVerifyGarmentByCodeCalled("inf-code"); + thenVerifyGetCombinationByPrendasCalled(1L, 2L); + thenVerifyRegisterAttemptCalled(); + } + + @Test + void givenValidRequestWithNewCombinationWhenRegisterAttemptThenReturnOk() throws Exception { + RegisterAttemptRequest request = givenValidRegisterAttemptRequest(); + PrendaModel sup = givenGarmentExists("sup-code", 1L); + PrendaModel inf = givenGarmentExists("inf-code", 2L); + givenCombinationNotFound(1L, 2L); + CombinationModel newCombination = givenCombinationCreated(sup, inf); + givenAttemptRegistered(456L); + + ResponseEntity response = whenCallRegisterAttempt(request); + + thenResponseOkWithMessage(response, "Combinacion registrada con ID: 456"); + thenVerifyCreateCombinationCalled(sup, inf); + } + + @Test + void givenInvalidGarmentCodeWhenRegisterAttemptThenReturn404() throws Exception { + RegisterAttemptRequest request = givenValidRegisterAttemptRequest(); + givenGarmentNotFound("sup-code"); + + ResponseEntity response = whenCallRegisterAttempt(request); + + thenResponseNotFound(response, "Prenda no encontrada"); + } + + // ========== addCombinationToFavorite Tests ========== + + @Test + void givenValidUrlWhenAddCombinationToFavoriteThenReturnOk() throws Exception { + String url = givenValidCombinationUrl(); + givenAuthenticatedUser("user@test.com"); + givenFavoriteAdded("Favorito agregado"); + + ResponseEntity response = whenCallAddCombinationToFavorite(url); + + thenResponseOk(response); + thenVerifyAddCombinationToFavouriteCalled(url, "user@test.com"); + } + + @Test + void givenEmptyUrlWhenAddCombinationToFavoriteThenReturn404() { + String url = ""; + + ResponseEntity response = whenCallAddCombinationToFavorite(url); + + thenResponseNotFound(response, "El parámetro: no puede estar vacío."); + } + + @Test + void givenNullUrlWhenAddCombinationToFavoriteThenReturn404() { + ResponseEntity response = whenCallAddCombinationToFavorite(null); + + thenResponseNotFound(response, "El parámetro: null no puede estar vacío."); + } + + @Test + void givenPlanLimitExceededWhenAddCombinationToFavoriteThenReturn403() throws Exception { + String url = givenValidCombinationUrl(); + givenAuthenticatedUser("user@test.com"); + givenPlanLimitExceeded(); + + ResponseEntity response = whenCallAddCombinationToFavorite(url); + + thenResponseForbidden(response); + thenResponseContainsUpgradeRequired(response); + } + + @Test + void givenUserNotFoundWhenAddCombinationToFavoriteThenReturn404() throws Exception { + String url = givenValidCombinationUrl(); + givenAuthenticatedUser("user@test.com"); + givenUserNotFound(); + + ResponseEntity response = whenCallAddCombinationToFavorite(url); + + thenResponseNotFound(response, "Usuario no encontrado"); + } + + @Test + void givenFavoriteAlreadyExistsWhenAddCombinationToFavoriteThenReturn404() throws Exception { + String url = givenValidCombinationUrl(); + givenAuthenticatedUser("user@test.com"); + givenFavoriteAlreadyExists(); + + ResponseEntity response = whenCallAddCombinationToFavorite(url); + + thenResponseNotFound(response, "Favorito ya existe"); + } + + // ========== deleteCombinationFromFavorite Tests ========== + + @Test + void givenValidUrlWhenDeleteCombinationFromFavoriteThenReturnOk() throws Exception { + String url = givenValidCombinationUrl(); + givenAuthenticatedUser("user@test.com"); + givenFavoriteDeleted("Favorito eliminado"); + + ResponseEntity response = whenCallDeleteCombinationFromFavorite(url); + + thenResponseOk(response); + thenVerifyDeleteCombinationFromFavoriteCalled(url, "user@test.com"); + } + + @Test + void givenFavoriteNotFoundWhenDeleteCombinationFromFavoriteThenReturn404() throws Exception { + String url = givenValidCombinationUrl(); + givenAuthenticatedUser("user@test.com"); + givenFavoriteNotFoundForDelete(); + + ResponseEntity response = whenCallDeleteCombinationFromFavorite(url); + + thenResponseNotFound(response, "Favorito no encontrado"); + } + + // ========== getFavorites Tests ========== + + @Test + void givenValidPageWhenGetFavoritesThenReturnOk() throws Exception { + givenAuthenticatedUser("user@test.com"); + givenFavoritesExist(); + + ResponseEntity response = whenCallGetFavorites(0); + + thenResponseOk(response); + thenVerifyGetFavoritesCalled("user@test.com", 0); + } + + @Test + void givenNegativePageWhenGetFavoritesThenReturn404() throws Exception { + givenAuthenticatedUser("user@test.com"); + givenNegativePageError(); + + ResponseEntity response = whenCallGetFavorites(-1); + + thenResponseNotFound(response, "Página menor que cero"); + } + + @Test + void givenNoFavoritesWhenGetFavoritesThenReturnOk() throws Exception { + givenAuthenticatedUser("user@test.com"); + givenNoFavorites(); + + ResponseEntity response = whenCallGetFavorites(0); + + thenResponseOk(response); + } + + // ========== GIVEN Methods ========== + + private RegisterAttemptRequest givenValidRegisterAttemptRequest() { + return new RegisterAttemptRequest( + "user@test.com", + "sup-code", + "inf-code", + "http://image.url/combo.jpg"); + } + + private PrendaModel givenGarmentExists(String code, Long id) { + PrendaModel garment = mock(PrendaModel.class); + when(garment.getId()).thenReturn(id); + when(getGarmentByCode.execute(code)).thenReturn(garment); + return garment; + } + + private void givenGarmentNotFound(String code) { + when(getGarmentByCode.execute(code)) + .thenThrow(new GarmentNotFoundException("Prenda no encontrada")); + } + + private CombinationModel givenCombinationExists(Long supId, Long infId) { + CombinationModel combination = mock(CombinationModel.class); + when(getCombinationByPrendas.execute(supId, infId)).thenReturn(combination); + return combination; + } + + private void givenCombinationNotFound(Long supId, Long infId) { + when(getCombinationByPrendas.execute(supId, infId)) + .thenThrow(new CombinationNotFoundException("Combinación no encontrada")); + } + + private CombinationModel givenCombinationCreated(PrendaModel sup, PrendaModel inf) { + CombinationModel combination = mock(CombinationModel.class); + when(createCombination.execute(sup, inf)).thenReturn(combination); + return combination; + } + + private void givenAttemptRegistered(Long attemptId) { + when(registerCombinationAttempt.execute(anyString(), any(CombinationModel.class), anyString())) + .thenReturn(attemptId); + } + + private String givenValidCombinationUrl() { + return "http://combination.url/combo123.jpg"; + } + + private void givenAuthenticatedUser(String email) { + Authentication authentication = mock(Authentication.class); + when(authentication.getName()).thenReturn(email); + SecurityContext securityContext = mock(SecurityContext.class); + when(securityContext.getAuthentication()).thenReturn(authentication); + SecurityContextHolder.setContext(securityContext); + } + + private void givenFavoriteAdded(String message) throws Exception { + when(addCombinationToFavourite.execute(anyString(), anyString())).thenReturn(message); + } + + private void givenPlanLimitExceeded() throws Exception { + PlanLimitExceededException exception = new PlanLimitExceededException( + "Plan limit exceeded", "favorites", 5, 10); + when(addCombinationToFavourite.execute(anyString(), anyString())).thenThrow(exception); + } + + private void givenUserNotFound() throws Exception { + when(addCombinationToFavourite.execute(anyString(), anyString())) + .thenThrow(new UserNotFoundException("Usuario no encontrado")); + } + + private void givenFavoriteAlreadyExists() throws Exception { + when(addCombinationToFavourite.execute(anyString(), anyString())) + .thenThrow(new UserCombinationFavoriteAlreadyExistsException("Favorito ya existe")); + } + + private void givenFavoriteDeleted(String message) throws Exception { + when(deleteCombinationFromFavorite.execute(anyString(), anyString())).thenReturn(message); + } + + private void givenFavoriteNotFoundForDelete() throws Exception { + when(deleteCombinationFromFavorite.execute(anyString(), anyString())) + .thenThrow(new UserCombinationFavoriteNotFoundException("Favorito no encontrado")); + } + + private void givenFavoritesExist() { + PageDTO favorites = new PageDTO<>( + Arrays.asList(mock(UserCombinationFavoriteModel.class)), + 0, 10, 1, 1, true); + when(getCombinationFavoritesForUserByEmail.execute(anyString(), anyInt())).thenReturn(favorites); + } + + private void givenNegativePageError() throws Exception { + when(getCombinationFavoritesForUserByEmail.execute(anyString(), eq(-1))) + .thenThrow(new PageLessThanZeroException("Página menor que cero")); + } + + private void givenNoFavorites() throws Exception { + when(getCombinationFavoritesForUserByEmail.execute(anyString(), anyInt())) + .thenThrow(new FavoritesException("No hay favoritos")); + } + + // ========== WHEN Methods ========== + + private ResponseEntity whenCallRegisterAttempt(RegisterAttemptRequest request) { + return controller.registerAttempt(request); + } + + private ResponseEntity whenCallAddCombinationToFavorite(String url) { + return controller.addCombinationToFavorite(url); + } + + private ResponseEntity whenCallDeleteCombinationFromFavorite(String url) { + return controller.deleteCombinationFromFavorite(url); + } + + private ResponseEntity whenCallGetFavorites(int page) { + return controller.getFavorites(page); + } + + // ========== THEN Methods ========== + + private void thenResponseOk(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + assertNotNull(response.getBody()); + } + + private void thenResponseOkWithMessage(ResponseEntity response, String expectedMessage) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expectedMessage, response.getBody()); + } + + private void thenResponseNotFound(ResponseEntity response, String expectedMessage) { + assertEquals(404, response.getStatusCode().value()); + assertEquals(expectedMessage, response.getBody()); + } + + private void thenResponseForbidden(ResponseEntity response) { + assertEquals(403, response.getStatusCode().value()); + } + + private void thenResponseContainsUpgradeRequired(ResponseEntity response) { + Map body = (Map) response.getBody(); + assertNotNull(body); + assertEquals(true, body.get("upgradeRequired")); + assertNotNull(body.get("error")); + assertNotNull(body.get("limitType")); + } + + private void thenVerifyGarmentByCodeCalled(String code) { + verify(getGarmentByCode, times(1)).execute(code); + } + + private void thenVerifyGetCombinationByPrendasCalled(Long supId, Long infId) { + verify(getCombinationByPrendas, times(1)).execute(supId, infId); + } + + private void thenVerifyCreateCombinationCalled(PrendaModel sup, PrendaModel inf) { + verify(createCombination, times(1)).execute(sup, inf); + } + + private void thenVerifyRegisterAttemptCalled() { + verify(registerCombinationAttempt, times(1)) + .execute(anyString(), any(CombinationModel.class), anyString()); + } + + private void thenVerifyAddCombinationToFavouriteCalled(String url, String email) throws Exception { + verify(addCombinationToFavourite, times(1)).execute(url, email); + } + + private void thenVerifyDeleteCombinationFromFavoriteCalled(String url, String email) throws Exception { + verify(deleteCombinationFromFavorite, times(1)).execute(url, email); + } + + private void thenVerifyGetFavoritesCalled(String email, int page) throws Exception { + verify(getCombinationFavoritesForUserByEmail, times(1)).execute(email, page); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/DashboardControllerTest.java b/src/test/java/com/outfitlab/project/presentation/DashboardControllerTest.java new file mode 100644 index 0000000..0e62674 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/DashboardControllerTest.java @@ -0,0 +1,226 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.model.dto.PrendaDashboardDTO.*; +import com.outfitlab.project.domain.useCases.dashboard.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class DashboardControllerTest { + + private GetTopPrendas getTopPrendas; + private GetActividadPorDias getActividadPorDias; + private GetTopCombos getTopCombos; + private GetColorConversion getColorConversion; + + private DashboardController controller; + + @BeforeEach + void setUp() { + getTopPrendas = mock(GetTopPrendas.class); + getActividadPorDias = mock(GetActividadPorDias.class); + getTopCombos = mock(GetTopCombos.class); + getColorConversion = mock(GetColorConversion.class); + + controller = new DashboardController( + getTopPrendas, + getActividadPorDias, + getTopCombos, + getColorConversion); + } + + // ========== getTopPrendas Tests ========== + + @Test + void givenDefaultParamsWhenGetTopPrendasThenReturnOk() { + List topPrendas = givenTopPrendasExist(); + + ResponseEntity> response = whenCallGetTopPrendas(5, null); + + thenResponseOkWithTopPrendas(response, topPrendas); + thenVerifyGetTopPrendasCalled(5, null); + } + + @Test + void givenBrandCodeWhenGetTopPrendasThenReturnOk() { + List topPrendas = givenTopPrendasExistForBrand(); + + ResponseEntity> response = whenCallGetTopPrendas(10, "nike"); + + thenResponseOkWithTopPrendas(response, topPrendas); + thenVerifyGetTopPrendasCalled(10, "nike"); + } + + // ========== getActividadPorDias Tests ========== + + @Test + void whenGetActividadPorDiasThenReturnOk() { + List actividad = givenActividadExists(); + + ResponseEntity> response = whenCallGetActividadPorDias(); + + thenResponseOkWithActividad(response, actividad); + thenVerifyGetActividadPorDiasCalled(); + } + + // ========== getTopCombos Tests ========== + + @Test + void givenDefaultParamsWhenGetTopCombosThenReturnOk() { + List topCombos = givenTopCombosExist(); + + ResponseEntity> response = whenCallGetTopCombos(5, null); + + thenResponseOkWithTopCombos(response, topCombos); + thenVerifyGetTopCombosCalled(5, null); + } + + @Test + void givenBrandCodeWhenGetTopCombosThenReturnOk() { + List topCombos = givenTopCombosExistForBrand(); + + ResponseEntity> response = whenCallGetTopCombos(3, "adidas"); + + thenResponseOkWithTopCombos(response, topCombos); + thenVerifyGetTopCombosCalled(3, "adidas"); + } + + // ========== getColorConversion Tests ========== + + @Test + void givenNoBrandCodeWhenGetColorConversionThenReturnOk() { + List colorConversion = givenColorConversionExists(); + + ResponseEntity> response = whenCallGetColorConversion(null); + + thenResponseOkWithColorConversion(response, colorConversion); + thenVerifyGetColorConversionCalled(null); + } + + @Test + void givenBrandCodeWhenGetColorConversionThenReturnOk() { + List colorConversion = givenColorConversionExistsForBrand(); + + ResponseEntity> response = whenCallGetColorConversion("puma"); + + thenResponseOkWithColorConversion(response, colorConversion); + thenVerifyGetColorConversionCalled("puma"); + } + + // ========== GIVEN Methods ========== + + private List givenTopPrendasExist() { + List topPrendas = Arrays.asList( + new TopPrenda(1L, "Prenda 1", "Rojo", "url1", 100, List.of()), + new TopPrenda(2L, "Prenda 2", "Azul", "url2", 80, List.of())); + when(getTopPrendas.execute(5, null)).thenReturn(topPrendas); + return topPrendas; + } + + private List givenTopPrendasExistForBrand() { + List topPrendas = Arrays.asList( + new TopPrenda(1L, "Nike Shirt", "Negro", "url1", 50, List.of())); + when(getTopPrendas.execute(10, "nike")).thenReturn(topPrendas); + return topPrendas; + } + + private List givenActividadExists() { + List actividad = Arrays.asList( + new DiaPrueba(1, 10), + new DiaPrueba(2, 15)); + when(getActividadPorDias.execute()).thenReturn(actividad); + return actividad; + } + + private List givenTopCombosExist() { + List topCombos = Arrays.asList( + new ComboPopular("combo1", "combo2", "url1", "url2", 50, 10)); + when(getTopCombos.execute(5, null)).thenReturn(topCombos); + return topCombos; + } + + private List givenTopCombosExistForBrand() { + List topCombos = Arrays.asList( + new ComboPopular("combo1", "combo2", "url1", "url2", 30, 5)); + when(getTopCombos.execute(3, "adidas")).thenReturn(topCombos); + return topCombos; + } + + private List givenColorConversionExists() { + List colorConversion = Arrays.asList( + new ColorConversion("Rojo", 100, 80, 80.0), + new ColorConversion("Azul", 50, 40, 80.0)); + when(getColorConversion.execute(null)).thenReturn(colorConversion); + return colorConversion; + } + + private List givenColorConversionExistsForBrand() { + List colorConversion = Arrays.asList( + new ColorConversion("Negro", 60, 50, 83.3)); + when(getColorConversion.execute("puma")).thenReturn(colorConversion); + return colorConversion; + } + + // ========== WHEN Methods ========== + + private ResponseEntity> whenCallGetTopPrendas(int topN, String brandCode) { + return controller.getTopPrendas(topN, brandCode); + } + + private ResponseEntity> whenCallGetActividadPorDias() { + return controller.getActividadPorDias(); + } + + private ResponseEntity> whenCallGetTopCombos(int topN, String brandCode) { + return controller.getTopCombos(topN, brandCode); + } + + private ResponseEntity> whenCallGetColorConversion(String brandCode) { + return controller.getColorConversion(brandCode); + } + + // ========== THEN Methods ========== + + private void thenResponseOkWithTopPrendas(ResponseEntity> response, List expected) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expected, response.getBody()); + } + + private void thenResponseOkWithActividad(ResponseEntity> response, List expected) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expected, response.getBody()); + } + + private void thenResponseOkWithTopCombos(ResponseEntity> response, List expected) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expected, response.getBody()); + } + + private void thenResponseOkWithColorConversion(ResponseEntity> response, + List expected) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expected, response.getBody()); + } + + private void thenVerifyGetTopPrendasCalled(int topN, String brandCode) { + verify(getTopPrendas, times(1)).execute(topN, brandCode); + } + + private void thenVerifyGetActividadPorDiasCalled() { + verify(getActividadPorDias, times(1)).execute(); + } + + private void thenVerifyGetTopCombosCalled(int topN, String brandCode) { + verify(getTopCombos, times(1)).execute(topN, brandCode); + } + + private void thenVerifyGetColorConversionCalled(String brandCode) { + verify(getColorConversion, times(1)).execute(brandCode); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/FashnControllerTest.java b/src/test/java/com/outfitlab/project/presentation/FashnControllerTest.java index a04e3d5..7f219e2 100644 --- a/src/test/java/com/outfitlab/project/presentation/FashnControllerTest.java +++ b/src/test/java/com/outfitlab/project/presentation/FashnControllerTest.java @@ -1,8 +1,6 @@ package com.outfitlab.project.presentation; -import com.outfitlab.project.domain.exceptions.FashnApiException; -import com.outfitlab.project.domain.exceptions.PredictionFailedException; -import com.outfitlab.project.domain.exceptions.PredictionTimeoutException; +import com.outfitlab.project.domain.exceptions.*; import com.outfitlab.project.domain.useCases.fashn.CombinePrendas; import com.outfitlab.project.domain.useCases.subscription.CheckUserPlanLimit; import com.outfitlab.project.domain.useCases.subscription.IncrementUsageCounter; @@ -27,7 +25,7 @@ class FashnControllerTest { private UserDetails mockUser; @BeforeEach - public void setUp() { + void setUp() { combinePrendas = mock(CombinePrendas.class); checkUserPlanLimit = mock(CheckUserPlanLimit.class); incrementUsageCounter = mock(IncrementUsageCounter.class); @@ -37,71 +35,117 @@ public void setUp() { } @Test - public void givenValidRequestWhenCombineThenReturnsOk() throws Exception { - CombineRequest request = new CombineRequest(); - request.setTop("top_url"); - request.setBottom("bottom_url"); - request.setAvatarType("fullbody"); + void givenValidRequestWhenCombineThenReturnsOk() throws Exception { + CombineRequest request = givenValidCombineRequest(); + givenCombineReturns("result_url"); - when(combinePrendas.execute(any(), any(UserDetails.class))).thenReturn("result_url"); + ResponseEntity response = whenExecuteCombine(request); - ResponseEntity response = fashnController.combine(request, mockUser); + thenShouldReturnOk(response, "result_url"); + thenVerifyCombineCalledOnce(); + } - assertEquals(200, response.getStatusCode().value()); - assertNotNull(response.getBody()); - assertEquals("OK", response.getBody().getStatus()); - assertEquals("result_url", response.getBody().getImageUrl()); + @Test + void givenPredictionFailedWhenCombineThenReturnsFailed() throws Exception { + CombineRequest request = givenRequestWithTop(); + givenCombineThrowsPredictionFailed("Prediction failed"); - verify(combinePrendas, times(1)).execute(any(), any(UserDetails.class)); + ResponseEntity response = whenExecuteCombine(request); + + thenShouldReturnFailed(response, "Prediction failed"); + } + + @Test + void givenPredictionTimeoutWhenCombineThenReturnsTimeout() throws Exception { + CombineRequest request = givenRequestWithBottom(); + givenCombineThrowsPredictionTimeout("Prediction timeout"); + + ResponseEntity response = whenExecuteCombine(request); + + thenShouldReturnTimeout(response, "Prediction timeout"); } @Test - public void givenPredictionFailedExceptionWhenCombineThenReturnsFailed() throws Exception { + void givenFashnApiExceptionWhenCombineThenReturnsApiError() throws Exception { + CombineRequest request = givenRequestWithTop(); + givenCombineThrowsApiError("Fashn API error"); + + ResponseEntity response = whenExecuteCombine(request); + + thenShouldReturnApiError(response, "Fashn API error"); + } + + private CombineRequest givenValidCombineRequest() { CombineRequest request = new CombineRequest(); request.setTop("top_url"); + request.setBottom("bottom_url"); request.setAvatarType("fullbody"); + return request; + } - when(combinePrendas.execute(any(), any(UserDetails.class))) - .thenThrow(new PredictionFailedException("Prediction failed")); - - ResponseEntity response = fashnController.combine(request, mockUser); - - assertEquals(502, response.getStatusCode().value()); - assertNotNull(response.getBody()); - assertEquals("FAILED", response.getBody().getStatus()); - assertEquals("Prediction failed", response.getBody().getErrorMessage()); + private CombineRequest givenRequestWithTop() { + CombineRequest request = new CombineRequest(); + request.setTop("top_url"); + request.setAvatarType("fullbody"); + return request; } - @Test - public void givenPredictionTimeoutExceptionWhenCombineThenReturnsTimeout() throws Exception { + private CombineRequest givenRequestWithBottom() { CombineRequest request = new CombineRequest(); request.setBottom("bottom_url"); request.setAvatarType("fullbody"); + return request; + } + private void givenCombineReturns(String url) throws Exception { when(combinePrendas.execute(any(), any(UserDetails.class))) - .thenThrow(new PredictionTimeoutException("Prediction timeout")); + .thenReturn(url); + } - ResponseEntity response = fashnController.combine(request, mockUser); + private void givenCombineThrowsPredictionFailed(String message) throws Exception { + when(combinePrendas.execute(any(), any(UserDetails.class))) + .thenThrow(new PredictionFailedException(message)); + } - assertEquals(504, response.getStatusCode().value()); - assertNotNull(response.getBody()); - assertEquals("TIMEOUT", response.getBody().getStatus()); - assertEquals("Prediction timeout", response.getBody().getErrorMessage()); + private void givenCombineThrowsPredictionTimeout(String message) throws Exception { + when(combinePrendas.execute(any(), any(UserDetails.class))) + .thenThrow(new PredictionTimeoutException(message)); } - @Test - public void givenFashnApiExceptionWhenCombineThenReturnsError() throws Exception { - CombineRequest request = new CombineRequest(); - request.setTop("top_url"); - request.setAvatarType("fullbody"); + private void givenCombineThrowsApiError(String message) throws Exception { + when(combinePrendas.execute(any(), any(UserDetails.class))) + .thenThrow(new FashnApiException(message)); + } - when(combinePrendas.execute(any(), any(UserDetails.class))).thenThrow(new FashnApiException("Fashn API error")); + private ResponseEntity whenExecuteCombine(CombineRequest request) { + return fashnController.combine(request, mockUser); + } - ResponseEntity response = fashnController.combine(request, mockUser); + private void thenShouldReturnOk(ResponseEntity response, String expectedUrl) { + assertEquals(200, response.getStatusCode().value()); + assertEquals("OK", response.getBody().getStatus()); + assertEquals(expectedUrl, response.getBody().getImageUrl()); + } + + private void thenVerifyCombineCalledOnce() throws PlanLimitExceededException, SubscriptionNotFoundException { + verify(combinePrendas, times(1)).execute(any(), any(UserDetails.class)); + } + + private void thenShouldReturnFailed(ResponseEntity response, String message) { + assertEquals(502, response.getStatusCode().value()); + assertEquals("FAILED", response.getBody().getStatus()); + assertEquals(message, response.getBody().getErrorMessage()); + } + + private void thenShouldReturnTimeout(ResponseEntity response, String message) { + assertEquals(504, response.getStatusCode().value()); + assertEquals("TIMEOUT", response.getBody().getStatus()); + assertEquals(message, response.getBody().getErrorMessage()); + } + private void thenShouldReturnApiError(ResponseEntity response, String message) { assertEquals(502, response.getStatusCode().value()); - assertNotNull(response.getBody()); assertEquals("ERROR", response.getBody().getStatus()); - assertEquals("Fashn API error", response.getBody().getErrorMessage()); + assertEquals(message, response.getBody().getErrorMessage()); } } diff --git a/src/test/java/com/outfitlab/project/presentation/GarmentControllerTest.java b/src/test/java/com/outfitlab/project/presentation/GarmentControllerTest.java new file mode 100644 index 0000000..cd36169 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/GarmentControllerTest.java @@ -0,0 +1,520 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.exceptions.*; +import com.outfitlab.project.domain.model.PrendaModel; +import com.outfitlab.project.domain.model.dto.GarmentDTO; +import com.outfitlab.project.domain.model.dto.PageDTO; +import com.outfitlab.project.domain.useCases.bucketImages.DeleteImage; +import com.outfitlab.project.domain.useCases.bucketImages.SaveImage; +import com.outfitlab.project.domain.useCases.combination.DeleteAllCombinationRelatedToGarment; +import com.outfitlab.project.domain.useCases.combinationAttempt.DeleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment; +import com.outfitlab.project.domain.useCases.garment.*; +import com.outfitlab.project.domain.useCases.recomendations.CreateSugerenciasByGarmentsCode; +import com.outfitlab.project.domain.useCases.recomendations.DeleteAllPrendaOcacionRelatedToGarment; +import com.outfitlab.project.domain.useCases.recomendations.DeleteGarmentRecomentationsRelatedToGarment; +import com.outfitlab.project.presentation.dto.GarmentRequestDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class GarmentControllerTest { + + private GetGarmentsByType getGarmentsByType; + private AddGarmentToFavorite addGarmentToFavorite; + private DeleteGarmentFromFavorite deleteGarmentFromFavorite; + private GetGarmentsFavoritesForUserByEmail getGarmentsFavoritesForUserByEmail; + private CreateGarment createGarment; + private SaveImage saveImage; + private DeleteGarment deleteGarment; + private GetGarmentByCode getGarmentByCode; + private DeleteImage deleteImage; + private UpdateGarment updateGarment; + private DeleteGarmentRecomentationsRelatedToGarment deleteGarmentRecomentationsRelatedToGarment; + private DeleteAllFavoritesRelatedToGarment deleteAllFavoritesRelatedToGarment; + private DeleteAllPrendaOcacionRelatedToGarment deleteAllPrendaOcacionRelatedToGarment; + private DeleteAllCombinationRelatedToGarment deleteAllCombinationRelatedToGarment; + private DeleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment deleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment; + private CreateSugerenciasByGarmentsCode createSugerenciasByGarmentsCode; + + private GarmentController controller; + private UserDetails mockUser; + + @BeforeEach + void setUp() { + getGarmentsByType = mock(GetGarmentsByType.class); + addGarmentToFavorite = mock(AddGarmentToFavorite.class); + deleteGarmentFromFavorite = mock(DeleteGarmentFromFavorite.class); + getGarmentsFavoritesForUserByEmail = mock(GetGarmentsFavoritesForUserByEmail.class); + createGarment = mock(CreateGarment.class); + saveImage = mock(SaveImage.class); + deleteGarment = mock(DeleteGarment.class); + getGarmentByCode = mock(GetGarmentByCode.class); + deleteImage = mock(DeleteImage.class); + updateGarment = mock(UpdateGarment.class); + deleteGarmentRecomentationsRelatedToGarment = mock(DeleteGarmentRecomentationsRelatedToGarment.class); + deleteAllFavoritesRelatedToGarment = mock(DeleteAllFavoritesRelatedToGarment.class); + deleteAllPrendaOcacionRelatedToGarment = mock(DeleteAllPrendaOcacionRelatedToGarment.class); + deleteAllCombinationRelatedToGarment = mock(DeleteAllCombinationRelatedToGarment.class); + deleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment = mock( + DeleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment.class); + createSugerenciasByGarmentsCode = mock(CreateSugerenciasByGarmentsCode.class); + + controller = new GarmentController( + getGarmentsByType, addGarmentToFavorite, deleteGarmentFromFavorite, + getGarmentsFavoritesForUserByEmail, createGarment, saveImage, deleteGarment, + null, getGarmentByCode, deleteImage, updateGarment, + deleteGarmentRecomentationsRelatedToGarment, deleteAllFavoritesRelatedToGarment, + deleteAllPrendaOcacionRelatedToGarment, deleteAllCombinationRelatedToGarment, + deleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment, + createSugerenciasByGarmentsCode); + + mockUser = mock(UserDetails.class); + } + + // ========== getGarmentsByType Tests ========== + + @Test + void givenValidTypeWhenGetGarmentsByTypeThenReturnOk() { + Page garments = givenGarmentsExistForType("superior"); + + ResponseEntity response = whenCallGetGarmentsByType(0, "superior"); + + thenResponseOkWithGarments(response); + thenVerifyGetGarmentsByTypeCalled("superior", 0); + } + + @Test + void givenInvalidTypeWhenGetGarmentsByTypeThenReturn404() { + givenGarmentsNotFoundForType("invalid"); + + ResponseEntity response = whenCallGetGarmentsByType(0, "invalid"); + + thenResponseNotFound(response, "Prendas no encontradas"); + } + + // ========== getAllGarments Tests ========== + + @Test + void givenValidPageWhenGetAllGarmentsThenReturnOk() { + givenGarmentsExistForType("superior"); + givenGarmentsExistForType("inferior"); + + ResponseEntity response = whenCallGetAllGarments(0); + + thenResponseOk(response); + } + + // ========== addGarmentToFavorite Tests ========== + + @Test + void givenValidGarmentCodeWhenAddGarmentToFavoriteThenReturnOk() throws Exception { + String garmentCode = "nike-shirt-001"; + givenFavoriteAdded("Favorito agregado"); + + ResponseEntity response = whenCallAddGarmentToFavorite(garmentCode); + + thenResponseOk(response); + thenVerifyAddGarmentToFavoriteCalled(garmentCode, "german@gmail.com"); + } + + @Test + void givenGarmentNotFoundWhenAddGarmentToFavoriteThenReturn404() throws Exception { + String garmentCode = "invalid-code"; + givenGarmentNotFoundForFavorite(garmentCode); + + ResponseEntity response = whenCallAddGarmentToFavorite(garmentCode); + + thenResponseNotFound(response, "Prenda no encontrada"); + } + + @Test + void givenFavoriteAlreadyExistsWhenAddGarmentToFavoriteThenReturn404() throws Exception { + String garmentCode = "nike-shirt-001"; + givenFavoriteAlreadyExists(garmentCode); + + ResponseEntity response = whenCallAddGarmentToFavorite(garmentCode); + + thenResponseNotFound(response, "Favorito ya existe"); + } + + // ========== deleteGarmentFromFavorite Tests ========== + + @Test + void givenValidGarmentCodeWhenDeleteGarmentFromFavoriteThenReturnOk() throws Exception { + String garmentCode = "nike-shirt-001"; + givenFavoriteDeleted("Favorito eliminado"); + + ResponseEntity response = whenCallDeleteGarmentFromFavorite(garmentCode); + + thenResponseOk(response); + thenVerifyDeleteGarmentFromFavoriteCalled(garmentCode, "german@gmail.com"); + } + + @Test + void givenFavoriteNotFoundWhenDeleteGarmentFromFavoriteThenReturn404() throws Exception { + String garmentCode = "invalid-code"; + givenFavoriteNotFoundForDelete(garmentCode); + + ResponseEntity response = whenCallDeleteGarmentFromFavorite(garmentCode); + + thenResponseNotFound(response, "Favorito no encontrado"); + } + + // ========== getFavorites Tests ========== + + @Test + void givenValidPageWhenGetFavoritesThenReturnOk() throws Exception { + givenFavoritesExist(); + + ResponseEntity response = whenCallGetFavorites(0); + + thenResponseOk(response); + } + + @Test + void givenNegativePageWhenGetFavoritesThenReturn404() throws Exception { + givenNegativePageError(); + + ResponseEntity response = whenCallGetFavorites(-1); + + thenResponseNotFound(response, "Página menor que cero"); + } + + // ========== newGarment Tests ========== + + @Test + void givenValidRequestWhenNewGarmentThenReturnOk() { + GarmentRequestDTO request = givenValidGarmentRequest(); + givenImageSaved("http://image.url/garment.jpg"); + givenGarmentCreated(); + givenSugerenciasCreated(); + + ResponseEntity response = whenCallNewGarment(request); + + thenResponseOkWithMessage(response, "Prenda creada correctamente."); + thenVerifyCreateGarmentCalled(); + } + + @Test + void givenInvalidBrandWhenNewGarmentThenReturn404() { + GarmentRequestDTO request = givenValidGarmentRequest(); + givenImageSaved("http://image.url/garment.jpg"); + givenBrandNotFound(); + + ResponseEntity response = whenCallNewGarment(request); + + thenResponseNotFound(response, "Marca no encontrada"); + } + + // ========== updateGarment Tests ========== + + @Test + void givenValidRequestWhenUpdateGarmentThenReturnOk() { + String garmentCode = "nike-shirt-001"; + GarmentRequestDTO request = givenValidGarmentRequestWithImage(); + PrendaModel existingGarment = givenGarmentExists(garmentCode); + givenImageSaved("http://image.url/new-garment.jpg"); + givenGarmentUpdated(); + givenSugerenciasCreated(); + + ResponseEntity response = whenCallUpdateGarment(garmentCode, request); + + thenResponseOkWithMessage(response, "Prenda actualizada correctamente."); + thenVerifyUpdateGarmentCalled(); + } + + @Test + void givenInvalidGarmentCodeWhenUpdateGarmentThenReturn404() { + String garmentCode = "invalid-code"; + GarmentRequestDTO request = givenValidGarmentRequest(); + givenGarmentNotFound(garmentCode); + + ResponseEntity response = whenCallUpdateGarment(garmentCode, request); + + thenResponseNotFound(response, "Prenda no encontrada"); + } + + // ========== deleteGarment Tests ========== + + @Test + void givenValidGarmentCodeWhenDeleteGarmentThenReturnOk() { + String garmentCode = "nike-shirt-001"; + PrendaModel garment = givenGarmentExistsForDelete(garmentCode); + givenAllRelatedRecordsDeleted(); + + ResponseEntity response = whenCallDeleteGarment(garmentCode); + + thenResponseOkWithMessage(response, "Prenda eliminada correctamente."); + thenVerifyDeleteGarmentCalled(); + } + + @Test + void givenInvalidGarmentCodeWhenDeleteGarmentThenReturn404() { + String garmentCode = "invalid-code"; + givenGarmentNotFound(garmentCode); + + ResponseEntity response = whenCallDeleteGarment(garmentCode); + + thenResponseNotFound(response, "Prenda no encontrada"); + } + + // ========== GIVEN Methods ========== + + private Page givenGarmentsExistForType(String type) { + PrendaModel garment1 = mock(PrendaModel.class); + PrendaModel garment2 = mock(PrendaModel.class); + + // Mock BrandModel for each garment + com.outfitlab.project.domain.model.BrandModel brand1 = mock( + com.outfitlab.project.domain.model.BrandModel.class); + com.outfitlab.project.domain.model.BrandModel brand2 = mock( + com.outfitlab.project.domain.model.BrandModel.class); + when(brand1.getNombre()).thenReturn("Nike"); + when(brand2.getNombre()).thenReturn("Adidas"); + when(garment1.getMarca()).thenReturn(brand1); + when(garment2.getMarca()).thenReturn(brand2); + + // Mock ColorModel for each garment + com.outfitlab.project.domain.model.ColorModel color1 = mock( + com.outfitlab.project.domain.model.ColorModel.class); + com.outfitlab.project.domain.model.ColorModel color2 = mock( + com.outfitlab.project.domain.model.ColorModel.class); + when(color1.getNombre()).thenReturn("Rojo"); + when(color2.getNombre()).thenReturn("Azul"); + when(garment1.getColor()).thenReturn(color1); + when(garment2.getColor()).thenReturn(color2); + + // Mock ClimaModel for each garment + com.outfitlab.project.domain.model.ClimaModel clima1 = mock( + com.outfitlab.project.domain.model.ClimaModel.class); + com.outfitlab.project.domain.model.ClimaModel clima2 = mock( + com.outfitlab.project.domain.model.ClimaModel.class); + when(clima1.getNombre()).thenReturn("Calido"); + when(clima2.getNombre()).thenReturn("Frio"); + when(garment1.getClimaAdecuado()).thenReturn(clima1); + when(garment2.getClimaAdecuado()).thenReturn(clima2); + + Page page = new PageImpl<>(Arrays.asList(garment1, garment2), PageRequest.of(0, 10), 2); + when(getGarmentsByType.execute(type, 0)).thenReturn(page); + return page; + } + + private void givenGarmentsNotFoundForType(String type) { + when(getGarmentsByType.execute(type, 0)) + .thenThrow(new GarmentNotFoundException("Prendas no encontradas")); + } + + private void givenFavoriteAdded(String message) throws Exception { + when(addGarmentToFavorite.execute(anyString(), anyString())).thenReturn(message); + } + + private void givenGarmentNotFoundForFavorite(String garmentCode) throws Exception { + when(addGarmentToFavorite.execute(eq(garmentCode), anyString())) + .thenThrow(new GarmentNotFoundException("Prenda no encontrada")); + } + + private void givenFavoriteAlreadyExists(String garmentCode) throws Exception { + when(addGarmentToFavorite.execute(eq(garmentCode), anyString())) + .thenThrow(new UserGarmentFavoriteAlreadyExistsException("Favorito ya existe")); + } + + private void givenFavoriteDeleted(String message) throws Exception { + when(deleteGarmentFromFavorite.execute(anyString(), anyString())).thenReturn(message); + } + + private void givenFavoriteNotFoundForDelete(String garmentCode) throws Exception { + when(deleteGarmentFromFavorite.execute(eq(garmentCode), anyString())) + .thenThrow(new UserGarmentFavoriteNotFoundException("Favorito no encontrado")); + } + + private void givenFavoritesExist() throws Exception { + PageDTO favorites = new PageDTO<>( + Arrays.asList(mock(PrendaModel.class), mock(PrendaModel.class)), + 0, 10, 2, 1, true); + when(getGarmentsFavoritesForUserByEmail.execute(anyString(), anyInt())).thenReturn(favorites); + } + + private void givenNegativePageError() throws Exception { + when(getGarmentsFavoritesForUserByEmail.execute(anyString(), eq(-1))) + .thenThrow(new PageLessThanZeroException("Página menor que cero")); + } + + private GarmentRequestDTO givenValidGarmentRequest() { + GarmentRequestDTO request = new GarmentRequestDTO(); + request.setNombre("Nike Shirt"); + request.setTipo("superior"); + request.setColorNombre("Rojo"); + request.setCodigoMarca("nike"); + request.setClimaNombre("Calido"); + request.setOcasionesNombres(Arrays.asList("Casual", "Deporte")); + request.setGenero("Masculino"); + request.setSugerencias(Arrays.asList("sug1", "sug2")); + request.setImagen(mock(MultipartFile.class)); + return request; + } + + private GarmentRequestDTO givenValidGarmentRequestWithImage() { + GarmentRequestDTO request = givenValidGarmentRequest(); + request.setImagen(mock(MultipartFile.class)); + request.setEvento("Casual"); // Add evento field + return request; + } + + private void givenImageSaved(String url) { + when(saveImage.execute(any(MultipartFile.class), anyString())).thenReturn(url); + } + + private void givenGarmentCreated() { + doNothing().when(createGarment).execute( + anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyList(), anyString()); + } + + private void givenSugerenciasCreated() { + doNothing().when(createSugerenciasByGarmentsCode).execute(anyString(), anyString(), anyList()); + } + + private void givenBrandNotFound() { + doThrow(new BrandsNotFoundException("Marca no encontrada")) + .when(createGarment).execute( + anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyList(), anyString()); + } + + private PrendaModel givenGarmentExists(String garmentCode) { + PrendaModel garment = mock(PrendaModel.class); + when(garment.getImagenUrl()).thenReturn("http://old-image.url/garment.jpg"); + when(garment.getMarca()).thenReturn(mock(com.outfitlab.project.domain.model.BrandModel.class)); + when(garment.getMarca().getCodigoMarca()).thenReturn("nike"); + when(getGarmentByCode.execute(garmentCode)).thenReturn(garment); + return garment; + } + + private void givenGarmentNotFound(String garmentCode) { + when(getGarmentByCode.execute(garmentCode)) + .thenThrow(new GarmentNotFoundException("Prenda no encontrada")); + } + + private void givenGarmentUpdated() { + doNothing().when(updateGarment).execute( + anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyString(), anyString(), anyList(), anyString()); + } + + private PrendaModel givenGarmentExistsForDelete(String garmentCode) { + PrendaModel garment = mock(PrendaModel.class); + when(garment.getImagenUrl()).thenReturn("http://image.url/garment.jpg"); + when(garment.getGarmentCode()).thenReturn(garmentCode); + when(garment.getMarca()).thenReturn(mock(com.outfitlab.project.domain.model.BrandModel.class)); + when(garment.getMarca().getCodigoMarca()).thenReturn("nike"); + when(getGarmentByCode.execute(garmentCode)).thenReturn(garment); + return garment; + } + + private void givenAllRelatedRecordsDeleted() { + doNothing().when(deleteGarmentRecomentationsRelatedToGarment).execute(anyString()); + doNothing().when(deleteAllFavoritesRelatedToGarment).execute(anyString()); + doNothing().when(deleteAllPrendaOcacionRelatedToGarment).execute(anyString()); + doNothing().when(deleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment).execute(anyString()); + doNothing().when(deleteAllCombinationRelatedToGarment).execute(anyString()); + doNothing().when(deleteGarment).execute(any(PrendaModel.class), anyString()); + } + + // ========== WHEN Methods ========== + + private ResponseEntity whenCallGetGarmentsByType(int page, String type) { + return controller.getGarmentsByType(page, type); + } + + private ResponseEntity whenCallGetAllGarments(int page) { + return controller.getAllGarments(page); + } + + private ResponseEntity whenCallAddGarmentToFavorite(String garmentCode) { + return controller.addGarmentToFavorite(garmentCode); + } + + private ResponseEntity whenCallDeleteGarmentFromFavorite(String garmentCode) { + return controller.deleteGarmentFromFavorite(garmentCode); + } + + private ResponseEntity whenCallGetFavorites(int page) { + return controller.getFavorites(page); + } + + private ResponseEntity whenCallNewGarment(GarmentRequestDTO request) { + return controller.newGarment(request, mockUser); + } + + private ResponseEntity whenCallUpdateGarment(String garmentCode, GarmentRequestDTO request) { + return controller.updateGarment(garmentCode, request, mockUser); + } + + private ResponseEntity whenCallDeleteGarment(String garmentCode) { + return controller.deleteGarment(garmentCode, mockUser); + } + + // ========== THEN Methods ========== + + private void thenResponseOk(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + assertNotNull(response.getBody()); + } + + private void thenResponseOkWithMessage(ResponseEntity response, String expectedMessage) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expectedMessage, response.getBody()); + } + + private void thenResponseOkWithGarments(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + Map body = (Map) response.getBody(); + assertNotNull(body); + assertNotNull(body.get("content")); + } + + private void thenResponseNotFound(ResponseEntity response, String expectedMessage) { + assertEquals(404, response.getStatusCode().value()); + assertEquals(expectedMessage, response.getBody()); + } + + private void thenVerifyGetGarmentsByTypeCalled(String type, int page) { + verify(getGarmentsByType, times(1)).execute(type, page); + } + + private void thenVerifyAddGarmentToFavoriteCalled(String garmentCode, String email) throws Exception { + verify(addGarmentToFavorite, times(1)).execute(garmentCode, email); + } + + private void thenVerifyDeleteGarmentFromFavoriteCalled(String garmentCode, String email) throws Exception { + verify(deleteGarmentFromFavorite, times(1)).execute(garmentCode, email); + } + + private void thenVerifyCreateGarmentCalled() { + verify(createGarment, times(1)).execute( + anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyList(), anyString()); + } + + private void thenVerifyUpdateGarmentCalled() { + verify(updateGarment, times(1)).execute( + anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyString(), anyString(), anyList(), anyString()); + } + + private void thenVerifyDeleteGarmentCalled() { + verify(deleteGarment, times(1)).execute(any(PrendaModel.class), anyString()); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/GarmentRecommendationAIControllerTest.java b/src/test/java/com/outfitlab/project/presentation/GarmentRecommendationAIControllerTest.java new file mode 100644 index 0000000..ba5f9d6 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/GarmentRecommendationAIControllerTest.java @@ -0,0 +1,122 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.model.PrendaModel; +import com.outfitlab.project.domain.model.dto.ConjuntoDTO; +import com.outfitlab.project.domain.useCases.garment.GetGarmentRecomendationByText; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class GarmentRecommendationAIControllerTest { + + private GetGarmentRecomendationByText getGarmentRecomendationByText; + private GarmentRecommendationAIController controller; + + @BeforeEach + void setUp() { + getGarmentRecomendationByText = mock(GetGarmentRecomendationByText.class); + controller = new GarmentRecommendationAIController(getGarmentRecomendationByText); + } + + // ========== recommendOutfit Tests ========== + + @Test + void givenValidRequestWhenRecommendOutfitThenReturnOk() { + GarmentRecommendationAIController.RecommendationRequest request = givenValidRecommendationRequest(); + List outfits = givenOutfitsExist(); + + ResponseEntity> response = whenCallRecommendOutfit(request); + + thenResponseOkWithOutfits(response, outfits); + thenVerifyGetGarmentRecomendationByTextCalled("outfit casual para verano", "user123"); + } + + @Test + void givenEmptyResultsWhenRecommendOutfitThenReturnNoContent() { + GarmentRecommendationAIController.RecommendationRequest request = givenValidRecommendationRequest(); + givenNoOutfitsFound(); + + ResponseEntity> response = whenCallRecommendOutfit(request); + + thenResponseNoContent(response); + } + + @Test + void givenSingleEmptyConjuntoWhenRecommendOutfitThenReturnNoContent() { + GarmentRecommendationAIController.RecommendationRequest request = givenValidRecommendationRequest(); + givenSingleEmptyConjunto(); + + ResponseEntity> response = whenCallRecommendOutfit(request); + + thenResponseNoContent(response); + } + + // ========== GIVEN Methods ========== + + private GarmentRecommendationAIController.RecommendationRequest givenValidRecommendationRequest() { + GarmentRecommendationAIController.RecommendationRequest request = new GarmentRecommendationAIController.RecommendationRequest(); + request.setPeticionUsuario("outfit casual para verano"); + request.setIdUsuario("user123"); + return request; + } + + private List givenOutfitsExist() { + ConjuntoDTO conjunto1 = mock(ConjuntoDTO.class); + ConjuntoDTO conjunto2 = mock(ConjuntoDTO.class); + + when(conjunto1.getPrendas()).thenReturn(Arrays.asList( + mock(PrendaModel.class), + mock(PrendaModel.class))); + when(conjunto2.getPrendas()).thenReturn(Arrays.asList( + mock(PrendaModel.class), + mock(PrendaModel.class))); + + List outfits = Arrays.asList(conjunto1, conjunto2); + when(getGarmentRecomendationByText.execute("outfit casual para verano", "user123")) + .thenReturn(outfits); + return outfits; + } + + private void givenNoOutfitsFound() { + when(getGarmentRecomendationByText.execute("outfit casual para verano", "user123")) + .thenReturn(new ArrayList<>()); + } + + private void givenSingleEmptyConjunto() { + ConjuntoDTO emptyConjunto = mock(ConjuntoDTO.class); + when(emptyConjunto.getPrendas()).thenReturn(new ArrayList<>()); + + when(getGarmentRecomendationByText.execute("outfit casual para verano", "user123")) + .thenReturn(Arrays.asList(emptyConjunto)); + } + + // ========== WHEN Methods ========== + + private ResponseEntity> whenCallRecommendOutfit( + GarmentRecommendationAIController.RecommendationRequest request) { + return controller.recommendOutfit(request); + } + + // ========== THEN Methods ========== + + private void thenResponseOkWithOutfits(ResponseEntity> response, List expected) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expected, response.getBody()); + } + + private void thenResponseNoContent(ResponseEntity> response) { + assertEquals(204, response.getStatusCode().value()); + assertNull(response.getBody()); + } + + private void thenVerifyGetGarmentRecomendationByTextCalled(String peticion, String userId) { + verify(getGarmentRecomendationByText, times(1)).execute(peticion, userId); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/RecomendationControllerTest.java b/src/test/java/com/outfitlab/project/presentation/RecomendationControllerTest.java new file mode 100644 index 0000000..b17ee5a --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/RecomendationControllerTest.java @@ -0,0 +1,147 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.model.GarmentRecomendationModel; +import com.outfitlab.project.domain.useCases.garment.GetGarmentRecomendation; +import com.outfitlab.project.domain.useCases.recomendations.DeleteRecomendationByPrimaryAndSecondaryGarmentCode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class RecomendationControllerTest { + + private GetGarmentRecomendation getGarmentRecomendation; + private DeleteRecomendationByPrimaryAndSecondaryGarmentCode deleteRecomendationByPrimaryAndSecondaryGarmentCode; + + private RecomendationController controller; + + @BeforeEach + void setUp() { + getGarmentRecomendation = mock(GetGarmentRecomendation.class); + deleteRecomendationByPrimaryAndSecondaryGarmentCode = mock( + DeleteRecomendationByPrimaryAndSecondaryGarmentCode.class); + + controller = new RecomendationController( + getGarmentRecomendation, + deleteRecomendationByPrimaryAndSecondaryGarmentCode); + } + + // ========== getRecomendations Tests ========== + + @Test + void givenValidGarmentCodeWhenGetRecomendationsThenReturnOk() { + String garmentCode = "nike-shirt-001"; + List recommendations = givenRecommendationsExist(garmentCode); + + ResponseEntity response = whenCallGetRecomendations(garmentCode); + + thenResponseOk(response); + thenResponseBodyEquals(response, recommendations); + thenVerifyGetGarmentRecomendationCalled(garmentCode); + } + + @Test + void givenInvalidGarmentCodeWhenGetRecomendationsThenReturn404() { + String garmentCode = "invalid-code"; + givenRecommendationNotFound(garmentCode); + + ResponseEntity response = whenCallGetRecomendations(garmentCode); + + thenResponseNotFound(response, "Recomendaciones no encontradas"); + } + + // ========== deleteRecomendation Tests ========== + + @Test + void givenValidParamsWhenDeleteRecomendationThenReturnOk() { + String primaryCode = "garment-001"; + String secondaryCode = "garment-002"; + String type = "complemento"; + String successMessage = givenRecommendationDeleted(primaryCode, secondaryCode, type); + + ResponseEntity response = whenCallDeleteRecomendation(primaryCode, secondaryCode, type); + + thenResponseOk(response); + thenResponseBodyEquals(response, successMessage); + thenVerifyDeleteRecomendationCalled(primaryCode, secondaryCode, type); + } + + @Test + void givenInvalidParamsWhenDeleteRecomendationThenReturn404() { + String primaryCode = "invalid-001"; + String secondaryCode = "invalid-002"; + String type = "complemento"; + givenDeleteRecommendationThrowError(primaryCode, secondaryCode, type); + + ResponseEntity response = whenCallDeleteRecomendation(primaryCode, secondaryCode, type); + + thenResponseNotFound(response, "Recomendación no encontrada"); + } + + // ========== GIVEN Methods ========== + + private List givenRecommendationsExist(String garmentCode) { + List recommendations = Arrays.asList( + mock(GarmentRecomendationModel.class), + mock(GarmentRecomendationModel.class)); + when(getGarmentRecomendation.execute(garmentCode)).thenReturn(recommendations); + return recommendations; + } + + private void givenRecommendationNotFound(String garmentCode) { + when(getGarmentRecomendation.execute(garmentCode)) + .thenThrow(new RuntimeException("Recomendaciones no encontradas")); + } + + private String givenRecommendationDeleted(String primaryCode, String secondaryCode, String type) { + String message = "Recomendación eliminada correctamente"; + when(deleteRecomendationByPrimaryAndSecondaryGarmentCode.execute(primaryCode, secondaryCode, type)) + .thenReturn(message); + return message; + } + + private void givenDeleteRecommendationThrowError(String primaryCode, String secondaryCode, String type) { + when(deleteRecomendationByPrimaryAndSecondaryGarmentCode.execute(primaryCode, secondaryCode, type)) + .thenThrow(new RuntimeException("Recomendación no encontrada")); + } + + // ========== WHEN Methods ========== + + private ResponseEntity whenCallGetRecomendations(String garmentCode) { + return controller.getRecomendations(garmentCode); + } + + private ResponseEntity whenCallDeleteRecomendation(String primaryCode, String secondaryCode, String type) { + return controller.deleteRecomendation(primaryCode, secondaryCode, type); + } + + // ========== THEN Methods ========== + + private void thenResponseOk(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + assertNotNull(response.getBody()); + } + + private void thenResponseBodyEquals(ResponseEntity response, Object expected) { + assertEquals(expected, response.getBody()); + } + + private void thenResponseNotFound(ResponseEntity response, String expectedMessage) { + assertEquals(404, response.getStatusCode().value()); + assertEquals(expectedMessage, response.getBody()); + } + + private void thenVerifyGetGarmentRecomendationCalled(String garmentCode) { + verify(getGarmentRecomendation, times(1)).execute(garmentCode); + } + + private void thenVerifyDeleteRecomendationCalled(String primaryCode, String secondaryCode, String type) { + verify(deleteRecomendationByPrimaryAndSecondaryGarmentCode, times(1)) + .execute(primaryCode, secondaryCode, type); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/SubscriptionControllerTest.java b/src/test/java/com/outfitlab/project/presentation/SubscriptionControllerTest.java new file mode 100644 index 0000000..b0749d0 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/SubscriptionControllerTest.java @@ -0,0 +1,315 @@ +package com.outfitlab.project.presentation; + +import com.mercadopago.exceptions.MPApiException; +import com.mercadopago.exceptions.MPException; +import com.mercadopago.net.MPResponse; +import com.outfitlab.project.domain.exceptions.SubscriptionNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.UserSubscriptionRepository; +import com.outfitlab.project.domain.model.UserSubscriptionModel; +import com.outfitlab.project.domain.useCases.subscription.CreateMercadoPagoPreference; +import com.outfitlab.project.domain.useCases.subscription.GetAllSubscription; +import com.outfitlab.project.domain.useCases.subscription.ProcessPaymentNotification; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class SubscriptionControllerTest { + + private CreateMercadoPagoPreference createMercadoPagoPreference; + private ProcessPaymentNotification processPaymentNotification; + private GetAllSubscription getAllSubscription; + private UserSubscriptionRepository userSubscriptionRepository; + + private SubscriptionController controller; + + @BeforeEach + void setUp() { + createMercadoPagoPreference = mock(CreateMercadoPagoPreference.class); + processPaymentNotification = mock(ProcessPaymentNotification.class); + getAllSubscription = mock(GetAllSubscription.class); + userSubscriptionRepository = mock(UserSubscriptionRepository.class); + + controller = new SubscriptionController( + createMercadoPagoPreference, + processPaymentNotification, + getAllSubscription, + userSubscriptionRepository); + } + + // ========== createPreference Tests ========== + + @Test + void givenValidRequestWhenCreatePreferenceThenReturnOk() throws MPException, MPApiException { + SubscriptionRequest request = givenValidSubscriptionRequest(); + String preferenceId = givenPreferenceCreated("pref-123456"); + + ResponseEntity response = whenCallCreatePreference(request); + + thenResponseOkWithPreferenceId(response, "pref-123456"); + thenVerifyCreatePreferenceCalled("PLAN-001", "user@example.com", new BigDecimal("99.99"), "ARS"); + } + + @Test + void givenMPApiExceptionWhenCreatePreferenceThenReturnError() throws MPException, MPApiException { + SubscriptionRequest request = givenValidSubscriptionRequest(); + givenMPApiException(); + + ResponseEntity response = whenCallCreatePreference(request); + + thenResponseInternalServerError(response); + thenResponseContainsError(response, "Error de MercadoPago API"); + } + + @Test + void givenMPExceptionWhenCreatePreferenceThenReturnError() throws MPException, MPApiException { + SubscriptionRequest request = givenValidSubscriptionRequest(); + givenMPException(); + + ResponseEntity response = whenCallCreatePreference(request); + + thenResponseInternalServerError(response); + thenResponseContainsError(response, "Error al crear preferencia"); + } + + // ========== handleMercadoPagoWebhook Tests ========== + + @Test + void givenValidPaymentNotificationWhenHandleWebhookThenReturnOk() throws MPException, MPApiException { + String paymentId = "12345"; + String topic = "payment"; + givenPaymentProcessed(); + + ResponseEntity response = whenCallHandleWebhook(paymentId, topic); + + thenResponseOk(response); + thenVerifyProcessPaymentNotificationCalled(12345L); + } + + @Test + void givenInvalidTopicWhenHandleWebhookThenReturnOk() { + String paymentId = "123"; + String topic = "merchant_order"; + + ResponseEntity response = whenCallHandleWebhook(paymentId, topic); + + thenResponseOk(response); + } + + @Test + void givenExceptionWhenHandleWebhookThenReturnError() throws MPException, MPApiException { + String paymentId = "12345"; + String topic = "payment"; + givenPaymentProcessingFailed(); + + ResponseEntity response = whenCallHandleWebhook(paymentId, topic); + + thenResponseInternalServerError(response); + } + + // ========== getSubscriptions Tests ========== + + @Test + void whenGetSubscriptionsThenReturnOk() { + givenAuthenticatedUser("user@example.com"); + givenPlansExist("user@example.com"); + + ResponseEntity response = whenCallGetSubscriptions(); + + thenResponseOkWithSubscriptionsData(response); + thenVerifyGetAllSubscriptionCalled("user@example.com"); + } + + // ========== getUserSubscription Tests ========== + + @Test + void givenAuthenticatedUserWhenGetUserSubscriptionThenReturnOk() throws SubscriptionNotFoundException { + givenAuthenticatedUser("user@example.com"); + givenUserSubscriptionModelExists(); + + ResponseEntity response = whenCallGetUserSubscription(); + + thenResponseOkWithUserSubscription(response); + thenVerifyUserSubscriptionRepositoryCalled("user@example.com"); + } + + @Test + void givenNoSubscriptionWhenGetUserSubscriptionThenReturnNotFound() throws SubscriptionNotFoundException { + givenAuthenticatedUser("user@example.com"); + givenUserSubscriptionModelNotFound(); + + ResponseEntity response = whenCallGetUserSubscription(); + + thenResponseNotFound(response); + } + + // ========== GIVEN Methods ========== + + private SubscriptionRequest givenValidSubscriptionRequest() { + SubscriptionRequest request = new SubscriptionRequest(); + request.setPlanId("PLAN-001"); + request.setUserEmail("user@example.com"); + request.setPrice(new BigDecimal("99.99")); + request.setCurrency("ARS"); + return request; + } + + private String givenPreferenceCreated(String preferenceId) throws MPException, MPApiException { + when(createMercadoPagoPreference.execute(anyString(), anyString(), any(BigDecimal.class), anyString())) + .thenReturn(preferenceId); + return preferenceId; + } + + private void givenMPApiException() throws MPException, MPApiException { + MPResponse mockResponse = mock(MPResponse.class); + when(mockResponse.getStatusCode()).thenReturn(500); + doThrow(new MPApiException("Error de MercadoPago API", mockResponse)) + .when(createMercadoPagoPreference) + .execute(anyString(), anyString(), any(BigDecimal.class), anyString()); + } + + private void givenMPException() throws MPException, MPApiException { + doThrow(new MPException("Error al crear preferencia")) + .when(createMercadoPagoPreference) + .execute(anyString(), anyString(), any(BigDecimal.class), anyString()); + } + + private void givenPaymentProcessed() throws MPException, MPApiException { + doNothing().when(processPaymentNotification).execute(anyLong()); + } + + private void givenPaymentProcessingFailed() throws MPException, MPApiException { + doThrow(new MPException("Error procesando pago")) + .when(processPaymentNotification).execute(anyLong()); + } + + private void givenPlansExist(String userEmail) { + List plans = Arrays.asList( + Map.of("id", "PLAN-001", "name", "Basic", "price", 99.99), + Map.of("id", "PLAN-002", "name", "Premium", "price", 199.99)); + doReturn(plans).when(getAllSubscription).execute(userEmail); + } + + private void givenAuthenticatedUser(String email) { + Authentication authentication = mock(Authentication.class); + when(authentication.getName()).thenReturn(email); + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getPrincipal()).thenReturn(email); + SecurityContext securityContext = mock(SecurityContext.class); + when(securityContext.getAuthentication()).thenReturn(authentication); + SecurityContextHolder.setContext(securityContext); + } + + private UserSubscriptionModel givenUserSubscriptionModelExists() throws SubscriptionNotFoundException { + UserSubscriptionModel subscription = mock(UserSubscriptionModel.class); + when(subscription.getPlanCode()).thenReturn("PLAN-001"); + when(subscription.getStatus()).thenReturn("active"); + when(subscription.getCombinationsUsed()).thenReturn(5); + when(subscription.getMaxCombinations()).thenReturn(10); + when(subscription.getFavoritesCount()).thenReturn(3); + when(subscription.getMaxFavorites()).thenReturn(20); + when(subscription.getModelsGenerated()).thenReturn(2); + when(subscription.getMaxModels()).thenReturn(5); + when(subscription.getDownloadsCount()).thenReturn(1); + when(subscription.getMaxDownloads()).thenReturn(10); + when(subscription.getGarmentsUploaded()).thenReturn(0); + when(subscription.getMaxGarments()).thenReturn(null); + when(userSubscriptionRepository.findByUserEmail(anyString())).thenReturn(subscription); + return subscription; + } + + private void givenUserSubscriptionModelNotFound() throws SubscriptionNotFoundException { + when(userSubscriptionRepository.findByUserEmail(anyString())) + .thenThrow(new SubscriptionNotFoundException("Suscripción no encontrada")); + } + + // ========== WHEN Methods ========== + + private ResponseEntity whenCallCreatePreference(SubscriptionRequest request) { + return controller.createPreference(request); + } + + private ResponseEntity whenCallHandleWebhook(String id, String topic) { + return controller.handleMercadoPagoWebhook(id, topic); + } + + private ResponseEntity whenCallGetSubscriptions() { + return controller.getSubscriptions(); + } + + private ResponseEntity whenCallGetUserSubscription() { + return controller.getUserSubscription(); + } + + // ========== THEN Methods ========== + + private void thenResponseOk(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + } + + private void thenResponseOkWithPreferenceId(ResponseEntity response, String expectedPreferenceId) { + assertEquals(200, response.getStatusCode().value()); + Map body = (Map) response.getBody(); + assertNotNull(body); + assertNotNull(body.get("initPoint")); + } + + private void thenResponseOkWithSubscriptionsData(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + Map body = (Map) response.getBody(); + assertNotNull(body); + assertNotNull(body.get("data")); + } + + private void thenResponseOkWithUserSubscription(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + Map body = (Map) response.getBody(); + assertNotNull(body); + assertTrue(body.containsKey("planCode")); + assertTrue(body.containsKey("usage")); + } + + private void thenResponseInternalServerError(ResponseEntity response) { + assertEquals(500, response.getStatusCode().value()); + } + + private void thenResponseNotFound(ResponseEntity response) { + assertEquals(404, response.getStatusCode().value()); + } + + private void thenResponseContainsError(ResponseEntity response, String expectedError) { + Map body = (Map) response.getBody(); + assertNotNull(body); + String error = (String) body.get("error"); + assertNotNull(error, "Error message should not be null"); + assertTrue(error.contains(expectedError) || error.contains("MercadoPago") || error.contains("preferencia"), + "Expected error to contain: " + expectedError + " but got: " + error); + } + + private void thenVerifyCreatePreferenceCalled(String planId, String userEmail, BigDecimal price, String currency) + throws MPException, MPApiException { + verify(createMercadoPagoPreference, times(1)).execute(planId, userEmail, price, currency); + } + + private void thenVerifyProcessPaymentNotificationCalled(Long paymentId) throws MPException, MPApiException { + verify(processPaymentNotification, times(1)).execute(paymentId); + } + + private void thenVerifyGetAllSubscriptionCalled(String userEmail) { + verify(getAllSubscription, times(1)).execute(userEmail); + } + + private void thenVerifyUserSubscriptionRepositoryCalled(String email) throws SubscriptionNotFoundException { + verify(userSubscriptionRepository, times(1)).findByUserEmail(email); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/UserControllerTest.java b/src/test/java/com/outfitlab/project/presentation/UserControllerTest.java new file mode 100644 index 0000000..55e9f60 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/UserControllerTest.java @@ -0,0 +1,592 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.exceptions.PasswordIsNotTheSame; +import com.outfitlab.project.domain.exceptions.UserAlreadyExistsException; +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.model.UserModel; +import com.outfitlab.project.domain.model.dto.LoginDTO; +import com.outfitlab.project.domain.model.dto.RegisterDTO; +import com.outfitlab.project.domain.useCases.brand.CreateBrand; +import com.outfitlab.project.domain.useCases.bucketImages.DeleteImage; +import com.outfitlab.project.domain.useCases.bucketImages.SaveImage; +import com.outfitlab.project.domain.useCases.subscription.AssignFreePlanToUser; +import com.outfitlab.project.domain.useCases.user.*; +import com.outfitlab.project.presentation.dto.EditProfileRequestDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class UserControllerTest { + + private RegisterUser registerUser; + private LoginUser loginUser; + private GetAllUsers getAllUsers; + private DesactivateUser desactivateUser; + private ActivateUser activateUser; + private ConvertToAdmin convertToAdmin; + private ConvertToUser convertToUser; + private CreateBrand createBrand; + private UpdateBrandUser updateBrandUser; + private SaveImage saveImage; + private GetUserByEmail getUserByEmail; + private UpdateUser updateUser; + private DeleteImage deleteImage; + private AssignFreePlanToUser assignFreePlanToUser; + private UserProfile userProfile; + private RefreshToken refreshToken; + + private UserController controller; + + @BeforeEach + void setUp() { + registerUser = mock(RegisterUser.class); + loginUser = mock(LoginUser.class); + getAllUsers = mock(GetAllUsers.class); + desactivateUser = mock(DesactivateUser.class); + activateUser = mock(ActivateUser.class); + convertToAdmin = mock(ConvertToAdmin.class); + convertToUser = mock(ConvertToUser.class); + createBrand = mock(CreateBrand.class); + updateBrandUser = mock(UpdateBrandUser.class); + saveImage = mock(SaveImage.class); + getUserByEmail = mock(GetUserByEmail.class); + updateUser = mock(UpdateUser.class); + deleteImage = mock(DeleteImage.class); + assignFreePlanToUser = mock(AssignFreePlanToUser.class); + userProfile = mock(UserProfile.class); + refreshToken = mock(RefreshToken.class); + + controller = new UserController( + registerUser, loginUser, getAllUsers, desactivateUser, activateUser, + convertToAdmin, convertToUser, createBrand, updateBrandUser, saveImage, + getUserByEmail, updateUser, deleteImage, assignFreePlanToUser, + userProfile, refreshToken); + } + + // ========== registerUser Tests ========== + + @Test + void givenValidRequestWhenRegisterUserThenReturnCreated() { + RegisterDTO request = givenValidRegisterRequest(); + UserModel newUser = givenUserRegistered("test@example.com", "Test User"); + givenFreePlanAssigned(); + + ResponseEntity response = whenCallRegisterUser(request); + + thenResponseCreated(response); + thenResponseContainsUserData(response, "test@example.com", "Test User"); + thenVerifyRegisterUserCalled(); + thenVerifyAssignFreePlanCalled("test@example.com", false); + } + + @Test + void givenExistingEmailWhenRegisterUserThenReturnBadRequest() { + RegisterDTO request = givenValidRegisterRequest(); + givenUserAlreadyExists(); + + ResponseEntity response = whenCallRegisterUser(request); + + thenResponseBadRequest(response); + } + + // ========== registerbrandAndUser Tests ========== + + @Test + void givenValidBrandRequestWhenRegisterBrandAndUserThenReturnCreated() { + RegisterDTO request = givenValidBrandRegisterRequest(); + UserModel newUser = givenUserRegistered("brand@example.com", "Brand User"); + givenFreePlanAssigned(); + givenBrandCreated("brand-code"); + givenBrandUpdatedInUser(); + givenImageSaved("http://logo.url/brand.png"); + + ResponseEntity response = whenCallRegisterBrandAndUser(request); + + thenResponseCreated(response); + thenVerifyCreateBrandCalled(); + thenVerifyUpdateBrandUserCalled(); + } + + // ========== loginUser Tests ========== + + @Test + void givenValidCredentialsWhenLoginUserThenReturnOk() { + LoginDTO loginDTO = givenValidLoginRequest(); + ResponseEntity loginResponse = givenLoginSuccessful(); + + ResponseEntity response = whenCallLoginUser(loginDTO); + + thenResponseOk(response); + thenVerifyLoginUserCalled(); + } + + @Test + void givenInvalidCredentialsWhenLoginUserThenReturnUnauthorized() { + LoginDTO loginDTO = givenValidLoginRequest(); + givenLoginFailed(); + + ResponseEntity response = whenCallLoginUser(loginDTO); + + thenResponseUnauthorized(response); + } + + // ========== refreshToken Tests ========== + + @Test + void givenValidRefreshTokenWhenRefreshTokenThenReturnOk() { + String refreshTokenValue = "valid-refresh-token"; + ResponseEntity tokenResponse = givenRefreshTokenSuccessful(); + + ResponseEntity response = whenCallRefreshToken(refreshTokenValue); + + thenResponseOk(response); + thenVerifyRefreshTokenCalled(refreshTokenValue); + } + + @Test + void givenInvalidRefreshTokenWhenRefreshTokenThenReturnUnauthorized() { + String refreshTokenValue = "invalid-token"; + givenRefreshTokenFailed(); + + ResponseEntity response = whenCallRefreshToken(refreshTokenValue); + + thenResponseUnauthorized(response); + } + + // ========== getAuthUserProfile Tests ========== + + @Test + void whenGetAuthUserProfileThenReturnOk() { + UserModel user = givenUserProfileExists(); + + ResponseEntity response = whenCallGetAuthUserProfile(); + + thenResponseOk(response); + thenVerifyUserProfileCalled(); + } + + @Test + void givenUserNotFoundWhenGetAuthUserProfileThenReturnUnauthorized() { + givenUserProfileNotFound(); + + ResponseEntity response = whenCallGetAuthUserProfile(); + + thenResponseUnauthorized(response); + } + + // ========== getAllUsers Tests ========== + + @Test + void whenGetAllUsersThenReturnOk() { + List users = givenUsersExist(); + + ResponseEntity response = whenCallGetAllUsers(); + + thenResponseOk(response); + thenVerifyGetAllUsersCalled(); + } + + @Test + void givenNoUsersWhenGetAllUsersThenReturn404() { + givenNoUsersFound(); + + ResponseEntity response = whenCallGetAllUsers(); + + thenResponseNotFound(response, "No hay usuarios"); + } + + // ========== desactivateUser Tests ========== + + @Test + void givenValidEmailWhenDesactivateUserThenReturnOk() { + String email = "user@example.com"; + givenUserDesactivated("Usuario desactivado"); + + ResponseEntity response = whenCallDesactivateUser(email); + + thenResponseOk(response); + thenVerifyDesactivateUserCalled(email); + } + + @Test + void givenInvalidEmailWhenDesactivateUserThenReturn404() { + String email = "invalid@example.com"; + givenUserNotFoundForDesactivate(email); + + ResponseEntity response = whenCallDesactivateUser(email); + + thenResponseNotFound(response, "Usuario no encontrado"); + } + + // ========== activateUser Tests ========== + + @Test + void givenValidEmailWhenActivateUserThenReturnOk() { + String email = "user@example.com"; + givenUserActivated("Usuario activado"); + + ResponseEntity response = whenCallActivateUser(email); + + thenResponseOk(response); + thenVerifyActivateUserCalled(email); + } + + // ========== convertToAdmin Tests ========== + + @Test + void givenValidEmailWhenConvertToAdminThenReturnOk() { + String email = "user@example.com"; + givenUserConvertedToAdmin("Usuario convertido a admin"); + + ResponseEntity response = whenCallConvertToAdmin(email); + + thenResponseOk(response); + thenVerifyConvertToAdminCalled(email); + } + + // ========== convertToUser Tests ========== + + @Test + void givenValidEmailWhenConvertToUserThenReturnOk() { + String email = "admin@example.com"; + givenUserConvertedToUser("Admin convertido a usuario"); + + ResponseEntity response = whenCallConvertToUser(email); + + thenResponseOk(response); + thenVerifyConvertToUserCalled(email); + } + + // ========== updateUser Tests ========== + + @Test + void givenValidRequestWhenUpdateUserThenReturnOk() { + String oldEmail = "old@example.com"; + EditProfileRequestDTO request = givenValidEditProfileRequest(); + UserModel updatedUser = givenUserUpdated(); + givenUserImageUrl("http://old-image.url/user.jpg"); + givenImageSaved("http://new-image.url/user.jpg"); // Add image save mock + + ResponseEntity response = whenCallUpdateUser(oldEmail, request); + + thenResponseOk(response); + thenResponseContainsMessage(response, "Perfil actualizado."); + thenVerifyUpdateUserCalled(); + } + + @Test + void givenPasswordMismatchWhenUpdateUserThenReturn404() { + String oldEmail = "user@example.com"; + EditProfileRequestDTO request = givenValidEditProfileRequest(); + givenPasswordMismatch(); + givenImageSaved("http://new-image.url/user.jpg"); // Add image save mock + + ResponseEntity response = whenCallUpdateUser(oldEmail, request); + + thenResponseNotFound(response, "Las contraseñas no coinciden"); + } + + // ========== GIVEN Methods ========== + + private RegisterDTO givenValidRegisterRequest() { + RegisterDTO request = new RegisterDTO(); + request.setEmail("test@example.com"); + request.setName("Test"); + request.setLastName("User"); + request.setPassword("Password123"); + return request; + } + + private RegisterDTO givenValidBrandRegisterRequest() { + RegisterDTO request = givenValidRegisterRequest(); + request.setEmail("brand@example.com"); + request.setBrandName("Test Brand"); + request.setUrlSite("http://brand.com"); + request.setLogoBrand(mock(MultipartFile.class)); + return request; + } + + private UserModel givenUserRegistered(String email, String name) { + UserModel user = mock(UserModel.class); + when(user.getEmail()).thenReturn(email); + when(user.getName()).thenReturn(name); + when(registerUser.execute(any(RegisterDTO.class))).thenReturn(user); + return user; + } + + private void givenUserAlreadyExists() { + when(registerUser.execute(any(RegisterDTO.class))) + .thenThrow(new UserAlreadyExistsException("El usuario ya existe")); + } + + private void givenFreePlanAssigned() { + doNothing().when(assignFreePlanToUser).execute(anyString(), anyBoolean()); + } + + private void givenBrandCreated(String brandCode) { + when(createBrand.execute(anyString(), anyString(), anyString())).thenReturn(brandCode); + } + + private void givenBrandUpdatedInUser() { + doNothing().when(updateBrandUser).execute(anyString(), anyString()); + } + + private void givenImageSaved(String url) { + when(saveImage.execute(any(MultipartFile.class), anyString())).thenReturn(url); + } + + private LoginDTO givenValidLoginRequest() { + LoginDTO loginDTO = new LoginDTO(); + loginDTO.setEmail("test@example.com"); + loginDTO.setPassword("Password123"); + return loginDTO; + } + + private ResponseEntity givenLoginSuccessful() { + ResponseEntity response = ResponseEntity.ok("Login successful"); + doReturn(response).when(loginUser).execute(any(LoginDTO.class)); + return response; + } + + private void givenLoginFailed() { + when(loginUser.execute(any(LoginDTO.class))) + .thenThrow(new UserNotFoundException("Usuario no encontrado")); + } + + private ResponseEntity givenRefreshTokenSuccessful() { + ResponseEntity response = ResponseEntity.ok("Token refreshed"); + doReturn(response).when(refreshToken).execute(anyString()); + return response; + } + + private void givenRefreshTokenFailed() { + when(refreshToken.execute(anyString())) + .thenThrow(new UserNotFoundException("Token inválido")); + } + + private UserModel givenUserProfileExists() { + UserModel user = mock(UserModel.class); + when(userProfile.execute()).thenReturn(user); + return user; + } + + private void givenUserProfileNotFound() { + when(userProfile.execute()).thenThrow(new UserNotFoundException("Usuario no encontrado")); + } + + private List givenUsersExist() { + UserModel user1 = mock(UserModel.class); + UserModel user2 = mock(UserModel.class); + List users = Arrays.asList(user1, user2); + doReturn(users).when(getAllUsers).execute(); + return users; + } + + private void givenNoUsersFound() { + when(getAllUsers.execute()).thenThrow(new UserNotFoundException("No hay usuarios")); + } + + private void givenUserDesactivated(String message) { + when(desactivateUser.execute(anyString())).thenReturn(message); + } + + private void givenUserNotFoundForDesactivate(String email) { + when(desactivateUser.execute(email)) + .thenThrow(new UserNotFoundException("Usuario no encontrado")); + } + + private void givenUserActivated(String message) { + when(activateUser.execute(anyString())).thenReturn(message); + } + + private void givenUserConvertedToAdmin(String message) { + when(convertToAdmin.execute(anyString())).thenReturn(message); + } + + private void givenUserConvertedToUser(String message) { + when(convertToUser.execute(anyString())).thenReturn(message); + } + + private EditProfileRequestDTO givenValidEditProfileRequest() { + EditProfileRequestDTO request = new EditProfileRequestDTO(); + request.setName("Updated"); + request.setLastname("User"); + request.setEmail("updated@example.com"); + request.setPassword("NewPassword123"); + request.setConfirmPassword("NewPassword123"); + request.setUserImg(mock(MultipartFile.class)); + return request; + } + + private UserModel givenUserUpdated() { + UserModel user = mock(UserModel.class); + when(updateUser.execute(anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyString())).thenReturn(user); + return user; + } + + private void givenUserImageUrl(String url) { + UserModel user = mock(UserModel.class); + when(user.getUserImg()).thenReturn(url); + when(getUserByEmail.execute(anyString())).thenReturn(user); + } + + private void givenPasswordMismatch() { + // Mock getUserByEmail to return a user with image URL + UserModel existingUser = mock(UserModel.class); + when(existingUser.getUserImg()).thenReturn("http://old-image.url/user.jpg"); + when(getUserByEmail.execute(anyString())).thenReturn(existingUser); + + // Mock updateUser to throw exception + when(updateUser.execute(anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyString())) + .thenThrow(new PasswordIsNotTheSame("Las contraseñas no coinciden")); + } + + // ========== WHEN Methods ========== + + private ResponseEntity whenCallRegisterUser(RegisterDTO request) { + return controller.registerUser(request); + } + + private ResponseEntity whenCallRegisterBrandAndUser(RegisterDTO request) { + return controller.registerbrandAndUser(request); + } + + private ResponseEntity whenCallLoginUser(LoginDTO loginDTO) { + return controller.loginUser(loginDTO); + } + + private ResponseEntity whenCallRefreshToken(String refreshTokenValue) { + Map request = Map.of("refresh_token", refreshTokenValue); + return controller.refreshToken(request); + } + + private ResponseEntity whenCallGetAuthUserProfile() { + return controller.getAuthUserProfile(); + } + + private ResponseEntity whenCallGetAllUsers() { + return controller.getAllUsers(); + } + + private ResponseEntity whenCallDesactivateUser(String email) { + return controller.desactivateUser(email); + } + + private ResponseEntity whenCallActivateUser(String email) { + return controller.activateUser(email); + } + + private ResponseEntity whenCallConvertToAdmin(String email) { + return controller.convertToAdmin(email); + } + + private ResponseEntity whenCallConvertToUser(String email) { + return controller.convertToUser(email); + } + + private ResponseEntity whenCallUpdateUser(String oldEmail, EditProfileRequestDTO request) { + return controller.updateUser(oldEmail, request); + } + + // ========== THEN Methods ========== + + private void thenResponseOk(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + assertNotNull(response.getBody()); + } + + private void thenResponseCreated(ResponseEntity response) { + assertEquals(201, response.getStatusCode().value()); + assertNotNull(response.getBody()); + } + + private void thenResponseBadRequest(ResponseEntity response) { + assertEquals(400, response.getStatusCode().value()); + } + + private void thenResponseUnauthorized(ResponseEntity response) { + assertEquals(401, response.getStatusCode().value()); + } + + private void thenResponseNotFound(ResponseEntity response, String expectedMessage) { + assertEquals(404, response.getStatusCode().value()); + assertEquals(expectedMessage, response.getBody()); + } + + private void thenResponseContainsUserData(ResponseEntity response, String email, String name) { + Map body = (Map) response.getBody(); + assertNotNull(body); + assertEquals(email, body.get("email")); + assertEquals(name, body.get("name")); + } + + private void thenResponseContainsMessage(ResponseEntity response, String expectedMessage) { + Map body = (Map) response.getBody(); + assertNotNull(body); + assertEquals(expectedMessage, body.get("message")); + } + + private void thenVerifyRegisterUserCalled() { + verify(registerUser, times(1)).execute(any(RegisterDTO.class)); + } + + private void thenVerifyAssignFreePlanCalled(String email, boolean isBrand) { + verify(assignFreePlanToUser, times(1)).execute(email, isBrand); + } + + private void thenVerifyCreateBrandCalled() { + verify(createBrand, times(1)).execute(anyString(), anyString(), anyString()); + } + + private void thenVerifyUpdateBrandUserCalled() { + verify(updateBrandUser, times(1)).execute(anyString(), anyString()); + } + + private void thenVerifyLoginUserCalled() { + verify(loginUser, times(1)).execute(any(LoginDTO.class)); + } + + private void thenVerifyRefreshTokenCalled(String token) { + verify(refreshToken, times(1)).execute(token); + } + + private void thenVerifyUserProfileCalled() { + verify(userProfile, times(1)).execute(); + } + + private void thenVerifyGetAllUsersCalled() { + verify(getAllUsers, times(1)).execute(); + } + + private void thenVerifyDesactivateUserCalled(String email) { + verify(desactivateUser, times(1)).execute(email); + } + + private void thenVerifyActivateUserCalled(String email) { + verify(activateUser, times(1)).execute(email); + } + + private void thenVerifyConvertToAdminCalled(String email) { + verify(convertToAdmin, times(1)).execute(email); + } + + private void thenVerifyConvertToUserCalled(String email) { + verify(convertToUser, times(1)).execute(email); + } + + private void thenVerifyUpdateUserCalled() { + verify(updateUser, times(1)).execute( + anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyString()); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/VerificationControllerTest.java b/src/test/java/com/outfitlab/project/presentation/VerificationControllerTest.java new file mode 100644 index 0000000..4cdaea8 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/VerificationControllerTest.java @@ -0,0 +1,102 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.useCases.user.VerifyEmail; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class VerificationControllerTest { + + private VerifyEmail verifyEmailUseCase; + private VerificationController controller; + + @BeforeEach + void setUp() { + verifyEmailUseCase = mock(VerifyEmail.class); + controller = new VerificationController(verifyEmailUseCase); + } + + @Test + void givenValidTokenWhenVerifyUserThenRedirectsToSuccess() throws Exception { + + String token = givenValidToken(); + + ResponseEntity response = whenVerifyUser(token); + + thenRedirectsToSuccess(response); + verify(verifyEmailUseCase, times(1)).execute(token); + } + + @Test + void givenInvalidTokenWhenVerifyUserThenRedirectsToUserNotFoundError() throws Exception { + + String token = givenInvalidToken(); + mockUserNotFoundException(token); + + ResponseEntity response = whenVerifyUser(token); + + thenRedirectsToUserNotFoundError(response); + verify(verifyEmailUseCase, times(1)).execute(token); + } + + @Test + void givenUnexpectedExceptionWhenVerifyUserThenRedirectsToInternalError() throws Exception { + + String token = givenAnyToken(); + mockUnexpectedException(token); + + ResponseEntity response = whenVerifyUser(token); + + thenRedirectsToInternalError(response); + verify(verifyEmailUseCase, times(1)).execute(token); + } + + private String givenValidToken() { + return "valid-token"; + } + + private String givenInvalidToken() { + return "invalid-token"; + } + + private String givenAnyToken() { + return "any-token"; + } + + private void mockUserNotFoundException(String token) throws Exception { + doThrow(new UserNotFoundException("No existe")).when(verifyEmailUseCase).execute(token); + } + + private void mockUnexpectedException(String token) throws Exception { + doThrow(new RuntimeException("boom")).when(verifyEmailUseCase).execute(token); + } + + private ResponseEntity whenVerifyUser(String token) throws Exception { + return controller.verifyUser(token); + } + + private void thenRedirectsToSuccess(ResponseEntity response) { + assertEquals(302, response.getStatusCode().value()); + assertTrue(response.getHeaders().getLocation().toString().contains("?verification=success")); + } + + private void thenRedirectsToUserNotFoundError(ResponseEntity response) { + assertEquals(302, response.getStatusCode().value()); + + String location = response.getHeaders().getLocation().toString(); + assertTrue(location.contains("?verification=error&message=")); + assertTrue(location.contains("Token+inv%C3%A1lido+o+expirado.")); + } + + private void thenRedirectsToInternalError(ResponseEntity response) { + assertEquals(302, response.getStatusCode().value()); + + String location = response.getHeaders().getLocation().toString(); + assertTrue(location.contains("?verification=error&message=")); + assertTrue(location.contains("Error+interno+del+servidor")); + } +} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 7c7b584..13dd4d8 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -17,4 +17,4 @@ docker: lifecycle-management: start_and_stop skip: in-tests: false - file: docker-compose-test.yml + file: docker-compose-test.yml \ No newline at end of file