diff --git a/backend/build.gradle b/backend/build.gradle index 4dba0f2c5..a6a53ce12 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -112,6 +112,7 @@ jacocoTestReport { '**/*Response*', '**/*Request*', '**/BaseTimeEntity', + '**/KakaoOAuthClient', '**/*Dto*', '**/S3*', '**/*Interceptor*', @@ -151,6 +152,7 @@ jacocoTestCoverageVerification { '*.*Exception*', '*.*Dto', '*.S3*', + '*.KakaoOAuthClient*', '*.*Response', '*.*Request', '*.BaseTimeEntity', diff --git a/backend/src/main/java/zipgo/auth/application/AuthService.java b/backend/src/main/java/zipgo/auth/application/AuthService.java index 69841c470..3e0abaad8 100644 --- a/backend/src/main/java/zipgo/auth/application/AuthService.java +++ b/backend/src/main/java/zipgo/auth/application/AuthService.java @@ -50,3 +50,4 @@ public void logout(Long memberId) { } } + diff --git a/backend/src/main/java/zipgo/auth/application/AuthServiceFacade.java b/backend/src/main/java/zipgo/auth/application/AuthServiceFacade.java index 0306d91bf..33c3e7985 100644 --- a/backend/src/main/java/zipgo/auth/application/AuthServiceFacade.java +++ b/backend/src/main/java/zipgo/auth/application/AuthServiceFacade.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import zipgo.auth.application.dto.OAuthMemberResponse; +import zipgo.auth.domain.OAuthClient; import zipgo.auth.dto.TokenDto; @Service @@ -12,9 +13,8 @@ public class AuthServiceFacade { private final AuthService authService; private final OAuthClient oAuthClient; - public TokenDto login(String authCode) { - String accessToken = oAuthClient.getAccessToken(authCode); - OAuthMemberResponse oAuthMemberResponse = oAuthClient.getMember(accessToken); + public TokenDto login(String authCode, String redirectUri) { + OAuthMemberResponse oAuthMemberResponse = oAuthClient.request(authCode, redirectUri); return authService.login(oAuthMemberResponse); } diff --git a/backend/src/main/java/zipgo/auth/domain/OAuthClient.java b/backend/src/main/java/zipgo/auth/domain/OAuthClient.java new file mode 100644 index 000000000..622d0cb84 --- /dev/null +++ b/backend/src/main/java/zipgo/auth/domain/OAuthClient.java @@ -0,0 +1,9 @@ +package zipgo.auth.domain; + +import zipgo.auth.application.dto.OAuthMemberResponse; + +public interface OAuthClient { + + OAuthMemberResponse request(String authCode, String redirectUri); + +} diff --git a/backend/src/main/java/zipgo/auth/application/OAuthClient.java b/backend/src/main/java/zipgo/auth/domain/OAuthMemberInfoClient.java similarity index 51% rename from backend/src/main/java/zipgo/auth/application/OAuthClient.java rename to backend/src/main/java/zipgo/auth/domain/OAuthMemberInfoClient.java index 7206114b3..d1a7329bb 100644 --- a/backend/src/main/java/zipgo/auth/application/OAuthClient.java +++ b/backend/src/main/java/zipgo/auth/domain/OAuthMemberInfoClient.java @@ -1,10 +1,8 @@ -package zipgo.auth.application; +package zipgo.auth.domain; import zipgo.auth.application.dto.OAuthMemberResponse; -public interface OAuthClient { - - String getAccessToken(String authCode); +public interface OAuthMemberInfoClient { OAuthMemberResponse getMember(String accessToken); diff --git a/backend/src/main/java/zipgo/auth/domain/OAuthTokenClient.java b/backend/src/main/java/zipgo/auth/domain/OAuthTokenClient.java new file mode 100644 index 000000000..e022e3da8 --- /dev/null +++ b/backend/src/main/java/zipgo/auth/domain/OAuthTokenClient.java @@ -0,0 +1,7 @@ +package zipgo.auth.domain; + +public interface OAuthTokenClient { + + String getAccessToken(String authCode, String redirectUri); + +} diff --git a/backend/src/main/java/zipgo/auth/infra/kakao/KakaoOAuthClient.java b/backend/src/main/java/zipgo/auth/infra/kakao/KakaoOAuthClient.java index 65e4cab59..2309fb802 100644 --- a/backend/src/main/java/zipgo/auth/infra/kakao/KakaoOAuthClient.java +++ b/backend/src/main/java/zipgo/auth/infra/kakao/KakaoOAuthClient.java @@ -1,101 +1,23 @@ package zipgo.auth.infra.kakao; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.RestTemplate; -import zipgo.auth.application.OAuthClient; import zipgo.auth.application.dto.OAuthMemberResponse; -import zipgo.auth.exception.OAuthResourceNotBringException; -import zipgo.auth.exception.OAuthTokenNotBringException; -import zipgo.auth.infra.kakao.config.KakaoCredentials; -import zipgo.auth.infra.kakao.dto.KakaoMemberResponse; -import zipgo.auth.infra.kakao.dto.KakaoTokenResponse; - -import static java.util.Objects.requireNonNull; -import static org.springframework.http.HttpMethod.GET; -import static org.springframework.http.HttpMethod.POST; -import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; +import zipgo.auth.domain.OAuthClient; +import zipgo.auth.domain.OAuthMemberInfoClient; +import zipgo.auth.domain.OAuthTokenClient; @Component @RequiredArgsConstructor public class KakaoOAuthClient implements OAuthClient { - private static final String ACCESS_TOKEN_URI = "https://kauth.kakao.com/oauth/token"; - private static final String USER_INFO_URI = "https://kapi.kakao.com/v2/user/me"; - private static final String GRANT_TYPE = "authorization_code"; - - private final KakaoCredentials kakaoCredentials; - private final RestTemplate restTemplate; - - @Override - public String getAccessToken(String authCode) { - HttpHeaders header = createRequestHeader(); - MultiValueMap body = createRequestBodyWithAuthCode(authCode); - HttpEntity> request = new HttpEntity<>(body, header); - ResponseEntity kakaoTokenResponse = getKakaoToken(request); - - return requireNonNull(requireNonNull(kakaoTokenResponse.getBody())).accessToken(); - } - - private HttpHeaders createRequestHeader() { - HttpHeaders header = new HttpHeaders(); - header.setContentType(APPLICATION_FORM_URLENCODED); - return header; - } - - private MultiValueMap createRequestBodyWithAuthCode(String authCode) { - MultiValueMap body = new LinkedMultiValueMap<>(); - body.add("grant_type", GRANT_TYPE); - body.add("client_id", kakaoCredentials.getClientId()); - body.add("redirect_uri", kakaoCredentials.getRedirectUri()); - body.add("client_secret", kakaoCredentials.getClientSecret()); - body.add("code", authCode); - return body; - } - - private ResponseEntity getKakaoToken(HttpEntity> request) { - try { - return restTemplate.exchange( - ACCESS_TOKEN_URI, - POST, - request, - KakaoTokenResponse.class - ); - } catch (HttpClientErrorException e) { - throw new OAuthTokenNotBringException(); - } - } + private final OAuthTokenClient kakaoOAuthTokenClient; + private final OAuthMemberInfoClient kakaoOAuthMemberInfoClient; @Override - public OAuthMemberResponse getMember(String accessToken) { - HttpEntity request = createRequest(accessToken); - ResponseEntity response = getKakaoMember(request); - return response.getBody(); - } - - private HttpEntity createRequest(String accessToken) { - HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth(accessToken); - return new HttpEntity<>(headers); - } - - private ResponseEntity getKakaoMember(HttpEntity request) { - try { - return restTemplate.exchange( - USER_INFO_URI, - GET, - request, - KakaoMemberResponse.class - ); - } catch (HttpClientErrorException e) { - throw new OAuthResourceNotBringException(); - } + public OAuthMemberResponse request(String authCode, String redirectUri) { + String accessToken = kakaoOAuthTokenClient.getAccessToken(authCode, redirectUri); + return kakaoOAuthMemberInfoClient.getMember(accessToken); } } diff --git a/backend/src/main/java/zipgo/auth/infra/kakao/KakaoOAuthMemberInfoClient.java b/backend/src/main/java/zipgo/auth/infra/kakao/KakaoOAuthMemberInfoClient.java new file mode 100644 index 000000000..2a22a8217 --- /dev/null +++ b/backend/src/main/java/zipgo/auth/infra/kakao/KakaoOAuthMemberInfoClient.java @@ -0,0 +1,50 @@ +package zipgo.auth.infra.kakao; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import zipgo.auth.application.dto.OAuthMemberResponse; +import zipgo.auth.domain.OAuthMemberInfoClient; +import zipgo.auth.exception.OAuthResourceNotBringException; +import zipgo.auth.infra.kakao.dto.KakaoMemberResponse; + +import static org.springframework.http.HttpMethod.GET; + +@Component +@RequiredArgsConstructor +public class KakaoOAuthMemberInfoClient implements OAuthMemberInfoClient { + + private static final String USER_INFO_URI = "https://kapi.kakao.com/v2/user/me"; + + private final RestTemplate restTemplate; + + @Override + public OAuthMemberResponse getMember(String accessToken) { + HttpEntity request = createRequest(accessToken); + ResponseEntity response = getKakaoMember(request); + return response.getBody(); + } + + private HttpEntity createRequest(String accessToken) { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + return new HttpEntity<>(headers); + } + + private ResponseEntity getKakaoMember(HttpEntity request) { + try { + return restTemplate.exchange( + USER_INFO_URI, + GET, + request, + KakaoMemberResponse.class + ); + } catch (HttpClientErrorException e) { + throw new OAuthResourceNotBringException(); + } + } +} diff --git a/backend/src/main/java/zipgo/auth/infra/kakao/KakaoOAuthTokenClient.java b/backend/src/main/java/zipgo/auth/infra/kakao/KakaoOAuthTokenClient.java new file mode 100644 index 000000000..c0498efc4 --- /dev/null +++ b/backend/src/main/java/zipgo/auth/infra/kakao/KakaoOAuthTokenClient.java @@ -0,0 +1,70 @@ +package zipgo.auth.infra.kakao; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import zipgo.auth.domain.OAuthTokenClient; +import zipgo.auth.exception.OAuthTokenNotBringException; +import zipgo.auth.infra.kakao.config.KakaoCredentials; +import zipgo.auth.infra.kakao.dto.KakaoTokenResponse; + +import static java.util.Objects.requireNonNull; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; + + +@Component +@RequiredArgsConstructor +public class KakaoOAuthTokenClient implements OAuthTokenClient { + + private static final String ACCESS_TOKEN_URI = "https://kauth.kakao.com/oauth/token"; + private static final String GRANT_TYPE = "authorization_code"; + + private final RestTemplate restTemplate; + private final KakaoCredentials kakaoCredentials; + + @Override + public String getAccessToken(String authCode, String redirectUri) { + HttpHeaders header = createRequestHeader(); + MultiValueMap body = createRequestBodyWithAuthCode(authCode, redirectUri); + HttpEntity> request = new HttpEntity<>(body, header); + ResponseEntity kakaoTokenResponse = getKakaoToken(request); + + return requireNonNull(requireNonNull(kakaoTokenResponse.getBody())).accessToken(); + } + + private HttpHeaders createRequestHeader() { + HttpHeaders header = new HttpHeaders(); + header.setContentType(APPLICATION_FORM_URLENCODED); + return header; + } + + private MultiValueMap createRequestBodyWithAuthCode(String authCode, String redirectUri) { + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("grant_type", GRANT_TYPE); + body.add("client_id", kakaoCredentials.getClientId()); + body.add("redirect_uri", redirectUri); + body.add("client_secret", kakaoCredentials.getClientSecret()); + body.add("code", authCode); + return body; + } + + private ResponseEntity getKakaoToken(HttpEntity> request) { + try { + return restTemplate.exchange( + ACCESS_TOKEN_URI, + POST, + request, + KakaoTokenResponse.class + ); + } catch (HttpClientErrorException e) { + throw new OAuthTokenNotBringException(); + } + } +} diff --git a/backend/src/main/java/zipgo/auth/presentation/AuthController.java b/backend/src/main/java/zipgo/auth/presentation/AuthController.java index c29887837..85bd593e1 100644 --- a/backend/src/main/java/zipgo/auth/presentation/AuthController.java +++ b/backend/src/main/java/zipgo/auth/presentation/AuthController.java @@ -32,15 +32,18 @@ @RequiredArgsConstructor public class AuthController { - private final AuthServiceFacade authServiceFacade; private final JwtProvider jwtProvider; private final RefreshTokenCookieProvider refreshTokenCookieProvider; + private final AuthServiceFacade authServiceFacade; private final MemberQueryService memberQueryService; private final PetQueryService petQueryService; @PostMapping("/login") - public ResponseEntity login(@RequestParam("code") String authCode) { - TokenDto tokenDto = authServiceFacade.login(authCode); + public ResponseEntity login( + @RequestParam("code") String authCode, + @RequestParam("redirect-uri") String redirectUri + ) { + TokenDto tokenDto = authServiceFacade.login(authCode, redirectUri); ResponseCookie cookie = refreshTokenCookieProvider.createCookie(tokenDto.refreshToken()); String memberId = jwtProvider.getPayload(tokenDto.accessToken()); diff --git a/backend/src/test/java/zipgo/auth/application/AuthServiceFacadeTest.java b/backend/src/test/java/zipgo/auth/application/AuthServiceFacadeTest.java index f2c9a1164..4e5ee76e4 100644 --- a/backend/src/test/java/zipgo/auth/application/AuthServiceFacadeTest.java +++ b/backend/src/test/java/zipgo/auth/application/AuthServiceFacadeTest.java @@ -9,6 +9,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import zipgo.auth.application.dto.OAuthMemberResponse; import zipgo.auth.application.fixture.MemberResponseSuccessFixture; +import zipgo.auth.domain.OAuthClient; import zipgo.auth.dto.TokenDto; import zipgo.auth.exception.OAuthResourceNotBringException; import zipgo.auth.exception.OAuthTokenNotBringException; @@ -38,16 +39,14 @@ class AuthServiceFacadeTest { @Test void 로그인에_성공하면_토큰을_발급한다() { // given - when(oAuthClient.getAccessToken("인가 코드")) - .thenReturn("엑세스 토큰"); - OAuthMemberResponse 서드파티_사용자_응답 = new MemberResponseSuccessFixture(); - when(oAuthClient.getMember("엑세스 토큰")) - .thenReturn(서드파티_사용자_응답); - when(authService.login(서드파티_사용자_응답)) + OAuthMemberResponse oAuthMemberResponse = new MemberResponseSuccessFixture(); + when(oAuthClient.request("인가 코드", "리다이렉트 유알아이")) + .thenReturn(oAuthMemberResponse); + when(authService.login(oAuthMemberResponse)) .thenReturn(TokenDto.of("생성된 엑세스 토큰", "생성된 리프레시 토큰")); // when - TokenDto 토큰 = authServiceFacade.login("인가 코드"); + TokenDto 토큰 = authServiceFacade.login("인가 코드", "리다이렉트 유알아이"); // then assertAll( @@ -59,11 +58,11 @@ class AuthServiceFacadeTest { @Test void 엑세스_토큰을_가져오지_못하면_예외가_발생한다() { // given - when(oAuthClient.getAccessToken("인가 코드")) + when(oAuthClient.request("인가 코드", "리다이렉트 유알아이")) .thenThrow(new OAuthTokenNotBringException()); // expect - assertThatThrownBy(() -> authServiceFacade.login("인가 코드")) + assertThatThrownBy(() -> authServiceFacade.login("인가 코드", "리다이렉트 유알아이")) .isInstanceOf(OAuthTokenNotBringException.class) .hasMessageContaining("서드파티 서비스에서 토큰을 받아오지 못했습니다. 잠시후 다시 시도해주세요."); } @@ -71,13 +70,11 @@ class AuthServiceFacadeTest { @Test void 사용자_정보를_가져오지_못하면_예외가_발생한다() { // given - when(oAuthClient.getAccessToken("인가 코드")) - .thenReturn("엑세스 토큰"); - when(oAuthClient.getMember("엑세스 토큰")) + when(oAuthClient.request("인가 코드", "리다이렉트 유알아이")) .thenThrow(new OAuthResourceNotBringException()); // expect - assertThatThrownBy(() -> authServiceFacade.login("인가 코드")) + assertThatThrownBy(() -> authServiceFacade.login("인가 코드", "리다이렉트 유알아이")) .isInstanceOf(OAuthResourceNotBringException.class) .hasMessageContaining("서드파티 서비스에서 정보를 받아오지 못했습니다. 잠시후 다시 시도해주세요"); } diff --git a/backend/src/test/java/zipgo/auth/infra/kakao/KakaoOAuthClientTest.java b/backend/src/test/java/zipgo/auth/infra/kakao/KakaoOAuthClientTest.java index 1d63853d7..da1ea2a91 100644 --- a/backend/src/test/java/zipgo/auth/infra/kakao/KakaoOAuthClientTest.java +++ b/backend/src/test/java/zipgo/auth/infra/kakao/KakaoOAuthClientTest.java @@ -3,30 +3,24 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; -import zipgo.auth.application.dto.OAuthMemberResponse; -import zipgo.auth.exception.OAuthResourceNotBringException; import zipgo.auth.exception.OAuthTokenNotBringException; import zipgo.auth.infra.kakao.config.KakaoCredentials; -import zipgo.auth.infra.kakao.dto.KakaoMemberResponse; import zipgo.auth.infra.kakao.dto.KakaoTokenResponse; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.when; -import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.POST; import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; @@ -36,95 +30,42 @@ class KakaoOAuthClientTest { private static final String ACCESS_TOKEN_URI = "https://kauth.kakao.com/oauth/token"; - private static final String USER_INFO_URI = "https://kapi.kakao.com/v2/user/me"; private static final String GRANT_TYPE = "authorization_code"; - @Mock private RestTemplate restTemplate; + @Mock + private KakaoOAuthTokenClient kakaoOAuthTokenClient; + + @Mock + private KakaoOAuthMemberInfoClient kakaoOAuthMemberInfoClient; + @InjectMocks private KakaoOAuthClient kakaoOAuthClient; @BeforeEach - public void setUp() { + void setUp() { KakaoCredentials kakaoCredentials = new KakaoCredentials("clientId", "redirectUri", "clientSecret"); - kakaoOAuthClient = new KakaoOAuthClient(kakaoCredentials, restTemplate); + restTemplate = Mockito.mock(RestTemplate.class); + kakaoOAuthTokenClient = new KakaoOAuthTokenClient(restTemplate, kakaoCredentials); + kakaoOAuthMemberInfoClient = new KakaoOAuthMemberInfoClient(restTemplate); + kakaoOAuthClient = new KakaoOAuthClient(kakaoOAuthTokenClient, kakaoOAuthMemberInfoClient); } - @Nested - class 카카오서버_성공_응답 { - - @Test - void accessToken_을_가져올_수_있다() { - // given - var 토큰_요청 = 토큰_요청_생성(); - var 응답 = ResponseEntity.ok( - new KakaoTokenResponse("accessToken", null, null, null, null, null) - ); - when(restTemplate.exchange(ACCESS_TOKEN_URI, POST, 토큰_요청, KakaoTokenResponse.class)) - .thenReturn(응답); - - // when - String accessToken = kakaoOAuthClient.getAccessToken("authCode"); - - // then - assertThat(accessToken).isEqualTo("accessToken"); - } - - @Test - void 사용자_정보를_가져올_수_있다() { - // given - var 정보_요청 = 사용자_정보_요청_생성(); - var 응답 = ResponseEntity.ok(KakaoMemberResponse.builder().build()); - when(restTemplate.exchange(USER_INFO_URI, GET, 정보_요청, KakaoMemberResponse.class)) - .thenReturn(응답); - - // when - OAuthMemberResponse oAuthMemberResponse = kakaoOAuthClient.getMember("accessToken"); - - // then - assertThat(oAuthMemberResponse).isNotNull(); - } - - } - - @Nested - class 카카오서버_실패_응답 { - - @Test - void 토큰_요청시_실패_응답을_받으면_예외가_발생한다() { - // given - var 요청 = 토큰_요청_생성(); - when(restTemplate.exchange( - ACCESS_TOKEN_URI, - POST, - 요청, - KakaoTokenResponse.class - )).thenThrow(HttpClientErrorException.class); - - // expect - assertThatThrownBy(() -> kakaoOAuthClient.getAccessToken("authCode")) - .isInstanceOf(OAuthTokenNotBringException.class) - .hasMessageContaining("서드파티 서비스에서 토큰을 받아오지 못했습니다. 잠시후 다시 시도해주세요."); - } - - @Test - void 사용자_정보_요청시_실패_응답을_받으면_예외가_발생한다() { - // given - var 요청 = 사용자_정보_요청_생성(); - when(restTemplate.exchange( - USER_INFO_URI, - GET, - 요청, - KakaoMemberResponse.class - )).thenThrow(HttpClientErrorException.class); - - // expect - assertThatThrownBy(() -> kakaoOAuthClient.getMember("accessToken")) - .isInstanceOf(OAuthResourceNotBringException.class) - .hasMessageContaining("서드파티 서비스에서 정보를 받아오지 못했습니다. 잠시후 다시 시도해주세요."); - } - + @Test + void 토큰을_가져오는데_실패하면_예외가_발생한다() { + // given + var 요청 = 토큰_요청_생성(); + when(restTemplate.exchange( + ACCESS_TOKEN_URI, + POST, + 요청, + KakaoTokenResponse.class + )).thenThrow(HttpClientErrorException.class); + + assertThatThrownBy(() -> kakaoOAuthClient.request("authCode", "redirectUri")) + .isInstanceOf(OAuthTokenNotBringException.class) + .hasMessageContaining("서드파티 서비스에서 토큰을 받아오지 못했습니다. 잠시후 다시 시도해주세요."); } private HttpEntity> 토큰_요청_생성() { @@ -141,10 +82,4 @@ class 카카오서버_실패_응답 { return new HttpEntity<>(body, header); } - private HttpEntity 사용자_정보_요청_생성() { - HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth("accessToken"); - return new HttpEntity<>(headers); - } - } diff --git a/backend/src/test/java/zipgo/auth/infra/kakao/KakaoOAuthMemberInfoClientTest.java b/backend/src/test/java/zipgo/auth/infra/kakao/KakaoOAuthMemberInfoClientTest.java new file mode 100644 index 000000000..e538fe7a9 --- /dev/null +++ b/backend/src/test/java/zipgo/auth/infra/kakao/KakaoOAuthMemberInfoClientTest.java @@ -0,0 +1,78 @@ +package zipgo.auth.infra.kakao; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +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; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import zipgo.auth.application.dto.OAuthMemberResponse; +import zipgo.auth.exception.OAuthResourceNotBringException; +import zipgo.auth.infra.kakao.config.KakaoCredentials; +import zipgo.auth.infra.kakao.dto.KakaoMemberResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpMethod.GET; + +@ExtendWith(MockitoExtension.class) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class KakaoOAuthMemberInfoClientTest { + + private static final String USER_INFO_URI = "https://kapi.kakao.com/v2/user/me"; + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private KakaoOAuthMemberInfoClient KakaoOAuthMemberInfoClient; + + @Test + void 사용자_정보를_가져올_수_있다() { + // given + var 정보_요청 = 사용자_정보_요청_생성(); + var 응답 = ResponseEntity.ok(KakaoMemberResponse.builder().build()); + when(restTemplate.exchange(USER_INFO_URI, GET, 정보_요청, KakaoMemberResponse.class)) + .thenReturn(응답); + + // when + OAuthMemberResponse oAuthMemberResponse = KakaoOAuthMemberInfoClient.getMember("accessToken"); + + // then + assertThat(oAuthMemberResponse).isNotNull(); + } + + @Test + void 사용자_정보_요청시_실패_응답을_받으면_예외가_발생한다() { + // given + var 요청 = 사용자_정보_요청_생성(); + when(restTemplate.exchange( + USER_INFO_URI, + GET, + 요청, + KakaoMemberResponse.class + )).thenThrow(HttpClientErrorException.class); + + // expect + assertThatThrownBy(() -> KakaoOAuthMemberInfoClient.getMember("accessToken")) + .isInstanceOf(OAuthResourceNotBringException.class) + .hasMessageContaining("서드파티 서비스에서 정보를 받아오지 못했습니다. 잠시후 다시 시도해주세요."); + } + + + private HttpEntity 사용자_정보_요청_생성() { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth("accessToken"); + return new HttpEntity<>(headers); + } + +} diff --git a/backend/src/test/java/zipgo/auth/infra/kakao/KakaoOAuthTokenClientTest.java b/backend/src/test/java/zipgo/auth/infra/kakao/KakaoOAuthTokenClientTest.java new file mode 100644 index 000000000..918568172 --- /dev/null +++ b/backend/src/test/java/zipgo/auth/infra/kakao/KakaoOAuthTokenClientTest.java @@ -0,0 +1,97 @@ +package zipgo.auth.infra.kakao; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +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; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import zipgo.auth.exception.OAuthTokenNotBringException; +import zipgo.auth.infra.kakao.config.KakaoCredentials; +import zipgo.auth.infra.kakao.dto.KakaoTokenResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; + +@ExtendWith(MockitoExtension.class) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class KakaoOAuthTokenClientTest { + + private static final String ACCESS_TOKEN_URI = "https://kauth.kakao.com/oauth/token"; + private static final String GRANT_TYPE = "authorization_code"; + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private KakaoOAuthTokenClient kakaoOAuthTokenClient; + + @BeforeEach + public void setUp() { + KakaoCredentials kakaoCredentials = new KakaoCredentials("clientId", "redirectUri", "clientSecret"); + kakaoOAuthTokenClient = new KakaoOAuthTokenClient(restTemplate, kakaoCredentials); + } + + @Test + void accessToken_을_가져올_수_있다() { + // given + var 토큰_요청 = 토큰_요청_생성(); + var 응답 = ResponseEntity.ok( + new KakaoTokenResponse("accessToken", null, null, null, null, null) + ); + when(restTemplate.exchange(ACCESS_TOKEN_URI, POST, 토큰_요청, KakaoTokenResponse.class)) + .thenReturn(응답); + + // when + String accessToken = kakaoOAuthTokenClient.getAccessToken("authCode", "redirectUri"); + + // then + assertThat(accessToken).isEqualTo("accessToken"); + } + + @Test + void 토큰_요청시_실패_응답을_받으면_예외가_발생한다() { + // given + var 요청 = 토큰_요청_생성(); + when(restTemplate.exchange( + ACCESS_TOKEN_URI, + POST, + 요청, + KakaoTokenResponse.class + )).thenThrow(HttpClientErrorException.class); + + // expect + assertThatThrownBy(() -> kakaoOAuthTokenClient.getAccessToken("authCode", "redirectUri")) + .isInstanceOf(OAuthTokenNotBringException.class) + .hasMessageContaining("서드파티 서비스에서 토큰을 받아오지 못했습니다. 잠시후 다시 시도해주세요."); + } + + + private HttpEntity> 토큰_요청_생성() { + HttpHeaders header = new HttpHeaders(); + header.setContentType(APPLICATION_FORM_URLENCODED); + + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("grant_type", GRANT_TYPE); + body.add("client_id", "clientId"); + body.add("redirect_uri", "redirectUri"); + body.add("client_secret", "clientSecret"); + body.add("code", "authCode"); + + return new HttpEntity<>(body, header); + } + +} diff --git a/backend/src/test/java/zipgo/auth/presentation/AuthControllerMockTest.java b/backend/src/test/java/zipgo/auth/presentation/AuthControllerMockTest.java index e2aaffee1..bd12337d0 100644 --- a/backend/src/test/java/zipgo/auth/presentation/AuthControllerMockTest.java +++ b/backend/src/test/java/zipgo/auth/presentation/AuthControllerMockTest.java @@ -36,7 +36,7 @@ class AuthControllerMockTest extends MockMvcTest { void 로그인_성공() throws Exception { // given var 토큰 = TokenDto.of("accessTokenValue", "refreshTokenValue"); - when(authServiceFacade.login("인가_코드")) + when(authServiceFacade.login("인가_코드", "리다이렉트 유알아이")) .thenReturn(토큰); var 리프레시_토큰_쿠키 = ResponseCookie.from("refreshToken", 토큰.refreshToken()).build(); when(refreshTokenCookieProvider.createCookie(토큰.refreshToken())) @@ -50,7 +50,8 @@ class AuthControllerMockTest extends MockMvcTest { // when var 요청 = mockMvc.perform(post("/auth/login") - .param("code", "인가_코드")) + .param("code", "인가_코드") + .param("redirect-uri", "리다이렉트 유알아이")) .andDo(로그인_성공_문서_생성()); // then @@ -61,7 +62,7 @@ class AuthControllerMockTest extends MockMvcTest { void 로그인_성공_후_사용자의_반려동물이_없다면_pets는_빈_배열이다() throws Exception { // given var 토큰 = TokenDto.of("accessTokenValue", "refreshTokenValue"); - when(authServiceFacade.login("인가_코드")) + when(authServiceFacade.login("인가_코드", "리다이렉트 유알아이")) .thenReturn(토큰); var 리프레시_토큰_쿠키 = ResponseCookie.from("refreshToken", 토큰.refreshToken()).build(); when(refreshTokenCookieProvider.createCookie(토큰.refreshToken())) @@ -75,7 +76,8 @@ class AuthControllerMockTest extends MockMvcTest { // when var 요청 = mockMvc.perform(post("/auth/login") - .param("code", "인가_코드")) + .param("code", "인가_코드") + .param("redirect-uri", "리다이렉트 유알아이")) .andDo(로그인_성공_반려동물_정보_없음_문서_생성()); // then @@ -85,12 +87,13 @@ class AuthControllerMockTest extends MockMvcTest { @Test void 자원_서버의_토큰을_가져오는데_실패하면_예외가_발생한다() throws Exception { // given - when(authServiceFacade.login("인가_코드")) + when(authServiceFacade.login("인가_코드", "리다이렉트 유알아이")) .thenThrow(new OAuthTokenNotBringException()); // when var 요청 = mockMvc.perform(post("/auth/login") - .param("code", "인가_코드")); + .param("code", "인가_코드") + .param("redirect-uri", "리다이렉트 유알아이")); // then 요청.andExpect(status().isBadGateway());