From 5d4f7e8729fddf94d7920128d3cfbad61de52415 Mon Sep 17 00:00:00 2001 From: Lucasr Date: Thu, 27 Nov 2025 16:56:40 -0300 Subject: [PATCH] agrego test para controladores y presentacion --- .../CombinationControllerTest.java | 408 ++++++++++++ .../presentation/DashboardControllerTest.java | 226 +++++++ .../presentation/GarmentControllerTest.java | 520 +++++++++++++++ ...GarmentRecommendationAIControllerTest.java | 122 ++++ .../RecomendationControllerTest.java | 147 +++++ .../SubscriptionControllerTest.java | 315 ++++++++++ .../presentation/UserControllerTest.java | 592 ++++++++++++++++++ 7 files changed, 2330 insertions(+) create mode 100644 src/test/java/com/outfitlab/project/presentation/CombinationControllerTest.java create mode 100644 src/test/java/com/outfitlab/project/presentation/DashboardControllerTest.java create mode 100644 src/test/java/com/outfitlab/project/presentation/GarmentControllerTest.java create mode 100644 src/test/java/com/outfitlab/project/presentation/GarmentRecommendationAIControllerTest.java create mode 100644 src/test/java/com/outfitlab/project/presentation/RecomendationControllerTest.java create mode 100644 src/test/java/com/outfitlab/project/presentation/SubscriptionControllerTest.java create mode 100644 src/test/java/com/outfitlab/project/presentation/UserControllerTest.java diff --git a/src/test/java/com/outfitlab/project/presentation/CombinationControllerTest.java b/src/test/java/com/outfitlab/project/presentation/CombinationControllerTest.java new file mode 100644 index 0000000..ca1f149 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/CombinationControllerTest.java @@ -0,0 +1,408 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.exceptions.*; +import com.outfitlab.project.domain.model.CombinationModel; +import com.outfitlab.project.domain.model.PrendaModel; +import com.outfitlab.project.domain.model.UserCombinationFavoriteModel; +import com.outfitlab.project.domain.model.dto.PageDTO; +import com.outfitlab.project.domain.useCases.combination.CreateCombination; +import com.outfitlab.project.domain.useCases.combination.GetCombinationByPrendas; +import com.outfitlab.project.domain.useCases.combinationAttempt.RegisterCombinationAttempt; +import com.outfitlab.project.domain.useCases.combinationFavorite.AddCombinationToFavourite; +import com.outfitlab.project.domain.useCases.combinationFavorite.DeleteCombinationFromFavorite; +import com.outfitlab.project.domain.useCases.combinationFavorite.GetCombinationFavoritesForUserByEmail; +import com.outfitlab.project.domain.useCases.garment.GetGarmentByCode; +import com.outfitlab.project.presentation.dto.RegisterAttemptRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class CombinationControllerTest { + + private AddCombinationToFavourite addCombinationToFavourite; + private DeleteCombinationFromFavorite deleteCombinationFromFavorite; + private GetCombinationFavoritesForUserByEmail getCombinationFavoritesForUserByEmail; + private GetGarmentByCode getGarmentByCode; + private GetCombinationByPrendas getCombinationByPrendas; + private CreateCombination createCombination; + private RegisterCombinationAttempt registerCombinationAttempt; + + private CombinationController controller; + + @BeforeEach + void setUp() { + addCombinationToFavourite = mock(AddCombinationToFavourite.class); + deleteCombinationFromFavorite = mock(DeleteCombinationFromFavorite.class); + getCombinationFavoritesForUserByEmail = mock(GetCombinationFavoritesForUserByEmail.class); + getGarmentByCode = mock(GetGarmentByCode.class); + getCombinationByPrendas = mock(GetCombinationByPrendas.class); + createCombination = mock(CreateCombination.class); + registerCombinationAttempt = mock(RegisterCombinationAttempt.class); + + controller = new CombinationController( + addCombinationToFavourite, + deleteCombinationFromFavorite, + getCombinationFavoritesForUserByEmail, + getGarmentByCode, + getCombinationByPrendas, + createCombination, + registerCombinationAttempt); + } + + // ========== registerAttempt Tests ========== + + @Test + void givenValidRequestWithExistingCombinationWhenRegisterAttemptThenReturnOk() throws Exception { + RegisterAttemptRequest request = givenValidRegisterAttemptRequest(); + PrendaModel sup = givenGarmentExists("sup-code", 1L); + PrendaModel inf = givenGarmentExists("inf-code", 2L); + CombinationModel combination = givenCombinationExists(1L, 2L); + givenAttemptRegistered(123L); + + ResponseEntity response = whenCallRegisterAttempt(request); + + thenResponseOkWithMessage(response, "Combinacion registrada con ID: 123"); + thenVerifyGarmentByCodeCalled("sup-code"); + thenVerifyGarmentByCodeCalled("inf-code"); + thenVerifyGetCombinationByPrendasCalled(1L, 2L); + thenVerifyRegisterAttemptCalled(); + } + + @Test + void givenValidRequestWithNewCombinationWhenRegisterAttemptThenReturnOk() throws Exception { + RegisterAttemptRequest request = givenValidRegisterAttemptRequest(); + PrendaModel sup = givenGarmentExists("sup-code", 1L); + PrendaModel inf = givenGarmentExists("inf-code", 2L); + givenCombinationNotFound(1L, 2L); + CombinationModel newCombination = givenCombinationCreated(sup, inf); + givenAttemptRegistered(456L); + + ResponseEntity response = whenCallRegisterAttempt(request); + + thenResponseOkWithMessage(response, "Combinacion registrada con ID: 456"); + thenVerifyCreateCombinationCalled(sup, inf); + } + + @Test + void givenInvalidGarmentCodeWhenRegisterAttemptThenReturn404() throws Exception { + RegisterAttemptRequest request = givenValidRegisterAttemptRequest(); + givenGarmentNotFound("sup-code"); + + ResponseEntity response = whenCallRegisterAttempt(request); + + thenResponseNotFound(response, "Prenda no encontrada"); + } + + // ========== addCombinationToFavorite Tests ========== + + @Test + void givenValidUrlWhenAddCombinationToFavoriteThenReturnOk() throws Exception { + String url = givenValidCombinationUrl(); + givenAuthenticatedUser("user@test.com"); + givenFavoriteAdded("Favorito agregado"); + + ResponseEntity response = whenCallAddCombinationToFavorite(url); + + thenResponseOk(response); + thenVerifyAddCombinationToFavouriteCalled(url, "user@test.com"); + } + + @Test + void givenEmptyUrlWhenAddCombinationToFavoriteThenReturn404() { + String url = ""; + + ResponseEntity response = whenCallAddCombinationToFavorite(url); + + thenResponseNotFound(response, "El parámetro: no puede estar vacío."); + } + + @Test + void givenNullUrlWhenAddCombinationToFavoriteThenReturn404() { + ResponseEntity response = whenCallAddCombinationToFavorite(null); + + thenResponseNotFound(response, "El parámetro: null no puede estar vacío."); + } + + @Test + void givenPlanLimitExceededWhenAddCombinationToFavoriteThenReturn403() throws Exception { + String url = givenValidCombinationUrl(); + givenAuthenticatedUser("user@test.com"); + givenPlanLimitExceeded(); + + ResponseEntity response = whenCallAddCombinationToFavorite(url); + + thenResponseForbidden(response); + thenResponseContainsUpgradeRequired(response); + } + + @Test + void givenUserNotFoundWhenAddCombinationToFavoriteThenReturn404() throws Exception { + String url = givenValidCombinationUrl(); + givenAuthenticatedUser("user@test.com"); + givenUserNotFound(); + + ResponseEntity response = whenCallAddCombinationToFavorite(url); + + thenResponseNotFound(response, "Usuario no encontrado"); + } + + @Test + void givenFavoriteAlreadyExistsWhenAddCombinationToFavoriteThenReturn404() throws Exception { + String url = givenValidCombinationUrl(); + givenAuthenticatedUser("user@test.com"); + givenFavoriteAlreadyExists(); + + ResponseEntity response = whenCallAddCombinationToFavorite(url); + + thenResponseNotFound(response, "Favorito ya existe"); + } + + // ========== deleteCombinationFromFavorite Tests ========== + + @Test + void givenValidUrlWhenDeleteCombinationFromFavoriteThenReturnOk() throws Exception { + String url = givenValidCombinationUrl(); + givenAuthenticatedUser("user@test.com"); + givenFavoriteDeleted("Favorito eliminado"); + + ResponseEntity response = whenCallDeleteCombinationFromFavorite(url); + + thenResponseOk(response); + thenVerifyDeleteCombinationFromFavoriteCalled(url, "user@test.com"); + } + + @Test + void givenFavoriteNotFoundWhenDeleteCombinationFromFavoriteThenReturn404() throws Exception { + String url = givenValidCombinationUrl(); + givenAuthenticatedUser("user@test.com"); + givenFavoriteNotFoundForDelete(); + + ResponseEntity response = whenCallDeleteCombinationFromFavorite(url); + + thenResponseNotFound(response, "Favorito no encontrado"); + } + + // ========== getFavorites Tests ========== + + @Test + void givenValidPageWhenGetFavoritesThenReturnOk() throws Exception { + givenAuthenticatedUser("user@test.com"); + givenFavoritesExist(); + + ResponseEntity response = whenCallGetFavorites(0); + + thenResponseOk(response); + thenVerifyGetFavoritesCalled("user@test.com", 0); + } + + @Test + void givenNegativePageWhenGetFavoritesThenReturn404() throws Exception { + givenAuthenticatedUser("user@test.com"); + givenNegativePageError(); + + ResponseEntity response = whenCallGetFavorites(-1); + + thenResponseNotFound(response, "Página menor que cero"); + } + + @Test + void givenNoFavoritesWhenGetFavoritesThenReturnOk() throws Exception { + givenAuthenticatedUser("user@test.com"); + givenNoFavorites(); + + ResponseEntity response = whenCallGetFavorites(0); + + thenResponseOk(response); + } + + // ========== GIVEN Methods ========== + + private RegisterAttemptRequest givenValidRegisterAttemptRequest() { + return new RegisterAttemptRequest( + "user@test.com", + "sup-code", + "inf-code", + "http://image.url/combo.jpg"); + } + + private PrendaModel givenGarmentExists(String code, Long id) { + PrendaModel garment = mock(PrendaModel.class); + when(garment.getId()).thenReturn(id); + when(getGarmentByCode.execute(code)).thenReturn(garment); + return garment; + } + + private void givenGarmentNotFound(String code) { + when(getGarmentByCode.execute(code)) + .thenThrow(new GarmentNotFoundException("Prenda no encontrada")); + } + + private CombinationModel givenCombinationExists(Long supId, Long infId) { + CombinationModel combination = mock(CombinationModel.class); + when(getCombinationByPrendas.execute(supId, infId)).thenReturn(combination); + return combination; + } + + private void givenCombinationNotFound(Long supId, Long infId) { + when(getCombinationByPrendas.execute(supId, infId)) + .thenThrow(new CombinationNotFoundException("Combinación no encontrada")); + } + + private CombinationModel givenCombinationCreated(PrendaModel sup, PrendaModel inf) { + CombinationModel combination = mock(CombinationModel.class); + when(createCombination.execute(sup, inf)).thenReturn(combination); + return combination; + } + + private void givenAttemptRegistered(Long attemptId) { + when(registerCombinationAttempt.execute(anyString(), any(CombinationModel.class), anyString())) + .thenReturn(attemptId); + } + + private String givenValidCombinationUrl() { + return "http://combination.url/combo123.jpg"; + } + + private void givenAuthenticatedUser(String email) { + Authentication authentication = mock(Authentication.class); + when(authentication.getName()).thenReturn(email); + SecurityContext securityContext = mock(SecurityContext.class); + when(securityContext.getAuthentication()).thenReturn(authentication); + SecurityContextHolder.setContext(securityContext); + } + + private void givenFavoriteAdded(String message) throws Exception { + when(addCombinationToFavourite.execute(anyString(), anyString())).thenReturn(message); + } + + private void givenPlanLimitExceeded() throws Exception { + PlanLimitExceededException exception = new PlanLimitExceededException( + "Plan limit exceeded", "favorites", 5, 10); + when(addCombinationToFavourite.execute(anyString(), anyString())).thenThrow(exception); + } + + private void givenUserNotFound() throws Exception { + when(addCombinationToFavourite.execute(anyString(), anyString())) + .thenThrow(new UserNotFoundException("Usuario no encontrado")); + } + + private void givenFavoriteAlreadyExists() throws Exception { + when(addCombinationToFavourite.execute(anyString(), anyString())) + .thenThrow(new UserCombinationFavoriteAlreadyExistsException("Favorito ya existe")); + } + + private void givenFavoriteDeleted(String message) throws Exception { + when(deleteCombinationFromFavorite.execute(anyString(), anyString())).thenReturn(message); + } + + private void givenFavoriteNotFoundForDelete() throws Exception { + when(deleteCombinationFromFavorite.execute(anyString(), anyString())) + .thenThrow(new UserCombinationFavoriteNotFoundException("Favorito no encontrado")); + } + + private void givenFavoritesExist() { + PageDTO favorites = new PageDTO<>( + Arrays.asList(mock(UserCombinationFavoriteModel.class)), + 0, 10, 1, 1, true); + when(getCombinationFavoritesForUserByEmail.execute(anyString(), anyInt())).thenReturn(favorites); + } + + private void givenNegativePageError() throws Exception { + when(getCombinationFavoritesForUserByEmail.execute(anyString(), eq(-1))) + .thenThrow(new PageLessThanZeroException("Página menor que cero")); + } + + private void givenNoFavorites() throws Exception { + when(getCombinationFavoritesForUserByEmail.execute(anyString(), anyInt())) + .thenThrow(new FavoritesException("No hay favoritos")); + } + + // ========== WHEN Methods ========== + + private ResponseEntity whenCallRegisterAttempt(RegisterAttemptRequest request) { + return controller.registerAttempt(request); + } + + private ResponseEntity whenCallAddCombinationToFavorite(String url) { + return controller.addCombinationToFavorite(url); + } + + private ResponseEntity whenCallDeleteCombinationFromFavorite(String url) { + return controller.deleteCombinationFromFavorite(url); + } + + private ResponseEntity whenCallGetFavorites(int page) { + return controller.getFavorites(page); + } + + // ========== THEN Methods ========== + + private void thenResponseOk(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + assertNotNull(response.getBody()); + } + + private void thenResponseOkWithMessage(ResponseEntity response, String expectedMessage) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expectedMessage, response.getBody()); + } + + private void thenResponseNotFound(ResponseEntity response, String expectedMessage) { + assertEquals(404, response.getStatusCode().value()); + assertEquals(expectedMessage, response.getBody()); + } + + private void thenResponseForbidden(ResponseEntity response) { + assertEquals(403, response.getStatusCode().value()); + } + + private void thenResponseContainsUpgradeRequired(ResponseEntity response) { + Map body = (Map) response.getBody(); + assertNotNull(body); + assertEquals(true, body.get("upgradeRequired")); + assertNotNull(body.get("error")); + assertNotNull(body.get("limitType")); + } + + private void thenVerifyGarmentByCodeCalled(String code) { + verify(getGarmentByCode, times(1)).execute(code); + } + + private void thenVerifyGetCombinationByPrendasCalled(Long supId, Long infId) { + verify(getCombinationByPrendas, times(1)).execute(supId, infId); + } + + private void thenVerifyCreateCombinationCalled(PrendaModel sup, PrendaModel inf) { + verify(createCombination, times(1)).execute(sup, inf); + } + + private void thenVerifyRegisterAttemptCalled() { + verify(registerCombinationAttempt, times(1)) + .execute(anyString(), any(CombinationModel.class), anyString()); + } + + private void thenVerifyAddCombinationToFavouriteCalled(String url, String email) throws Exception { + verify(addCombinationToFavourite, times(1)).execute(url, email); + } + + private void thenVerifyDeleteCombinationFromFavoriteCalled(String url, String email) throws Exception { + verify(deleteCombinationFromFavorite, times(1)).execute(url, email); + } + + private void thenVerifyGetFavoritesCalled(String email, int page) throws Exception { + verify(getCombinationFavoritesForUserByEmail, times(1)).execute(email, page); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/DashboardControllerTest.java b/src/test/java/com/outfitlab/project/presentation/DashboardControllerTest.java new file mode 100644 index 0000000..0e62674 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/DashboardControllerTest.java @@ -0,0 +1,226 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.model.dto.PrendaDashboardDTO.*; +import com.outfitlab.project.domain.useCases.dashboard.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class DashboardControllerTest { + + private GetTopPrendas getTopPrendas; + private GetActividadPorDias getActividadPorDias; + private GetTopCombos getTopCombos; + private GetColorConversion getColorConversion; + + private DashboardController controller; + + @BeforeEach + void setUp() { + getTopPrendas = mock(GetTopPrendas.class); + getActividadPorDias = mock(GetActividadPorDias.class); + getTopCombos = mock(GetTopCombos.class); + getColorConversion = mock(GetColorConversion.class); + + controller = new DashboardController( + getTopPrendas, + getActividadPorDias, + getTopCombos, + getColorConversion); + } + + // ========== getTopPrendas Tests ========== + + @Test + void givenDefaultParamsWhenGetTopPrendasThenReturnOk() { + List topPrendas = givenTopPrendasExist(); + + ResponseEntity> response = whenCallGetTopPrendas(5, null); + + thenResponseOkWithTopPrendas(response, topPrendas); + thenVerifyGetTopPrendasCalled(5, null); + } + + @Test + void givenBrandCodeWhenGetTopPrendasThenReturnOk() { + List topPrendas = givenTopPrendasExistForBrand(); + + ResponseEntity> response = whenCallGetTopPrendas(10, "nike"); + + thenResponseOkWithTopPrendas(response, topPrendas); + thenVerifyGetTopPrendasCalled(10, "nike"); + } + + // ========== getActividadPorDias Tests ========== + + @Test + void whenGetActividadPorDiasThenReturnOk() { + List actividad = givenActividadExists(); + + ResponseEntity> response = whenCallGetActividadPorDias(); + + thenResponseOkWithActividad(response, actividad); + thenVerifyGetActividadPorDiasCalled(); + } + + // ========== getTopCombos Tests ========== + + @Test + void givenDefaultParamsWhenGetTopCombosThenReturnOk() { + List topCombos = givenTopCombosExist(); + + ResponseEntity> response = whenCallGetTopCombos(5, null); + + thenResponseOkWithTopCombos(response, topCombos); + thenVerifyGetTopCombosCalled(5, null); + } + + @Test + void givenBrandCodeWhenGetTopCombosThenReturnOk() { + List topCombos = givenTopCombosExistForBrand(); + + ResponseEntity> response = whenCallGetTopCombos(3, "adidas"); + + thenResponseOkWithTopCombos(response, topCombos); + thenVerifyGetTopCombosCalled(3, "adidas"); + } + + // ========== getColorConversion Tests ========== + + @Test + void givenNoBrandCodeWhenGetColorConversionThenReturnOk() { + List colorConversion = givenColorConversionExists(); + + ResponseEntity> response = whenCallGetColorConversion(null); + + thenResponseOkWithColorConversion(response, colorConversion); + thenVerifyGetColorConversionCalled(null); + } + + @Test + void givenBrandCodeWhenGetColorConversionThenReturnOk() { + List colorConversion = givenColorConversionExistsForBrand(); + + ResponseEntity> response = whenCallGetColorConversion("puma"); + + thenResponseOkWithColorConversion(response, colorConversion); + thenVerifyGetColorConversionCalled("puma"); + } + + // ========== GIVEN Methods ========== + + private List givenTopPrendasExist() { + List topPrendas = Arrays.asList( + new TopPrenda(1L, "Prenda 1", "Rojo", "url1", 100, List.of()), + new TopPrenda(2L, "Prenda 2", "Azul", "url2", 80, List.of())); + when(getTopPrendas.execute(5, null)).thenReturn(topPrendas); + return topPrendas; + } + + private List givenTopPrendasExistForBrand() { + List topPrendas = Arrays.asList( + new TopPrenda(1L, "Nike Shirt", "Negro", "url1", 50, List.of())); + when(getTopPrendas.execute(10, "nike")).thenReturn(topPrendas); + return topPrendas; + } + + private List givenActividadExists() { + List actividad = Arrays.asList( + new DiaPrueba(1, 10), + new DiaPrueba(2, 15)); + when(getActividadPorDias.execute()).thenReturn(actividad); + return actividad; + } + + private List givenTopCombosExist() { + List topCombos = Arrays.asList( + new ComboPopular("combo1", "combo2", "url1", "url2", 50, 10)); + when(getTopCombos.execute(5, null)).thenReturn(topCombos); + return topCombos; + } + + private List givenTopCombosExistForBrand() { + List topCombos = Arrays.asList( + new ComboPopular("combo1", "combo2", "url1", "url2", 30, 5)); + when(getTopCombos.execute(3, "adidas")).thenReturn(topCombos); + return topCombos; + } + + private List givenColorConversionExists() { + List colorConversion = Arrays.asList( + new ColorConversion("Rojo", 100, 80, 80.0), + new ColorConversion("Azul", 50, 40, 80.0)); + when(getColorConversion.execute(null)).thenReturn(colorConversion); + return colorConversion; + } + + private List givenColorConversionExistsForBrand() { + List colorConversion = Arrays.asList( + new ColorConversion("Negro", 60, 50, 83.3)); + when(getColorConversion.execute("puma")).thenReturn(colorConversion); + return colorConversion; + } + + // ========== WHEN Methods ========== + + private ResponseEntity> whenCallGetTopPrendas(int topN, String brandCode) { + return controller.getTopPrendas(topN, brandCode); + } + + private ResponseEntity> whenCallGetActividadPorDias() { + return controller.getActividadPorDias(); + } + + private ResponseEntity> whenCallGetTopCombos(int topN, String brandCode) { + return controller.getTopCombos(topN, brandCode); + } + + private ResponseEntity> whenCallGetColorConversion(String brandCode) { + return controller.getColorConversion(brandCode); + } + + // ========== THEN Methods ========== + + private void thenResponseOkWithTopPrendas(ResponseEntity> response, List expected) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expected, response.getBody()); + } + + private void thenResponseOkWithActividad(ResponseEntity> response, List expected) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expected, response.getBody()); + } + + private void thenResponseOkWithTopCombos(ResponseEntity> response, List expected) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expected, response.getBody()); + } + + private void thenResponseOkWithColorConversion(ResponseEntity> response, + List expected) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expected, response.getBody()); + } + + private void thenVerifyGetTopPrendasCalled(int topN, String brandCode) { + verify(getTopPrendas, times(1)).execute(topN, brandCode); + } + + private void thenVerifyGetActividadPorDiasCalled() { + verify(getActividadPorDias, times(1)).execute(); + } + + private void thenVerifyGetTopCombosCalled(int topN, String brandCode) { + verify(getTopCombos, times(1)).execute(topN, brandCode); + } + + private void thenVerifyGetColorConversionCalled(String brandCode) { + verify(getColorConversion, times(1)).execute(brandCode); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/GarmentControllerTest.java b/src/test/java/com/outfitlab/project/presentation/GarmentControllerTest.java new file mode 100644 index 0000000..cd36169 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/GarmentControllerTest.java @@ -0,0 +1,520 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.exceptions.*; +import com.outfitlab.project.domain.model.PrendaModel; +import com.outfitlab.project.domain.model.dto.GarmentDTO; +import com.outfitlab.project.domain.model.dto.PageDTO; +import com.outfitlab.project.domain.useCases.bucketImages.DeleteImage; +import com.outfitlab.project.domain.useCases.bucketImages.SaveImage; +import com.outfitlab.project.domain.useCases.combination.DeleteAllCombinationRelatedToGarment; +import com.outfitlab.project.domain.useCases.combinationAttempt.DeleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment; +import com.outfitlab.project.domain.useCases.garment.*; +import com.outfitlab.project.domain.useCases.recomendations.CreateSugerenciasByGarmentsCode; +import com.outfitlab.project.domain.useCases.recomendations.DeleteAllPrendaOcacionRelatedToGarment; +import com.outfitlab.project.domain.useCases.recomendations.DeleteGarmentRecomentationsRelatedToGarment; +import com.outfitlab.project.presentation.dto.GarmentRequestDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class GarmentControllerTest { + + private GetGarmentsByType getGarmentsByType; + private AddGarmentToFavorite addGarmentToFavorite; + private DeleteGarmentFromFavorite deleteGarmentFromFavorite; + private GetGarmentsFavoritesForUserByEmail getGarmentsFavoritesForUserByEmail; + private CreateGarment createGarment; + private SaveImage saveImage; + private DeleteGarment deleteGarment; + private GetGarmentByCode getGarmentByCode; + private DeleteImage deleteImage; + private UpdateGarment updateGarment; + private DeleteGarmentRecomentationsRelatedToGarment deleteGarmentRecomentationsRelatedToGarment; + private DeleteAllFavoritesRelatedToGarment deleteAllFavoritesRelatedToGarment; + private DeleteAllPrendaOcacionRelatedToGarment deleteAllPrendaOcacionRelatedToGarment; + private DeleteAllCombinationRelatedToGarment deleteAllCombinationRelatedToGarment; + private DeleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment deleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment; + private CreateSugerenciasByGarmentsCode createSugerenciasByGarmentsCode; + + private GarmentController controller; + private UserDetails mockUser; + + @BeforeEach + void setUp() { + getGarmentsByType = mock(GetGarmentsByType.class); + addGarmentToFavorite = mock(AddGarmentToFavorite.class); + deleteGarmentFromFavorite = mock(DeleteGarmentFromFavorite.class); + getGarmentsFavoritesForUserByEmail = mock(GetGarmentsFavoritesForUserByEmail.class); + createGarment = mock(CreateGarment.class); + saveImage = mock(SaveImage.class); + deleteGarment = mock(DeleteGarment.class); + getGarmentByCode = mock(GetGarmentByCode.class); + deleteImage = mock(DeleteImage.class); + updateGarment = mock(UpdateGarment.class); + deleteGarmentRecomentationsRelatedToGarment = mock(DeleteGarmentRecomentationsRelatedToGarment.class); + deleteAllFavoritesRelatedToGarment = mock(DeleteAllFavoritesRelatedToGarment.class); + deleteAllPrendaOcacionRelatedToGarment = mock(DeleteAllPrendaOcacionRelatedToGarment.class); + deleteAllCombinationRelatedToGarment = mock(DeleteAllCombinationRelatedToGarment.class); + deleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment = mock( + DeleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment.class); + createSugerenciasByGarmentsCode = mock(CreateSugerenciasByGarmentsCode.class); + + controller = new GarmentController( + getGarmentsByType, addGarmentToFavorite, deleteGarmentFromFavorite, + getGarmentsFavoritesForUserByEmail, createGarment, saveImage, deleteGarment, + null, getGarmentByCode, deleteImage, updateGarment, + deleteGarmentRecomentationsRelatedToGarment, deleteAllFavoritesRelatedToGarment, + deleteAllPrendaOcacionRelatedToGarment, deleteAllCombinationRelatedToGarment, + deleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment, + createSugerenciasByGarmentsCode); + + mockUser = mock(UserDetails.class); + } + + // ========== getGarmentsByType Tests ========== + + @Test + void givenValidTypeWhenGetGarmentsByTypeThenReturnOk() { + Page garments = givenGarmentsExistForType("superior"); + + ResponseEntity response = whenCallGetGarmentsByType(0, "superior"); + + thenResponseOkWithGarments(response); + thenVerifyGetGarmentsByTypeCalled("superior", 0); + } + + @Test + void givenInvalidTypeWhenGetGarmentsByTypeThenReturn404() { + givenGarmentsNotFoundForType("invalid"); + + ResponseEntity response = whenCallGetGarmentsByType(0, "invalid"); + + thenResponseNotFound(response, "Prendas no encontradas"); + } + + // ========== getAllGarments Tests ========== + + @Test + void givenValidPageWhenGetAllGarmentsThenReturnOk() { + givenGarmentsExistForType("superior"); + givenGarmentsExistForType("inferior"); + + ResponseEntity response = whenCallGetAllGarments(0); + + thenResponseOk(response); + } + + // ========== addGarmentToFavorite Tests ========== + + @Test + void givenValidGarmentCodeWhenAddGarmentToFavoriteThenReturnOk() throws Exception { + String garmentCode = "nike-shirt-001"; + givenFavoriteAdded("Favorito agregado"); + + ResponseEntity response = whenCallAddGarmentToFavorite(garmentCode); + + thenResponseOk(response); + thenVerifyAddGarmentToFavoriteCalled(garmentCode, "german@gmail.com"); + } + + @Test + void givenGarmentNotFoundWhenAddGarmentToFavoriteThenReturn404() throws Exception { + String garmentCode = "invalid-code"; + givenGarmentNotFoundForFavorite(garmentCode); + + ResponseEntity response = whenCallAddGarmentToFavorite(garmentCode); + + thenResponseNotFound(response, "Prenda no encontrada"); + } + + @Test + void givenFavoriteAlreadyExistsWhenAddGarmentToFavoriteThenReturn404() throws Exception { + String garmentCode = "nike-shirt-001"; + givenFavoriteAlreadyExists(garmentCode); + + ResponseEntity response = whenCallAddGarmentToFavorite(garmentCode); + + thenResponseNotFound(response, "Favorito ya existe"); + } + + // ========== deleteGarmentFromFavorite Tests ========== + + @Test + void givenValidGarmentCodeWhenDeleteGarmentFromFavoriteThenReturnOk() throws Exception { + String garmentCode = "nike-shirt-001"; + givenFavoriteDeleted("Favorito eliminado"); + + ResponseEntity response = whenCallDeleteGarmentFromFavorite(garmentCode); + + thenResponseOk(response); + thenVerifyDeleteGarmentFromFavoriteCalled(garmentCode, "german@gmail.com"); + } + + @Test + void givenFavoriteNotFoundWhenDeleteGarmentFromFavoriteThenReturn404() throws Exception { + String garmentCode = "invalid-code"; + givenFavoriteNotFoundForDelete(garmentCode); + + ResponseEntity response = whenCallDeleteGarmentFromFavorite(garmentCode); + + thenResponseNotFound(response, "Favorito no encontrado"); + } + + // ========== getFavorites Tests ========== + + @Test + void givenValidPageWhenGetFavoritesThenReturnOk() throws Exception { + givenFavoritesExist(); + + ResponseEntity response = whenCallGetFavorites(0); + + thenResponseOk(response); + } + + @Test + void givenNegativePageWhenGetFavoritesThenReturn404() throws Exception { + givenNegativePageError(); + + ResponseEntity response = whenCallGetFavorites(-1); + + thenResponseNotFound(response, "Página menor que cero"); + } + + // ========== newGarment Tests ========== + + @Test + void givenValidRequestWhenNewGarmentThenReturnOk() { + GarmentRequestDTO request = givenValidGarmentRequest(); + givenImageSaved("http://image.url/garment.jpg"); + givenGarmentCreated(); + givenSugerenciasCreated(); + + ResponseEntity response = whenCallNewGarment(request); + + thenResponseOkWithMessage(response, "Prenda creada correctamente."); + thenVerifyCreateGarmentCalled(); + } + + @Test + void givenInvalidBrandWhenNewGarmentThenReturn404() { + GarmentRequestDTO request = givenValidGarmentRequest(); + givenImageSaved("http://image.url/garment.jpg"); + givenBrandNotFound(); + + ResponseEntity response = whenCallNewGarment(request); + + thenResponseNotFound(response, "Marca no encontrada"); + } + + // ========== updateGarment Tests ========== + + @Test + void givenValidRequestWhenUpdateGarmentThenReturnOk() { + String garmentCode = "nike-shirt-001"; + GarmentRequestDTO request = givenValidGarmentRequestWithImage(); + PrendaModel existingGarment = givenGarmentExists(garmentCode); + givenImageSaved("http://image.url/new-garment.jpg"); + givenGarmentUpdated(); + givenSugerenciasCreated(); + + ResponseEntity response = whenCallUpdateGarment(garmentCode, request); + + thenResponseOkWithMessage(response, "Prenda actualizada correctamente."); + thenVerifyUpdateGarmentCalled(); + } + + @Test + void givenInvalidGarmentCodeWhenUpdateGarmentThenReturn404() { + String garmentCode = "invalid-code"; + GarmentRequestDTO request = givenValidGarmentRequest(); + givenGarmentNotFound(garmentCode); + + ResponseEntity response = whenCallUpdateGarment(garmentCode, request); + + thenResponseNotFound(response, "Prenda no encontrada"); + } + + // ========== deleteGarment Tests ========== + + @Test + void givenValidGarmentCodeWhenDeleteGarmentThenReturnOk() { + String garmentCode = "nike-shirt-001"; + PrendaModel garment = givenGarmentExistsForDelete(garmentCode); + givenAllRelatedRecordsDeleted(); + + ResponseEntity response = whenCallDeleteGarment(garmentCode); + + thenResponseOkWithMessage(response, "Prenda eliminada correctamente."); + thenVerifyDeleteGarmentCalled(); + } + + @Test + void givenInvalidGarmentCodeWhenDeleteGarmentThenReturn404() { + String garmentCode = "invalid-code"; + givenGarmentNotFound(garmentCode); + + ResponseEntity response = whenCallDeleteGarment(garmentCode); + + thenResponseNotFound(response, "Prenda no encontrada"); + } + + // ========== GIVEN Methods ========== + + private Page givenGarmentsExistForType(String type) { + PrendaModel garment1 = mock(PrendaModel.class); + PrendaModel garment2 = mock(PrendaModel.class); + + // Mock BrandModel for each garment + com.outfitlab.project.domain.model.BrandModel brand1 = mock( + com.outfitlab.project.domain.model.BrandModel.class); + com.outfitlab.project.domain.model.BrandModel brand2 = mock( + com.outfitlab.project.domain.model.BrandModel.class); + when(brand1.getNombre()).thenReturn("Nike"); + when(brand2.getNombre()).thenReturn("Adidas"); + when(garment1.getMarca()).thenReturn(brand1); + when(garment2.getMarca()).thenReturn(brand2); + + // Mock ColorModel for each garment + com.outfitlab.project.domain.model.ColorModel color1 = mock( + com.outfitlab.project.domain.model.ColorModel.class); + com.outfitlab.project.domain.model.ColorModel color2 = mock( + com.outfitlab.project.domain.model.ColorModel.class); + when(color1.getNombre()).thenReturn("Rojo"); + when(color2.getNombre()).thenReturn("Azul"); + when(garment1.getColor()).thenReturn(color1); + when(garment2.getColor()).thenReturn(color2); + + // Mock ClimaModel for each garment + com.outfitlab.project.domain.model.ClimaModel clima1 = mock( + com.outfitlab.project.domain.model.ClimaModel.class); + com.outfitlab.project.domain.model.ClimaModel clima2 = mock( + com.outfitlab.project.domain.model.ClimaModel.class); + when(clima1.getNombre()).thenReturn("Calido"); + when(clima2.getNombre()).thenReturn("Frio"); + when(garment1.getClimaAdecuado()).thenReturn(clima1); + when(garment2.getClimaAdecuado()).thenReturn(clima2); + + Page page = new PageImpl<>(Arrays.asList(garment1, garment2), PageRequest.of(0, 10), 2); + when(getGarmentsByType.execute(type, 0)).thenReturn(page); + return page; + } + + private void givenGarmentsNotFoundForType(String type) { + when(getGarmentsByType.execute(type, 0)) + .thenThrow(new GarmentNotFoundException("Prendas no encontradas")); + } + + private void givenFavoriteAdded(String message) throws Exception { + when(addGarmentToFavorite.execute(anyString(), anyString())).thenReturn(message); + } + + private void givenGarmentNotFoundForFavorite(String garmentCode) throws Exception { + when(addGarmentToFavorite.execute(eq(garmentCode), anyString())) + .thenThrow(new GarmentNotFoundException("Prenda no encontrada")); + } + + private void givenFavoriteAlreadyExists(String garmentCode) throws Exception { + when(addGarmentToFavorite.execute(eq(garmentCode), anyString())) + .thenThrow(new UserGarmentFavoriteAlreadyExistsException("Favorito ya existe")); + } + + private void givenFavoriteDeleted(String message) throws Exception { + when(deleteGarmentFromFavorite.execute(anyString(), anyString())).thenReturn(message); + } + + private void givenFavoriteNotFoundForDelete(String garmentCode) throws Exception { + when(deleteGarmentFromFavorite.execute(eq(garmentCode), anyString())) + .thenThrow(new UserGarmentFavoriteNotFoundException("Favorito no encontrado")); + } + + private void givenFavoritesExist() throws Exception { + PageDTO favorites = new PageDTO<>( + Arrays.asList(mock(PrendaModel.class), mock(PrendaModel.class)), + 0, 10, 2, 1, true); + when(getGarmentsFavoritesForUserByEmail.execute(anyString(), anyInt())).thenReturn(favorites); + } + + private void givenNegativePageError() throws Exception { + when(getGarmentsFavoritesForUserByEmail.execute(anyString(), eq(-1))) + .thenThrow(new PageLessThanZeroException("Página menor que cero")); + } + + private GarmentRequestDTO givenValidGarmentRequest() { + GarmentRequestDTO request = new GarmentRequestDTO(); + request.setNombre("Nike Shirt"); + request.setTipo("superior"); + request.setColorNombre("Rojo"); + request.setCodigoMarca("nike"); + request.setClimaNombre("Calido"); + request.setOcasionesNombres(Arrays.asList("Casual", "Deporte")); + request.setGenero("Masculino"); + request.setSugerencias(Arrays.asList("sug1", "sug2")); + request.setImagen(mock(MultipartFile.class)); + return request; + } + + private GarmentRequestDTO givenValidGarmentRequestWithImage() { + GarmentRequestDTO request = givenValidGarmentRequest(); + request.setImagen(mock(MultipartFile.class)); + request.setEvento("Casual"); // Add evento field + return request; + } + + private void givenImageSaved(String url) { + when(saveImage.execute(any(MultipartFile.class), anyString())).thenReturn(url); + } + + private void givenGarmentCreated() { + doNothing().when(createGarment).execute( + anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyList(), anyString()); + } + + private void givenSugerenciasCreated() { + doNothing().when(createSugerenciasByGarmentsCode).execute(anyString(), anyString(), anyList()); + } + + private void givenBrandNotFound() { + doThrow(new BrandsNotFoundException("Marca no encontrada")) + .when(createGarment).execute( + anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyList(), anyString()); + } + + private PrendaModel givenGarmentExists(String garmentCode) { + PrendaModel garment = mock(PrendaModel.class); + when(garment.getImagenUrl()).thenReturn("http://old-image.url/garment.jpg"); + when(garment.getMarca()).thenReturn(mock(com.outfitlab.project.domain.model.BrandModel.class)); + when(garment.getMarca().getCodigoMarca()).thenReturn("nike"); + when(getGarmentByCode.execute(garmentCode)).thenReturn(garment); + return garment; + } + + private void givenGarmentNotFound(String garmentCode) { + when(getGarmentByCode.execute(garmentCode)) + .thenThrow(new GarmentNotFoundException("Prenda no encontrada")); + } + + private void givenGarmentUpdated() { + doNothing().when(updateGarment).execute( + anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyString(), anyString(), anyList(), anyString()); + } + + private PrendaModel givenGarmentExistsForDelete(String garmentCode) { + PrendaModel garment = mock(PrendaModel.class); + when(garment.getImagenUrl()).thenReturn("http://image.url/garment.jpg"); + when(garment.getGarmentCode()).thenReturn(garmentCode); + when(garment.getMarca()).thenReturn(mock(com.outfitlab.project.domain.model.BrandModel.class)); + when(garment.getMarca().getCodigoMarca()).thenReturn("nike"); + when(getGarmentByCode.execute(garmentCode)).thenReturn(garment); + return garment; + } + + private void givenAllRelatedRecordsDeleted() { + doNothing().when(deleteGarmentRecomentationsRelatedToGarment).execute(anyString()); + doNothing().when(deleteAllFavoritesRelatedToGarment).execute(anyString()); + doNothing().when(deleteAllPrendaOcacionRelatedToGarment).execute(anyString()); + doNothing().when(deleteAllCombinationAttempsRelatedToCombinationsRelatedToGarment).execute(anyString()); + doNothing().when(deleteAllCombinationRelatedToGarment).execute(anyString()); + doNothing().when(deleteGarment).execute(any(PrendaModel.class), anyString()); + } + + // ========== WHEN Methods ========== + + private ResponseEntity whenCallGetGarmentsByType(int page, String type) { + return controller.getGarmentsByType(page, type); + } + + private ResponseEntity whenCallGetAllGarments(int page) { + return controller.getAllGarments(page); + } + + private ResponseEntity whenCallAddGarmentToFavorite(String garmentCode) { + return controller.addGarmentToFavorite(garmentCode); + } + + private ResponseEntity whenCallDeleteGarmentFromFavorite(String garmentCode) { + return controller.deleteGarmentFromFavorite(garmentCode); + } + + private ResponseEntity whenCallGetFavorites(int page) { + return controller.getFavorites(page); + } + + private ResponseEntity whenCallNewGarment(GarmentRequestDTO request) { + return controller.newGarment(request, mockUser); + } + + private ResponseEntity whenCallUpdateGarment(String garmentCode, GarmentRequestDTO request) { + return controller.updateGarment(garmentCode, request, mockUser); + } + + private ResponseEntity whenCallDeleteGarment(String garmentCode) { + return controller.deleteGarment(garmentCode, mockUser); + } + + // ========== THEN Methods ========== + + private void thenResponseOk(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + assertNotNull(response.getBody()); + } + + private void thenResponseOkWithMessage(ResponseEntity response, String expectedMessage) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expectedMessage, response.getBody()); + } + + private void thenResponseOkWithGarments(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + Map body = (Map) response.getBody(); + assertNotNull(body); + assertNotNull(body.get("content")); + } + + private void thenResponseNotFound(ResponseEntity response, String expectedMessage) { + assertEquals(404, response.getStatusCode().value()); + assertEquals(expectedMessage, response.getBody()); + } + + private void thenVerifyGetGarmentsByTypeCalled(String type, int page) { + verify(getGarmentsByType, times(1)).execute(type, page); + } + + private void thenVerifyAddGarmentToFavoriteCalled(String garmentCode, String email) throws Exception { + verify(addGarmentToFavorite, times(1)).execute(garmentCode, email); + } + + private void thenVerifyDeleteGarmentFromFavoriteCalled(String garmentCode, String email) throws Exception { + verify(deleteGarmentFromFavorite, times(1)).execute(garmentCode, email); + } + + private void thenVerifyCreateGarmentCalled() { + verify(createGarment, times(1)).execute( + anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyList(), anyString()); + } + + private void thenVerifyUpdateGarmentCalled() { + verify(updateGarment, times(1)).execute( + anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyString(), anyString(), anyList(), anyString()); + } + + private void thenVerifyDeleteGarmentCalled() { + verify(deleteGarment, times(1)).execute(any(PrendaModel.class), anyString()); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/GarmentRecommendationAIControllerTest.java b/src/test/java/com/outfitlab/project/presentation/GarmentRecommendationAIControllerTest.java new file mode 100644 index 0000000..ba5f9d6 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/GarmentRecommendationAIControllerTest.java @@ -0,0 +1,122 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.model.PrendaModel; +import com.outfitlab.project.domain.model.dto.ConjuntoDTO; +import com.outfitlab.project.domain.useCases.garment.GetGarmentRecomendationByText; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class GarmentRecommendationAIControllerTest { + + private GetGarmentRecomendationByText getGarmentRecomendationByText; + private GarmentRecommendationAIController controller; + + @BeforeEach + void setUp() { + getGarmentRecomendationByText = mock(GetGarmentRecomendationByText.class); + controller = new GarmentRecommendationAIController(getGarmentRecomendationByText); + } + + // ========== recommendOutfit Tests ========== + + @Test + void givenValidRequestWhenRecommendOutfitThenReturnOk() { + GarmentRecommendationAIController.RecommendationRequest request = givenValidRecommendationRequest(); + List outfits = givenOutfitsExist(); + + ResponseEntity> response = whenCallRecommendOutfit(request); + + thenResponseOkWithOutfits(response, outfits); + thenVerifyGetGarmentRecomendationByTextCalled("outfit casual para verano", "user123"); + } + + @Test + void givenEmptyResultsWhenRecommendOutfitThenReturnNoContent() { + GarmentRecommendationAIController.RecommendationRequest request = givenValidRecommendationRequest(); + givenNoOutfitsFound(); + + ResponseEntity> response = whenCallRecommendOutfit(request); + + thenResponseNoContent(response); + } + + @Test + void givenSingleEmptyConjuntoWhenRecommendOutfitThenReturnNoContent() { + GarmentRecommendationAIController.RecommendationRequest request = givenValidRecommendationRequest(); + givenSingleEmptyConjunto(); + + ResponseEntity> response = whenCallRecommendOutfit(request); + + thenResponseNoContent(response); + } + + // ========== GIVEN Methods ========== + + private GarmentRecommendationAIController.RecommendationRequest givenValidRecommendationRequest() { + GarmentRecommendationAIController.RecommendationRequest request = new GarmentRecommendationAIController.RecommendationRequest(); + request.setPeticionUsuario("outfit casual para verano"); + request.setIdUsuario("user123"); + return request; + } + + private List givenOutfitsExist() { + ConjuntoDTO conjunto1 = mock(ConjuntoDTO.class); + ConjuntoDTO conjunto2 = mock(ConjuntoDTO.class); + + when(conjunto1.getPrendas()).thenReturn(Arrays.asList( + mock(PrendaModel.class), + mock(PrendaModel.class))); + when(conjunto2.getPrendas()).thenReturn(Arrays.asList( + mock(PrendaModel.class), + mock(PrendaModel.class))); + + List outfits = Arrays.asList(conjunto1, conjunto2); + when(getGarmentRecomendationByText.execute("outfit casual para verano", "user123")) + .thenReturn(outfits); + return outfits; + } + + private void givenNoOutfitsFound() { + when(getGarmentRecomendationByText.execute("outfit casual para verano", "user123")) + .thenReturn(new ArrayList<>()); + } + + private void givenSingleEmptyConjunto() { + ConjuntoDTO emptyConjunto = mock(ConjuntoDTO.class); + when(emptyConjunto.getPrendas()).thenReturn(new ArrayList<>()); + + when(getGarmentRecomendationByText.execute("outfit casual para verano", "user123")) + .thenReturn(Arrays.asList(emptyConjunto)); + } + + // ========== WHEN Methods ========== + + private ResponseEntity> whenCallRecommendOutfit( + GarmentRecommendationAIController.RecommendationRequest request) { + return controller.recommendOutfit(request); + } + + // ========== THEN Methods ========== + + private void thenResponseOkWithOutfits(ResponseEntity> response, List expected) { + assertEquals(200, response.getStatusCode().value()); + assertEquals(expected, response.getBody()); + } + + private void thenResponseNoContent(ResponseEntity> response) { + assertEquals(204, response.getStatusCode().value()); + assertNull(response.getBody()); + } + + private void thenVerifyGetGarmentRecomendationByTextCalled(String peticion, String userId) { + verify(getGarmentRecomendationByText, times(1)).execute(peticion, userId); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/RecomendationControllerTest.java b/src/test/java/com/outfitlab/project/presentation/RecomendationControllerTest.java new file mode 100644 index 0000000..b17ee5a --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/RecomendationControllerTest.java @@ -0,0 +1,147 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.model.GarmentRecomendationModel; +import com.outfitlab.project.domain.useCases.garment.GetGarmentRecomendation; +import com.outfitlab.project.domain.useCases.recomendations.DeleteRecomendationByPrimaryAndSecondaryGarmentCode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class RecomendationControllerTest { + + private GetGarmentRecomendation getGarmentRecomendation; + private DeleteRecomendationByPrimaryAndSecondaryGarmentCode deleteRecomendationByPrimaryAndSecondaryGarmentCode; + + private RecomendationController controller; + + @BeforeEach + void setUp() { + getGarmentRecomendation = mock(GetGarmentRecomendation.class); + deleteRecomendationByPrimaryAndSecondaryGarmentCode = mock( + DeleteRecomendationByPrimaryAndSecondaryGarmentCode.class); + + controller = new RecomendationController( + getGarmentRecomendation, + deleteRecomendationByPrimaryAndSecondaryGarmentCode); + } + + // ========== getRecomendations Tests ========== + + @Test + void givenValidGarmentCodeWhenGetRecomendationsThenReturnOk() { + String garmentCode = "nike-shirt-001"; + List recommendations = givenRecommendationsExist(garmentCode); + + ResponseEntity response = whenCallGetRecomendations(garmentCode); + + thenResponseOk(response); + thenResponseBodyEquals(response, recommendations); + thenVerifyGetGarmentRecomendationCalled(garmentCode); + } + + @Test + void givenInvalidGarmentCodeWhenGetRecomendationsThenReturn404() { + String garmentCode = "invalid-code"; + givenRecommendationNotFound(garmentCode); + + ResponseEntity response = whenCallGetRecomendations(garmentCode); + + thenResponseNotFound(response, "Recomendaciones no encontradas"); + } + + // ========== deleteRecomendation Tests ========== + + @Test + void givenValidParamsWhenDeleteRecomendationThenReturnOk() { + String primaryCode = "garment-001"; + String secondaryCode = "garment-002"; + String type = "complemento"; + String successMessage = givenRecommendationDeleted(primaryCode, secondaryCode, type); + + ResponseEntity response = whenCallDeleteRecomendation(primaryCode, secondaryCode, type); + + thenResponseOk(response); + thenResponseBodyEquals(response, successMessage); + thenVerifyDeleteRecomendationCalled(primaryCode, secondaryCode, type); + } + + @Test + void givenInvalidParamsWhenDeleteRecomendationThenReturn404() { + String primaryCode = "invalid-001"; + String secondaryCode = "invalid-002"; + String type = "complemento"; + givenDeleteRecommendationThrowError(primaryCode, secondaryCode, type); + + ResponseEntity response = whenCallDeleteRecomendation(primaryCode, secondaryCode, type); + + thenResponseNotFound(response, "Recomendación no encontrada"); + } + + // ========== GIVEN Methods ========== + + private List givenRecommendationsExist(String garmentCode) { + List recommendations = Arrays.asList( + mock(GarmentRecomendationModel.class), + mock(GarmentRecomendationModel.class)); + when(getGarmentRecomendation.execute(garmentCode)).thenReturn(recommendations); + return recommendations; + } + + private void givenRecommendationNotFound(String garmentCode) { + when(getGarmentRecomendation.execute(garmentCode)) + .thenThrow(new RuntimeException("Recomendaciones no encontradas")); + } + + private String givenRecommendationDeleted(String primaryCode, String secondaryCode, String type) { + String message = "Recomendación eliminada correctamente"; + when(deleteRecomendationByPrimaryAndSecondaryGarmentCode.execute(primaryCode, secondaryCode, type)) + .thenReturn(message); + return message; + } + + private void givenDeleteRecommendationThrowError(String primaryCode, String secondaryCode, String type) { + when(deleteRecomendationByPrimaryAndSecondaryGarmentCode.execute(primaryCode, secondaryCode, type)) + .thenThrow(new RuntimeException("Recomendación no encontrada")); + } + + // ========== WHEN Methods ========== + + private ResponseEntity whenCallGetRecomendations(String garmentCode) { + return controller.getRecomendations(garmentCode); + } + + private ResponseEntity whenCallDeleteRecomendation(String primaryCode, String secondaryCode, String type) { + return controller.deleteRecomendation(primaryCode, secondaryCode, type); + } + + // ========== THEN Methods ========== + + private void thenResponseOk(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + assertNotNull(response.getBody()); + } + + private void thenResponseBodyEquals(ResponseEntity response, Object expected) { + assertEquals(expected, response.getBody()); + } + + private void thenResponseNotFound(ResponseEntity response, String expectedMessage) { + assertEquals(404, response.getStatusCode().value()); + assertEquals(expectedMessage, response.getBody()); + } + + private void thenVerifyGetGarmentRecomendationCalled(String garmentCode) { + verify(getGarmentRecomendation, times(1)).execute(garmentCode); + } + + private void thenVerifyDeleteRecomendationCalled(String primaryCode, String secondaryCode, String type) { + verify(deleteRecomendationByPrimaryAndSecondaryGarmentCode, times(1)) + .execute(primaryCode, secondaryCode, type); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/SubscriptionControllerTest.java b/src/test/java/com/outfitlab/project/presentation/SubscriptionControllerTest.java new file mode 100644 index 0000000..b0749d0 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/SubscriptionControllerTest.java @@ -0,0 +1,315 @@ +package com.outfitlab.project.presentation; + +import com.mercadopago.exceptions.MPApiException; +import com.mercadopago.exceptions.MPException; +import com.mercadopago.net.MPResponse; +import com.outfitlab.project.domain.exceptions.SubscriptionNotFoundException; +import com.outfitlab.project.domain.interfaces.repositories.UserSubscriptionRepository; +import com.outfitlab.project.domain.model.UserSubscriptionModel; +import com.outfitlab.project.domain.useCases.subscription.CreateMercadoPagoPreference; +import com.outfitlab.project.domain.useCases.subscription.GetAllSubscription; +import com.outfitlab.project.domain.useCases.subscription.ProcessPaymentNotification; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class SubscriptionControllerTest { + + private CreateMercadoPagoPreference createMercadoPagoPreference; + private ProcessPaymentNotification processPaymentNotification; + private GetAllSubscription getAllSubscription; + private UserSubscriptionRepository userSubscriptionRepository; + + private SubscriptionController controller; + + @BeforeEach + void setUp() { + createMercadoPagoPreference = mock(CreateMercadoPagoPreference.class); + processPaymentNotification = mock(ProcessPaymentNotification.class); + getAllSubscription = mock(GetAllSubscription.class); + userSubscriptionRepository = mock(UserSubscriptionRepository.class); + + controller = new SubscriptionController( + createMercadoPagoPreference, + processPaymentNotification, + getAllSubscription, + userSubscriptionRepository); + } + + // ========== createPreference Tests ========== + + @Test + void givenValidRequestWhenCreatePreferenceThenReturnOk() throws MPException, MPApiException { + SubscriptionRequest request = givenValidSubscriptionRequest(); + String preferenceId = givenPreferenceCreated("pref-123456"); + + ResponseEntity response = whenCallCreatePreference(request); + + thenResponseOkWithPreferenceId(response, "pref-123456"); + thenVerifyCreatePreferenceCalled("PLAN-001", "user@example.com", new BigDecimal("99.99"), "ARS"); + } + + @Test + void givenMPApiExceptionWhenCreatePreferenceThenReturnError() throws MPException, MPApiException { + SubscriptionRequest request = givenValidSubscriptionRequest(); + givenMPApiException(); + + ResponseEntity response = whenCallCreatePreference(request); + + thenResponseInternalServerError(response); + thenResponseContainsError(response, "Error de MercadoPago API"); + } + + @Test + void givenMPExceptionWhenCreatePreferenceThenReturnError() throws MPException, MPApiException { + SubscriptionRequest request = givenValidSubscriptionRequest(); + givenMPException(); + + ResponseEntity response = whenCallCreatePreference(request); + + thenResponseInternalServerError(response); + thenResponseContainsError(response, "Error al crear preferencia"); + } + + // ========== handleMercadoPagoWebhook Tests ========== + + @Test + void givenValidPaymentNotificationWhenHandleWebhookThenReturnOk() throws MPException, MPApiException { + String paymentId = "12345"; + String topic = "payment"; + givenPaymentProcessed(); + + ResponseEntity response = whenCallHandleWebhook(paymentId, topic); + + thenResponseOk(response); + thenVerifyProcessPaymentNotificationCalled(12345L); + } + + @Test + void givenInvalidTopicWhenHandleWebhookThenReturnOk() { + String paymentId = "123"; + String topic = "merchant_order"; + + ResponseEntity response = whenCallHandleWebhook(paymentId, topic); + + thenResponseOk(response); + } + + @Test + void givenExceptionWhenHandleWebhookThenReturnError() throws MPException, MPApiException { + String paymentId = "12345"; + String topic = "payment"; + givenPaymentProcessingFailed(); + + ResponseEntity response = whenCallHandleWebhook(paymentId, topic); + + thenResponseInternalServerError(response); + } + + // ========== getSubscriptions Tests ========== + + @Test + void whenGetSubscriptionsThenReturnOk() { + givenAuthenticatedUser("user@example.com"); + givenPlansExist("user@example.com"); + + ResponseEntity response = whenCallGetSubscriptions(); + + thenResponseOkWithSubscriptionsData(response); + thenVerifyGetAllSubscriptionCalled("user@example.com"); + } + + // ========== getUserSubscription Tests ========== + + @Test + void givenAuthenticatedUserWhenGetUserSubscriptionThenReturnOk() throws SubscriptionNotFoundException { + givenAuthenticatedUser("user@example.com"); + givenUserSubscriptionModelExists(); + + ResponseEntity response = whenCallGetUserSubscription(); + + thenResponseOkWithUserSubscription(response); + thenVerifyUserSubscriptionRepositoryCalled("user@example.com"); + } + + @Test + void givenNoSubscriptionWhenGetUserSubscriptionThenReturnNotFound() throws SubscriptionNotFoundException { + givenAuthenticatedUser("user@example.com"); + givenUserSubscriptionModelNotFound(); + + ResponseEntity response = whenCallGetUserSubscription(); + + thenResponseNotFound(response); + } + + // ========== GIVEN Methods ========== + + private SubscriptionRequest givenValidSubscriptionRequest() { + SubscriptionRequest request = new SubscriptionRequest(); + request.setPlanId("PLAN-001"); + request.setUserEmail("user@example.com"); + request.setPrice(new BigDecimal("99.99")); + request.setCurrency("ARS"); + return request; + } + + private String givenPreferenceCreated(String preferenceId) throws MPException, MPApiException { + when(createMercadoPagoPreference.execute(anyString(), anyString(), any(BigDecimal.class), anyString())) + .thenReturn(preferenceId); + return preferenceId; + } + + private void givenMPApiException() throws MPException, MPApiException { + MPResponse mockResponse = mock(MPResponse.class); + when(mockResponse.getStatusCode()).thenReturn(500); + doThrow(new MPApiException("Error de MercadoPago API", mockResponse)) + .when(createMercadoPagoPreference) + .execute(anyString(), anyString(), any(BigDecimal.class), anyString()); + } + + private void givenMPException() throws MPException, MPApiException { + doThrow(new MPException("Error al crear preferencia")) + .when(createMercadoPagoPreference) + .execute(anyString(), anyString(), any(BigDecimal.class), anyString()); + } + + private void givenPaymentProcessed() throws MPException, MPApiException { + doNothing().when(processPaymentNotification).execute(anyLong()); + } + + private void givenPaymentProcessingFailed() throws MPException, MPApiException { + doThrow(new MPException("Error procesando pago")) + .when(processPaymentNotification).execute(anyLong()); + } + + private void givenPlansExist(String userEmail) { + List plans = Arrays.asList( + Map.of("id", "PLAN-001", "name", "Basic", "price", 99.99), + Map.of("id", "PLAN-002", "name", "Premium", "price", 199.99)); + doReturn(plans).when(getAllSubscription).execute(userEmail); + } + + private void givenAuthenticatedUser(String email) { + Authentication authentication = mock(Authentication.class); + when(authentication.getName()).thenReturn(email); + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getPrincipal()).thenReturn(email); + SecurityContext securityContext = mock(SecurityContext.class); + when(securityContext.getAuthentication()).thenReturn(authentication); + SecurityContextHolder.setContext(securityContext); + } + + private UserSubscriptionModel givenUserSubscriptionModelExists() throws SubscriptionNotFoundException { + UserSubscriptionModel subscription = mock(UserSubscriptionModel.class); + when(subscription.getPlanCode()).thenReturn("PLAN-001"); + when(subscription.getStatus()).thenReturn("active"); + when(subscription.getCombinationsUsed()).thenReturn(5); + when(subscription.getMaxCombinations()).thenReturn(10); + when(subscription.getFavoritesCount()).thenReturn(3); + when(subscription.getMaxFavorites()).thenReturn(20); + when(subscription.getModelsGenerated()).thenReturn(2); + when(subscription.getMaxModels()).thenReturn(5); + when(subscription.getDownloadsCount()).thenReturn(1); + when(subscription.getMaxDownloads()).thenReturn(10); + when(subscription.getGarmentsUploaded()).thenReturn(0); + when(subscription.getMaxGarments()).thenReturn(null); + when(userSubscriptionRepository.findByUserEmail(anyString())).thenReturn(subscription); + return subscription; + } + + private void givenUserSubscriptionModelNotFound() throws SubscriptionNotFoundException { + when(userSubscriptionRepository.findByUserEmail(anyString())) + .thenThrow(new SubscriptionNotFoundException("Suscripción no encontrada")); + } + + // ========== WHEN Methods ========== + + private ResponseEntity whenCallCreatePreference(SubscriptionRequest request) { + return controller.createPreference(request); + } + + private ResponseEntity whenCallHandleWebhook(String id, String topic) { + return controller.handleMercadoPagoWebhook(id, topic); + } + + private ResponseEntity whenCallGetSubscriptions() { + return controller.getSubscriptions(); + } + + private ResponseEntity whenCallGetUserSubscription() { + return controller.getUserSubscription(); + } + + // ========== THEN Methods ========== + + private void thenResponseOk(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + } + + private void thenResponseOkWithPreferenceId(ResponseEntity response, String expectedPreferenceId) { + assertEquals(200, response.getStatusCode().value()); + Map body = (Map) response.getBody(); + assertNotNull(body); + assertNotNull(body.get("initPoint")); + } + + private void thenResponseOkWithSubscriptionsData(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + Map body = (Map) response.getBody(); + assertNotNull(body); + assertNotNull(body.get("data")); + } + + private void thenResponseOkWithUserSubscription(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + Map body = (Map) response.getBody(); + assertNotNull(body); + assertTrue(body.containsKey("planCode")); + assertTrue(body.containsKey("usage")); + } + + private void thenResponseInternalServerError(ResponseEntity response) { + assertEquals(500, response.getStatusCode().value()); + } + + private void thenResponseNotFound(ResponseEntity response) { + assertEquals(404, response.getStatusCode().value()); + } + + private void thenResponseContainsError(ResponseEntity response, String expectedError) { + Map body = (Map) response.getBody(); + assertNotNull(body); + String error = (String) body.get("error"); + assertNotNull(error, "Error message should not be null"); + assertTrue(error.contains(expectedError) || error.contains("MercadoPago") || error.contains("preferencia"), + "Expected error to contain: " + expectedError + " but got: " + error); + } + + private void thenVerifyCreatePreferenceCalled(String planId, String userEmail, BigDecimal price, String currency) + throws MPException, MPApiException { + verify(createMercadoPagoPreference, times(1)).execute(planId, userEmail, price, currency); + } + + private void thenVerifyProcessPaymentNotificationCalled(Long paymentId) throws MPException, MPApiException { + verify(processPaymentNotification, times(1)).execute(paymentId); + } + + private void thenVerifyGetAllSubscriptionCalled(String userEmail) { + verify(getAllSubscription, times(1)).execute(userEmail); + } + + private void thenVerifyUserSubscriptionRepositoryCalled(String email) throws SubscriptionNotFoundException { + verify(userSubscriptionRepository, times(1)).findByUserEmail(email); + } +} diff --git a/src/test/java/com/outfitlab/project/presentation/UserControllerTest.java b/src/test/java/com/outfitlab/project/presentation/UserControllerTest.java new file mode 100644 index 0000000..55e9f60 --- /dev/null +++ b/src/test/java/com/outfitlab/project/presentation/UserControllerTest.java @@ -0,0 +1,592 @@ +package com.outfitlab.project.presentation; + +import com.outfitlab.project.domain.exceptions.PasswordIsNotTheSame; +import com.outfitlab.project.domain.exceptions.UserAlreadyExistsException; +import com.outfitlab.project.domain.exceptions.UserNotFoundException; +import com.outfitlab.project.domain.model.UserModel; +import com.outfitlab.project.domain.model.dto.LoginDTO; +import com.outfitlab.project.domain.model.dto.RegisterDTO; +import com.outfitlab.project.domain.useCases.brand.CreateBrand; +import com.outfitlab.project.domain.useCases.bucketImages.DeleteImage; +import com.outfitlab.project.domain.useCases.bucketImages.SaveImage; +import com.outfitlab.project.domain.useCases.subscription.AssignFreePlanToUser; +import com.outfitlab.project.domain.useCases.user.*; +import com.outfitlab.project.presentation.dto.EditProfileRequestDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class UserControllerTest { + + private RegisterUser registerUser; + private LoginUser loginUser; + private GetAllUsers getAllUsers; + private DesactivateUser desactivateUser; + private ActivateUser activateUser; + private ConvertToAdmin convertToAdmin; + private ConvertToUser convertToUser; + private CreateBrand createBrand; + private UpdateBrandUser updateBrandUser; + private SaveImage saveImage; + private GetUserByEmail getUserByEmail; + private UpdateUser updateUser; + private DeleteImage deleteImage; + private AssignFreePlanToUser assignFreePlanToUser; + private UserProfile userProfile; + private RefreshToken refreshToken; + + private UserController controller; + + @BeforeEach + void setUp() { + registerUser = mock(RegisterUser.class); + loginUser = mock(LoginUser.class); + getAllUsers = mock(GetAllUsers.class); + desactivateUser = mock(DesactivateUser.class); + activateUser = mock(ActivateUser.class); + convertToAdmin = mock(ConvertToAdmin.class); + convertToUser = mock(ConvertToUser.class); + createBrand = mock(CreateBrand.class); + updateBrandUser = mock(UpdateBrandUser.class); + saveImage = mock(SaveImage.class); + getUserByEmail = mock(GetUserByEmail.class); + updateUser = mock(UpdateUser.class); + deleteImage = mock(DeleteImage.class); + assignFreePlanToUser = mock(AssignFreePlanToUser.class); + userProfile = mock(UserProfile.class); + refreshToken = mock(RefreshToken.class); + + controller = new UserController( + registerUser, loginUser, getAllUsers, desactivateUser, activateUser, + convertToAdmin, convertToUser, createBrand, updateBrandUser, saveImage, + getUserByEmail, updateUser, deleteImage, assignFreePlanToUser, + userProfile, refreshToken); + } + + // ========== registerUser Tests ========== + + @Test + void givenValidRequestWhenRegisterUserThenReturnCreated() { + RegisterDTO request = givenValidRegisterRequest(); + UserModel newUser = givenUserRegistered("test@example.com", "Test User"); + givenFreePlanAssigned(); + + ResponseEntity response = whenCallRegisterUser(request); + + thenResponseCreated(response); + thenResponseContainsUserData(response, "test@example.com", "Test User"); + thenVerifyRegisterUserCalled(); + thenVerifyAssignFreePlanCalled("test@example.com", false); + } + + @Test + void givenExistingEmailWhenRegisterUserThenReturnBadRequest() { + RegisterDTO request = givenValidRegisterRequest(); + givenUserAlreadyExists(); + + ResponseEntity response = whenCallRegisterUser(request); + + thenResponseBadRequest(response); + } + + // ========== registerbrandAndUser Tests ========== + + @Test + void givenValidBrandRequestWhenRegisterBrandAndUserThenReturnCreated() { + RegisterDTO request = givenValidBrandRegisterRequest(); + UserModel newUser = givenUserRegistered("brand@example.com", "Brand User"); + givenFreePlanAssigned(); + givenBrandCreated("brand-code"); + givenBrandUpdatedInUser(); + givenImageSaved("http://logo.url/brand.png"); + + ResponseEntity response = whenCallRegisterBrandAndUser(request); + + thenResponseCreated(response); + thenVerifyCreateBrandCalled(); + thenVerifyUpdateBrandUserCalled(); + } + + // ========== loginUser Tests ========== + + @Test + void givenValidCredentialsWhenLoginUserThenReturnOk() { + LoginDTO loginDTO = givenValidLoginRequest(); + ResponseEntity loginResponse = givenLoginSuccessful(); + + ResponseEntity response = whenCallLoginUser(loginDTO); + + thenResponseOk(response); + thenVerifyLoginUserCalled(); + } + + @Test + void givenInvalidCredentialsWhenLoginUserThenReturnUnauthorized() { + LoginDTO loginDTO = givenValidLoginRequest(); + givenLoginFailed(); + + ResponseEntity response = whenCallLoginUser(loginDTO); + + thenResponseUnauthorized(response); + } + + // ========== refreshToken Tests ========== + + @Test + void givenValidRefreshTokenWhenRefreshTokenThenReturnOk() { + String refreshTokenValue = "valid-refresh-token"; + ResponseEntity tokenResponse = givenRefreshTokenSuccessful(); + + ResponseEntity response = whenCallRefreshToken(refreshTokenValue); + + thenResponseOk(response); + thenVerifyRefreshTokenCalled(refreshTokenValue); + } + + @Test + void givenInvalidRefreshTokenWhenRefreshTokenThenReturnUnauthorized() { + String refreshTokenValue = "invalid-token"; + givenRefreshTokenFailed(); + + ResponseEntity response = whenCallRefreshToken(refreshTokenValue); + + thenResponseUnauthorized(response); + } + + // ========== getAuthUserProfile Tests ========== + + @Test + void whenGetAuthUserProfileThenReturnOk() { + UserModel user = givenUserProfileExists(); + + ResponseEntity response = whenCallGetAuthUserProfile(); + + thenResponseOk(response); + thenVerifyUserProfileCalled(); + } + + @Test + void givenUserNotFoundWhenGetAuthUserProfileThenReturnUnauthorized() { + givenUserProfileNotFound(); + + ResponseEntity response = whenCallGetAuthUserProfile(); + + thenResponseUnauthorized(response); + } + + // ========== getAllUsers Tests ========== + + @Test + void whenGetAllUsersThenReturnOk() { + List users = givenUsersExist(); + + ResponseEntity response = whenCallGetAllUsers(); + + thenResponseOk(response); + thenVerifyGetAllUsersCalled(); + } + + @Test + void givenNoUsersWhenGetAllUsersThenReturn404() { + givenNoUsersFound(); + + ResponseEntity response = whenCallGetAllUsers(); + + thenResponseNotFound(response, "No hay usuarios"); + } + + // ========== desactivateUser Tests ========== + + @Test + void givenValidEmailWhenDesactivateUserThenReturnOk() { + String email = "user@example.com"; + givenUserDesactivated("Usuario desactivado"); + + ResponseEntity response = whenCallDesactivateUser(email); + + thenResponseOk(response); + thenVerifyDesactivateUserCalled(email); + } + + @Test + void givenInvalidEmailWhenDesactivateUserThenReturn404() { + String email = "invalid@example.com"; + givenUserNotFoundForDesactivate(email); + + ResponseEntity response = whenCallDesactivateUser(email); + + thenResponseNotFound(response, "Usuario no encontrado"); + } + + // ========== activateUser Tests ========== + + @Test + void givenValidEmailWhenActivateUserThenReturnOk() { + String email = "user@example.com"; + givenUserActivated("Usuario activado"); + + ResponseEntity response = whenCallActivateUser(email); + + thenResponseOk(response); + thenVerifyActivateUserCalled(email); + } + + // ========== convertToAdmin Tests ========== + + @Test + void givenValidEmailWhenConvertToAdminThenReturnOk() { + String email = "user@example.com"; + givenUserConvertedToAdmin("Usuario convertido a admin"); + + ResponseEntity response = whenCallConvertToAdmin(email); + + thenResponseOk(response); + thenVerifyConvertToAdminCalled(email); + } + + // ========== convertToUser Tests ========== + + @Test + void givenValidEmailWhenConvertToUserThenReturnOk() { + String email = "admin@example.com"; + givenUserConvertedToUser("Admin convertido a usuario"); + + ResponseEntity response = whenCallConvertToUser(email); + + thenResponseOk(response); + thenVerifyConvertToUserCalled(email); + } + + // ========== updateUser Tests ========== + + @Test + void givenValidRequestWhenUpdateUserThenReturnOk() { + String oldEmail = "old@example.com"; + EditProfileRequestDTO request = givenValidEditProfileRequest(); + UserModel updatedUser = givenUserUpdated(); + givenUserImageUrl("http://old-image.url/user.jpg"); + givenImageSaved("http://new-image.url/user.jpg"); // Add image save mock + + ResponseEntity response = whenCallUpdateUser(oldEmail, request); + + thenResponseOk(response); + thenResponseContainsMessage(response, "Perfil actualizado."); + thenVerifyUpdateUserCalled(); + } + + @Test + void givenPasswordMismatchWhenUpdateUserThenReturn404() { + String oldEmail = "user@example.com"; + EditProfileRequestDTO request = givenValidEditProfileRequest(); + givenPasswordMismatch(); + givenImageSaved("http://new-image.url/user.jpg"); // Add image save mock + + ResponseEntity response = whenCallUpdateUser(oldEmail, request); + + thenResponseNotFound(response, "Las contraseñas no coinciden"); + } + + // ========== GIVEN Methods ========== + + private RegisterDTO givenValidRegisterRequest() { + RegisterDTO request = new RegisterDTO(); + request.setEmail("test@example.com"); + request.setName("Test"); + request.setLastName("User"); + request.setPassword("Password123"); + return request; + } + + private RegisterDTO givenValidBrandRegisterRequest() { + RegisterDTO request = givenValidRegisterRequest(); + request.setEmail("brand@example.com"); + request.setBrandName("Test Brand"); + request.setUrlSite("http://brand.com"); + request.setLogoBrand(mock(MultipartFile.class)); + return request; + } + + private UserModel givenUserRegistered(String email, String name) { + UserModel user = mock(UserModel.class); + when(user.getEmail()).thenReturn(email); + when(user.getName()).thenReturn(name); + when(registerUser.execute(any(RegisterDTO.class))).thenReturn(user); + return user; + } + + private void givenUserAlreadyExists() { + when(registerUser.execute(any(RegisterDTO.class))) + .thenThrow(new UserAlreadyExistsException("El usuario ya existe")); + } + + private void givenFreePlanAssigned() { + doNothing().when(assignFreePlanToUser).execute(anyString(), anyBoolean()); + } + + private void givenBrandCreated(String brandCode) { + when(createBrand.execute(anyString(), anyString(), anyString())).thenReturn(brandCode); + } + + private void givenBrandUpdatedInUser() { + doNothing().when(updateBrandUser).execute(anyString(), anyString()); + } + + private void givenImageSaved(String url) { + when(saveImage.execute(any(MultipartFile.class), anyString())).thenReturn(url); + } + + private LoginDTO givenValidLoginRequest() { + LoginDTO loginDTO = new LoginDTO(); + loginDTO.setEmail("test@example.com"); + loginDTO.setPassword("Password123"); + return loginDTO; + } + + private ResponseEntity givenLoginSuccessful() { + ResponseEntity response = ResponseEntity.ok("Login successful"); + doReturn(response).when(loginUser).execute(any(LoginDTO.class)); + return response; + } + + private void givenLoginFailed() { + when(loginUser.execute(any(LoginDTO.class))) + .thenThrow(new UserNotFoundException("Usuario no encontrado")); + } + + private ResponseEntity givenRefreshTokenSuccessful() { + ResponseEntity response = ResponseEntity.ok("Token refreshed"); + doReturn(response).when(refreshToken).execute(anyString()); + return response; + } + + private void givenRefreshTokenFailed() { + when(refreshToken.execute(anyString())) + .thenThrow(new UserNotFoundException("Token inválido")); + } + + private UserModel givenUserProfileExists() { + UserModel user = mock(UserModel.class); + when(userProfile.execute()).thenReturn(user); + return user; + } + + private void givenUserProfileNotFound() { + when(userProfile.execute()).thenThrow(new UserNotFoundException("Usuario no encontrado")); + } + + private List givenUsersExist() { + UserModel user1 = mock(UserModel.class); + UserModel user2 = mock(UserModel.class); + List users = Arrays.asList(user1, user2); + doReturn(users).when(getAllUsers).execute(); + return users; + } + + private void givenNoUsersFound() { + when(getAllUsers.execute()).thenThrow(new UserNotFoundException("No hay usuarios")); + } + + private void givenUserDesactivated(String message) { + when(desactivateUser.execute(anyString())).thenReturn(message); + } + + private void givenUserNotFoundForDesactivate(String email) { + when(desactivateUser.execute(email)) + .thenThrow(new UserNotFoundException("Usuario no encontrado")); + } + + private void givenUserActivated(String message) { + when(activateUser.execute(anyString())).thenReturn(message); + } + + private void givenUserConvertedToAdmin(String message) { + when(convertToAdmin.execute(anyString())).thenReturn(message); + } + + private void givenUserConvertedToUser(String message) { + when(convertToUser.execute(anyString())).thenReturn(message); + } + + private EditProfileRequestDTO givenValidEditProfileRequest() { + EditProfileRequestDTO request = new EditProfileRequestDTO(); + request.setName("Updated"); + request.setLastname("User"); + request.setEmail("updated@example.com"); + request.setPassword("NewPassword123"); + request.setConfirmPassword("NewPassword123"); + request.setUserImg(mock(MultipartFile.class)); + return request; + } + + private UserModel givenUserUpdated() { + UserModel user = mock(UserModel.class); + when(updateUser.execute(anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyString())).thenReturn(user); + return user; + } + + private void givenUserImageUrl(String url) { + UserModel user = mock(UserModel.class); + when(user.getUserImg()).thenReturn(url); + when(getUserByEmail.execute(anyString())).thenReturn(user); + } + + private void givenPasswordMismatch() { + // Mock getUserByEmail to return a user with image URL + UserModel existingUser = mock(UserModel.class); + when(existingUser.getUserImg()).thenReturn("http://old-image.url/user.jpg"); + when(getUserByEmail.execute(anyString())).thenReturn(existingUser); + + // Mock updateUser to throw exception + when(updateUser.execute(anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyString())) + .thenThrow(new PasswordIsNotTheSame("Las contraseñas no coinciden")); + } + + // ========== WHEN Methods ========== + + private ResponseEntity whenCallRegisterUser(RegisterDTO request) { + return controller.registerUser(request); + } + + private ResponseEntity whenCallRegisterBrandAndUser(RegisterDTO request) { + return controller.registerbrandAndUser(request); + } + + private ResponseEntity whenCallLoginUser(LoginDTO loginDTO) { + return controller.loginUser(loginDTO); + } + + private ResponseEntity whenCallRefreshToken(String refreshTokenValue) { + Map request = Map.of("refresh_token", refreshTokenValue); + return controller.refreshToken(request); + } + + private ResponseEntity whenCallGetAuthUserProfile() { + return controller.getAuthUserProfile(); + } + + private ResponseEntity whenCallGetAllUsers() { + return controller.getAllUsers(); + } + + private ResponseEntity whenCallDesactivateUser(String email) { + return controller.desactivateUser(email); + } + + private ResponseEntity whenCallActivateUser(String email) { + return controller.activateUser(email); + } + + private ResponseEntity whenCallConvertToAdmin(String email) { + return controller.convertToAdmin(email); + } + + private ResponseEntity whenCallConvertToUser(String email) { + return controller.convertToUser(email); + } + + private ResponseEntity whenCallUpdateUser(String oldEmail, EditProfileRequestDTO request) { + return controller.updateUser(oldEmail, request); + } + + // ========== THEN Methods ========== + + private void thenResponseOk(ResponseEntity response) { + assertEquals(200, response.getStatusCode().value()); + assertNotNull(response.getBody()); + } + + private void thenResponseCreated(ResponseEntity response) { + assertEquals(201, response.getStatusCode().value()); + assertNotNull(response.getBody()); + } + + private void thenResponseBadRequest(ResponseEntity response) { + assertEquals(400, response.getStatusCode().value()); + } + + private void thenResponseUnauthorized(ResponseEntity response) { + assertEquals(401, response.getStatusCode().value()); + } + + private void thenResponseNotFound(ResponseEntity response, String expectedMessage) { + assertEquals(404, response.getStatusCode().value()); + assertEquals(expectedMessage, response.getBody()); + } + + private void thenResponseContainsUserData(ResponseEntity response, String email, String name) { + Map body = (Map) response.getBody(); + assertNotNull(body); + assertEquals(email, body.get("email")); + assertEquals(name, body.get("name")); + } + + private void thenResponseContainsMessage(ResponseEntity response, String expectedMessage) { + Map body = (Map) response.getBody(); + assertNotNull(body); + assertEquals(expectedMessage, body.get("message")); + } + + private void thenVerifyRegisterUserCalled() { + verify(registerUser, times(1)).execute(any(RegisterDTO.class)); + } + + private void thenVerifyAssignFreePlanCalled(String email, boolean isBrand) { + verify(assignFreePlanToUser, times(1)).execute(email, isBrand); + } + + private void thenVerifyCreateBrandCalled() { + verify(createBrand, times(1)).execute(anyString(), anyString(), anyString()); + } + + private void thenVerifyUpdateBrandUserCalled() { + verify(updateBrandUser, times(1)).execute(anyString(), anyString()); + } + + private void thenVerifyLoginUserCalled() { + verify(loginUser, times(1)).execute(any(LoginDTO.class)); + } + + private void thenVerifyRefreshTokenCalled(String token) { + verify(refreshToken, times(1)).execute(token); + } + + private void thenVerifyUserProfileCalled() { + verify(userProfile, times(1)).execute(); + } + + private void thenVerifyGetAllUsersCalled() { + verify(getAllUsers, times(1)).execute(); + } + + private void thenVerifyDesactivateUserCalled(String email) { + verify(desactivateUser, times(1)).execute(email); + } + + private void thenVerifyActivateUserCalled(String email) { + verify(activateUser, times(1)).execute(email); + } + + private void thenVerifyConvertToAdminCalled(String email) { + verify(convertToAdmin, times(1)).execute(email); + } + + private void thenVerifyConvertToUserCalled(String email) { + verify(convertToUser, times(1)).execute(email); + } + + private void thenVerifyUpdateUserCalled() { + verify(updateUser, times(1)).execute( + anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyString()); + } +}