From 21393ef58d2538b57c6458da4ac6695dd67785b4 Mon Sep 17 00:00:00 2001 From: JOO Date: Thu, 29 Oct 2026 00:44:52 +0900 Subject: [PATCH 1/4] =?UTF-8?q?Feat:=20=ED=83=88=ED=87=B4=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EC=9D=98=20=ED=81=B4=EB=9E=98=EC=8A=A4=C2=B7=EC=A2=8C?= =?UTF-8?q?=EC=84=9D=C2=B7=EC=BD=98=ED=85=90=EC=B8=A0=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ClassMemberRepository.java | 4 ++ .../repository/ClassroomRepository.java | 2 + .../service/ClassMemberServiceImpl.java | 6 +- .../service/ClassroomServiceImpl.java | 6 +- .../service/UserClassWithdrawalService.java | 55 +++++++++++++++++++ .../user/repository/UserBattleRepository.java | 8 +++ 6 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/swyp/picke/domain/classroom/service/UserClassWithdrawalService.java diff --git a/src/main/java/com/swyp/picke/domain/classroom/repository/ClassMemberRepository.java b/src/main/java/com/swyp/picke/domain/classroom/repository/ClassMemberRepository.java index 3d7eb9b6..29496189 100644 --- a/src/main/java/com/swyp/picke/domain/classroom/repository/ClassMemberRepository.java +++ b/src/main/java/com/swyp/picke/domain/classroom/repository/ClassMemberRepository.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -16,6 +17,9 @@ public interface ClassMemberRepository extends JpaRepository /** 참여용: 상태와 무관하게 이 유저의 멤버 행 (UK(class_id, user_id) 라 최대 1건) */ Optional findByClassroomIdAndUserId(Long classroomId, Long userId); + @EntityGraph(attributePaths = "classroom") + List findByUserIdAndStatus(Long userId, ClassMemberStatus status); + Optional findByIdAndClassroomId(Long id, Long classroomId); /** 의견·댓글 작성자 표시 이름 일괄 조회용. 나간·내보낸 멤버의 글은 숨기므로 ACTIVE 만 조회한다 */ diff --git a/src/main/java/com/swyp/picke/domain/classroom/repository/ClassroomRepository.java b/src/main/java/com/swyp/picke/domain/classroom/repository/ClassroomRepository.java index 23224117..f0c52b68 100644 --- a/src/main/java/com/swyp/picke/domain/classroom/repository/ClassroomRepository.java +++ b/src/main/java/com/swyp/picke/domain/classroom/repository/ClassroomRepository.java @@ -26,6 +26,8 @@ public interface ClassroomRepository extends JpaRepository { Optional findByIdAndStatus(Long id, ClassStatus status); + List findByOwnerIdAndStatus(Long ownerId, ClassStatus status); + /** 중복 생성 판정용: 같은 운영자가 같은 요청 키로 만든 클래스 */ Optional findByOwnerIdAndCreateRequestKey(Long ownerId, String createRequestKey); diff --git a/src/main/java/com/swyp/picke/domain/classroom/service/ClassMemberServiceImpl.java b/src/main/java/com/swyp/picke/domain/classroom/service/ClassMemberServiceImpl.java index 167b21ae..eb052cb5 100644 --- a/src/main/java/com/swyp/picke/domain/classroom/service/ClassMemberServiceImpl.java +++ b/src/main/java/com/swyp/picke/domain/classroom/service/ClassMemberServiceImpl.java @@ -17,6 +17,7 @@ import com.swyp.picke.domain.user.entity.User; import com.swyp.picke.domain.user.repository.UserBattleRepository; import com.swyp.picke.domain.user.repository.UserRepository; +import com.swyp.picke.domain.user.enums.UserStatus; import com.swyp.picke.global.common.exception.CustomException; import com.swyp.picke.global.common.exception.ErrorCode; import java.time.LocalDateTime; @@ -59,6 +60,9 @@ public ClassDetailResponse join(Long userId, ClassJoinRequest request) { LocalDateTime now = LocalDateTime.now(KST); String inviteCode = normalizeInviteCode(request.inviteCode()); String displayName = normalizeDisplayName(request.displayName()); + User user = userRepository.findByIdForUpdate(userId) + .filter(candidate -> candidate.getStatus() == UserStatus.ACTIVE) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); Classroom classroom = classroomRepository.findByInviteCodeAndStatusForUpdate(inviteCode, ClassStatus.ACTIVE) .orElseThrow(() -> codeNotUsable(inviteCode)); @@ -77,8 +81,6 @@ public ClassDetailResponse join(Long userId, ClassJoinRequest request) { if (existing.isPresent()) { existing.get().rejoin(displayName, now); } else { - User user = userRepository.findById(userId) - .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); classMemberRepository.save(ClassMember.builder() .classroom(classroom) .user(user) diff --git a/src/main/java/com/swyp/picke/domain/classroom/service/ClassroomServiceImpl.java b/src/main/java/com/swyp/picke/domain/classroom/service/ClassroomServiceImpl.java index 3e2501f6..a4f6daea 100644 --- a/src/main/java/com/swyp/picke/domain/classroom/service/ClassroomServiceImpl.java +++ b/src/main/java/com/swyp/picke/domain/classroom/service/ClassroomServiceImpl.java @@ -25,6 +25,7 @@ import com.swyp.picke.domain.ticket.enums.TicketStatus; import com.swyp.picke.domain.ticket.repository.ClassTicketRepository; import com.swyp.picke.domain.user.entity.User; +import com.swyp.picke.domain.user.enums.UserStatus; import com.swyp.picke.domain.user.repository.UserRepository; import com.swyp.picke.global.common.exception.CustomException; import com.swyp.picke.global.common.exception.ErrorCode; @@ -77,8 +78,11 @@ public class ClassroomServiceImpl implements ClassroomService { @Transactional public ClassDetailResponse create(Long userId, ClassCreateRequest request) { LocalDateTime now = LocalDateTime.now(KST); - User owner = userRepository.findById(userId) + User owner = userRepository.findByIdForUpdate(userId) .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + if (owner.getStatus() != UserStatus.ACTIVE) { + throw new CustomException(ErrorCode.USER_NOT_FOUND); + } ClassTicket ticket = findUsableTicket(userId, now); if ((request.battleId() == null) == (request.topicVersionId() == null)) { throw new CustomException(ErrorCode.CLASS_BATTLE_SOURCE_INVALID); diff --git a/src/main/java/com/swyp/picke/domain/classroom/service/UserClassWithdrawalService.java b/src/main/java/com/swyp/picke/domain/classroom/service/UserClassWithdrawalService.java new file mode 100644 index 00000000..b8d90e9b --- /dev/null +++ b/src/main/java/com/swyp/picke/domain/classroom/service/UserClassWithdrawalService.java @@ -0,0 +1,55 @@ +package com.swyp.picke.domain.classroom.service; + +import com.swyp.picke.domain.classroom.entity.ClassMember; +import com.swyp.picke.domain.classroom.entity.Classroom; +import com.swyp.picke.domain.classroom.enums.ClassMemberStatus; +import com.swyp.picke.domain.classroom.enums.ClassStatus; +import com.swyp.picke.domain.classroom.repository.ClassMemberRepository; +import com.swyp.picke.domain.classroom.repository.ClassroomRepository; +import com.swyp.picke.domain.perspective.enums.PerspectiveStatus; +import com.swyp.picke.domain.perspective.repository.PerspectiveCommentRepository; +import com.swyp.picke.domain.perspective.repository.PerspectiveRepository; +import com.swyp.picke.domain.user.entity.User; +import com.swyp.picke.domain.user.repository.UserBattleRepository; +import java.time.LocalDateTime; +import java.util.Set; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** Applies account-withdrawal policy to live class memberships and authored discussion content. */ +@Service +@RequiredArgsConstructor +public class UserClassWithdrawalService { + + private final ClassroomRepository classroomRepository; + private final ClassMemberRepository classMemberRepository; + private final UserBattleRepository userBattleRepository; + private final PerspectiveRepository perspectiveRepository; + private final PerspectiveCommentRepository perspectiveCommentRepository; + + @Transactional + public void apply(User user, LocalDateTime now) { + Long userId = user.getId(); + + // A deleted owner cannot continue operating an active class. Keep historical records for reports/audit. + List ownedClasses = classroomRepository.findByOwnerIdAndStatus(userId, ClassStatus.ACTIVE); + ownedClasses.forEach(classroom -> classroom.delete(now)); + + // Match the normal leave rule: a member who has started the class keeps their seat consumed. + List activeMemberships = classMemberRepository.findByUserIdAndStatus(userId, ClassMemberStatus.ACTIVE); + Set activeClassroomIds = activeMemberships.stream() + .map(member -> member.getClassroom().getId()).collect(java.util.stream.Collectors.toSet()); + Set classroomsWithActivity = activeClassroomIds.isEmpty() ? Set.of() + : userBattleRepository.findClassroomIdsWithActivity(userId, activeClassroomIds); + for (ClassMember member : activeMemberships) { + boolean hasActivity = classroomsWithActivity.contains(member.getClassroom().getId()); + member.leave(now, hasActivity); + } + + // Hide authored text from public and class screens while preserving rows referenced by historical records. + perspectiveRepository.hideAllByUserId(userId, PerspectiveStatus.HIDDEN); + perspectiveCommentRepository.hideAllByUserId(userId); + } +} diff --git a/src/main/java/com/swyp/picke/domain/user/repository/UserBattleRepository.java b/src/main/java/com/swyp/picke/domain/user/repository/UserBattleRepository.java index a9254337..30e98539 100644 --- a/src/main/java/com/swyp/picke/domain/user/repository/UserBattleRepository.java +++ b/src/main/java/com/swyp/picke/domain/user/repository/UserBattleRepository.java @@ -5,9 +5,13 @@ import com.swyp.picke.domain.user.entity.User; import com.swyp.picke.domain.user.entity.UserBattle; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; import java.util.Optional; +import java.util.Collection; +import java.util.Set; public interface UserBattleRepository extends JpaRepository { @@ -17,6 +21,10 @@ public interface UserBattleRepository extends JpaRepository { // 클래스 내 활동 이력(사전투표 이후) 존재 여부 — 나가기/내보내기 시 좌석 유지 판정(F15) boolean existsByUserIdAndClassroomId(Long userId, Long classroomId); + @Query("select distinct ub.classroom.id from UserBattle ub where ub.user.id = :userId and ub.classroom.id in :classroomIds") + Set findClassroomIdsWithActivity(@Param("userId") Long userId, + @Param("classroomIds") Collection classroomIds); + // 클래스 결과 집계·리포트 대상 선별: 이 클래스 안의 진행 기록 전체 (user 는 LAZY) List findByClassroomId(Long classroomId); } From 9cce42ca3247b544743c12b3c55cc722a4244b35 Mon Sep 17 00:00:00 2001 From: JOO Date: Thu, 29 Oct 2026 00:46:36 +0900 Subject: [PATCH 2/4] =?UTF-8?q?Feat:=20=ED=83=88=ED=87=B4=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EC=9D=98=20JWT=20=EC=9D=B8=EC=A6=9D=20=EC=B0=A8?= =?UTF-8?q?=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../picke/domain/oauth/jwt/JwtFilter.java | 37 +++++++++++++++++-- .../domain/oauth/service/AuthService.java | 8 +++- .../user/repository/UserRepository.java | 6 +++ .../picke/global/config/SecurityConfig.java | 4 +- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/swyp/picke/domain/oauth/jwt/JwtFilter.java b/src/main/java/com/swyp/picke/domain/oauth/jwt/JwtFilter.java index 7f3463ca..d8c1d71c 100644 --- a/src/main/java/com/swyp/picke/domain/oauth/jwt/JwtFilter.java +++ b/src/main/java/com/swyp/picke/domain/oauth/jwt/JwtFilter.java @@ -2,13 +2,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.swyp.picke.domain.user.repository.UserDailyActivityRepository; +import com.swyp.picke.domain.user.repository.UserRepository; import com.swyp.picke.global.common.exception.ErrorCode; import com.swyp.picke.global.common.response.ApiResponse; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -22,13 +22,25 @@ import java.util.Set; @Slf4j -@RequiredArgsConstructor public class JwtFilter extends OncePerRequestFilter { private final JwtProvider jwtProvider; private final UserDailyActivityRepository userDailyActivityRepository; + private final UserRepository userRepository; private final ObjectMapper objectMapper = new ObjectMapper(); + public JwtFilter(JwtProvider jwtProvider, UserDailyActivityRepository userDailyActivityRepository, + UserRepository userRepository) { + this.jwtProvider = jwtProvider; + this.userDailyActivityRepository = userDailyActivityRepository; + this.userRepository = userRepository; + } + + /** Compatibility constructor for isolated filter tests; production wiring always supplies UserRepository. */ + public JwtFilter(JwtProvider jwtProvider, UserDailyActivityRepository userDailyActivityRepository) { + this(jwtProvider, userDailyActivityRepository, null); + } + // 1. 화이트리스트 상수 관리 private static final List WHITELIST = List.of( "/api/v1/admob/reward", @@ -96,6 +108,11 @@ protected void doFilterInternal(HttpServletRequest request, } Long userId = jwtProvider.getUserId(token); + ErrorCode inactiveAccountError = accountError(userId); + if (inactiveAccountError != null) { + setErrorResponse(response, inactiveAccountError); + return; + } String role = jwtProvider.getRole(token); String authorityName = (role != null && role.startsWith("ROLE_")) ? role : "ROLE_" + role; @@ -124,6 +141,20 @@ protected void doFilterInternal(HttpServletRequest request, } } + private ErrorCode accountError(Long userId) { + if (userRepository == null) { + return null; + } + return userRepository.findById(userId) + .map(user -> switch (user.getStatus()) { + case ACTIVE, PENDING -> null; + case SUSPENDED -> ErrorCode.USER_SUSPENDED; + case BANNED -> ErrorCode.USER_BANNED; + case DELETED -> ErrorCode.AUTH_UNAUTHORIZED; + }) + .orElse(ErrorCode.AUTH_UNAUTHORIZED); + } + /** * DAU/MAU 집계를 위한 활동 기록. 대시보드 집계용 부가 기능이라 실패해도 인증 흐름을 막지 않는다. */ @@ -163,4 +194,4 @@ private boolean isWhitelisted(String uri) { } return WHITELIST.stream().anyMatch(white -> uri.equals(white) || uri.startsWith(white)); } -} \ No newline at end of file +} diff --git a/src/main/java/com/swyp/picke/domain/oauth/service/AuthService.java b/src/main/java/com/swyp/picke/domain/oauth/service/AuthService.java index 2a0839e5..a2de4b89 100644 --- a/src/main/java/com/swyp/picke/domain/oauth/service/AuthService.java +++ b/src/main/java/com/swyp/picke/domain/oauth/service/AuthService.java @@ -33,6 +33,7 @@ import com.swyp.picke.domain.user.repository.UserTendencyScoreRepository; import com.swyp.picke.domain.user.repository.UserWithdrawalRepository; import com.swyp.picke.domain.user.service.CreditService; +import com.swyp.picke.domain.classroom.service.UserClassWithdrawalService; import com.swyp.picke.global.common.exception.CustomException; import com.swyp.picke.global.common.exception.ErrorCode; import lombok.RequiredArgsConstructor; @@ -99,6 +100,7 @@ public class AuthService { private final UserDailyActivityRepository userDailyActivityRepository; private final JwtProvider jwtProvider; private final CreditService creditService; + private final UserClassWithdrawalService userClassWithdrawalService; private final PasswordEncoder passwordEncoder; public LoginResponse login(String provider, LoginRequest request) { @@ -272,7 +274,7 @@ public void logout(Long userId) { } public void withdraw(Long userId, WithdrawRequest request) { - User user = userRepository.findById(userId) + User user = userRepository.findByIdForUpdate(userId) .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); refreshTokenRepository.deleteByUser(user); @@ -280,6 +282,8 @@ public void withdraw(Long userId, WithdrawRequest request) { throw new CustomException(ErrorCode.USER_ALREADY_WITHDRAWN); } + userClassWithdrawalService.apply(user, LocalDateTime.now(java.time.ZoneId.of("Asia/Seoul"))); + if (!userWithdrawalRepository.existsByUser_Id(userId)) { userWithdrawalRepository.save(UserWithdrawal.builder() .user(user) @@ -371,4 +375,4 @@ private String hashToken(String token) { throw new RuntimeException("토큰 해시 실패", e); } } -} \ No newline at end of file +} diff --git a/src/main/java/com/swyp/picke/domain/user/repository/UserRepository.java b/src/main/java/com/swyp/picke/domain/user/repository/UserRepository.java index 9bf3d747..6f0b5fce 100644 --- a/src/main/java/com/swyp/picke/domain/user/repository/UserRepository.java +++ b/src/main/java/com/swyp/picke/domain/user/repository/UserRepository.java @@ -8,9 +8,11 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Optional; +import jakarta.persistence.LockModeType; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -20,6 +22,10 @@ public interface UserRepository extends JpaRepository { Optional findTopByOrderByIdDesc(); boolean existsByUserTag(String userTag); + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("select u from User u where u.id = :id") + Optional findByIdForUpdate(@Param("id") Long id); + @Query("select u.credit from User u where u.id = :id") Integer findCreditById(@Param("id") Long id); diff --git a/src/main/java/com/swyp/picke/global/config/SecurityConfig.java b/src/main/java/com/swyp/picke/global/config/SecurityConfig.java index d2bf3f0e..70507baf 100644 --- a/src/main/java/com/swyp/picke/global/config/SecurityConfig.java +++ b/src/main/java/com/swyp/picke/global/config/SecurityConfig.java @@ -3,6 +3,7 @@ import com.swyp.picke.domain.oauth.jwt.JwtFilter; import com.swyp.picke.domain.oauth.jwt.JwtProvider; import com.swyp.picke.domain.user.repository.UserDailyActivityRepository; +import com.swyp.picke.domain.user.repository.UserRepository; import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; @@ -30,6 +31,7 @@ public class SecurityConfig { private final JwtProvider jwtProvider; private final UserDailyActivityRepository userDailyActivityRepository; + private final UserRepository userRepository; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -77,7 +79,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/api/v1/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) - .addFilterBefore(new JwtFilter(jwtProvider, userDailyActivityRepository), + .addFilterBefore(new JwtFilter(jwtProvider, userDailyActivityRepository, userRepository), UsernamePasswordAuthenticationFilter.class); return http.build(); From 3a812809839df84f554e4ff1291c67f55810acdd Mon Sep 17 00:00:00 2001 From: JOO Date: Thu, 29 Oct 2026 00:47:45 +0900 Subject: [PATCH 3/4] =?UTF-8?q?Docs:=20=ED=83=88=ED=87=B4=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20API=20=EB=AA=85=EC=84=B8=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api-specs/oauth-api.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/api-specs/oauth-api.md b/docs/api-specs/oauth-api.md index e665ff06..37141459 100644 --- a/docs/api-specs/oauth-api.md +++ b/docs/api-specs/oauth-api.md @@ -153,6 +153,9 @@ - `users`, `user_social_accounts`, `auth_refresh_tokens` 연관 데이터를 함께 처리합니다. - 사용자 도메인 상세 정리는 `user` 정책에 따라 함께 처리합니다. - 탈퇴 사유는 별도 이력 테이블에 저장합니다. +- 운영 중인 클래스는 소프트 삭제해 새 참여·운영을 막고, 참여 중 클래스 멤버십은 `LEFT`로 바꿉니다. +- 투표 등 활동 이력이 있는 멤버의 좌석은 유지하고, 활동이 없으면 좌석을 반환합니다. +- 작성한 의견·댓글은 공개 화면에서 숨기며, 리포트·이력 참조를 위해 행은 보존합니다. 요청 헤더: From d9b417dd00528c83917ea4b7ddc1573f49283e9a Mon Sep 17 00:00:00 2001 From: JOO Date: Thu, 29 Oct 2026 00:48:08 +0900 Subject: [PATCH 4/4] =?UTF-8?q?Test:=20=ED=83=88=ED=87=B4=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=ED=94=BD?= =?UTF-8?q?=EC=8A=A4=EC=B2=98=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../swyp/picke/domain/oauth/service/OAuthServiceTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/swyp/picke/domain/oauth/service/OAuthServiceTest.java b/src/test/java/com/swyp/picke/domain/oauth/service/OAuthServiceTest.java index 81dce108..14a17a9b 100644 --- a/src/test/java/com/swyp/picke/domain/oauth/service/OAuthServiceTest.java +++ b/src/test/java/com/swyp/picke/domain/oauth/service/OAuthServiceTest.java @@ -31,6 +31,7 @@ import com.swyp.picke.domain.user.repository.UserTendencyScoreRepository; import com.swyp.picke.domain.user.repository.UserWithdrawalRepository; import com.swyp.picke.domain.user.service.CreditService; +import com.swyp.picke.domain.classroom.service.UserClassWithdrawalService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -64,6 +65,7 @@ class OAuthServiceTest { @Mock private UserDailyActivityRepository userDailyActivityRepository; @Mock private JwtProvider jwtProvider; @Mock private CreditService creditService; + @Mock private UserClassWithdrawalService userClassWithdrawalService; @Mock private PasswordEncoder passwordEncoder; private AuthService authService; @@ -76,7 +78,7 @@ void setUp() { socialAccountRepository, localAccountRepository, refreshTokenRepository, userProfileRepository, userSettingsRepository, userTendencyScoreRepository, userWithdrawalRepository, userDailyActivityRepository, - jwtProvider, creditService, passwordEncoder + jwtProvider, creditService, userClassWithdrawalService, passwordEncoder ); } @@ -98,7 +100,7 @@ void setUp() { when(kakaoOAuthClient.getAccessToken(anyString(), anyString())).thenReturn("mock-access-token"); when(kakaoOAuthClient.getUserInfo(anyString())).thenReturn(userInfo); // 여기서 null이 안 들어가게 고정 - var socialAccount = mock(com.swyp.picke.domain.oauth.entity.UserSocialAccount.class); + var socialAccount = mock(UserSocialAccount.class); when(socialAccount.getUser()).thenReturn(user); when(socialAccountRepository.findByProviderAndProviderUserId(anyString(), anyString())) .thenReturn(Optional.of(socialAccount)); @@ -395,4 +397,4 @@ void setUp() { verify(socialAccountRepository, times(1)).delete(socialAccount); assertThat(user.getStatus()).isEqualTo(UserStatus.DELETED); } -} \ No newline at end of file +}