Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """
{
Expand All @@ -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"
}
""";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.moru.server.domain.member.dto;

import com.fasterxml.jackson.annotation.JsonAlias;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JsonAlias는 Jackson 역직렬화에만 적용되고 springdoc이 만드는 OpenAPI 스키마엔 반영 안 된다고 합니다. @Schema description에 텍스트로만 적어놨는데, 스키마 필드 목록엔 여전히 token만 나와서 문서만 보고 연동하는 프론트 개발자는 identityToken 지원 여부를 놓칠 수 있다고 하네용.
해결법: 별도 스키마 수정까진 필요 없고, 앱 팀에 별도 공지하거나 PR 설명에 "identityToken도 받음" 명시하면 좋을 것 같습니다!

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;

Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Loading