diff --git a/src/main/java/coffeemeet/server/admin/presentation/AdminController.java b/src/main/java/coffeemeet/server/admin/presentation/AdminController.java index 502dd11f..9380173c 100644 --- a/src/main/java/coffeemeet/server/admin/presentation/AdminController.java +++ b/src/main/java/coffeemeet/server/admin/presentation/AdminController.java @@ -51,6 +51,7 @@ public class AdminController { private static final String REQUEST_WITHOUT_SESSION_MESSAGE = "SESSION 값이 존재하지 않습니다."; private static final String ADMIN_SESSION_ATTRIBUTE = "adminId"; + private final AdminService adminService; private final ReportService reportService; private final InquiryService inquiryService; diff --git a/src/main/java/coffeemeet/server/auth/presentation/AuthController.java b/src/main/java/coffeemeet/server/auth/presentation/AuthController.java index 4e394f93..cf359a26 100644 --- a/src/main/java/coffeemeet/server/auth/presentation/AuthController.java +++ b/src/main/java/coffeemeet/server/auth/presentation/AuthController.java @@ -28,10 +28,4 @@ public ResponseEntity logout(@Login AuthInfo authInfo) { return ResponseEntity.ok().build(); } - @PostMapping("/delete") - public ResponseEntity delete(@Login AuthInfo authInfo) { - authService.delete(authInfo.userId()); - return ResponseEntity.ok().build(); - } - } diff --git a/src/main/java/coffeemeet/server/auth/service/AuthService.java b/src/main/java/coffeemeet/server/auth/service/AuthService.java index 151da26c..fb692a3a 100644 --- a/src/main/java/coffeemeet/server/auth/service/AuthService.java +++ b/src/main/java/coffeemeet/server/auth/service/AuthService.java @@ -7,10 +7,8 @@ import coffeemeet.server.auth.domain.JwtTokenProvider; import coffeemeet.server.auth.implement.RefreshTokenCommand; import coffeemeet.server.common.execption.InvalidAuthException; -import coffeemeet.server.user.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -18,7 +16,6 @@ public class AuthService { private static final String EXPIRED_REFRESH_TOKEN_MESSAGE = "리프레시 토큰(%s)이 만료되었습니다. 다시 로그인해 주세요."; - private final UserService userService; private final AuthTokensGenerator authTokensGenerator; private final JwtTokenProvider jwtTokenProvider; private final RefreshTokenCommand refreshTokenCommand; @@ -37,10 +34,4 @@ public void logout(Long userId) { refreshTokenCommand.deleteRefreshToken(userId); } - @Transactional - public void delete(Long userId) { - userService.deleteUser(userId); - refreshTokenCommand.deleteRefreshToken(userId); - } - } diff --git a/src/main/java/coffeemeet/server/common/config/ScheduleConfig.java b/src/main/java/coffeemeet/server/common/config/ScheduleConfig.java new file mode 100644 index 00000000..5d6371b0 --- /dev/null +++ b/src/main/java/coffeemeet/server/common/config/ScheduleConfig.java @@ -0,0 +1,24 @@ +package coffeemeet.server.common.config; + +import coffeemeet.server.user.service.UserService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@EnableScheduling +@RequiredArgsConstructor +public class ScheduleConfig { + + private final UserService userService; + + @Scheduled(cron = "0 0 3 * * *") + public void checkUserDeleted() { + log.info("탈퇴 후 30일이 지난 회원 정보 제거"); + userService.deleteUserInfos(); + } + +} diff --git a/src/main/java/coffeemeet/server/common/presentation/resolver/UserArgumentResolver.java b/src/main/java/coffeemeet/server/common/presentation/resolver/UserArgumentResolver.java index 445a9c92..ba883442 100644 --- a/src/main/java/coffeemeet/server/common/presentation/resolver/UserArgumentResolver.java +++ b/src/main/java/coffeemeet/server/common/presentation/resolver/UserArgumentResolver.java @@ -23,6 +23,7 @@ public class UserArgumentResolver implements HandlerMethodArgumentResolver { private static final int AUTHENTICATION_PREFIX_LENGTH = 7; private static final String HEADER_AUTHENTICATION_FAILED_MESSAGE = "(%s)는 잘못된 권한 헤더입니다."; + private final JwtTokenProvider jwtTokenProvider; private final RefreshTokenQuery refreshTokenQuery; diff --git a/src/main/java/coffeemeet/server/oauth/implement/client/OAuthMemberClientComposite.java b/src/main/java/coffeemeet/server/oauth/implement/client/OAuthMemberClientRegistry.java similarity index 91% rename from src/main/java/coffeemeet/server/oauth/implement/client/OAuthMemberClientComposite.java rename to src/main/java/coffeemeet/server/oauth/implement/client/OAuthMemberClientRegistry.java index ece1393e..62553c46 100644 --- a/src/main/java/coffeemeet/server/oauth/implement/client/OAuthMemberClientComposite.java +++ b/src/main/java/coffeemeet/server/oauth/implement/client/OAuthMemberClientRegistry.java @@ -13,12 +13,13 @@ import org.springframework.stereotype.Component; @Component -public class OAuthMemberClientComposite { +public class OAuthMemberClientRegistry { private static final String INVALID_LOGIN_TYPE_MESSAGE = "로그인 타입(%s)에 일치하는 타입이 없습니다."; + private final Map mapping; - public OAuthMemberClientComposite(Set clients) { + public OAuthMemberClientRegistry(Set clients) { this.mapping = clients.stream().collect( Collectors.toUnmodifiableMap(OAuthMemberClient::oAuthProvider, Function.identity()) ); diff --git a/src/main/java/coffeemeet/server/oauth/implement/client/OAuthMemberUnlinkRegistry.java b/src/main/java/coffeemeet/server/oauth/implement/client/OAuthMemberUnlinkRegistry.java new file mode 100644 index 00000000..b7c62325 --- /dev/null +++ b/src/main/java/coffeemeet/server/oauth/implement/client/OAuthMemberUnlinkRegistry.java @@ -0,0 +1,40 @@ +package coffeemeet.server.oauth.implement.client; + +import static coffeemeet.server.auth.exception.AuthErrorCode.INVALID_LOGIN_TYPE; + +import coffeemeet.server.common.execption.InvalidAuthException; +import coffeemeet.server.oauth.infrastructure.OAuthUnlinkDetail; +import coffeemeet.server.user.domain.OAuthProvider; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.springframework.stereotype.Component; + +@Component +public class OAuthMemberUnlinkRegistry { + + private static final String INVALID_LOGIN_TYPE_MESSAGE = "로그인 타입(%s)에 일치하는 타입이 없습니다."; + + private final Map mapping; + + public OAuthMemberUnlinkRegistry(Set details) { + this.mapping = details.stream().collect( + Collectors.toUnmodifiableMap(OAuthUnlinkClient::oAuthProvider, Function.identity()) + ); + } + + public OAuthUnlinkDetail unlink(OAuthProvider oAuthProvider, String accessToken) { + return getClient(oAuthProvider).unlink(accessToken); + } + + private OAuthUnlinkClient getClient(OAuthProvider oAuthProvider) { + return Optional.ofNullable(mapping.get(oAuthProvider)) + .orElseThrow(() -> new InvalidAuthException( + INVALID_LOGIN_TYPE, + String.format(INVALID_LOGIN_TYPE_MESSAGE, oAuthProvider)) + ); + } + +} diff --git a/src/main/java/coffeemeet/server/oauth/implement/client/OAuthUnlinkClient.java b/src/main/java/coffeemeet/server/oauth/implement/client/OAuthUnlinkClient.java new file mode 100644 index 00000000..2608af54 --- /dev/null +++ b/src/main/java/coffeemeet/server/oauth/implement/client/OAuthUnlinkClient.java @@ -0,0 +1,12 @@ +package coffeemeet.server.oauth.implement.client; + +import coffeemeet.server.oauth.infrastructure.OAuthUnlinkDetail; +import coffeemeet.server.user.domain.OAuthProvider; + +public interface OAuthUnlinkClient { + + OAuthProvider oAuthProvider(); + + OAuthUnlinkDetail unlink(String accessToken); + +} diff --git a/src/main/java/coffeemeet/server/oauth/implement/client/kakao/KakaoMemberClient.java b/src/main/java/coffeemeet/server/oauth/implement/client/kakao/KakaoMemberClient.java index 1ee0a48a..7dad76dd 100644 --- a/src/main/java/coffeemeet/server/oauth/implement/client/kakao/KakaoMemberClient.java +++ b/src/main/java/coffeemeet/server/oauth/implement/client/kakao/KakaoMemberClient.java @@ -2,7 +2,7 @@ import coffeemeet.server.oauth.domain.OAuthMemberDetail; import coffeemeet.server.oauth.implement.client.OAuthMemberClient; -import coffeemeet.server.oauth.infrastructure.kakao.KakaoClient; +import coffeemeet.server.oauth.infrastructure.kakao.KakaoFetchClient; import coffeemeet.server.oauth.infrastructure.kakao.dto.KakaoMemberDetail; import coffeemeet.server.oauth.infrastructure.kakao.dto.KakaoTokens; import coffeemeet.server.user.domain.OAuthProvider; @@ -13,7 +13,7 @@ @RequiredArgsConstructor public class KakaoMemberClient implements OAuthMemberClient { - private final KakaoClient kakaoClient; + private final KakaoFetchClient kakaoClient; @Override public OAuthProvider oAuthProvider() { diff --git a/src/main/java/coffeemeet/server/oauth/implement/client/naver/NaverMemberClient.java b/src/main/java/coffeemeet/server/oauth/implement/client/naver/NaverMemberClient.java index f0cd5208..d5de4165 100644 --- a/src/main/java/coffeemeet/server/oauth/implement/client/naver/NaverMemberClient.java +++ b/src/main/java/coffeemeet/server/oauth/implement/client/naver/NaverMemberClient.java @@ -2,7 +2,7 @@ import coffeemeet.server.oauth.domain.OAuthMemberDetail; import coffeemeet.server.oauth.implement.client.OAuthMemberClient; -import coffeemeet.server.oauth.infrastructure.naver.NaverClient; +import coffeemeet.server.oauth.infrastructure.naver.NaverFetchClient; import coffeemeet.server.oauth.infrastructure.naver.dto.NaverMemberDetail; import coffeemeet.server.oauth.infrastructure.naver.dto.NaverTokens; import coffeemeet.server.user.domain.OAuthProvider; @@ -13,7 +13,7 @@ @RequiredArgsConstructor public class NaverMemberClient implements OAuthMemberClient { - private final NaverClient naverClient; + private final NaverFetchClient naverClient; @Override public OAuthProvider oAuthProvider() { diff --git a/src/main/java/coffeemeet/server/oauth/implement/provider/AuthCodeRequestUrlProviderComposite.java b/src/main/java/coffeemeet/server/oauth/implement/provider/AuthCodeRequestUrlProviderRegistry.java similarity index 90% rename from src/main/java/coffeemeet/server/oauth/implement/provider/AuthCodeRequestUrlProviderComposite.java rename to src/main/java/coffeemeet/server/oauth/implement/provider/AuthCodeRequestUrlProviderRegistry.java index b65f9722..89ee0d37 100644 --- a/src/main/java/coffeemeet/server/oauth/implement/provider/AuthCodeRequestUrlProviderComposite.java +++ b/src/main/java/coffeemeet/server/oauth/implement/provider/AuthCodeRequestUrlProviderRegistry.java @@ -12,12 +12,12 @@ import org.springframework.stereotype.Component; @Component -public class AuthCodeRequestUrlProviderComposite { +public class AuthCodeRequestUrlProviderRegistry { private static final String INVALID_LOGIN_TYPE_MESSAGE = "로그인 타입(%s)에 일치하는 타입이 없습니다."; private final Map mapping; - public AuthCodeRequestUrlProviderComposite(Set providers) { + public AuthCodeRequestUrlProviderRegistry(Set providers) { this.mapping = providers.stream().collect( Collectors.toUnmodifiableMap( AuthCodeRequestUrlProvider::oAuthProvider, diff --git a/src/main/java/coffeemeet/server/oauth/infrastructure/OAuthUnlinkDetail.java b/src/main/java/coffeemeet/server/oauth/infrastructure/OAuthUnlinkDetail.java new file mode 100644 index 00000000..90f6f802 --- /dev/null +++ b/src/main/java/coffeemeet/server/oauth/infrastructure/OAuthUnlinkDetail.java @@ -0,0 +1,9 @@ +package coffeemeet.server.oauth.infrastructure; + +public record OAuthUnlinkDetail( + Long id, + String accessToken, + String result +) { + +} diff --git a/src/main/java/coffeemeet/server/oauth/infrastructure/kakao/KakaoClient.java b/src/main/java/coffeemeet/server/oauth/infrastructure/kakao/KakaoFetchClient.java similarity index 98% rename from src/main/java/coffeemeet/server/oauth/infrastructure/kakao/KakaoClient.java rename to src/main/java/coffeemeet/server/oauth/infrastructure/kakao/KakaoFetchClient.java index c34a91e6..942a6b61 100644 --- a/src/main/java/coffeemeet/server/oauth/infrastructure/kakao/KakaoClient.java +++ b/src/main/java/coffeemeet/server/oauth/infrastructure/kakao/KakaoFetchClient.java @@ -24,7 +24,7 @@ @Component @RequiredArgsConstructor -public class KakaoClient { +public class KakaoFetchClient { private static final String REQUEST_TOKEN_URL = "https://kauth.kakao.com/oauth/token"; private static final String REQUEST_INFO_URL = "https://kapi.kakao.com/v2/user/me"; @@ -58,7 +58,6 @@ public KakaoMemberDetail fetchMember(String accessToken) { httpHeaders.set(AUTHORIZATION, BEARER_TYPE + accessToken); HttpEntity request = new HttpEntity<>(httpHeaders); - KakaoMemberDetail response = restTemplate.exchange(REQUEST_INFO_URL, HttpMethod.GET, request, KakaoMemberDetail.class).getBody(); diff --git a/src/main/java/coffeemeet/server/oauth/infrastructure/kakao/KakaoUnlinkClient.java b/src/main/java/coffeemeet/server/oauth/infrastructure/kakao/KakaoUnlinkClient.java new file mode 100644 index 00000000..30b6f10d --- /dev/null +++ b/src/main/java/coffeemeet/server/oauth/infrastructure/kakao/KakaoUnlinkClient.java @@ -0,0 +1,46 @@ +package coffeemeet.server.oauth.infrastructure.kakao; + +import static coffeemeet.server.oauth.utils.constant.OAuthConstant.AUTHORIZATION; +import static coffeemeet.server.oauth.utils.constant.OAuthConstant.BEARER_TYPE; + +import coffeemeet.server.oauth.implement.client.OAuthUnlinkClient; +import coffeemeet.server.oauth.infrastructure.OAuthUnlinkDetail; +import coffeemeet.server.oauth.infrastructure.kakao.dto.KakaoUnlinkDetail; +import coffeemeet.server.user.domain.OAuthProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import org.springframework.http.HttpMethod; +import org.springframework.web.client.RestTemplate; + +@Component +@RequiredArgsConstructor +public class KakaoUnlinkClient implements OAuthUnlinkClient { + + private static final String UNLINK_USER_URL = "https://kapi.kakao.com/v1/user/unlink"; + + private final RestTemplate restTemplate; + + @Override + public OAuthProvider oAuthProvider() { + return OAuthProvider.KAKAO; + } + + public OAuthUnlinkDetail unlink(String accessToken) { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + httpHeaders.set(AUTHORIZATION, BEARER_TYPE + accessToken); + + HttpEntity request = new HttpEntity<>(httpHeaders); + + KakaoUnlinkDetail response = restTemplate.exchange(UNLINK_USER_URL, HttpMethod.POST, request, + KakaoUnlinkDetail.class).getBody(); + + assert response != null; + return response.toOAuthUnlinkDetail(); + } + +} diff --git a/src/main/java/coffeemeet/server/oauth/infrastructure/kakao/dto/KakaoUnlinkDetail.java b/src/main/java/coffeemeet/server/oauth/infrastructure/kakao/dto/KakaoUnlinkDetail.java new file mode 100644 index 00000000..e1713d3f --- /dev/null +++ b/src/main/java/coffeemeet/server/oauth/infrastructure/kakao/dto/KakaoUnlinkDetail.java @@ -0,0 +1,11 @@ +package coffeemeet.server.oauth.infrastructure.kakao.dto; + +import coffeemeet.server.oauth.infrastructure.OAuthUnlinkDetail; + +public record KakaoUnlinkDetail(Long userId) { + + public OAuthUnlinkDetail toOAuthUnlinkDetail() { + return new OAuthUnlinkDetail(this.userId(), null, null); + } + +} diff --git a/src/main/java/coffeemeet/server/oauth/infrastructure/naver/NaverClient.java b/src/main/java/coffeemeet/server/oauth/infrastructure/naver/NaverFetchClient.java similarity index 98% rename from src/main/java/coffeemeet/server/oauth/infrastructure/naver/NaverClient.java rename to src/main/java/coffeemeet/server/oauth/infrastructure/naver/NaverFetchClient.java index 4a0dc9a6..3b434977 100644 --- a/src/main/java/coffeemeet/server/oauth/infrastructure/naver/NaverClient.java +++ b/src/main/java/coffeemeet/server/oauth/infrastructure/naver/NaverFetchClient.java @@ -27,7 +27,7 @@ @Component @RequiredArgsConstructor -public class NaverClient { +public class NaverFetchClient { private static final String REQUEST_TOKEN_URL = "https://nid.naver.com/oauth2.0/token"; private static final String REQUEST_INFO_URL = "https://openapi.naver.com/v1/nid/me"; diff --git a/src/main/java/coffeemeet/server/oauth/infrastructure/naver/NaverUnlinkClient.java b/src/main/java/coffeemeet/server/oauth/infrastructure/naver/NaverUnlinkClient.java new file mode 100644 index 00000000..7a2f7827 --- /dev/null +++ b/src/main/java/coffeemeet/server/oauth/infrastructure/naver/NaverUnlinkClient.java @@ -0,0 +1,55 @@ +package coffeemeet.server.oauth.infrastructure.naver; + +import static coffeemeet.server.oauth.utils.constant.OAuthConstant.AUTHORIZATION; +import static coffeemeet.server.oauth.utils.constant.OAuthConstant.AUTHORIZATION_CODE; +import static coffeemeet.server.oauth.utils.constant.OAuthConstant.BEARER_TYPE; +import static coffeemeet.server.oauth.utils.constant.OAuthConstant.CLIENT_ID; +import static coffeemeet.server.oauth.utils.constant.OAuthConstant.CLIENT_SECRET; +import static coffeemeet.server.oauth.utils.constant.OAuthConstant.GRANT_TYPE; + +import coffeemeet.server.oauth.config.naver.NaverProperties; +import coffeemeet.server.oauth.implement.client.OAuthUnlinkClient; +import coffeemeet.server.oauth.infrastructure.OAuthUnlinkDetail; +import coffeemeet.server.oauth.infrastructure.naver.dto.NaverUnlinkDetail; +import coffeemeet.server.user.domain.OAuthProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +@RequiredArgsConstructor +public class NaverUnlinkClient implements OAuthUnlinkClient { + + private static final String UNLINK_USER_URL = "https://nid.naver.com/oauth2.0/token"; + + private final RestTemplate restTemplate; + private final NaverProperties naverProperties; + + @Override + public OAuthProvider oAuthProvider() { + return OAuthProvider.NAVER; + } + + public OAuthUnlinkDetail unlink(String accessToken) { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + httpHeaders.set(AUTHORIZATION, BEARER_TYPE + accessToken); + httpHeaders.set(CLIENT_ID, naverProperties.getClientId()); + httpHeaders.set(CLIENT_SECRET, naverProperties.getClientSecret()); + httpHeaders.set(GRANT_TYPE, AUTHORIZATION_CODE); + + HttpEntity request = new HttpEntity<>(httpHeaders); + + NaverUnlinkDetail response = restTemplate.exchange(UNLINK_USER_URL, HttpMethod.POST, request, + NaverUnlinkDetail.class).getBody(); + + assert response != null; + return response.toOAuthUnlinkDetail(); + } + +} diff --git a/src/main/java/coffeemeet/server/oauth/infrastructure/naver/dto/NaverUnlinkDetail.java b/src/main/java/coffeemeet/server/oauth/infrastructure/naver/dto/NaverUnlinkDetail.java new file mode 100644 index 00000000..896ebc7b --- /dev/null +++ b/src/main/java/coffeemeet/server/oauth/infrastructure/naver/dto/NaverUnlinkDetail.java @@ -0,0 +1,14 @@ +package coffeemeet.server.oauth.infrastructure.naver.dto; + +import coffeemeet.server.oauth.infrastructure.OAuthUnlinkDetail; + +public record NaverUnlinkDetail( + String accessToken, + String result +) { + + public OAuthUnlinkDetail toOAuthUnlinkDetail() { + return new OAuthUnlinkDetail(null, this.accessToken, this.result); + } + +} diff --git a/src/main/java/coffeemeet/server/oauth/presentation/OAuthController.java b/src/main/java/coffeemeet/server/oauth/presentation/OAuthController.java index 86907a9e..cc2d9edb 100644 --- a/src/main/java/coffeemeet/server/oauth/presentation/OAuthController.java +++ b/src/main/java/coffeemeet/server/oauth/presentation/OAuthController.java @@ -1,5 +1,7 @@ package coffeemeet.server.oauth.presentation; +import coffeemeet.server.common.annotation.Login; +import coffeemeet.server.common.domain.AuthInfo; import coffeemeet.server.oauth.service.OAuthService; import coffeemeet.server.user.domain.OAuthProvider; import jakarta.servlet.http.HttpServletResponse; @@ -9,6 +11,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -27,4 +30,10 @@ public ResponseEntity redirectAuthCodeRequestUrl(@PathVariable OAuthProvid return new ResponseEntity<>(HttpStatus.FOUND); } + @PostMapping("/delete") + public ResponseEntity unlink(@Login AuthInfo authInfo, OAuthProvider oAuthProvider) { + oAuthService.unlink(authInfo.userId(), authInfo.refreshToken(), oAuthProvider); + return ResponseEntity.ok().build(); + } + } diff --git a/src/main/java/coffeemeet/server/oauth/service/OAuthService.java b/src/main/java/coffeemeet/server/oauth/service/OAuthService.java index 21cd2d33..ba3a615e 100644 --- a/src/main/java/coffeemeet/server/oauth/service/OAuthService.java +++ b/src/main/java/coffeemeet/server/oauth/service/OAuthService.java @@ -1,7 +1,11 @@ package coffeemeet.server.oauth.service; -import coffeemeet.server.oauth.implement.provider.AuthCodeRequestUrlProviderComposite; +import coffeemeet.server.auth.implement.RefreshTokenCommand; +import coffeemeet.server.oauth.implement.client.OAuthMemberUnlinkRegistry; +import coffeemeet.server.oauth.implement.provider.AuthCodeRequestUrlProviderRegistry; import coffeemeet.server.user.domain.OAuthProvider; +import coffeemeet.server.user.implement.UserCommand; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -9,10 +13,20 @@ @RequiredArgsConstructor public class OAuthService { - private final AuthCodeRequestUrlProviderComposite authCodeRequestUrlProviderComposite; + private final AuthCodeRequestUrlProviderRegistry authCodeRequestUrlProviderRegistry; + private final OAuthMemberUnlinkRegistry oAuthMemberUnlinkRegistry; + private final UserCommand userCommand; + private final RefreshTokenCommand refreshTokenCommand; public String getAuthCodeRequestUrl(OAuthProvider oAuthProvider) { - return authCodeRequestUrlProviderComposite.provide(oAuthProvider); + return authCodeRequestUrlProviderRegistry.provide(oAuthProvider); + } + + @Transactional + public void unlink(Long userId, String accessToken, OAuthProvider oAuthProvider) { + oAuthMemberUnlinkRegistry.unlink(oAuthProvider, accessToken); + userCommand.deleteUser(userId); + refreshTokenCommand.deleteRefreshToken(userId); } } diff --git a/src/main/java/coffeemeet/server/user/domain/User.java b/src/main/java/coffeemeet/server/user/domain/User.java index ea2a4639..69efe717 100644 --- a/src/main/java/coffeemeet/server/user/domain/User.java +++ b/src/main/java/coffeemeet/server/user/domain/User.java @@ -22,6 +22,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import java.time.LocalDateTime; import java.util.Objects; import lombok.AccessLevel; import lombok.Getter; @@ -63,9 +64,11 @@ public class User extends AdvancedBaseEntity { @Enumerated(EnumType.STRING) private UserStatus userStatus; + @Column(nullable = false) private boolean isDeleted; - private boolean isBlacklisted; + @Column(name = "updated_at") + private LocalDateTime updatedAt; @Column(nullable = false) private boolean isRegistered; @@ -79,7 +82,7 @@ public void registerUser(@NonNull Profile profile) { this.reportInfo = new ReportInfo(); this.userStatus = IDLE; this.isDeleted = false; - this.isBlacklisted = false; + this.updatedAt = null; this.isRegistered = true; } @@ -137,8 +140,18 @@ public void matching() { this.userStatus = MATCHING; } - public void convertToBlacklist() { - this.isBlacklisted = true; + public void delete() { + this.isDeleted = true; + this.updatedAt = LocalDateTime.now(); + } + + public boolean leave() { + return this.updatedAt.isBefore(LocalDateTime.now()); + } + + public void deletedWithdraw() { + this.isDeleted = false; + this.updatedAt = null; } @Override diff --git a/src/main/java/coffeemeet/server/user/implement/UserCommand.java b/src/main/java/coffeemeet/server/user/implement/UserCommand.java index 45b52e9c..6beaa028 100644 --- a/src/main/java/coffeemeet/server/user/implement/UserCommand.java +++ b/src/main/java/coffeemeet/server/user/implement/UserCommand.java @@ -30,6 +30,11 @@ public void updateUser(User user) { } public void deleteUser(Long userId) { + User user = userQuery.getUserById(userId); + user.delete(); + } + + public void deleteUserInfo(Long userId) { interestRepository.deleteById(userId); userRepository.deleteById(userId); } diff --git a/src/main/java/coffeemeet/server/user/implement/UserQuery.java b/src/main/java/coffeemeet/server/user/implement/UserQuery.java index e6d92527..0ffb8bfb 100644 --- a/src/main/java/coffeemeet/server/user/implement/UserQuery.java +++ b/src/main/java/coffeemeet/server/user/implement/UserQuery.java @@ -91,4 +91,8 @@ public List getUsersByRoom(ChattingRoom room) { return userRepository.findAllByChattingRoom(room); } + public List getDeletedUsers() { + return userRepository.findDeletedUsers(); + } + } diff --git a/src/main/java/coffeemeet/server/user/infrastructure/UserRepository.java b/src/main/java/coffeemeet/server/user/infrastructure/UserRepository.java index 9a715ca8..bcffac81 100644 --- a/src/main/java/coffeemeet/server/user/infrastructure/UserRepository.java +++ b/src/main/java/coffeemeet/server/user/infrastructure/UserRepository.java @@ -25,4 +25,7 @@ public interface UserRepository extends JpaRepository { List findAllByChattingRoom(ChattingRoom chattingRoom); + @Query(value = "select * from users where is_deleted = true", nativeQuery = true) + List findDeletedUsers(); + } diff --git a/src/main/java/coffeemeet/server/user/presentation/UserController.java b/src/main/java/coffeemeet/server/user/presentation/UserController.java index f43c6a16..7ecba82b 100644 --- a/src/main/java/coffeemeet/server/user/presentation/UserController.java +++ b/src/main/java/coffeemeet/server/user/presentation/UserController.java @@ -12,6 +12,7 @@ import coffeemeet.server.user.presentation.dto.UpdateProfileHTTP; import coffeemeet.server.user.presentation.dto.UserProfileHTTP; import coffeemeet.server.user.presentation.dto.UserStatusHTTP; +import coffeemeet.server.user.service.UserProfileService; import coffeemeet.server.user.service.UserService; import coffeemeet.server.user.service.dto.LoginDetailsDto; import coffeemeet.server.user.service.dto.MyProfileDto; @@ -43,6 +44,7 @@ public class UserController { private final UserService userService; + private final UserProfileService userProfileService; @PostMapping("/sign-up") public ResponseEntity signup(@Valid @RequestBody SignupHTTP.Request request) { @@ -59,13 +61,13 @@ public ResponseEntity login(@PathVariable OAuthProvid @GetMapping("/{userId}") public ResponseEntity getUserProfile(@PathVariable Long userId) { - UserProfileDto response = userService.findUserProfile(userId); + UserProfileDto response = userProfileService.findUserProfile(userId); return ResponseEntity.ok(UserProfileHTTP.Response.of(response)); } @GetMapping("/me") public ResponseEntity getMyProfile(@Login AuthInfo authInfo) { - MyProfileDto response = userService.findMyProfile(authInfo.userId()); + MyProfileDto response = userProfileService.findMyProfile(authInfo.userId()); return ResponseEntity.ok(MyProfileHTTP.Response.of(response)); } @@ -82,7 +84,7 @@ public ResponseEntity updateProfileImage( @Login AuthInfo authInfo, @RequestPart("profileImage") @NotNull MultipartFile profileImage) { - userService.updateProfileImage( + userProfileService.updateProfileImage( authInfo.userId(), FileUtils.convertMultipartFileToFile(profileImage)); return ResponseEntity.ok().build(); @@ -91,7 +93,7 @@ public ResponseEntity updateProfileImage( @PatchMapping("/me") public ResponseEntity updateProfileInfo(@Login AuthInfo authInfo, @Valid @RequestBody UpdateProfileHTTP.Request request) { - userService.updateProfileInfo(authInfo.userId(), request.nickname(), request.interests()); + userProfileService.updateProfileInfo(authInfo.userId(), request.nickname(), request.interests()); return ResponseEntity.ok().build(); } diff --git a/src/main/java/coffeemeet/server/user/service/UserProfileService.java b/src/main/java/coffeemeet/server/user/service/UserProfileService.java new file mode 100644 index 00000000..703efa3d --- /dev/null +++ b/src/main/java/coffeemeet/server/user/service/UserProfileService.java @@ -0,0 +1,81 @@ +package coffeemeet.server.user.service; + +import static coffeemeet.server.common.domain.S3KeyPrefix.PROFILE_IMAGE; +import static coffeemeet.server.oauth.utils.constant.OAuthConstant.DEFAULT_IMAGE_URL; + +import coffeemeet.server.certification.domain.Certification; +import coffeemeet.server.certification.implement.CertificationQuery; +import coffeemeet.server.common.infrastructure.S3ObjectStorage; +import coffeemeet.server.user.domain.Keyword; +import coffeemeet.server.user.domain.User; +import coffeemeet.server.user.implement.InterestCommand; +import coffeemeet.server.user.implement.InterestQuery; +import coffeemeet.server.user.implement.UserCommand; +import coffeemeet.server.user.implement.UserQuery; +import coffeemeet.server.user.service.dto.MyProfileDto; +import coffeemeet.server.user.service.dto.UserProfileDto; +import jakarta.transaction.Transactional; +import java.io.File; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class UserProfileService { + + private final UserQuery userQuery; + private final InterestQuery interestQuery; + private final CertificationQuery certificationQuery; + private final S3ObjectStorage objectStorage; + private final UserCommand userCommand; + private final InterestCommand interestCommand; + + public UserProfileDto findUserProfile(Long userId) { + User user = userQuery.getUserById(userId); + List keywords = interestQuery.getKeywordsByUserId(userId); + Certification certification = certificationQuery.getCertificationByUserId(userId); + return UserProfileDto.of(user, keywords, certification); + } + + public MyProfileDto findMyProfile(Long userId) { + User user = userQuery.getUserById(userId); + List keywords = interestQuery.getKeywordsByUserId(userId); + Certification certification = certificationQuery.getCertificationByUserId(userId); + return MyProfileDto.of(user, keywords, certification); + } + + public void updateProfileImage(Long userId, File file) { + User user = userQuery.getUserById(userId); + deleteCurrentProfileImage(user.getOauthInfo().getProfileImageUrl()); + + String key = objectStorage.generateKey(PROFILE_IMAGE); + objectStorage.upload(key, file); + user.updateProfileImageUrl(objectStorage.getUrl(key)); + userCommand.updateUser(user); + } + + @Transactional + public void updateProfileInfo(Long userId, String nickname, + List keywords) { + User user = userQuery.getUserById(userId); + if (nickname != null) { + userCommand.updateUserInfo(user, nickname); + } + if (keywords != null && !keywords.isEmpty()) { + interestCommand.updateInterests(user, keywords); + } + } + + private void deleteCurrentProfileImage(String profileImageUrl) { + if (!profileImageUrl.equals(DEFAULT_IMAGE_URL)) { + String currentKey = objectStorage.extractKey(profileImageUrl, + PROFILE_IMAGE); + if (currentKey.isBlank()) { + return; + } + objectStorage.delete(currentKey); + } + } + +} diff --git a/src/main/java/coffeemeet/server/user/service/UserService.java b/src/main/java/coffeemeet/server/user/service/UserService.java index d24bbdf7..377c6d18 100644 --- a/src/main/java/coffeemeet/server/user/service/UserService.java +++ b/src/main/java/coffeemeet/server/user/service/UserService.java @@ -1,18 +1,16 @@ package coffeemeet.server.user.service; -import static coffeemeet.server.common.domain.S3KeyPrefix.PROFILE_IMAGE; import static coffeemeet.server.common.execption.GlobalErrorCode.BAD_REQUEST_ERROR; -import static coffeemeet.server.oauth.utils.constant.OAuthConstant.DEFAULT_IMAGE_URL; import coffeemeet.server.auth.domain.AuthTokens; import coffeemeet.server.auth.domain.AuthTokensGenerator; import coffeemeet.server.certification.domain.Certification; import coffeemeet.server.certification.implement.CertificationQuery; -import coffeemeet.server.common.domain.ObjectStorage; import coffeemeet.server.common.execption.BadRequestException; import coffeemeet.server.matching.implement.MatchingQueueCommand; import coffeemeet.server.oauth.domain.OAuthMemberDetail; -import coffeemeet.server.oauth.implement.client.OAuthMemberClientComposite; +import coffeemeet.server.oauth.implement.client.OAuthMemberClientRegistry; +import coffeemeet.server.oauth.implement.client.OAuthMemberUnlinkRegistry; import coffeemeet.server.user.domain.Email; import coffeemeet.server.user.domain.Keyword; import coffeemeet.server.user.domain.OAuthInfo; @@ -25,12 +23,8 @@ import coffeemeet.server.user.implement.UserCommand; import coffeemeet.server.user.implement.UserQuery; import coffeemeet.server.user.service.dto.LoginDetailsDto; -import coffeemeet.server.user.service.dto.MyProfileDto; -import coffeemeet.server.user.service.dto.UserProfileDto; import coffeemeet.server.user.service.dto.UserStatusDto; -import java.io.File; import java.time.LocalDateTime; -import java.util.Collections; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -41,8 +35,8 @@ public class UserService { private static final String INVALID_REQUEST_MESSAGE = "사용자 상태에 맞지 않는 요청입니다."; - private final ObjectStorage objectStorage; - private final OAuthMemberClientComposite oAuthMemberClientComposite; + + private final OAuthMemberClientRegistry oAuthMemberClientRegistry; private final CertificationQuery certificationQuery; private final AuthTokensGenerator authTokensGenerator; @@ -62,64 +56,46 @@ public void signup(Long userId, String nickname, List keywords) { } public LoginDetailsDto login(OAuthProvider oAuthProvider, String authCode) { - OAuthMemberDetail memberDetail = oAuthMemberClientComposite.fetch(oAuthProvider, authCode); + OAuthMemberDetail memberDetail = oAuthMemberClientRegistry.fetch(oAuthProvider, authCode); OAuthInfo oauthInfo = new OAuthInfo(memberDetail.oAuthProvider(), memberDetail.oAuthProviderId(), new Email(memberDetail.email()), memberDetail.profileImage()); User user = userQuery.getUserByOAuthInfoOrDefault(oauthInfo); + if (user.isRegistered()) { // TODO: 12/21/23 회원가입 중간에 나갈 때 예외 터지는 오류 잡기 - List interests = interestQuery.getKeywordsByUserId(user.getId()); - Certification certification = certificationQuery.getCertificationByUserId(user.getId()); - AuthTokens authTokens = authTokensGenerator.generate(user.getId()); - return LoginDetailsDto.of(user, interests, certification, authTokens); + if (user.leave()) { + // 연결 끊기 + userCommand.deleteUserInfo(user.getId()); + return LoginDetailsDto.of(null, null, null, null); + } else { + user.deletedWithdraw(); + AuthTokens authTokens = authTokensGenerator.generate(user.getId()); + return LoginDetailsDto.of(user, null, null, authTokens); + } } - userCommand.saveUser(user); - return LoginDetailsDto.of(user, Collections.emptyList(), null, null); - } - - public UserProfileDto findUserProfile(Long userId) { - User user = userQuery.getUserById(userId); - List keywords = interestQuery.getKeywordsByUserId(userId); + Long userId = userCommand.saveUser(user); + List interests = interestQuery.getKeywordsByUserId(userId); Certification certification = certificationQuery.getCertificationByUserId(userId); - return UserProfileDto.of(user, keywords, certification); - } - - public MyProfileDto findMyProfile(Long userId) { - User user = userQuery.getUserById(userId); - List keywords = interestQuery.getKeywordsByUserId(userId); - Certification certification = certificationQuery.getCertificationByUserId(userId); - return MyProfileDto.of(user, keywords, certification); - } - - public void updateProfileImage(Long userId, File file) { - User user = userQuery.getUserById(userId); - deleteCurrentProfileImage(user.getOauthInfo().getProfileImageUrl()); - - String key = objectStorage.generateKey(PROFILE_IMAGE); - objectStorage.upload(key, file); - user.updateProfileImageUrl(objectStorage.getUrl(key)); - userCommand.updateUser(user); - } - - @Transactional - public void updateProfileInfo(Long userId, String nickname, - List keywords) { - User user = userQuery.getUserById(userId); - if (nickname != null) { - userCommand.updateUserInfo(user, nickname); - } - if (keywords != null && !keywords.isEmpty()) { - interestCommand.updateInterests(user, keywords); - } + AuthTokens authTokens = authTokensGenerator.generate(userId); + return LoginDetailsDto.of(user, interests, certification, authTokens); } public void checkDuplicatedNickname(String nickname) { userQuery.hasDuplicatedNickname(nickname); } - public void deleteUser(Long userId) { - userCommand.deleteUser(userId); + public void deleteUserInfos() { + List users = userQuery.getDeletedUsers(); + LocalDateTime today = LocalDateTime.now(); + + List deletedUsers = users.stream() + .filter(user -> user.getUpdatedAt() != null) + .filter( + user -> user.getUpdatedAt().isAfter(today) || user.getUpdatedAt() + .isEqual(today)) + .toList(); + deletedUsers.forEach(u -> userCommand.deleteUserInfo(u.getId())); } public void registerOrUpdateNotificationToken(Long useId, String token) { @@ -158,7 +134,8 @@ private UserStatusDto handleMatchingUser(Long userId, Certification certificatio private UserStatusDto handleChattingUser(User user) { Long chattingRoomId = user.getChattingRoom().getId(); String chattingRoomName = user.getChattingRoom().getName(); - return UserStatusDto.of(UserStatus.CHATTING_UNCONNECTED, null, chattingRoomId, chattingRoomName, + return UserStatusDto.of(UserStatus.CHATTING_UNCONNECTED, null, chattingRoomId, + chattingRoomName, null, null); } @@ -167,15 +144,4 @@ private UserStatusDto handleReportedUser(User user) { return UserStatusDto.of(UserStatus.REPORTED, null, null, null, null, penaltyExpiration); } - private void deleteCurrentProfileImage(String profileImageUrl) { - if (!profileImageUrl.equals(DEFAULT_IMAGE_URL)) { - String currentKey = objectStorage.extractKey(profileImageUrl, - PROFILE_IMAGE); - if (currentKey.isBlank()) { - return; - } - objectStorage.delete(currentKey); - } - } - } diff --git a/src/test/java/coffeemeet/server/auth/presentation/AuthControllerTest.java b/src/test/java/coffeemeet/server/auth/presentation/AuthControllerTest.java index 1f7e531c..73c10f8e 100644 --- a/src/test/java/coffeemeet/server/auth/presentation/AuthControllerTest.java +++ b/src/test/java/coffeemeet/server/auth/presentation/AuthControllerTest.java @@ -94,29 +94,4 @@ void logoutTest() throws Exception { .andExpect(status().isOk()); } - @DisplayName("사용자는 회원탈퇴 할 수 있다.") - @Test - void deleteTest() throws Exception { - // given - RefreshToken refreshToken = AuthFixture.refreshToken(); - willDoNothing().given(authService).delete(anyLong()); - given(refreshTokenQuery.getRefreshToken(anyLong())).willReturn(refreshToken); - - // when, then - mockMvc.perform(post("/api/v1/auth/delete") - .header("Authorization", TOKEN) - .contentType(MediaType.APPLICATION_JSON) - ) - .andDo(document("auth-delete", - resourceDetails().tag("인증").description("회원탈퇴"), - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - requestHeaders( - headerWithName("Authorization").description("토큰") - ) - ) - ) - .andExpect(status().isOk()); - } - } diff --git a/src/test/java/coffeemeet/server/auth/service/AuthServiceTest.java b/src/test/java/coffeemeet/server/auth/service/AuthServiceTest.java index 14ba36c1..62e573a1 100644 --- a/src/test/java/coffeemeet/server/auth/service/AuthServiceTest.java +++ b/src/test/java/coffeemeet/server/auth/service/AuthServiceTest.java @@ -15,6 +15,7 @@ import coffeemeet.server.auth.implement.RefreshTokenCommand; import coffeemeet.server.common.execption.InvalidAuthException; import coffeemeet.server.common.fixture.AuthFixture; +import coffeemeet.server.oauth.service.OAuthService; import coffeemeet.server.user.service.UserService; import org.instancio.Instancio; import org.junit.jupiter.api.DisplayName; @@ -32,6 +33,9 @@ class AuthServiceTest { @InjectMocks private AuthService authService; + @InjectMocks + private OAuthService oAuthService; + @Mock private UserService userService; @@ -89,17 +93,4 @@ void logoutTest() { .doesNotThrowAnyException(); } - @DisplayName("회원 탈퇴 시킬 수 있다.") - @Test - void deleteTest() { - // given - Long userId = Instancio.create(Long.class); - willDoNothing().given(refreshTokenCommand).deleteRefreshToken(anyLong()); - willDoNothing().given(userService).deleteUser(anyLong()); - - // when, then - assertThatCode(() -> authService.delete(userId)) - .doesNotThrowAnyException(); - } - } diff --git a/src/test/java/coffeemeet/server/oauth/authcode/AuthCodeRequestUrlProviderCompositeTest.java b/src/test/java/coffeemeet/server/oauth/authcode/AuthCodeRequestUrlProviderRegistryTest.java similarity index 88% rename from src/test/java/coffeemeet/server/oauth/authcode/AuthCodeRequestUrlProviderCompositeTest.java rename to src/test/java/coffeemeet/server/oauth/authcode/AuthCodeRequestUrlProviderRegistryTest.java index 2ef7e037..b7af8f6f 100644 --- a/src/test/java/coffeemeet/server/oauth/authcode/AuthCodeRequestUrlProviderCompositeTest.java +++ b/src/test/java/coffeemeet/server/oauth/authcode/AuthCodeRequestUrlProviderRegistryTest.java @@ -4,7 +4,7 @@ import static org.mockito.BDDMockito.given; import coffeemeet.server.oauth.implement.provider.AuthCodeRequestUrlProvider; -import coffeemeet.server.oauth.implement.provider.AuthCodeRequestUrlProviderComposite; +import coffeemeet.server.oauth.implement.provider.AuthCodeRequestUrlProviderRegistry; import coffeemeet.server.user.domain.OAuthProvider; import java.util.Collections; import java.util.HashSet; @@ -17,7 +17,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class AuthCodeRequestUrlProviderCompositeTest { +class AuthCodeRequestUrlProviderRegistryTest { @Mock private AuthCodeRequestUrlProvider provider; @@ -26,7 +26,7 @@ class AuthCodeRequestUrlProviderCompositeTest { private Set providers; @InjectMocks - private AuthCodeRequestUrlProviderComposite composite; + private AuthCodeRequestUrlProviderRegistry composite; @DisplayName("sns 의 redirect url 을 제공할 수 있다.") @Test @@ -39,7 +39,7 @@ void provideTest() { given(provider.provide()).willReturn(resultUrl); providers = new HashSet<>(Collections.singletonList(provider)); - composite = new AuthCodeRequestUrlProviderComposite(providers); + composite = new AuthCodeRequestUrlProviderRegistry(providers); // when String expectedUrl = composite.provide(oAuthProvider); diff --git a/src/test/java/coffeemeet/server/oauth/client/OAuthMemberClientCompositeTest.java b/src/test/java/coffeemeet/server/oauth/client/OAuthMemberClientCompositeTest.java index 9f1c2ac8..956093c1 100644 --- a/src/test/java/coffeemeet/server/oauth/client/OAuthMemberClientCompositeTest.java +++ b/src/test/java/coffeemeet/server/oauth/client/OAuthMemberClientCompositeTest.java @@ -6,7 +6,7 @@ import coffeemeet.server.common.fixture.OauthFixture; import coffeemeet.server.oauth.domain.OAuthMemberDetail; import coffeemeet.server.oauth.implement.client.OAuthMemberClient; -import coffeemeet.server.oauth.implement.client.OAuthMemberClientComposite; +import coffeemeet.server.oauth.implement.client.OAuthMemberClientRegistry; import coffeemeet.server.user.domain.OAuthProvider; import java.util.Collections; import java.util.HashSet; @@ -28,7 +28,7 @@ class OAuthMemberClientCompositeTest { private Set clients; @InjectMocks - private OAuthMemberClientComposite composite; + private OAuthMemberClientRegistry oAuthMemberClientRegistry; @DisplayName("sns 로부터 사용자 정보를 가져올 수 있다.") @Test @@ -42,10 +42,10 @@ void fetchTest() { given(client.fetch(authCode)).willReturn(response); clients = new HashSet<>(Collections.singletonList(client)); - composite = new OAuthMemberClientComposite(clients); + oAuthMemberClientRegistry = new OAuthMemberClientRegistry(clients); // when - OAuthMemberDetail expectedResponse = composite.fetch(oAuthProvider, authCode); + OAuthMemberDetail expectedResponse = oAuthMemberClientRegistry.fetch(oAuthProvider, authCode); // then assertThat(response).isEqualTo(expectedResponse); diff --git a/src/test/java/coffeemeet/server/oauth/implement/client/kakao/KakaoMemberClientTest.java b/src/test/java/coffeemeet/server/oauth/implement/client/kakao/KakaoMemberClientTest.java index 6bf403cb..4161cb45 100644 --- a/src/test/java/coffeemeet/server/oauth/implement/client/kakao/KakaoMemberClientTest.java +++ b/src/test/java/coffeemeet/server/oauth/implement/client/kakao/KakaoMemberClientTest.java @@ -5,7 +5,7 @@ import static org.mockito.BDDMockito.given; import coffeemeet.server.common.fixture.OauthFixture; -import coffeemeet.server.oauth.infrastructure.kakao.KakaoClient; +import coffeemeet.server.oauth.infrastructure.kakao.KakaoFetchClient; import coffeemeet.server.oauth.infrastructure.kakao.dto.KakaoMemberDetail; import coffeemeet.server.oauth.infrastructure.kakao.dto.KakaoTokens; import coffeemeet.server.user.domain.OAuthProvider; @@ -23,7 +23,7 @@ class KakaoMemberClientTest { KakaoMemberClient kakaoMemberClient; @Mock - KakaoClient kakaoClient; + KakaoFetchClient kakaoClient; @DisplayName("카카오 프로바이더를 가져올 수 있다.") @Test diff --git a/src/test/java/coffeemeet/server/oauth/implement/client/naver/NaverMemberClientTest.java b/src/test/java/coffeemeet/server/oauth/implement/client/naver/NaverMemberClientTest.java index 49f06f35..30734582 100644 --- a/src/test/java/coffeemeet/server/oauth/implement/client/naver/NaverMemberClientTest.java +++ b/src/test/java/coffeemeet/server/oauth/implement/client/naver/NaverMemberClientTest.java @@ -5,7 +5,7 @@ import static org.mockito.BDDMockito.given; import coffeemeet.server.common.fixture.OauthFixture; -import coffeemeet.server.oauth.infrastructure.naver.NaverClient; +import coffeemeet.server.oauth.infrastructure.naver.NaverFetchClient; import coffeemeet.server.oauth.infrastructure.naver.dto.NaverMemberDetail; import coffeemeet.server.oauth.infrastructure.naver.dto.NaverTokens; import coffeemeet.server.user.domain.OAuthProvider; @@ -23,7 +23,7 @@ class NaverMemberClientTest { private NaverMemberClient naverMemberClient; @Mock - private NaverClient naverClient; + private NaverFetchClient naverClient; @DisplayName("네이버 프로바이더를 가져올 수 있다.") @Test diff --git a/src/test/java/coffeemeet/server/oauth/infrastructure/kakao/KakaoClientTest.java b/src/test/java/coffeemeet/server/oauth/infrastructure/kakao/KakaoClientTest.java index b62043a6..d476a929 100644 --- a/src/test/java/coffeemeet/server/oauth/infrastructure/kakao/KakaoClientTest.java +++ b/src/test/java/coffeemeet/server/oauth/infrastructure/kakao/KakaoClientTest.java @@ -33,7 +33,7 @@ class KakaoClientTest { private KakaoProperties kakaoProperties; @InjectMocks - private KakaoClient kakaoClient; + private KakaoFetchClient kakaoClient; @DisplayName("카카오로부터 인증 토큰을 받을 수 있다.") @Test diff --git a/src/test/java/coffeemeet/server/oauth/infrastructure/naver/NaverClientTest.java b/src/test/java/coffeemeet/server/oauth/infrastructure/naver/NaverClientTest.java index 24cd622c..e0d6549e 100644 --- a/src/test/java/coffeemeet/server/oauth/infrastructure/naver/NaverClientTest.java +++ b/src/test/java/coffeemeet/server/oauth/infrastructure/naver/NaverClientTest.java @@ -34,7 +34,7 @@ class NaverClientTest { private NaverProperties naverProperties; @InjectMocks - private NaverClient naverClient; + private NaverFetchClient naverClient; @DisplayName("네이버로부터 인증 토큰을 받을 수 있다.") @Test diff --git a/src/test/java/coffeemeet/server/oauth/presentation/OAuthControllerTest.java b/src/test/java/coffeemeet/server/oauth/presentation/OAuthControllerTest.java index efae7862..1d47817d 100644 --- a/src/test/java/coffeemeet/server/oauth/presentation/OAuthControllerTest.java +++ b/src/test/java/coffeemeet/server/oauth/presentation/OAuthControllerTest.java @@ -2,10 +2,16 @@ import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document; import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.resourceDetails; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; @@ -14,9 +20,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import coffeemeet.server.auth.domain.RefreshToken; import coffeemeet.server.common.config.ControllerTestConfig; +import coffeemeet.server.common.fixture.AuthFixture; import coffeemeet.server.oauth.service.OAuthService; import coffeemeet.server.user.domain.OAuthProvider; +import coffeemeet.server.user.service.UserService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -29,6 +38,9 @@ class OAuthControllerTest extends ControllerTestConfig { @MockBean private OAuthService oAuthService; + @MockBean + private UserService userService; + @DisplayName("sns 접근 권한 url로 redirect 할 수 있다.") @Test void redirectAuthCodeRequestUrlTest() throws Exception { @@ -59,4 +71,29 @@ void redirectAuthCodeRequestUrlTest() throws Exception { .andExpect(redirectedUrl(expectedRedirectUrl)); } + @DisplayName("사용자는 회원탈퇴 할 수 있다.") + @Test + void deleteTest() throws Exception { + // given + RefreshToken refreshToken = AuthFixture.refreshToken(); + willDoNothing().given(userService).deleteUser(anyLong(), anyString(), any()); + given(refreshTokenQuery.getRefreshToken(anyLong())).willReturn(refreshToken); + + // when, then + mockMvc.perform(post("/api/v1/oauth2.0/delete") + .header("Authorization", TOKEN) + .contentType(MediaType.APPLICATION_JSON) + ) + .andDo(document("auth-delete", + resourceDetails().tag("인증").description("회원탈퇴"), + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName("Authorization").description("토큰") + ) + ) + ) + .andExpect(status().isOk()); + } + } diff --git a/src/test/java/coffeemeet/server/oauth/service/OAuthServiceTest.java b/src/test/java/coffeemeet/server/oauth/service/OAuthServiceTest.java index fcf87897..8686c6bc 100644 --- a/src/test/java/coffeemeet/server/oauth/service/OAuthServiceTest.java +++ b/src/test/java/coffeemeet/server/oauth/service/OAuthServiceTest.java @@ -3,8 +3,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; -import coffeemeet.server.oauth.implement.client.OAuthMemberClientComposite; -import coffeemeet.server.oauth.implement.provider.AuthCodeRequestUrlProviderComposite; +import coffeemeet.server.oauth.implement.client.OAuthMemberClientRegistry; +import coffeemeet.server.oauth.implement.provider.AuthCodeRequestUrlProviderRegistry; import coffeemeet.server.user.domain.OAuthProvider; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,10 +20,10 @@ class OAuthServiceTest { private OAuthService oAuthService; @Mock - private AuthCodeRequestUrlProviderComposite authCodeRequestUrlProviderComposite; + private AuthCodeRequestUrlProviderRegistry authCodeRequestUrlProviderRegistry; @Mock - private OAuthMemberClientComposite oAuthMemberClientComposite; + private OAuthMemberClientRegistry oAuthMemberClientRegistry; @DisplayName("로그인 타입에 맞는 redirect url 을 생성할 수 있다.") @Test @@ -31,7 +31,7 @@ void getAuthCodeRequestUrlTest() { // given String expectedUrl = "https://example.com"; - given(authCodeRequestUrlProviderComposite.provide(OAuthProvider.KAKAO)).willReturn(expectedUrl); + given(authCodeRequestUrlProviderRegistry.provide(OAuthProvider.KAKAO)).willReturn(expectedUrl); // when String result = oAuthService.getAuthCodeRequestUrl(OAuthProvider.KAKAO); diff --git a/src/test/java/coffeemeet/server/user/presentation/UserControllerTest.java b/src/test/java/coffeemeet/server/user/presentation/UserControllerTest.java index cbae9d8b..b064f78d 100644 --- a/src/test/java/coffeemeet/server/user/presentation/UserControllerTest.java +++ b/src/test/java/coffeemeet/server/user/presentation/UserControllerTest.java @@ -44,6 +44,7 @@ import coffeemeet.server.user.presentation.dto.UpdateProfileHTTP; import coffeemeet.server.user.presentation.dto.UserProfileHTTP; import coffeemeet.server.user.presentation.dto.UserStatusHTTP; +import coffeemeet.server.user.service.UserProfileService; import coffeemeet.server.user.service.UserService; import coffeemeet.server.user.service.dto.LoginDetailsDto; import coffeemeet.server.user.service.dto.MyProfileDto; @@ -65,6 +66,9 @@ class UserControllerTest extends ControllerTestConfig { @MockBean private UserService userService; + @MockBean + private UserProfileService userProfileService; + @BeforeEach void setUp() { RefreshToken refreshToken = AuthFixture.refreshToken(); @@ -150,7 +154,7 @@ void findUserProfileTest() throws Exception { UserProfileHTTP.Response expectedResponse = UserFixture.userProfileHTTPResponse( response); - given(userService.findUserProfile(anyLong())).willReturn(response); + given(userProfileService.findUserProfile(anyLong())).willReturn(response); // when, then mockMvc.perform(get("/api/v1/users/{id}", USER_ID) @@ -184,7 +188,7 @@ void findMyProfileTest() throws Exception { MyProfileHTTP.Response expectedResponse = UserFixture.myProfileHTTPResponse(response); given(jwtTokenProvider.extractUserId(TOKEN)).willReturn(USER_ID); - given(userService.findMyProfile(anyLong())).willReturn(response); + given(userProfileService.findMyProfile(anyLong())).willReturn(response); // when, then mockMvc.perform(get("/api/v1/users/me") @@ -249,7 +253,7 @@ void updateProfileInfoTest() throws Exception { given(jwtTokenProvider.extractUserId(TOKEN)).willReturn(USER_ID); willDoNothing().given( - userService).updateProfileInfo(any(), any(), any()); + userProfileService).updateProfileInfo(any(), any(), any()); // when, then mockMvc.perform(patch("/api/v1/users/me") diff --git a/src/test/java/coffeemeet/server/user/service/UserProfileServiceTest.java b/src/test/java/coffeemeet/server/user/service/UserProfileServiceTest.java new file mode 100644 index 00000000..a4d1a542 --- /dev/null +++ b/src/test/java/coffeemeet/server/user/service/UserProfileServiceTest.java @@ -0,0 +1,156 @@ +package coffeemeet.server.user.service; + +import static coffeemeet.server.common.domain.S3KeyPrefix.PROFILE_IMAGE; +import static coffeemeet.server.common.fixture.CertificationFixture.certification; +import static coffeemeet.server.common.fixture.UserFixture.keywords; +import static coffeemeet.server.common.fixture.UserFixture.user; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.Mockito.verify; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; + +import coffeemeet.server.certification.domain.Certification; +import coffeemeet.server.certification.implement.CertificationQuery; +import coffeemeet.server.common.infrastructure.S3ObjectStorage; +import coffeemeet.server.user.domain.Keyword; +import coffeemeet.server.user.domain.User; +import coffeemeet.server.user.implement.InterestCommand; +import coffeemeet.server.user.implement.InterestQuery; +import coffeemeet.server.user.implement.UserCommand; +import coffeemeet.server.user.implement.UserQuery; +import coffeemeet.server.user.service.dto.MyProfileDto; +import coffeemeet.server.user.service.dto.UserProfileDto; +import jakarta.transaction.Transactional; +import java.io.File; +import java.io.IOException; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class UserProfileServiceTest { + + @InjectMocks + private UserProfileService userProfileService; + + @Mock + private S3ObjectStorage objectStorage; + + @Mock + private UserQuery userQuery; + + @Mock + private InterestQuery interestQuery; + + @Mock + private CertificationQuery certificationQuery; + + @Mock + private UserCommand userCommand; + + @Mock + private InterestCommand interestCommand; + + @DisplayName("사용자의 프로필을 조회할 수 있다.") + @Test + void findUserProfileTest() { + // given + User user = user(); + Certification certification = certification(); + List keywords = keywords(); + UserProfileDto response = UserProfileDto.of(user, keywords, certification); + + given(userQuery.getUserById(anyLong())).willReturn(user); + given(interestQuery.getKeywordsByUserId(anyLong())).willReturn(keywords); + given(certificationQuery.getCertificationByUserId(anyLong())).willReturn(certification); + + // when + UserProfileDto result = userProfileService.findUserProfile(user.getId()); + + // then + assertAll( + () -> assertThat(result.nickname()).isEqualTo(response.nickname()), + () -> assertThat(result.department()).isEqualTo(response.department()), + () -> assertThat(result.profileImageUrl()).isEqualTo(response.profileImageUrl()) + ); + } + + @DisplayName("본인의 프로필을 조회할 수 있다.") + @Test + void findMyProfileTest() { + // given + User user = user(); + Certification certification = certification(user); + List keywords = keywords(); + MyProfileDto response = MyProfileDto.of(user, keywords, certification); + + given(userQuery.getUserById(anyLong())).willReturn(user); + given(interestQuery.getKeywordsByUserId(anyLong())).willReturn(response.interests()); + given(certificationQuery.getCertificationByUserId(anyLong())).willReturn(certification); + + // when + MyProfileDto result = userProfileService.findMyProfile(user.getId()); + + // then + assertAll( + () -> assertThat(result.nickname()).isEqualTo(response.nickname()), + () -> assertThat(result.profileImageUrl()).isEqualTo(response.profileImageUrl()), + () -> assertThat(result.companyName()).isEqualTo(response.companyName()), + () -> assertThat(result.department()).isEqualTo(response.department()), + () -> assertThat(result.department()).isEqualTo(response.department()) + ); + } + + + @DisplayName("프로필 사진을 수정할 수 있다.") + @Test + void updateProfileImage() throws IOException { + // given + User user = user(); + File file = File.createTempFile("temp", "png"); + + given(userQuery.getUserById(anyLong())).willReturn(user); + given(objectStorage.generateKey(PROFILE_IMAGE)).willReturn("key"); + given(objectStorage.getUrl(anyString())).willReturn("newImageUrl"); + given(objectStorage.extractKey(any(), eq(PROFILE_IMAGE))).willReturn(""); + + // when + userProfileService.updateProfileImage(user.getId(), file); + + // then + assertThat(user.getOauthInfo().getProfileImageUrl()).isEqualTo("newImageUrl"); + } + + @DisplayName("프로필 정보를 수정할 수 있다.") + @Transactional + @Test + void updateProfileInfo() { + // given + User user = user(); + + String newNickname = "새닉네임"; + List newKeywords = keywords(); + + given(userQuery.getUserById(any())).willReturn(user); + willDoNothing().given(userCommand).updateUserInfo(any(), any()); + willDoNothing().given(interestCommand).updateInterests(any(), any()); + + // when + userProfileService.updateProfileInfo(user.getId(), newNickname, newKeywords); + + // then + verify(userCommand).updateUserInfo(any(User.class), anyString()); + verify(interestCommand).updateInterests(any(User.class), anyList()); + } + +} \ No newline at end of file diff --git a/src/test/java/coffeemeet/server/user/service/UserServiceTest.java b/src/test/java/coffeemeet/server/user/service/UserServiceTest.java index b4fd2295..323ced6e 100644 --- a/src/test/java/coffeemeet/server/user/service/UserServiceTest.java +++ b/src/test/java/coffeemeet/server/user/service/UserServiceTest.java @@ -1,6 +1,5 @@ package coffeemeet.server.user.service; -import static coffeemeet.server.common.domain.S3KeyPrefix.PROFILE_IMAGE; import static coffeemeet.server.common.fixture.AuthFixture.authTokens; import static coffeemeet.server.common.fixture.CertificationFixture.certification; import static coffeemeet.server.common.fixture.ChattingFixture.chattingRoom; @@ -19,23 +18,23 @@ import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.Mockito.only; -import static org.mockito.Mockito.verify; import coffeemeet.server.auth.domain.AuthTokens; import coffeemeet.server.auth.domain.AuthTokensGenerator; +import coffeemeet.server.auth.implement.RefreshTokenCommand; import coffeemeet.server.certification.domain.Certification; import coffeemeet.server.certification.implement.CertificationQuery; -import coffeemeet.server.common.domain.ObjectStorage; import coffeemeet.server.common.fixture.UserFixture; import coffeemeet.server.matching.implement.MatchingQueueCommand; import coffeemeet.server.oauth.domain.OAuthMemberDetail; -import coffeemeet.server.oauth.implement.client.OAuthMemberClientComposite; +import coffeemeet.server.oauth.implement.client.OAuthMemberClientRegistry; +import coffeemeet.server.oauth.implement.client.OAuthMemberUnlinkRegistry; import coffeemeet.server.user.domain.Keyword; +import coffeemeet.server.user.domain.OAuthProvider; import coffeemeet.server.user.domain.User; import coffeemeet.server.user.domain.UserStatus; import coffeemeet.server.user.implement.InterestCommand; @@ -44,11 +43,7 @@ import coffeemeet.server.user.implement.UserQuery; import coffeemeet.server.user.presentation.dto.SignupHTTP; import coffeemeet.server.user.service.dto.LoginDetailsDto; -import coffeemeet.server.user.service.dto.MyProfileDto; -import coffeemeet.server.user.service.dto.UserProfileDto; import coffeemeet.server.user.service.dto.UserStatusDto; -import java.io.File; -import java.io.IOException; import java.time.LocalDateTime; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -58,7 +53,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.transaction.annotation.Transactional; @ExtendWith(MockitoExtension.class) class UserServiceTest { @@ -67,10 +61,10 @@ class UserServiceTest { private UserService userService; @Mock - private ObjectStorage objectStorage; + private OAuthMemberClientRegistry oAuthMemberClientRegistry; @Mock - private OAuthMemberClientComposite oAuthMemberClientComposite; + private OAuthMemberUnlinkRegistry oAuthMemberUnlinkRegistry; @Mock private AuthTokensGenerator authTokensGenerator; @@ -87,6 +81,9 @@ class UserServiceTest { @Mock private UserCommand userCommand; + @Mock + private RefreshTokenCommand refreshTokenCommand; + @Mock private CertificationQuery certificationQuery; @@ -123,7 +120,7 @@ void loginTest() { LoginDetailsDto expectedResponse = LoginDetailsDto.of(user, keywords, certification, authTokens); - given(oAuthMemberClientComposite.fetch(any(), anyString())).willReturn(response); + given(oAuthMemberClientRegistry.fetch(any(), anyString())).willReturn(response); given(userQuery.getUserByOAuthInfoOrDefault(any())).willReturn(user); given(interestQuery.getKeywordsByUserId(anyLong())).willReturn(keywords); given(certificationQuery.getCertificationByUserId(anyLong())).willReturn(certification); @@ -145,55 +142,6 @@ void loginTest() { ); } - @DisplayName("사용자의 프로필을 조회할 수 있다.") - @Test - void findUserProfileTest() { - // given - User user = user(); - Certification certification = certification(); - List keywords = keywords(); - UserProfileDto response = UserProfileDto.of(user, keywords, certification); - - given(userQuery.getUserById(anyLong())).willReturn(user); - given(interestQuery.getKeywordsByUserId(anyLong())).willReturn(keywords); - given(certificationQuery.getCertificationByUserId(anyLong())).willReturn(certification); - - // when - UserProfileDto result = userService.findUserProfile(user.getId()); - - // then - assertAll( - () -> assertThat(result.nickname()).isEqualTo(response.nickname()), - () -> assertThat(result.department()).isEqualTo(response.department()), - () -> assertThat(result.profileImageUrl()).isEqualTo(response.profileImageUrl()) - ); - } - - @DisplayName("본인의 프로필을 조회할 수 있다.") - @Test - void findMyProfileTest() { - // given - User user = user(); - Certification certification = certification(user); - List keywords = keywords(); - MyProfileDto response = MyProfileDto.of(user, keywords, certification); - - given(userQuery.getUserById(anyLong())).willReturn(user); - given(interestQuery.getKeywordsByUserId(anyLong())).willReturn(response.interests()); - given(certificationQuery.getCertificationByUserId(anyLong())).willReturn(certification); - - // when - MyProfileDto result = userService.findMyProfile(user.getId()); - - // then - assertAll( - () -> assertThat(result.nickname()).isEqualTo(response.nickname()), - () -> assertThat(result.profileImageUrl()).isEqualTo(response.profileImageUrl()), - () -> assertThat(result.companyName()).isEqualTo(response.companyName()), - () -> assertThat(result.department()).isEqualTo(response.department()), - () -> assertThat(result.department()).isEqualTo(response.department()) - ); - } @DisplayName("아이디로 사용자를 조회할 수 있다.") @Test @@ -217,58 +165,22 @@ void getUserById() { ); } - @DisplayName("프로필 사진을 수정할 수 있다.") + @DisplayName("탈퇴할 수 있다.") @Test - void updateProfileImage() throws IOException { + void deleteUser() { // given User user = user(); - File file = File.createTempFile("temp", "png"); + String accessToken = "accessToken"; given(userQuery.getUserById(anyLong())).willReturn(user); - given(objectStorage.generateKey(PROFILE_IMAGE)).willReturn("key"); - given(objectStorage.getUrl(anyString())).willReturn("newImageUrl"); - given(objectStorage.extractKey(any(), eq(PROFILE_IMAGE))).willReturn(""); // when - userService.updateProfileImage(user.getId(), file); + userService.deleteUser(user.getId(), accessToken, KAKAO); // then - assertThat(user.getOauthInfo().getProfileImageUrl()).isEqualTo("newImageUrl"); - } - - @DisplayName("프로필 정보를 수정할 수 있다.") - @Transactional - @Test - void updateProfileInfo() { - // given - User user = user(); - - String newNickname = "새닉네임"; - List newKeywords = keywords(); - - given(userQuery.getUserById(any())).willReturn(user); - willDoNothing().given(userCommand).updateUserInfo(any(), any()); - willDoNothing().given(interestCommand).updateInterests(any(), any()); - - // when - userService.updateProfileInfo(user.getId(), newNickname, newKeywords); - - // then - verify(userCommand).updateUserInfo(any(User.class), anyString()); - verify(interestCommand).updateInterests(any(User.class), anyList()); - } - - @DisplayName("탈퇴할 수 있다.") - @Test - void deleteUser() { - // given - User user = user(); - - willDoNothing().given(userCommand).deleteUser(user.getId()); - - // when, then - assertThatCode(() -> userService.deleteUser(user.getId())) - .doesNotThrowAnyException(); + assertTrue(user.isDeleted()); + assertNotNull(user.getPrivacyDateTime()); + assertNotNull(userQuery.getUserById(user.getId())); } @DisplayName("닉네임 중복을 검사할 수 있다.") @@ -415,4 +327,19 @@ void getUserStatusReportedTest() { } } + @DisplayName("회원 탈퇴 시킬 수 있다.") + @Test + void deleteTest() { + // given + User user = user(); + Long userId = user.getId(); + String accessToken = "accessToken"; + + given(userQuery.getUserById(anyLong())).willReturn(user); + + // when, then + assertThatCode(() -> userService.deleteUser(userId, accessToken, OAuthProvider.KAKAO)) + .doesNotThrowAnyException(); + } + }