From 3bc4b003f16f9a0ff72e7c76372d8dc136fbf11f Mon Sep 17 00:00:00 2001 From: joona95 Date: Wed, 15 Oct 2025 21:42:59 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=9D=B4=EC=9C=A0=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/src/user/api/UserController.java | 8 +++- .../user/application/UserFacadeService.java | 3 +- .../app/src/user/application/UserService.java | 12 +++++- .../application/UserWithdrawalService.java | 27 ++++++++++++ .../application/dto/UserWithdrawRequest.java | 11 +++++ .../app/src/user/domain/UserWithdrawal.java | 42 +++++++++++++++++++ .../user/infra/UserWithdrawalRepository.java | 9 ++++ 7 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/recipe/app/src/user/application/UserWithdrawalService.java create mode 100644 src/main/java/com/recipe/app/src/user/application/dto/UserWithdrawRequest.java create mode 100644 src/main/java/com/recipe/app/src/user/domain/UserWithdrawal.java create mode 100644 src/main/java/com/recipe/app/src/user/infra/UserWithdrawalRepository.java diff --git a/src/main/java/com/recipe/app/src/user/api/UserController.java b/src/main/java/com/recipe/app/src/user/api/UserController.java index e54f52f3..c58944f0 100644 --- a/src/main/java/com/recipe/app/src/user/api/UserController.java +++ b/src/main/java/com/recipe/app/src/user/api/UserController.java @@ -11,6 +11,7 @@ import com.recipe.app.src.user.application.dto.UserSocialLoginResponse; import com.recipe.app.src.user.application.dto.UserTokenRefreshRequest; import com.recipe.app.src.user.application.dto.UserTokenRefreshResponse; +import com.recipe.app.src.user.application.dto.UserWithdrawRequest; import com.recipe.app.src.user.domain.User; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -98,9 +99,12 @@ public void patchUser(@Parameter(hidden = true) User user, @Operation(summary = "회원 탈퇴 API") @DeleteMapping @LoginCheck - public void deleteUser(HttpServletRequest request, @Parameter(hidden = true) User user) { + public void deleteUser(HttpServletRequest request, + @Parameter(hidden = true) User user, + @Parameter(name = "회원 탈퇴 요청 정보") + @RequestBody(required = false) UserWithdrawRequest withdrawRequest) { - userFacadeService.deleteUser(user, request); + userFacadeService.deleteUser(user, request, withdrawRequest); } @Operation(summary = "로그아웃 API") diff --git a/src/main/java/com/recipe/app/src/user/application/UserFacadeService.java b/src/main/java/com/recipe/app/src/user/application/UserFacadeService.java index 0a7f03bb..18329be4 100644 --- a/src/main/java/com/recipe/app/src/user/application/UserFacadeService.java +++ b/src/main/java/com/recipe/app/src/user/application/UserFacadeService.java @@ -11,6 +11,7 @@ import com.recipe.app.src.recipe.application.youtube.YoutubeViewService; import com.recipe.app.src.recipe.domain.Recipe; import com.recipe.app.src.user.application.dto.UserProfileResponse; +import com.recipe.app.src.user.application.dto.UserWithdrawRequest; import com.recipe.app.src.user.domain.User; import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Service; @@ -75,6 +76,6 @@ public void deleteUser(User user, HttpServletRequest request) { blogScrapService.deleteAllByUserId(user.getUserId()); blogViewService.deleteAllByUserId(user.getUserId()); - userService.withdraw(user, request); + userService.withdraw(user, request, withdrawRequest); } } diff --git a/src/main/java/com/recipe/app/src/user/application/UserService.java b/src/main/java/com/recipe/app/src/user/application/UserService.java index e92418dd..9312b6b4 100644 --- a/src/main/java/com/recipe/app/src/user/application/UserService.java +++ b/src/main/java/com/recipe/app/src/user/application/UserService.java @@ -10,6 +10,7 @@ import com.recipe.app.src.user.application.dto.UserSocialLoginResponse; import com.recipe.app.src.user.application.dto.UserTokenRefreshRequest; import com.recipe.app.src.user.application.dto.UserTokenRefreshResponse; +import com.recipe.app.src.user.application.dto.UserWithdrawRequest; import com.recipe.app.src.user.domain.User; import com.recipe.app.src.user.exception.NotFoundUserException; import com.recipe.app.src.user.exception.UserTokenNotExistException; @@ -30,12 +31,15 @@ public class UserService { private final JwtUtil jwtUtil; private final BadWordFiltering badWordFiltering; private final UserAuthClientService userAuthClientService; + private final UserWithdrawalService userWithdrawalService; - public UserService(UserRepository userRepository, JwtUtil jwtUtil, BadWordFiltering badWordFiltering, UserAuthClientService userAuthClientService) { + public UserService(UserRepository userRepository, JwtUtil jwtUtil, BadWordFiltering badWordFiltering, + UserAuthClientService userAuthClientService, UserWithdrawalService userWithdrawalService) { this.userRepository = userRepository; this.jwtUtil = jwtUtil; this.badWordFiltering = badWordFiltering; this.userAuthClientService = userAuthClientService; + this.userWithdrawalService = userWithdrawalService; } @Transactional(readOnly = true) @@ -112,11 +116,15 @@ public void update(User user, UserProfileRequest request) { } @Transactional - public void withdraw(User user, HttpServletRequest request) { + public void withdraw(User user, HttpServletRequest request, UserWithdrawRequest withdrawRequest) { userRepository.delete(user); logout(request); + + if (withdrawRequest != null && StringUtils.hasText(withdrawRequest.getWithdrawalReason())) { + userWithdrawalService.saveWithdrawalReason(user.getUserId(), withdrawRequest.getWithdrawalReason()); + } } @Transactional diff --git a/src/main/java/com/recipe/app/src/user/application/UserWithdrawalService.java b/src/main/java/com/recipe/app/src/user/application/UserWithdrawalService.java new file mode 100644 index 00000000..7421609d --- /dev/null +++ b/src/main/java/com/recipe/app/src/user/application/UserWithdrawalService.java @@ -0,0 +1,27 @@ +package com.recipe.app.src.user.application; + +import com.recipe.app.src.user.domain.UserWithdrawal; +import com.recipe.app.src.user.infra.UserWithdrawalRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class UserWithdrawalService { + + private final UserWithdrawalRepository userWithdrawalRepository; + + public UserWithdrawalService(UserWithdrawalRepository userWithdrawalRepository) { + this.userWithdrawalRepository = userWithdrawalRepository; + } + + @Transactional + public void saveWithdrawalReason(Long userId, String withdrawalReason) { + + UserWithdrawal userWithdrawal = UserWithdrawal.builder() + .userId(userId) + .withdrawalReason(withdrawalReason) + .build(); + + userWithdrawalRepository.save(userWithdrawal); + } +} diff --git a/src/main/java/com/recipe/app/src/user/application/dto/UserWithdrawRequest.java b/src/main/java/com/recipe/app/src/user/application/dto/UserWithdrawRequest.java new file mode 100644 index 00000000..bb46478b --- /dev/null +++ b/src/main/java/com/recipe/app/src/user/application/dto/UserWithdrawRequest.java @@ -0,0 +1,11 @@ +package com.recipe.app.src.user.application.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class UserWithdrawRequest { + + private String withdrawalReason; +} diff --git a/src/main/java/com/recipe/app/src/user/domain/UserWithdrawal.java b/src/main/java/com/recipe/app/src/user/domain/UserWithdrawal.java new file mode 100644 index 00000000..90c59c91 --- /dev/null +++ b/src/main/java/com/recipe/app/src/user/domain/UserWithdrawal.java @@ -0,0 +1,42 @@ +package com.recipe.app.src.user.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +@Table(name = "UserWithdrawal") +public class UserWithdrawal { + + @Id + @Column(name = "userWithdrawalId", nullable = false, updatable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer userWithdrawalId; + + @Column(name = "userId", nullable = false) + private Long userId; + + @Column(name = "withdrawalReason", length = 200) + private String withdrawalReason; + + @Column(name = "createdAt", nullable = false, updatable = false) + private LocalDateTime createdAt = LocalDateTime.now(); + + @Builder + public UserWithdrawal(Long userId, String withdrawalReason) { + this.userId = userId; + this.withdrawalReason = withdrawalReason; + this.createdAt = LocalDateTime.now(); + } +} diff --git a/src/main/java/com/recipe/app/src/user/infra/UserWithdrawalRepository.java b/src/main/java/com/recipe/app/src/user/infra/UserWithdrawalRepository.java new file mode 100644 index 00000000..a1e65478 --- /dev/null +++ b/src/main/java/com/recipe/app/src/user/infra/UserWithdrawalRepository.java @@ -0,0 +1,9 @@ +package com.recipe.app.src.user.infra; + +import com.recipe.app.src.user.domain.UserWithdrawal; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserWithdrawalRepository extends JpaRepository { +} From 8ae4e60a23af6833ee498dff58a8b9876e21af5b Mon Sep 17 00:00:00 2001 From: joona95 Date: Wed, 15 Oct 2025 21:47:49 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=A0=95=EC=B1=85=20=EC=88=98=EC=A0=95=20(?= =?UTF-8?q?=EC=9D=BC=EB=B6=80=20=EC=A0=95=EB=B3=B4=20soft=20delete)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 회원탈퇴 시 개인정보 관련 항목은 hard delete, 레시피 등 공유 항목은 soft delete 하도록 수정 --- .../recipe/app/src/common/entity/BaseEntity.java | 11 +++++++++++ .../ingredient/application/IngredientService.java | 8 -------- .../app/src/recipe/application/RecipeService.java | 10 ---------- .../app/src/user/application/UserFacadeService.java | 13 ++++++++++--- .../app/src/user/application/UserService.java | 4 +++- .../java/com/recipe/app/src/user/domain/User.java | 8 ++++++++ 6 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/recipe/app/src/common/entity/BaseEntity.java b/src/main/java/com/recipe/app/src/common/entity/BaseEntity.java index 69aaec1d..d29172d0 100644 --- a/src/main/java/com/recipe/app/src/common/entity/BaseEntity.java +++ b/src/main/java/com/recipe/app/src/common/entity/BaseEntity.java @@ -25,4 +25,15 @@ public abstract class BaseEntity { @Column(name = "updatedAt", nullable = false) private LocalDateTime updatedAt = LocalDateTime.now(); + @Column(name = "deletedAt") + private LocalDateTime deletedAt; + + public void markAsDeleted() { + this.deletedAt = LocalDateTime.now(); + } + + public boolean isDeleted() { + return this.deletedAt != null; + } + } \ No newline at end of file diff --git a/src/main/java/com/recipe/app/src/ingredient/application/IngredientService.java b/src/main/java/com/recipe/app/src/ingredient/application/IngredientService.java index 427d850e..1b12b635 100644 --- a/src/main/java/com/recipe/app/src/ingredient/application/IngredientService.java +++ b/src/main/java/com/recipe/app/src/ingredient/application/IngredientService.java @@ -51,14 +51,6 @@ public IngredientCreateResponse create(Long userId, IngredientRequest request) { return new IngredientCreateResponse(ingredient.getIngredientId()); } - @Transactional - public void deleteAllByUserId(long userId) { - - List ingredients = ingredientRepository.findByUserId(userId); - - ingredientRepository.deleteAll(ingredients); - } - @Transactional(readOnly = true) public Ingredient findByIngredientId(Long ingredientId) { diff --git a/src/main/java/com/recipe/app/src/recipe/application/RecipeService.java b/src/main/java/com/recipe/app/src/recipe/application/RecipeService.java index 5f50724b..e0bb9988 100644 --- a/src/main/java/com/recipe/app/src/recipe/application/RecipeService.java +++ b/src/main/java/com/recipe/app/src/recipe/application/RecipeService.java @@ -85,16 +85,6 @@ private Recipe findByUserIdAndRecipeId(User user, Long recipeId) { }); } - @Transactional - public void deleteAllByUserId(long userId) { - - List recipes = recipeRepository.findByUserId(userId); - - recipeScrapService.deleteAllByUserId(userId); - recipeViewService.deleteAllByUserId(userId); - recipeRepository.deleteAll(recipes); - } - @Transactional(readOnly = true) public long countRecipeScrapByUserId(long userId) { diff --git a/src/main/java/com/recipe/app/src/user/application/UserFacadeService.java b/src/main/java/com/recipe/app/src/user/application/UserFacadeService.java index 18329be4..a0df8d1c 100644 --- a/src/main/java/com/recipe/app/src/user/application/UserFacadeService.java +++ b/src/main/java/com/recipe/app/src/user/application/UserFacadeService.java @@ -3,8 +3,10 @@ import com.recipe.app.src.fridge.application.FridgeService; import com.recipe.app.src.fridgeBasket.application.FridgeBasketService; import com.recipe.app.src.ingredient.application.IngredientService; +import com.recipe.app.src.recipe.application.RecipeScrapService; import com.recipe.app.src.recipe.application.RecipeSearchService; import com.recipe.app.src.recipe.application.RecipeService; +import com.recipe.app.src.recipe.application.RecipeViewService; import com.recipe.app.src.recipe.application.blog.BlogScrapService; import com.recipe.app.src.recipe.application.blog.BlogViewService; import com.recipe.app.src.recipe.application.youtube.YoutubeScrapService; @@ -25,6 +27,8 @@ public class UserFacadeService { private final UserService userService; private final RecipeService recipeService; private final RecipeSearchService recipeSearchService; + private final RecipeScrapService recipeScrapService; + private final RecipeViewService recipeViewService; private final YoutubeScrapService youtubeScrapService; private final YoutubeViewService youtubeViewService; private final BlogScrapService blogScrapService; @@ -36,6 +40,7 @@ public class UserFacadeService { private static final int USER_PROFILE_RECIPE_CNT = 6; public UserFacadeService(UserService userService, RecipeService recipeService, RecipeSearchService recipeSearchService, + RecipeScrapService recipeScrapService, RecipeViewService recipeViewService, YoutubeScrapService youtubeScrapService, YoutubeViewService youtubeViewService, BlogScrapService blogScrapService, BlogViewService blogViewService, FridgeService fridgeService, FridgeBasketService fridgeBasketService, IngredientService ingredientService) { @@ -43,6 +48,8 @@ public UserFacadeService(UserService userService, RecipeService recipeService, R this.userService = userService; this.recipeService = recipeService; this.recipeSearchService = recipeSearchService; + this.recipeScrapService = recipeScrapService; + this.recipeViewService = recipeViewService; this.youtubeScrapService = youtubeScrapService; this.youtubeViewService = youtubeViewService; this.blogScrapService = blogScrapService; @@ -65,12 +72,12 @@ public UserProfileResponse findUserProfile(User user) { } @Transactional - public void deleteUser(User user, HttpServletRequest request) { + public void deleteUser(User user, HttpServletRequest request, UserWithdrawRequest withdrawRequest) { fridgeService.deleteAllByUserId(user.getUserId()); fridgeBasketService.deleteAllByUserId(user.getUserId()); - recipeService.deleteAllByUserId(user.getUserId()); - ingredientService.deleteAllByUserId(user.getUserId()); + recipeScrapService.deleteAllByUserId(user.getUserId()); + recipeViewService.deleteAllByUserId(user.getUserId()); youtubeScrapService.deleteAllByUserId(user.getUserId()); youtubeViewService.deleteAllByUserId(user.getUserId()); blogScrapService.deleteAllByUserId(user.getUserId()); diff --git a/src/main/java/com/recipe/app/src/user/application/UserService.java b/src/main/java/com/recipe/app/src/user/application/UserService.java index 9312b6b4..021f9732 100644 --- a/src/main/java/com/recipe/app/src/user/application/UserService.java +++ b/src/main/java/com/recipe/app/src/user/application/UserService.java @@ -118,7 +118,9 @@ public void update(User user, UserProfileRequest request) { @Transactional public void withdraw(User user, HttpServletRequest request, UserWithdrawRequest withdrawRequest) { - userRepository.delete(user); + user.maskPersonalInfo(); + user.markAsDeleted(); + userRepository.save(user); logout(request); diff --git a/src/main/java/com/recipe/app/src/user/domain/User.java b/src/main/java/com/recipe/app/src/user/domain/User.java index cd932b83..501eea6a 100644 --- a/src/main/java/com/recipe/app/src/user/domain/User.java +++ b/src/main/java/com/recipe/app/src/user/domain/User.java @@ -87,4 +87,12 @@ public void changeDeviceToken(String deviceToken) { this.deviceToken = deviceToken; } + + public void maskPersonalInfo() { + this.email = null; + this.phoneNumber = null; + this.nickname = "탈퇴한 사용자"; + this.profileImgUrl = ProfileImage.getInitProfileImgUrl(); + this.deviceToken = null; + } } From b7980041ce48f1f6c35bc832f3b10d792936a477 Mon Sep 17 00:00:00 2001 From: joona95 Date: Wed, 15 Oct 2025 21:55:57 +0900 Subject: [PATCH 3/4] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=A0=95=EC=B1=85=20=EC=88=98=EC=A0=95=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/IngredientServiceTest.groovy | 30 ------- .../application/RecipeServiceTest.groovy | 33 -------- .../application/UserFacadeServiceTest.groovy | 47 +++++++++-- .../user/application/UserServiceTest.groovy | 81 ++++++++++++++++++- .../UserWithdrawalServiceTest.groovy | 49 +++++++++++ 5 files changed, 167 insertions(+), 73 deletions(-) create mode 100644 src/test/groovy/com/recipe/app/src/user/application/UserWithdrawalServiceTest.groovy diff --git a/src/test/groovy/com/recipe/app/src/ingredient/application/IngredientServiceTest.groovy b/src/test/groovy/com/recipe/app/src/ingredient/application/IngredientServiceTest.groovy index ad81d313..b48bb985 100644 --- a/src/test/groovy/com/recipe/app/src/ingredient/application/IngredientServiceTest.groovy +++ b/src/test/groovy/com/recipe/app/src/ingredient/application/IngredientServiceTest.groovy @@ -121,36 +121,6 @@ class IngredientServiceTest extends Specification { result.ingredientId == ingredient.ingredientId } - def "특정 유저의 재료 목록 제거"() { - - given: - Long userId = 1 - List ingredients = [ - Ingredient.builder() - .ingredientId(1) - .ingredientCategoryId(1) - .ingredientName("재료1") - .ingredientIconId(1) - .userId(1) - .build(), - Ingredient.builder() - .ingredientId(2) - .ingredientCategoryId(2) - .ingredientName("재료2") - .ingredientIconId(1) - .userId(1) - .build() - ] - - ingredientRepository.findByUserId(userId) >> ingredients - - when: - ingredientService.deleteAllByUserId(userId) - - then: - 1 * ingredientRepository.deleteAll(ingredients) - } - def "아이디로 재료 조회"() { given: diff --git a/src/test/groovy/com/recipe/app/src/recipe/application/RecipeServiceTest.groovy b/src/test/groovy/com/recipe/app/src/recipe/application/RecipeServiceTest.groovy index a044218b..912673c6 100644 --- a/src/test/groovy/com/recipe/app/src/recipe/application/RecipeServiceTest.groovy +++ b/src/test/groovy/com/recipe/app/src/recipe/application/RecipeServiceTest.groovy @@ -535,39 +535,6 @@ class RecipeServiceTest extends Specification { e.message == "레시피 정보를 찾지 못하였습니다." } - def "특정 유저의 레시피 목록 삭제"() { - - given: - Long userId = 1 - - List recipes = [ - Recipe.builder() - .recipeNm("제목") - .introduction("테스트설명") - .level(RecipeLevel.NORMAL) - .userId(1) - .isHidden(false) - .build(), - Recipe.builder() - .recipeNm("제목") - .introduction("설명") - .level(RecipeLevel.NORMAL) - .userId(2) - .isHidden(true) - .build(), - ] - - recipeRepository.findByUserId(userId) >> recipes - - when: - recipeService.deleteAllByUserId(userId) - - then: - 1 * recipeScrapService.deleteAllByUserId(userId) - 1 * recipeViewService.deleteAllByUserId(userId) - 1 * recipeRepository.deleteAll(recipes) - } - def "특정 유저의 레시피 스크랩 수 조회"() { given: diff --git a/src/test/groovy/com/recipe/app/src/user/application/UserFacadeServiceTest.groovy b/src/test/groovy/com/recipe/app/src/user/application/UserFacadeServiceTest.groovy index 7944b768..4c18527f 100644 --- a/src/test/groovy/com/recipe/app/src/user/application/UserFacadeServiceTest.groovy +++ b/src/test/groovy/com/recipe/app/src/user/application/UserFacadeServiceTest.groovy @@ -3,8 +3,10 @@ package com.recipe.app.src.user.application import com.recipe.app.src.fridge.application.FridgeService import com.recipe.app.src.fridgeBasket.application.FridgeBasketService import com.recipe.app.src.ingredient.application.IngredientService +import com.recipe.app.src.recipe.application.RecipeScrapService import com.recipe.app.src.recipe.application.RecipeSearchService import com.recipe.app.src.recipe.application.RecipeService +import com.recipe.app.src.recipe.application.RecipeViewService import com.recipe.app.src.recipe.application.blog.BlogScrapService import com.recipe.app.src.recipe.application.blog.BlogViewService import com.recipe.app.src.recipe.application.youtube.YoutubeScrapService @@ -12,6 +14,7 @@ import com.recipe.app.src.recipe.application.youtube.YoutubeViewService import com.recipe.app.src.recipe.domain.Recipe import com.recipe.app.src.recipe.domain.RecipeLevel import com.recipe.app.src.user.application.dto.UserProfileResponse +import com.recipe.app.src.user.application.dto.UserWithdrawRequest import com.recipe.app.src.user.domain.LoginProvider import com.recipe.app.src.user.domain.User import jakarta.servlet.http.HttpServletRequest @@ -23,6 +26,8 @@ class UserFacadeServiceTest extends Specification { private UserService userService = Mock() private RecipeService recipeService = Mock() private RecipeSearchService recipeSearchService = Mock() + private RecipeScrapService recipeScrapService = Mock() + private RecipeViewService recipeViewService = Mock() private YoutubeScrapService youtubeScrapService = Mock() private YoutubeViewService youtubeViewService = Mock() private BlogScrapService blogScrapService = Mock() @@ -31,7 +36,8 @@ class UserFacadeServiceTest extends Specification { private FridgeBasketService fridgeBasketService = Mock() private IngredientService ingredientService = Mock() private UserFacadeService userFacadeService = new UserFacadeService(userService, recipeService, recipeSearchService, - youtubeScrapService, youtubeViewService, blogScrapService, blogViewService, fridgeService, fridgeBasketService, ingredientService) + recipeScrapService, recipeViewService, youtubeScrapService, youtubeViewService, blogScrapService, blogViewService, + fridgeService, fridgeBasketService, ingredientService) def "유저 프로필 조회"() { @@ -87,7 +93,7 @@ class UserFacadeServiceTest extends Specification { result.userRecipes.thumbnailImgUrl == recipes.imgUrl } - def "유저 삭제"() { + def "유저 삭제 - 탈퇴 사유 없음"() { given: User user = User.builder() @@ -97,19 +103,48 @@ class UserFacadeServiceTest extends Specification { .build() HttpServletRequest request = Mock() + UserWithdrawRequest withdrawRequest = null when: - userFacadeService.deleteUser(user, request) + userFacadeService.deleteUser(user, request, withdrawRequest) then: 1 * fridgeService.deleteAllByUserId(user.userId) 1 * fridgeBasketService.deleteAllByUserId(user.userId) - 1 * recipeService.deleteAllByUserId(user.userId) - 1 * ingredientService.deleteAllByUserId(user.userId) + 1 * recipeScrapService.deleteAllByUserId(user.userId) + 1 * recipeViewService.deleteAllByUserId(user.userId) 1 * youtubeScrapService.deleteAllByUserId(user.userId) 1 * youtubeViewService.deleteAllByUserId(user.userId) 1 * blogScrapService.deleteAllByUserId(user.userId) 1 * blogViewService.deleteAllByUserId(user.userId) - 1 * userService.withdraw(user, request) + 1 * userService.withdraw(user, request, withdrawRequest) + } + + def "유저 삭제 - 탈퇴 사유 있음"() { + + given: + User user = User.builder() + .userId(1) + .socialId("naver_1") + .nickname("테스터1") + .build() + + HttpServletRequest request = Mock() + UserWithdrawRequest withdrawRequest = new UserWithdrawRequest() + withdrawRequest.withdrawalReason = "서비스가 만족스럽지 않아서" + + when: + userFacadeService.deleteUser(user, request, withdrawRequest) + + then: + 1 * fridgeService.deleteAllByUserId(user.userId) + 1 * fridgeBasketService.deleteAllByUserId(user.userId) + 1 * recipeScrapService.deleteAllByUserId(user.userId) + 1 * recipeViewService.deleteAllByUserId(user.userId) + 1 * youtubeScrapService.deleteAllByUserId(user.userId) + 1 * youtubeViewService.deleteAllByUserId(user.userId) + 1 * blogScrapService.deleteAllByUserId(user.userId) + 1 * blogViewService.deleteAllByUserId(user.userId) + 1 * userService.withdraw(user, request, withdrawRequest) } } diff --git a/src/test/groovy/com/recipe/app/src/user/application/UserServiceTest.groovy b/src/test/groovy/com/recipe/app/src/user/application/UserServiceTest.groovy index 02f72fdd..86e360cd 100644 --- a/src/test/groovy/com/recipe/app/src/user/application/UserServiceTest.groovy +++ b/src/test/groovy/com/recipe/app/src/user/application/UserServiceTest.groovy @@ -17,7 +17,8 @@ class UserServiceTest extends Specification { private JwtUtil jwtUtil = Mock() private BadWordFiltering badWordService = Mock() private UserAuthClientService userAuthClientService = Mock() - private UserService userService = new UserService(userRepository, jwtUtil, badWordService, userAuthClientService) + private UserWithdrawalService userWithdrawalService = Mock() + private UserService userService = new UserService(userRepository, jwtUtil, badWordService, userAuthClientService, userWithdrawalService) def "유저 아이디로 유저 조회"() { @@ -267,7 +268,7 @@ class UserServiceTest extends Specification { "update_profile" | "update_nickname" } - def "유저 탈퇴"() { + def "유저 탈퇴 - 탈퇴 사유 없음"() { given: HttpServletRequest request = Mock() @@ -276,17 +277,89 @@ class UserServiceTest extends Specification { .userId(1) .socialId("kakao_1") .nickname("테스터1") + .email("test@test.com") + .phoneNumber("010-1234-5678") .build() + UserWithdrawRequest withdrawRequest = null + + when: + userService.withdraw(user, request, withdrawRequest) + + then: + 1 * userRepository.save(user) >> { args -> + def savedUser = args.get(0) as User + savedUser.nickname == "탈퇴한 사용자" + savedUser.email == null + savedUser.phoneNumber == null + savedUser.deletedAt != null + } + 1 * jwtUtil.resolveAccessToken(request) + 1 * jwtUtil.setAccessTokenBlacklist(_) + 1 * jwtUtil.getUserId(_) + 1 * jwtUtil.removeRefreshToken(_) + 0 * userWithdrawalService.saveWithdrawalReason(_, _) + } + + def "유저 탈퇴 - 탈퇴 사유 있음"() { + + given: + HttpServletRequest request = Mock() + + User user = User.builder() + .userId(1) + .socialId("kakao_1") + .nickname("테스터1") + .email("test@test.com") + .phoneNumber("010-1234-5678") + .build() + + UserWithdrawRequest withdrawRequest = new UserWithdrawRequest() + withdrawRequest.withdrawalReason = "서비스가 만족스럽지 않아서" + when: - userService.withdraw(user, request) + userService.withdraw(user, request, withdrawRequest) then: - 1 * userRepository.delete(user) + 1 * userRepository.save(user) >> { args -> + def savedUser = args.get(0) as User + savedUser.nickname == "탈퇴한 사용자" + savedUser.email == null + savedUser.phoneNumber == null + savedUser.deletedAt != null + } + 1 * jwtUtil.resolveAccessToken(request) + 1 * jwtUtil.setAccessTokenBlacklist(_) + 1 * jwtUtil.getUserId(_) + 1 * jwtUtil.removeRefreshToken(_) + 1 * userWithdrawalService.saveWithdrawalReason(user.userId, withdrawRequest.withdrawalReason) + } + + def "유저 탈퇴 - 탈퇴 사유가 빈 문자열인 경우"() { + + given: + HttpServletRequest request = Mock() + + User user = User.builder() + .userId(1) + .socialId("kakao_1") + .nickname("테스터1") + .email("test@test.com") + .build() + + UserWithdrawRequest withdrawRequest = new UserWithdrawRequest() + withdrawRequest.withdrawalReason = "" + + when: + userService.withdraw(user, request, withdrawRequest) + + then: + 1 * userRepository.save(user) 1 * jwtUtil.resolveAccessToken(request) 1 * jwtUtil.setAccessTokenBlacklist(_) 1 * jwtUtil.getUserId(_) 1 * jwtUtil.removeRefreshToken(_) + 0 * userWithdrawalService.saveWithdrawalReason(_, _) } def "디바이스 토큰 수정"() { diff --git a/src/test/groovy/com/recipe/app/src/user/application/UserWithdrawalServiceTest.groovy b/src/test/groovy/com/recipe/app/src/user/application/UserWithdrawalServiceTest.groovy new file mode 100644 index 00000000..46886a34 --- /dev/null +++ b/src/test/groovy/com/recipe/app/src/user/application/UserWithdrawalServiceTest.groovy @@ -0,0 +1,49 @@ +package com.recipe.app.src.user.application + +import com.recipe.app.src.user.domain.UserWithdrawal +import com.recipe.app.src.user.infra.UserWithdrawalRepository +import spock.lang.Specification + +class UserWithdrawalServiceTest extends Specification { + + private UserWithdrawalRepository userWithdrawalRepository = Mock() + private UserWithdrawalService userWithdrawalService = new UserWithdrawalService(userWithdrawalRepository) + + def "탈퇴 사유 저장"() { + + given: + Long userId = 1L + String withdrawalReason = "서비스가 만족스럽지 않아서" + + when: + userWithdrawalService.saveWithdrawalReason(userId, withdrawalReason) + + then: + 1 * userWithdrawalRepository.save(_) >> { args -> + def userWithdrawal = args.get(0) as UserWithdrawal + + userWithdrawal.userId == userId + userWithdrawal.withdrawalReason == withdrawalReason + userWithdrawal.createdAt != null + } + } + + def "탈퇴 사유 저장 - 사유가 null인 경우도 저장"() { + + given: + Long userId = 1L + String withdrawalReason = null + + when: + userWithdrawalService.saveWithdrawalReason(userId, withdrawalReason) + + then: + 1 * userWithdrawalRepository.save(_) >> { args -> + def userWithdrawal = args.get(0) as UserWithdrawal + + userWithdrawal.userId == userId + userWithdrawal.withdrawalReason == null + userWithdrawal.createdAt != null + } + } +} From 1692536bf6a1d6aa5b1248ac75db5088f2854d69 Mon Sep 17 00:00:00 2001 From: joona95 Date: Wed, 15 Oct 2025 22:11:37 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9?= =?UTF-8?q?=20=EC=86=8C=EC=8A=A4=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/recipe/app/src/common/config/JwtFilter.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/recipe/app/src/common/config/JwtFilter.java b/src/main/java/com/recipe/app/src/common/config/JwtFilter.java index adc314c6..11762324 100644 --- a/src/main/java/com/recipe/app/src/common/config/JwtFilter.java +++ b/src/main/java/com/recipe/app/src/common/config/JwtFilter.java @@ -29,8 +29,6 @@ public class JwtFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - System.out.println(jwtUtil.createAccessToken(18L)); - String accessToken = jwtUtil.resolveAccessToken((HttpServletRequest) request); String requestURI = ((HttpServletRequest) request).getRequestURI();