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/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/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/RecomendationConfig.java b/src/main/java/com/outfitlab/project/infrastructure/config/RecomendationConfig.java index 6043a48..efe6061 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/config/RecomendationConfig.java +++ b/src/main/java/com/outfitlab/project/infrastructure/config/RecomendationConfig.java @@ -31,6 +31,11 @@ public PrendaOcacionRepository prendaOcacionRepository(PrendaOcacionJpaRepositor return new PrendaOcacionRepositoryImpl(prendaOcacionJpaRepository); } + @Bean + public DeleteRecomendationByPrimaryAndSecondaryGarmentCode deleteRecomendationByPrimaryAndSecondaryGarmentCode(GarmentRecomendationRepository recomendationRepository){ + return new DeleteRecomendationByPrimaryAndSecondaryGarmentCode(recomendationRepository); + } + @Bean public CreateSugerenciasByGarmentsCode createSugerenciasByGarmentsCode(GarmentRecomendationRepository garmentRecomendationRepository){ return new CreateSugerenciasByGarmentsCode(garmentRecomendationRepository); 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/GarmentRepositoryImpl.java b/src/main/java/com/outfitlab/project/infrastructure/repositories/GarmentRepositoryImpl.java index cda1ca7..8cfb63d 100644 --- a/src/main/java/com/outfitlab/project/infrastructure/repositories/GarmentRepositoryImpl.java +++ b/src/main/java/com/outfitlab/project/infrastructure/repositories/GarmentRepositoryImpl.java @@ -20,7 +20,7 @@ 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; 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/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/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/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..cd8a761 --- /dev/null +++ b/src/test/java/com/outfitlab/project/domain/useCases/recomendations/DeleteAllPrendaOcacionRelatedToGarmentTest.java @@ -0,0 +1,75 @@ +package com.outfitlab.project.domain.useCases.recomendations; + +import com.outfitlab.project.domain.interfaces.repositories.PrendaOcacionRepository; +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 PrendaOcacionRepository prendaOcacionRepository = mock(PrendaOcacionRepository.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(prendaOcacionRepository); + } + + + @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(prendaOcacionRepository).deleteAllPrendaOcacionByGarment(garmentCode); + } + + private void whenExecuteDeleteAll(String garmentCode) { + deleteAllPrendaOcacionRelatedToGarment.execute(garmentCode); + } + + private void thenRepositoryDeleteAllWasCalled(String expectedGarmentCode, int times) { + verify(prendaOcacionRepository, times(times)).deleteAllPrendaOcacionByGarment(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/UserRepositoryImplTest.java b/src/test/java/com/outfitlab/project/infrastructure/repositories/UserRepositoryImplTest.java new file mode 100644 index 0000000..3edcbec --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/UserRepositoryImplTest.java @@ -0,0 +1,343 @@ +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.ArrayList; +import java.util.List; +import java.util.Optional; + +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 givenExistingEmailWhenFindUserByEmailThenReturnUserModel() { + // ---------- GIVEN --------- + String email = "test@mail.com"; + + UserEntity entity = new UserEntity(); + entity.setId(1L); + entity.setEmail(email); + entity.setName("Juan"); + entity.setLastName("Lopez"); + + when(userJpaRepository.findByEmail(email)).thenReturn(entity); + + // ---------- WHEN -------- + UserModel result = userRepository.findUserByEmail(email); + + // ---------- THEN -------- + assertThat(result).isNotNull(); + assertThat(result.getEmail()).isEqualTo(email); + assertThat(result.getId()).isEqualTo(1L); + verify(userJpaRepository).findByEmail(email); + } + + @Test + void givenValidTokenWhenFindUserByVerificationTokenThenReturnUserModel() { + String token = "abc123"; + + UserEntity entity = new UserEntity(); + entity.setId(1L); + entity.setEmail("test@mail.com"); + entity.setVerificationToken(token); + + when(userJpaRepository.findByVerificationToken(token)).thenReturn(entity); + + UserModel result = userRepository.findUserByVerificationToken(token); + + assertThat(result).isNotNull(); + assertThat(result.getEmail()).isEqualTo("test@mail.com"); + verify(userJpaRepository).findByVerificationToken(token); + } + + @Test + void givenInvalidTokenWhenFindUserByVerificationTokenThenThrowException() { + String token = "def456"; + when(userJpaRepository.findByVerificationToken(token)).thenReturn(null); + + assertThatThrownBy(() -> userRepository.findUserByVerificationToken(token)) + .isInstanceOf(UserNotFoundException.class); + + verify(userJpaRepository).findByVerificationToken(token); + } + + @Test + void givenExistingIdWhenFindByIdThenReturnUserModel() { + Long id = 1L; + + UserEntity entity = new UserEntity(); + entity.setId(id); + entity.setEmail("test@mail.com"); + + when(userJpaRepository.findById(id)).thenReturn(Optional.of(entity)); + + UserModel result = userRepository.findById(id); + + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(id); + assertThat(result.getEmail()).isEqualTo("test@mail.com"); + verify(userJpaRepository).findById(id); + } + + @Test + void givenNonExistingIdWhenFindByIdThenThrowException() { + Long id = 99L; + when(userJpaRepository.findById(id)).thenReturn(Optional.empty()); + + + assertThatThrownBy(() -> userRepository.findById(id)) + .isInstanceOf(UserNotFoundException.class); + + verify(userJpaRepository).findById(id); + } + + @Test + void givenUserModelWhenSaveUserThenPersistAndReturnUserModel() { + UserModel model = new UserModel(); + model.setEmail("save@mail.com"); + model.setName("Juan"); + + UserEntity savedEntity = new UserEntity(model); + savedEntity.setId(1L); + + when(userJpaRepository.save(any(UserEntity.class))) + .thenReturn(savedEntity); + + UserModel result = userRepository.saveUser(model); + System.out.println(result); + + assertThat(result).isNotNull(); + assertThat(result.getEmail()).isEqualTo("save@mail.com"); + assertThat(result.getId()).isNull(); + verify(userJpaRepository).save(any(UserEntity.class)); + } + + @Test + void givenExistingUsersWhenFindAllThenReturnUserList() { + List entities = new ArrayList<>(); + + UserEntity u1 = new UserEntity(); + u1.setId(1L); + u1.setEmail("u1@mail.com"); + + UserEntity u2 = new UserEntity(); + u2.setId(2L); + u2.setEmail("u2@mail.com"); + + entities.add(u1); + entities.add(u2); + + when(userJpaRepository.findAll()).thenReturn(entities); + + List result = userRepository.findAll(); + + assertThat(result).hasSize(2); + assertThat(result.get(0).getEmail()).isEqualTo("u1@mail.com"); + assertThat(result.get(1).getEmail()).isEqualTo("u2@mail.com"); + verify(userJpaRepository).findAll(); + } + + @Test + void givenExistingEmailWhenDesactivateUserThenStatusIsSetToFalse() { + String email = "test@mail.com"; + + UserEntity entity = new UserEntity(); + entity.setEmail(email); + entity.setStatus(true); + + when(userJpaRepository.findByEmail(email)).thenReturn(entity); + + userRepository.desactivateUser(email); + + assertThat(entity.isStatus()).isFalse(); + verify(userJpaRepository).findByEmail(email); + verify(userJpaRepository).save(entity); + } + + @Test + void givenNonExistingEmailWhenDesactivateUserThenThrowException() { + String email = "test15@mail.com"; + when(userJpaRepository.findByEmail(email)).thenReturn(null); + + assertThatThrownBy(() -> userRepository.desactivateUser(email)) + .isInstanceOf(UserNotFoundException.class); + + verify(userJpaRepository).findByEmail(email); + verify(userJpaRepository, never()).save(any()); + } + + @Test + void givenExistingEmailWhenActivateUserThenStatusTrueAndBrandApprovedTrue() { + String email = "test@mail.com"; + + UserEntity entity = new UserEntity(); + entity.setEmail(email); + entity.setStatus(false); + entity.setBrandApproved(false); + + when(userJpaRepository.findByEmail(email)).thenReturn(entity); + + userRepository.activateUser(email); + + assertThat(entity.isStatus()).isTrue(); + assertThat(entity.isBrandApproved()).isTrue(); + verify(userJpaRepository).findByEmail(email); + verify(userJpaRepository).save(entity); + } + + @Test + void givenNonExistingEmailWhenActivateUserThenThrowException() { + String email = "test17@mail.com"; + when(userJpaRepository.findByEmail(email)).thenReturn(null); + + assertThatThrownBy(() -> userRepository.activateUser(email)) + .isInstanceOf(UserNotFoundException.class); + + verify(userJpaRepository).findByEmail(email); + verify(userJpaRepository, never()).save(any()); + } + + @Test + void givenExistingEmailWhenConvertToAdminThenRoleIsAdmin() { + String email = "test@mail.com"; + + UserEntity entity = new UserEntity(); + entity.setEmail(email); + entity.setRole(Role.USER); + + when(userJpaRepository.findByEmail(email)).thenReturn(entity); + + userRepository.convertToAdmin(email); + + assertThat(entity.getRole()).isEqualTo(Role.ADMIN); + verify(userJpaRepository).save(entity); + } + + @Test + void givenExistingEmailWhenConvertToUserThenRoleIsUser() { + String email = "test@mail.com"; + + UserEntity entity = new UserEntity(); + entity.setEmail(email); + entity.setRole(Role.ADMIN); + + when(userJpaRepository.findByEmail(email)).thenReturn(entity); + + userRepository.convertToUser(email); + + assertThat(entity.getRole()).isEqualTo(Role.USER); + verify(userJpaRepository).save(entity); + } + + @Test + void givenValidUserAndBrandWHENUpdateBrandUserTHENUserIsUpdatedCorrectly() { + String email = "user@mail.com"; + String brandCode = "TESTBRAND"; + + UserEntity user = new UserEntity(); + user.setEmail(email); + + MarcaEntity brand = new MarcaEntity(); + brand.setCodigoMarca(brandCode); + + when(userJpaRepository.findByEmail(email)).thenReturn(user); + when(brandJpaRepository.findByCodigoMarca(brandCode)).thenReturn(brand); + when(userJpaRepository.save(user)).thenReturn(user); + + userRepository.updateBrandUser(email, brandCode); + + assertThat(user.getBrand()).isEqualTo(brand); + assertThat(user.getRole()).isEqualTo(Role.BRAND); + assertThat(user.isBrandApproved()).isFalse(); + verify(userJpaRepository).save(user); + } + + @Test + void givenBrandCodeWhenGetEmailUserRelatedThenReturnEmail() { + String brandCode = "TESTBRAND"; + + UserEntity user = new UserEntity(); + user.setEmail("brand@mail.com"); + + when(userJpaRepository.findByBrand_CodigoMarca(brandCode)).thenReturn(user); + + String email = userRepository.getEmailUserRelatedToBrandByBrandCode(brandCode); + + assertThat(email).isEqualTo("brand@mail.com"); + verify(userJpaRepository).findByBrand_CodigoMarca(brandCode); + } + + @Test + void givenUserDataWhenUpdateUserThenPersistChangesAndReturnModel() { + UserEntity entity = new UserEntity(); + entity.setEmail("old@mail.com"); + entity.setPassword("oldpass"); + entity.setUserImageUrl("old.png"); + + when(userJpaRepository.findByEmail("old@mail.com")).thenReturn(entity); + when(userJpaRepository.save(entity)).thenReturn(entity); + + UserModel result = userRepository.updateUser( + "old@mail.com", + "newName", + "newLName", + "new@mail.com", + "newpass", + "new.png" + ); + + assertThat(entity.getName()).isEqualTo("newName"); + assertThat(entity.getLastName()).isEqualTo("newLName"); + assertThat(entity.getEmail()).isEqualTo("new@mail.com"); + assertThat(entity.getPassword()).isEqualTo("newpass"); + assertThat(entity.getUserImageUrl()).isEqualTo("new.png"); + + assertThat(result.getEmail()).isEqualTo("new@mail.com"); + verify(userJpaRepository).save(entity); + } + + @Test + void givenEmptyPasswordWhenUpdateUserThenPasswordIsNotModified() { + UserEntity entity = new UserEntity(); + entity.setEmail("old@mail.com"); + entity.setPassword("oldpass"); + + when(userJpaRepository.findByEmail("old@mail.com")).thenReturn(entity); + when(userJpaRepository.save(entity)).thenReturn(entity); + + userRepository.updateUser( + "old@mail.com", + "newName", + "newLNAme", + "new@mail.com", + "", + "image.png" + ); + + assertThat(entity.getPassword()).isEqualTo("oldpass"); + assertThat(entity.getEmail()).isEqualTo("new@mail.com"); + } +} \ 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..95ee2b4 --- /dev/null +++ b/src/test/java/com/outfitlab/project/infrastructure/repositories/UserSubscriptionRepositoryImplTest.java @@ -0,0 +1,263 @@ +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 givenEmailWhenFindByUserEmailThenReturnSubscriptionModel() throws SubscriptionNotFoundException { + UserEntity user = new UserEntity(); + user.setEmail("test@mail.com"); + + SubscriptionEntity plan = new SubscriptionEntity(); + plan.setPlanCode("BASIC"); + + UserSubscriptionEntity entity = new UserSubscriptionEntity(); + entity.setUser(user); + entity.setSubscription(plan); + entity.setCombinationsUsed(10); + + when(userSubscriptionJpaRepository.findByUserEmail("test@mail.com")) + .thenReturn(Optional.of(entity)); + + UserSubscriptionModel result = repository.findByUserEmail("test@mail.com"); + + assertThat(result).isNotNull(); + assertThat(result.getCombinationsUsed()).isEqualTo(10); + verify(userSubscriptionJpaRepository).findByUserEmail("test@mail.com"); + } + + @Test + void givenEmailWhenSubscriptionNotFoundThenThrowException() { + when(userSubscriptionJpaRepository.findByUserEmail("missing@mail.com")) + .thenReturn(Optional.empty()); + + assertThatThrownBy(() -> repository.findByUserEmail("missing@mail.com")) + .isInstanceOf(SubscriptionNotFoundException.class) + .hasMessageContaining("missing@mail.com"); + + verify(userSubscriptionJpaRepository).findByUserEmail("missing@mail.com"); + } + + @Test + void givenSubscriptionModelWhenSaveThenPersistAndReturnModel() { + UserEntity user = new UserEntity(); + user.setEmail("user@mail.com"); + user.setId(5L); + + SubscriptionEntity plan = new SubscriptionEntity(); + plan.setPlanCode("PREMIUM"); + + when(userJpaRepository.findByEmail("user@mail.com")).thenReturn(user); + when(subscriptionJpaRepository.findByPlanCode("PREMIUM")).thenReturn(Optional.of(plan)); + + UserSubscriptionEntity savedEntity = new UserSubscriptionEntity(); + savedEntity.setUser(user); + savedEntity.setSubscription(plan); + savedEntity.setCombinationsUsed(3); + + when(userSubscriptionJpaRepository.save(any(UserSubscriptionEntity.class))).thenReturn(savedEntity); + + UserSubscriptionModel model = new UserSubscriptionModel(); + model.setUserEmail("user@mail.com"); + model.setPlanCode("PREMIUM"); + model.setCombinationsUsed(3); + + UserSubscriptionModel result = repository.save(model); + + assertThat(result).isNotNull(); + assertThat(result.getCombinationsUsed()).isEqualTo(3); + verify(userSubscriptionJpaRepository).save(any(UserSubscriptionEntity.class)); + } + + @Test + void givenInvalidUserWhenSaveThenThrowException() { + UserSubscriptionModel model = new UserSubscriptionModel(); + model.setUserEmail("fail@mail.com"); + + when(userJpaRepository.findByEmail("fail@mail.com")).thenReturn(null); + + assertThatThrownBy(() -> repository.save(model)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Usuario no encontrado"); + } + + @Test + void givenInvalidPlanWhenSaveThenThrowException() { + UserSubscriptionModel model = new UserSubscriptionModel(); + model.setUserEmail("user@mail.com"); + model.setPlanCode("PLAN A"); + + when(userJpaRepository.findByEmail("user@mail.com")).thenReturn(new UserEntity()); + when(subscriptionJpaRepository.findByPlanCode("PLAN A")).thenReturn(Optional.empty()); + + assertThatThrownBy(() -> repository.save(model)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Plan no encontrado"); + } + + @Test + void givenSubscriptionModelWhenUpdateThenUpdateAndReturnModel() { + SubscriptionEntity oldPlan = new SubscriptionEntity(); + oldPlan.setPlanCode("ESTANDAR"); + + UserSubscriptionEntity existingUserSubs = new UserSubscriptionEntity(); + existingUserSubs.setCombinationsUsed(1); + existingUserSubs.setUser(new UserEntity("test@mail.com")); + existingUserSubs.setSubscription(oldPlan); + + SubscriptionEntity newPlan = new SubscriptionEntity(); + newPlan.setPlanCode("PRO"); + + when(userSubscriptionJpaRepository.findByUserEmail("test@mail.com")) + .thenReturn(Optional.of(existingUserSubs)); + when(subscriptionJpaRepository.findByPlanCode("ESTANDAR")) + .thenReturn(Optional.of(oldPlan)); + when(subscriptionJpaRepository.findByPlanCode("PRO")) + .thenReturn(Optional.of(newPlan)); + when(userSubscriptionJpaRepository.save(existingUserSubs)).thenReturn(existingUserSubs); + + UserSubscriptionModel model = new UserSubscriptionModel(); + model.setUserEmail("test@mail.com"); + model.setPlanCode("PRO"); + model.setCombinationsUsed(7); + + UserSubscriptionModel result = repository.update(model); + + assertThat(existingUserSubs.getSubscription()).isEqualTo(newPlan); + assertThat(existingUserSubs.getCombinationsUsed()).isEqualTo(7); + assertThat(result).isNotNull(); + } + + @Test + void givenNonExistingSubscriptionWhenUpdateThenThrowException() { + UserSubscriptionModel model = new UserSubscriptionModel(); + model.setUserEmail("noexiste@mail.com"); + + when(userSubscriptionJpaRepository.findByUserEmail("noexiste@mail.com")) + .thenReturn(Optional.empty()); + + assertThatThrownBy(() -> repository.update(model)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Suscripción no encontrada"); + } + + @Test + void givenInvalidPlanWhenUpdateThenThrowException() { + UserSubscriptionModel model = new UserSubscriptionModel(); + model.setUserEmail("test@mail.com"); + model.setPlanCode("TURBO"); + + when(userSubscriptionJpaRepository.findByUserEmail("test@mail.com")) + .thenReturn(Optional.of(new UserSubscriptionEntity())); + when(subscriptionJpaRepository.findByPlanCode("TURBO")) + .thenReturn(Optional.empty()); + + assertThatThrownBy(() -> repository.update(model)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Plan no encontrado"); + } + + @Test + void givenEmailAndCombinationsWhenIncrementCounterThenCallsIncrementCombinations() { + UserEntity user = new UserEntity(); + user.setId(1L); + + when(userJpaRepository.findByEmail("test@mail.com")).thenReturn(user); + + repository.incrementCounter("test@mail.com", "combinations"); + + verify(userSubscriptionJpaRepository).incrementCombinationsByUserId(1L); + } + + @Test + void givenEmailAndFavoritesWhenIncrementCounterThenCallsIncrementFavorites() { + UserEntity user = new UserEntity(); + user.setId(1L); + + when(userJpaRepository.findByEmail("test@mail.com")).thenReturn(user); + + repository.incrementCounter("test@mail.com", "favorites"); + + verify(userSubscriptionJpaRepository).incrementFavoritesByUserId(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 givenNonexistentUserWhenIncrementCounterThenThrowException() { + when(userJpaRepository.findByEmail("noexiste@mail.com")).thenReturn(null); + + assertThatThrownBy(() -> repository.incrementCounter("noexiste@mail.com", "combinations")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Usuario no encontrado"); + } + + @Test + void givenEmailAndFavoritesWhenDecrementCounterThenCallsDecrementFavorites() { + UserEntity user = new UserEntity(); + user.setId(2L); + + when(userJpaRepository.findByEmail("test@mail.com")).thenReturn(user); + + repository.decrementCounter("test@mail.com", "favorites"); + + verify(userSubscriptionJpaRepository).decrementFavoritesByUserId(2L); + } + + @Test + void givenNonFavoriteCounterWhenDecrementCounterThenDoNothing() { + repository.decrementCounter("test@mail.com", "models"); + + verify(userSubscriptionJpaRepository, never()).decrementFavoritesByUserId(anyLong()); + } + + @Test + void givenNonexistentUserWhenDecrementCounterThenThrowException() { + when(userJpaRepository.findByEmail("noexiste@mail.com")).thenReturn(null); + + assertThatThrownBy(() -> repository.decrementCounter("noexiste@mail.com", "favorites")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Usuario no encontrado"); + } + +} \ 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/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/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