diff --git a/backend/src/main/java/zipgo/auth/application/AuthService.java b/backend/src/main/java/zipgo/auth/application/AuthService.java index acbf12773..69841c470 100644 --- a/backend/src/main/java/zipgo/auth/application/AuthService.java +++ b/backend/src/main/java/zipgo/auth/application/AuthService.java @@ -16,18 +16,13 @@ @RequiredArgsConstructor public class AuthService { - private final OAuthClient oAuthClient; private final JwtProvider jwtProvider; private final MemberRepository memberRepository; private final RefreshTokenRepository refreshTokenRepository; - public TokenDto login(String authCode) { - String accessToken = oAuthClient.getAccessToken(authCode); - OAuthMemberResponse oAuthMemberResponse = oAuthClient.getMember(accessToken); - + public TokenDto login(OAuthMemberResponse oAuthMemberResponse) { Member member = memberRepository.findByEmail(oAuthMemberResponse.getEmail()) .orElseGet(() -> memberRepository.save(oAuthMemberResponse.toMember())); - return createTokens(member.getId()); } diff --git a/backend/src/main/java/zipgo/auth/application/AuthServiceFacade.java b/backend/src/main/java/zipgo/auth/application/AuthServiceFacade.java new file mode 100644 index 000000000..0306d91bf --- /dev/null +++ b/backend/src/main/java/zipgo/auth/application/AuthServiceFacade.java @@ -0,0 +1,29 @@ +package zipgo.auth.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import zipgo.auth.application.dto.OAuthMemberResponse; +import zipgo.auth.dto.TokenDto; + +@Service +@RequiredArgsConstructor +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); + return authService.login(oAuthMemberResponse); + } + + public String renewAccessTokenBy(String refreshToken) { + return authService.renewAccessTokenBy(refreshToken); + } + + public void logout(Long memberId) { + authService.logout(memberId); + } + +} diff --git a/backend/src/main/java/zipgo/auth/presentation/AuthController.java b/backend/src/main/java/zipgo/auth/presentation/AuthController.java index c104f473e..c29887837 100644 --- a/backend/src/main/java/zipgo/auth/presentation/AuthController.java +++ b/backend/src/main/java/zipgo/auth/presentation/AuthController.java @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import zipgo.auth.application.AuthService; +import zipgo.auth.application.AuthServiceFacade; import zipgo.auth.dto.AccessTokenResponse; import zipgo.auth.dto.AuthCredentials; import zipgo.auth.dto.AuthResponse; @@ -32,7 +32,7 @@ @RequiredArgsConstructor public class AuthController { - private final AuthService authService; + private final AuthServiceFacade authServiceFacade; private final JwtProvider jwtProvider; private final RefreshTokenCookieProvider refreshTokenCookieProvider; private final MemberQueryService memberQueryService; @@ -40,7 +40,7 @@ public class AuthController { @PostMapping("/login") public ResponseEntity login(@RequestParam("code") String authCode) { - TokenDto tokenDto = authService.login(authCode); + TokenDto tokenDto = authServiceFacade.login(authCode); ResponseCookie cookie = refreshTokenCookieProvider.createCookie(tokenDto.refreshToken()); String memberId = jwtProvider.getPayload(tokenDto.accessToken()); @@ -54,13 +54,13 @@ public ResponseEntity login(@RequestParam("code") String authCode @GetMapping("/refresh") public ResponseEntity renewTokens(@CookieValue(value = REFRESH_TOKEN) String refreshToken) { - String accessToken = authService.renewAccessTokenBy(refreshToken); + String accessToken = authServiceFacade.renewAccessTokenBy(refreshToken); return ResponseEntity.ok(AccessTokenResponse.from(accessToken)); } @PostMapping("/logout") public ResponseEntity logout(@Auth AuthCredentials authCredentials) { - authService.logout(authCredentials.id()); + authServiceFacade.logout(authCredentials.id()); ResponseCookie logoutCookie = refreshTokenCookieProvider.createLogoutCookie(); return ResponseEntity.ok() .header(SET_COOKIE, logoutCookie.toString()) diff --git a/backend/src/test/java/zipgo/auth/application/AuthServiceFacadeTest.java b/backend/src/test/java/zipgo/auth/application/AuthServiceFacadeTest.java new file mode 100644 index 000000000..f2c9a1164 --- /dev/null +++ b/backend/src/test/java/zipgo/auth/application/AuthServiceFacadeTest.java @@ -0,0 +1,121 @@ +package zipgo.auth.application; + +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 zipgo.auth.application.dto.OAuthMemberResponse; +import zipgo.auth.application.fixture.MemberResponseSuccessFixture; +import zipgo.auth.dto.TokenDto; +import zipgo.auth.exception.OAuthResourceNotBringException; +import zipgo.auth.exception.OAuthTokenNotBringException; +import zipgo.auth.exception.TokenExpiredException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@SuppressWarnings("NonAsciiCharacters") +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class AuthServiceFacadeTest { + + @Mock + private OAuthClient oAuthClient; + + @Mock + private AuthService authService; + + @InjectMocks + private AuthServiceFacade authServiceFacade; + + @Test + void 로그인에_성공하면_토큰을_발급한다() { + // given + when(oAuthClient.getAccessToken("인가 코드")) + .thenReturn("엑세스 토큰"); + OAuthMemberResponse 서드파티_사용자_응답 = new MemberResponseSuccessFixture(); + when(oAuthClient.getMember("엑세스 토큰")) + .thenReturn(서드파티_사용자_응답); + when(authService.login(서드파티_사용자_응답)) + .thenReturn(TokenDto.of("생성된 엑세스 토큰", "생성된 리프레시 토큰")); + + // when + TokenDto 토큰 = authServiceFacade.login("인가 코드"); + + // then + assertAll( + () -> assertThat(토큰.accessToken()).isEqualTo("생성된 엑세스 토큰"), + () -> assertThat(토큰.refreshToken()).isEqualTo("생성된 리프레시 토큰") + ); + } + + @Test + void 엑세스_토큰을_가져오지_못하면_예외가_발생한다() { + // given + when(oAuthClient.getAccessToken("인가 코드")) + .thenThrow(new OAuthTokenNotBringException()); + + // expect + assertThatThrownBy(() -> authServiceFacade.login("인가 코드")) + .isInstanceOf(OAuthTokenNotBringException.class) + .hasMessageContaining("서드파티 서비스에서 토큰을 받아오지 못했습니다. 잠시후 다시 시도해주세요."); + } + + @Test + void 사용자_정보를_가져오지_못하면_예외가_발생한다() { + // given + when(oAuthClient.getAccessToken("인가 코드")) + .thenReturn("엑세스 토큰"); + when(oAuthClient.getMember("엑세스 토큰")) + .thenThrow(new OAuthResourceNotBringException()); + + // expect + assertThatThrownBy(() -> authServiceFacade.login("인가 코드")) + .isInstanceOf(OAuthResourceNotBringException.class) + .hasMessageContaining("서드파티 서비스에서 정보를 받아오지 못했습니다. 잠시후 다시 시도해주세요"); + } + + + @Test + void 엑세스_토큰을_갱신할_수_있다() { + // given + when(authService.renewAccessTokenBy("리프레시 토큰")) + .thenReturn("갱신된 엑세스 토큰"); + // when + String 리프레시_토큰 = authServiceFacade.renewAccessTokenBy("리프레시 토큰"); + + // then + assertThat(리프레시_토큰).isNotEmpty(); + } + + @Test + void 만료된_엑세스_토큰은_갱신시_예외가_발생한다() { + // given + when(authService.renewAccessTokenBy("리프레시 토큰")) + .thenThrow(new TokenExpiredException()); + // expect + assertThatThrownBy(() -> authServiceFacade.renewAccessTokenBy("리프레시 토큰")) + .isInstanceOf(TokenExpiredException.class) + .hasMessageContaining("만료된 토큰입니다. 올바른 토큰으로 다시 시도해주세요."); + } + + @Test + void 로그아웃_할_수_있다() { + // given + Long memberId = 123L; + + // when + authServiceFacade.logout(memberId); + + // then + verify(authService, times(1)).logout(memberId); + } + +} diff --git a/backend/src/test/java/zipgo/auth/application/AuthServiceMockTest.java b/backend/src/test/java/zipgo/auth/application/AuthServiceMockTest.java deleted file mode 100644 index 58a264672..000000000 --- a/backend/src/test/java/zipgo/auth/application/AuthServiceMockTest.java +++ /dev/null @@ -1,105 +0,0 @@ -package zipgo.auth.application; - -import java.util.Optional; - -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 zipgo.auth.domain.repository.RefreshTokenRepository; -import zipgo.auth.dto.TokenDto; -import zipgo.auth.infra.kakao.KakaoOAuthClient; -import zipgo.auth.infra.kakao.dto.KakaoMemberResponse; -import zipgo.auth.support.JwtProvider; -import zipgo.member.domain.Member; -import zipgo.member.domain.repository.MemberRepository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.Mockito.when; -import static zipgo.member.domain.fixture.MemberFixture.식별자_없는_멤버; -import static zipgo.member.domain.fixture.MemberFixture.식별자_있는_멤버; - -@ExtendWith(MockitoExtension.class) -@SuppressWarnings("NonAsciiCharacters") -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -class AuthServiceMockTest { - - @Mock - private KakaoOAuthClient oAuthClient; - - @Mock - private JwtProvider jwtProvider; - - @Mock - private MemberRepository memberRepository; - - @Mock - private RefreshTokenRepository refreshTokenRepository; - - @InjectMocks - private AuthService authService; - - @Test - void 로그인에_성공하면_토큰을_발급한다() { - // given - 카카오_토큰_받기_성공(); - Member 저장된_멤버 = 식별자_있는_멤버(); - when(memberRepository.findByEmail("이메일")) - .thenReturn(Optional.of(저장된_멤버)); - when(jwtProvider.createAccessToken(저장된_멤버.getId())) - .thenReturn("생성된 엑세스 토큰"); - when(jwtProvider.createRefreshToken()) - .thenReturn("생성된 리프레시 토큰"); - when(jwtProvider.createRefreshToken()) - .thenReturn("생성된 리프레시 토큰"); - - // when - TokenDto 토큰 = authService.login("코드"); - - // then - assertAll( - () -> assertThat(토큰.accessToken()).isEqualTo("생성된 엑세스 토큰"), - () -> assertThat(토큰.refreshToken()).isEqualTo("생성된 리프레시 토큰") - ); - } - - @Test - void 새로_가입한_회원의_토큰을_발급한다() { - // given - 카카오_토큰_받기_성공(); - when(memberRepository.findByEmail("이메일")) - .thenReturn(Optional.empty()); - when(memberRepository.save(식별자_없는_멤버())) - .thenReturn(식별자_있는_멤버()); - when(jwtProvider.createAccessToken(1L)) - .thenReturn("생성된 토큰"); - - // when - TokenDto 토큰 = authService.login("코드"); - - // then - assertThat(토큰.accessToken()).isEqualTo("생성된 토큰"); - } - - private void 카카오_토큰_받기_성공() { - when(oAuthClient.getAccessToken("코드")) - .thenReturn("토큰"); - when(oAuthClient.getMember("토큰")) - .thenReturn(카카오_응답()); - } - - private KakaoMemberResponse 카카오_응답() { - return KakaoMemberResponse.builder().kakaoAccount(KakaoMemberResponse.KakaoAccount.builder() - .email("이메일") - .profile(KakaoMemberResponse.Profile.builder() - .nickname("이름") - .picture("사진") - .build()) - .build()).build(); - } - -} diff --git a/backend/src/test/java/zipgo/auth/application/AuthServiceTest.java b/backend/src/test/java/zipgo/auth/application/AuthServiceTest.java index 16cb12808..e2fe1f6d9 100644 --- a/backend/src/test/java/zipgo/auth/application/AuthServiceTest.java +++ b/backend/src/test/java/zipgo/auth/application/AuthServiceTest.java @@ -3,13 +3,22 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import zipgo.auth.application.dto.OAuthMemberResponse; +import zipgo.auth.application.fixture.MemberResponseSuccessFixture; import zipgo.auth.domain.RefreshToken; import zipgo.auth.domain.repository.RefreshTokenRepository; +import zipgo.auth.dto.TokenDto; +import zipgo.auth.exception.TokenExpiredException; import zipgo.auth.exception.TokenInvalidException; import zipgo.auth.support.JwtProvider; +import zipgo.common.config.JwtCredentials; import zipgo.common.service.ServiceTest; +import zipgo.member.domain.Member; +import zipgo.member.domain.repository.MemberRepository; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; class AuthServiceTest extends ServiceTest { @@ -19,9 +28,39 @@ class AuthServiceTest extends ServiceTest { @Autowired private JwtProvider jwtProvider; + @Autowired + private MemberRepository memberRepository; + @Autowired private AuthService authService; + @Test + void 로그인시_기존_회원이라면_토큰만_발급한다() { + // given + memberRepository.save(Member.builder().email("이메일").name("이름").build()); + + // when + TokenDto 생성된_토큰 = authService.login(new MemberResponseSuccessFixture()); + + // then + assertThat(생성된_토큰).isNotNull(); + } + + @Test + void 로그인시_새_회원은_가입_후_토큰을_발급한다() { + // given + OAuthMemberResponse 외부_인프라_사용자_응답 = new MemberResponseSuccessFixture(); + + // when + TokenDto 생성된_토큰 = authService.login(외부_인프라_사용자_응답); + + // then + assertAll( + () -> assertThat(memberRepository.findByEmail(외부_인프라_사용자_응답.getEmail())).isNotEmpty(), + () -> assertThat(생성된_토큰).isNotNull() + ); + } + @Test void 토큰_갱신시_리프레시_토큰을_받아_엑세스_토큰을_발급한다() { // given @@ -45,6 +84,22 @@ class AuthServiceTest extends ServiceTest { .hasMessageContaining("잘못된 토큰입니다. 올바른 토큰으로 다시 시도해주세요"); } + @Test + void 토큰_갱신시_리프레시_토큰이_만료됐다면_에외가_발생한다() { + // given + JwtProvider 만료된_토큰_생성기 = new JwtProvider(new JwtCredentials( + "this1-is2-zipgo3-test4-secret5-key6", + -9999, + -9999 + )); + String 만료된_토큰 = 만료된_토큰_생성기.createRefreshToken(); + + // expect + assertThatThrownBy(() -> authService.renewAccessTokenBy(만료된_토큰)) + .isInstanceOf(TokenExpiredException.class) + .hasMessageContaining("만료된 토큰입니다. 올바른 토큰으로 다시 시도해주세요"); + } + @Test void 로그아웃시_저장된_토큰이_사라진다() { // given diff --git a/backend/src/test/java/zipgo/auth/application/fixture/MemberResponseSuccessFixture.java b/backend/src/test/java/zipgo/auth/application/fixture/MemberResponseSuccessFixture.java new file mode 100644 index 000000000..43e35c571 --- /dev/null +++ b/backend/src/test/java/zipgo/auth/application/fixture/MemberResponseSuccessFixture.java @@ -0,0 +1,32 @@ +package zipgo.auth.application.fixture; + +import zipgo.auth.application.dto.OAuthMemberResponse; +import zipgo.member.domain.Member; + +public class MemberResponseSuccessFixture implements OAuthMemberResponse { + + @Override + public String getEmail() { + return "이메일"; + } + + @Override + public String getNickName() { + return "이름"; + } + + @Override + public String getPicture() { + return "사진"; + } + + @Override + public Member toMember() { + return Member.builder() + .name(getNickName()) + .profileImgUrl(getPicture()) + .email(getEmail()) + .build(); + } + +} 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 d6b6fd93f..1d63853d7 100644 --- a/backend/src/test/java/zipgo/auth/infra/kakao/KakaoOAuthClientTest.java +++ b/backend/src/test/java/zipgo/auth/infra/kakao/KakaoOAuthClientTest.java @@ -6,6 +6,7 @@ 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.junit.jupiter.MockitoExtension; import org.springframework.http.HttpEntity; @@ -41,6 +42,7 @@ class KakaoOAuthClientTest { @Mock private RestTemplate restTemplate; + @InjectMocks private KakaoOAuthClient kakaoOAuthClient; @BeforeEach diff --git a/backend/src/test/java/zipgo/auth/presentation/AuthControllerMockTest.java b/backend/src/test/java/zipgo/auth/presentation/AuthControllerMockTest.java index cdb6dd717..e2aaffee1 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(authService.login("인가_코드")) + when(authServiceFacade.login("인가_코드")) .thenReturn(토큰); var 리프레시_토큰_쿠키 = ResponseCookie.from("refreshToken", 토큰.refreshToken()).build(); when(refreshTokenCookieProvider.createCookie(토큰.refreshToken())) @@ -61,7 +61,7 @@ class AuthControllerMockTest extends MockMvcTest { void 로그인_성공_후_사용자의_반려동물이_없다면_pets는_빈_배열이다() throws Exception { // given var 토큰 = TokenDto.of("accessTokenValue", "refreshTokenValue"); - when(authService.login("인가_코드")) + when(authServiceFacade.login("인가_코드")) .thenReturn(토큰); var 리프레시_토큰_쿠키 = ResponseCookie.from("refreshToken", 토큰.refreshToken()).build(); when(refreshTokenCookieProvider.createCookie(토큰.refreshToken())) @@ -85,7 +85,7 @@ class AuthControllerMockTest extends MockMvcTest { @Test void 자원_서버의_토큰을_가져오는데_실패하면_예외가_발생한다() throws Exception { // given - when(authService.login("인가_코드")) + when(authServiceFacade.login("인가_코드")) .thenThrow(new OAuthTokenNotBringException()); // when diff --git a/backend/src/test/java/zipgo/auth/presentation/AuthControllerMvcTest.java b/backend/src/test/java/zipgo/auth/presentation/AuthControllerTest.java similarity index 91% rename from backend/src/test/java/zipgo/auth/presentation/AuthControllerMvcTest.java rename to backend/src/test/java/zipgo/auth/presentation/AuthControllerTest.java index 6c2ebe571..f6b1294d8 100644 --- a/backend/src/test/java/zipgo/auth/presentation/AuthControllerMvcTest.java +++ b/backend/src/test/java/zipgo/auth/presentation/AuthControllerTest.java @@ -1,6 +1,5 @@ package zipgo.auth.presentation; -import com.epages.restdocs.apispec.ResourceSnippetDetails; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -28,11 +27,6 @@ class AuthControllerTest extends AcceptanceTest { private static final String TEST_SECRET_KEY = "this1-is2-zipgo3-test4-secret5-key6"; - private ResourceSnippetDetails API_정보 = resourceDetails() - .summary("토큰 갱신 및 로그아웃") - .description("access token을 갱신하고 로그아웃을 합니다"); - - @Autowired private RefreshTokenRepository refreshTokenRepository; @@ -127,18 +121,20 @@ class 로그아웃 { private RestDocumentationFilter 토큰_갱신_성공_문서_생성() { return document("access token 갱신 성공", - API_정보, + resourceDetails().summary("토큰 갱신").description("access token을 갱신합니다"), responseFields( fieldWithPath("accessToken").description("갱신된 accessToken").type(JsonFieldType.STRING) )); } private RestDocumentationFilter 토큰_갱신_실패_문서_생성() { - return document("access token 갱신 실패 (유효하지 않은 인증 형식)", API_정보.responseSchema(에러_응답_형식)); + return document("access token 갱신 실패 (유효하지 않은 인증 형식)", resourceDetails() + .summary("토큰 갱신").responseSchema(에러_응답_형식)); } private RestDocumentationFilter 로그아웃_성공_문서_생성() { - return document("로그아웃 성공", API_정보, + return document("로그아웃 성공", resourceDetails() + .summary("로그아웃"), responseHeaders( headerWithName(SET_COOKIE).description("로그아웃 리프레시 토큰 쿠키") ) @@ -146,7 +142,7 @@ class 로그아웃 { } private RestDocumentationFilter 로그아웃_실패_문서_생성() { - return document("로그아웃 실패 (유효하지 않은 인증 형식)", API_정보.responseSchema(에러_응답_형식)); + return document("로그아웃 실패 (유효하지 않은 인증 형식)", resourceDetails().summary("로그아웃").responseSchema(에러_응답_형식)); } } diff --git a/backend/src/test/java/zipgo/common/acceptance/MockMvcTest.java b/backend/src/test/java/zipgo/common/acceptance/MockMvcTest.java index 51a8ee5a3..c3c3851b2 100644 --- a/backend/src/test/java/zipgo/common/acceptance/MockMvcTest.java +++ b/backend/src/test/java/zipgo/common/acceptance/MockMvcTest.java @@ -14,7 +14,7 @@ import zipgo.admin.application.AdminService; import zipgo.admin.presentation.AdminController; import zipgo.aspect.QueryCounter; -import zipgo.auth.application.AuthService; +import zipgo.auth.application.AuthServiceFacade; import zipgo.auth.presentation.AuthController; import zipgo.auth.presentation.AuthInterceptor; import zipgo.auth.presentation.JwtMandatoryArgumentResolver; @@ -69,7 +69,7 @@ public abstract class MockMvcTest { protected PetQueryService petQueryService; @MockBean - protected AuthService authService; + protected AuthServiceFacade authServiceFacade; @MockBean protected RefreshTokenCookieProvider refreshTokenCookieProvider;