diff --git a/src/main/java/com/moru/server/domain/member/controller/AuthController.java b/src/main/java/com/moru/server/domain/member/controller/AuthController.java index 7ed590c..2675ab7 100644 --- a/src/main/java/com/moru/server/domain/member/controller/AuthController.java +++ b/src/main/java/com/moru/server/domain/member/controller/AuthController.java @@ -39,7 +39,7 @@ public class AuthController { - Authorization 헤더 없이 호출합니다. - kakao: token에 카카오 accessToken을 전달합니다. - google: token에 Google idToken을 전달합니다. - - apple: token에 Apple identityToken을 전달합니다. + - apple: identityToken에 Apple identityToken을 전달합니다. token도 호환됩니다. """; private static final String KAKAO_REQUEST_EXAMPLE = """ { @@ -55,7 +55,7 @@ public class AuthController { """; private static final String APPLE_REQUEST_EXAMPLE = """ { - "token": "apple_identity_token", + "identityToken": "apple_identity_token", "authorizationCode": "apple_authorization_code" } """; diff --git a/src/main/java/com/moru/server/domain/member/dto/AuthRequestDTO.java b/src/main/java/com/moru/server/domain/member/dto/AuthRequestDTO.java index 257eb03..cd6e6b9 100644 --- a/src/main/java/com/moru/server/domain/member/dto/AuthRequestDTO.java +++ b/src/main/java/com/moru/server/domain/member/dto/AuthRequestDTO.java @@ -1,5 +1,6 @@ package com.moru.server.domain.member.dto; +import com.fasterxml.jackson.annotation.JsonAlias; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -8,7 +9,8 @@ public record AuthRequestDTO() { @Schema(description = "소셜 로그인 요청") public record SocialLoginRequest( @NotBlank(message = "소셜 토큰은 필수입니다.") - @Schema(description = "소셜 플랫폼에서 발급받은 토큰. kakao는 accessToken, google은 idToken, apple은 identityToken을 전달합니다.", example = "kakao_access_token") + @JsonAlias("identityToken") + @Schema(description = "소셜 플랫폼에서 발급받은 토큰. kakao는 accessToken, google은 idToken, apple은 token 또는 identityToken을 전달합니다.", example = "kakao_access_token") String token, @Schema(description = "Apple 로그인 필수 인가 코드. kakao/google은 null로 전달합니다.", example = "apple_authorization_code") diff --git a/src/test/java/com/moru/server/domain/member/controller/AuthControllerTest.java b/src/test/java/com/moru/server/domain/member/controller/AuthControllerTest.java new file mode 100644 index 0000000..9b1e374 --- /dev/null +++ b/src/test/java/com/moru/server/domain/member/controller/AuthControllerTest.java @@ -0,0 +1,103 @@ +package com.moru.server.domain.member.controller; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import com.moru.server.domain.member.dto.AuthRequestDTO; +import com.moru.server.domain.member.dto.AuthResponseDTO; +import com.moru.server.domain.member.entity.enums.OAuthProvider; +import com.moru.server.domain.member.service.command.auth.AuthCommandService; + +@ActiveProfiles("test") +@SpringBootTest +@AutoConfigureMockMvc +class AuthControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private AuthCommandService authCommandService; + + @Test + void acceptsIdentityTokenForAppleLogin() throws Exception { + AuthRequestDTO.SocialLoginRequest request = + new AuthRequestDTO.SocialLoginRequest("apple-identity-token", "apple-authorization-code"); + when(authCommandService.loginWithSocial(OAuthProvider.APPLE, request)) + .thenReturn(socialLoginResponse()); + + mockMvc.perform(post("/auth/login/apple") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "identityToken": "apple-identity-token", + "authorizationCode": "apple-authorization-code" + } + """)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isSuccess").value(true)) + .andExpect(jsonPath("$.result.memberId").value(1)); + + verify(authCommandService).loginWithSocial(OAuthProvider.APPLE, request); + } + + @Test + void keepsSupportingTokenForAppleLogin() throws Exception { + AuthRequestDTO.SocialLoginRequest request = + new AuthRequestDTO.SocialLoginRequest("apple-identity-token", "apple-authorization-code"); + when(authCommandService.loginWithSocial(OAuthProvider.APPLE, request)) + .thenReturn(socialLoginResponse()); + + mockMvc.perform(post("/auth/login/apple") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "token": "apple-identity-token", + "authorizationCode": "apple-authorization-code" + } + """)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isSuccess").value(true)); + + verify(authCommandService).loginWithSocial(OAuthProvider.APPLE, request); + } + + @Test + void rejectsAppleLoginWithoutSocialToken() throws Exception { + mockMvc.perform(post("/auth/login/apple") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "authorizationCode": "apple-authorization-code" + } + """)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.isSuccess").value(false)) + .andExpect(jsonPath("$.code").value("COMMON400")); + + verifyNoInteractions(authCommandService); + } + + private AuthResponseDTO.SocialLoginResponse socialLoginResponse() { + return AuthResponseDTO.SocialLoginResponse.builder() + .memberId(1L) + .accessToken("access-token") + .refreshToken("refresh-token") + .isNewMember(true) + .onboardingCompleted(false) + .build(); + } +}