diff --git a/src/main/java/com/moabam/api/application/AuthenticationService.java b/src/main/java/com/moabam/api/application/AuthenticationService.java index 7c407afc..11397a03 100644 --- a/src/main/java/com/moabam/api/application/AuthenticationService.java +++ b/src/main/java/com/moabam/api/application/AuthenticationService.java @@ -2,13 +2,16 @@ import static com.moabam.global.common.util.OAuthParameterNames.*; -import java.io.IOException; - -import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.web.util.UriComponentsBuilder; import com.moabam.api.dto.AuthorizationCodeRequest; +import com.moabam.api.dto.AuthorizationCodeResponse; +import com.moabam.api.dto.AuthorizationTokenRequest; +import com.moabam.api.dto.AuthorizationTokenResponse; import com.moabam.api.dto.OAuthMapper; import com.moabam.global.common.util.GlobalConstant; import com.moabam.global.config.OAuthConfig; @@ -23,8 +26,9 @@ public class AuthenticationService { private final OAuthConfig oAuthConfig; + private final OAuth2AuthorizationServerRequestService oauth2AuthorizationServerRequestService; - private String getAuthorizaionCodeUri() { + private String getAuthorizationCodeUri() { AuthorizationCodeRequest authorizationCodeRequest = OAuthMapper.toAuthorizationCodeRequest(oAuthConfig); return generateQueryParamsWith(authorizationCodeRequest); } @@ -44,14 +48,45 @@ private String generateQueryParamsWith(AuthorizationCodeRequest authorizationCod return authorizationCodeUri.toUriString(); } - public void redirectToLoginPage(HttpServletResponse httpServletResponse) { - String authorizationCodeUri = getAuthorizaionCodeUri(); + private void validAuthorizationGrant(String code) { + if (code == null) { + throw new BadRequestException(ErrorMessage.GRANT_FAILED); + } + } + + private AuthorizationTokenResponse issueTokenToAuthorizationServer(String code) { + AuthorizationTokenRequest authorizationTokenRequest = OAuthMapper.toAuthorizationTokenRequest(oAuthConfig, + code); + MultiValueMap uriParams = generateTokenRequest(authorizationTokenRequest); + ResponseEntity authorizationTokenResponse = + oauth2AuthorizationServerRequestService.requestAuthorizationServer(oAuthConfig.provider().tokenUri(), + uriParams); + + return authorizationTokenResponse.getBody(); + } - try { - httpServletResponse.setContentType(MediaType.APPLICATION_FORM_URLENCODED + GlobalConstant.CHARSET_UTF_8); - httpServletResponse.sendRedirect(authorizationCodeUri); - } catch (IOException e) { - throw new BadRequestException(ErrorMessage.REQUEST_FAILD); + private MultiValueMap generateTokenRequest(AuthorizationTokenRequest authorizationTokenRequest) { + MultiValueMap contents = new LinkedMultiValueMap<>(); + contents.add(GRANT_TYPE, authorizationTokenRequest.grantType()); + contents.add(CLIENT_ID, authorizationTokenRequest.clientId()); + contents.add(REDIRECT_URI, authorizationTokenRequest.redirectUri()); + contents.add(CODE, authorizationTokenRequest.code()); + + if (authorizationTokenRequest.clientSecret() != null) { + contents.add(CLIENT_SECRET, authorizationTokenRequest.clientSecret()); } + + return contents; + } + + public void redirectToLoginPage(HttpServletResponse httpServletResponse) { + String authorizationCodeUri = getAuthorizationCodeUri(); + oauth2AuthorizationServerRequestService.loginRequest(httpServletResponse, authorizationCodeUri); + } + + public void requestToken(AuthorizationCodeResponse authorizationCodeResponse) { + validAuthorizationGrant(authorizationCodeResponse.code()); + issueTokenToAuthorizationServer(authorizationCodeResponse.code()); + // TODO 발급한 토큰으로 사용자의 정보 얻어와야함 : 프로필 & 닉네임 } } diff --git a/src/main/java/com/moabam/api/application/OAuth2AuthorizationServerRequestService.java b/src/main/java/com/moabam/api/application/OAuth2AuthorizationServerRequestService.java new file mode 100644 index 00000000..339f1503 --- /dev/null +++ b/src/main/java/com/moabam/api/application/OAuth2AuthorizationServerRequestService.java @@ -0,0 +1,55 @@ +package com.moabam.api.application; + +import java.io.IOException; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import com.moabam.api.dto.AuthorizationTokenResponse; +import com.moabam.global.common.util.GlobalConstant; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.model.ErrorMessage; + +import jakarta.servlet.http.HttpServletResponse; + +@Service +public class OAuth2AuthorizationServerRequestService { + + private final RestTemplate restTemplate; + + public OAuth2AuthorizationServerRequestService() { + restTemplate = new RestTemplate(); + } + + public void loginRequest(HttpServletResponse httpServletResponse, String authorizationCodeUri) { + try { + httpServletResponse.setContentType(MediaType.APPLICATION_FORM_URLENCODED + GlobalConstant.CHARSET_UTF_8); + httpServletResponse.sendRedirect(authorizationCodeUri); + } catch (IOException e) { + throw new BadRequestException(ErrorMessage.REQUEST_FAILED); + } + } + + public ResponseEntity requestAuthorizationServer(String tokenUri, + MultiValueMap uriParams) { + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.CONTENT_TYPE, + MediaType.APPLICATION_FORM_URLENCODED_VALUE + GlobalConstant.CHARSET_UTF_8); + HttpEntity> httpEntity = new HttpEntity<>(uriParams, headers); + + ResponseEntity authorizationTokenResponse = restTemplate.exchange(tokenUri, + HttpMethod.POST, httpEntity, AuthorizationTokenResponse.class); + + if (authorizationTokenResponse.getStatusCode().isError()) { + throw new BadRequestException(ErrorMessage.REQUEST_FAILED); + } + + return authorizationTokenResponse; + } +} diff --git a/src/main/java/com/moabam/api/domain/entity/Member.java b/src/main/java/com/moabam/api/domain/entity/Member.java index f7108bac..1d51cb4d 100644 --- a/src/main/java/com/moabam/api/domain/entity/Member.java +++ b/src/main/java/com/moabam/api/domain/entity/Member.java @@ -29,7 +29,7 @@ @Entity @Getter @Table(name = "member") -@SQLDelete(sql = "UPDATE member SET deleted_at = CURRENT_TIMESTAMP where participant_id = ?") +@SQLDelete(sql = "UPDATE member SET deleted_at = CURRENT_TIMESTAMP where id = ?") @Where(clause = "deleted_at IS NOT NULL") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Member extends BaseTimeEntity { @@ -72,7 +72,7 @@ public class Member extends BaseTimeEntity { @Enumerated(EnumType.STRING) @Column(name = "role", nullable = false) - @ColumnDefault("USER") + @ColumnDefault("'USER'") private Role role; @Column(name = "deleted_at") @@ -87,4 +87,24 @@ private Member(Long id, String socialId, String nickname, String profileImage, B this.bug = requireNonNull(bug); this.role = Role.USER; } + + public void enterMorningRoom() { + currentMorningCount++; + } + + public void enterNightRoom() { + currentNightCount++; + } + + public void exitMorningRoom() { + if (currentMorningCount > 0) { + currentMorningCount--; + } + } + + public void exitNightRoom() { + if (currentMorningCount > 0) { + currentNightCount--; + } + } } diff --git a/src/main/java/com/moabam/api/dto/AuthorizationCodeResponse.java b/src/main/java/com/moabam/api/dto/AuthorizationCodeResponse.java new file mode 100644 index 00000000..da8c19c8 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/AuthorizationCodeResponse.java @@ -0,0 +1,10 @@ +package com.moabam.api.dto; + +public record AuthorizationCodeResponse( + String code, + String error, + String errorDescription, + String state +) { + +} diff --git a/src/main/java/com/moabam/api/dto/AuthorizationTokenRequest.java b/src/main/java/com/moabam/api/dto/AuthorizationTokenRequest.java new file mode 100644 index 00000000..17468c76 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/AuthorizationTokenRequest.java @@ -0,0 +1,24 @@ +package com.moabam.api.dto; + +import static java.util.Objects.*; + +import lombok.Builder; + +public record AuthorizationTokenRequest( + String grantType, + String clientId, + String redirectUri, + String code, + String clientSecret +) { + + @Builder + public AuthorizationTokenRequest(String grantType, String clientId, String redirectUri, String code, + String clientSecret) { + this.grantType = requireNonNull(grantType); + this.clientId = requireNonNull(clientId); + this.redirectUri = requireNonNull(redirectUri); + this.code = requireNonNull(code); + this.clientSecret = clientSecret; + } +} diff --git a/src/main/java/com/moabam/api/dto/AuthorizationTokenResponse.java b/src/main/java/com/moabam/api/dto/AuthorizationTokenResponse.java new file mode 100644 index 00000000..80a0d5c6 --- /dev/null +++ b/src/main/java/com/moabam/api/dto/AuthorizationTokenResponse.java @@ -0,0 +1,15 @@ +package com.moabam.api.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record AuthorizationTokenResponse( + @JsonProperty("token_type") String tokenType, + @JsonProperty("access_token") String accessToken, + @JsonProperty("id_token") String idToken, + @JsonProperty("expires_in") String expiresIn, + @JsonProperty("refresh_token") String refreshToken, + @JsonProperty("refresh_token_expires_in") String refreshTokenExpiresIn, + @JsonProperty("scope") String scope +) { + +} diff --git a/src/main/java/com/moabam/api/dto/OAuthMapper.java b/src/main/java/com/moabam/api/dto/OAuthMapper.java index 8b82483b..6550f32f 100644 --- a/src/main/java/com/moabam/api/dto/OAuthMapper.java +++ b/src/main/java/com/moabam/api/dto/OAuthMapper.java @@ -15,4 +15,14 @@ public static AuthorizationCodeRequest toAuthorizationCodeRequest(OAuthConfig oA .scope(oAuthConfig.client().scope()) .build(); } + + public static AuthorizationTokenRequest toAuthorizationTokenRequest(OAuthConfig oAuthConfig, String code) { + return AuthorizationTokenRequest.builder() + .grantType(oAuthConfig.client().authorizationGrantType()) + .clientId(oAuthConfig.client().clientId()) + .redirectUri(oAuthConfig.provider().redirectUri()) + .code(code) + .clientSecret(oAuthConfig.client().clientSecret()) + .build(); + } } diff --git a/src/main/java/com/moabam/api/presentation/MemberController.java b/src/main/java/com/moabam/api/presentation/MemberController.java index fd2e730f..b2c60743 100644 --- a/src/main/java/com/moabam/api/presentation/MemberController.java +++ b/src/main/java/com/moabam/api/presentation/MemberController.java @@ -1,10 +1,12 @@ package com.moabam.api.presentation; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.moabam.api.application.AuthenticationService; +import com.moabam.api.dto.AuthorizationCodeResponse; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -20,4 +22,9 @@ public class MemberController { public void socialLogin(HttpServletResponse httpServletResponse) { authenticationService.redirectToLoginPage(httpServletResponse); } + + @GetMapping("/login/kakao/oauth") + public void authorizationTokenIssue(@ModelAttribute AuthorizationCodeResponse authorizationCodeResponse) { + authenticationService.requestToken(authorizationCodeResponse); + } } diff --git a/src/main/java/com/moabam/global/common/util/OAuthParameterNames.java b/src/main/java/com/moabam/global/common/util/OAuthParameterNames.java index efc65e59..05e26dea 100644 --- a/src/main/java/com/moabam/global/common/util/OAuthParameterNames.java +++ b/src/main/java/com/moabam/global/common/util/OAuthParameterNames.java @@ -11,4 +11,6 @@ public class OAuthParameterNames { public static final String CLIENT_ID = "client_id"; public static final String REDIRECT_URI = "redirect_uri"; public static final String SCOPE = "scope"; + public static final String GRANT_TYPE = "grant_type"; + public static final String CLIENT_SECRET = "client_secret"; } diff --git a/src/main/java/com/moabam/global/config/OAuthConfig.java b/src/main/java/com/moabam/global/config/OAuthConfig.java index cff7f45b..c83b6a77 100644 --- a/src/main/java/com/moabam/global/config/OAuthConfig.java +++ b/src/main/java/com/moabam/global/config/OAuthConfig.java @@ -13,6 +13,7 @@ public record OAuthConfig( public record Client( String provider, String clientId, + String clientSecret, String authorizationGrantType, List scope ) { @@ -21,7 +22,8 @@ public record Client( public record Provider( String authorizationUri, - String redirectUri + String redirectUri, + String tokenUri ) { } diff --git a/src/main/java/com/moabam/global/error/model/ErrorMessage.java b/src/main/java/com/moabam/global/error/model/ErrorMessage.java index 522e1dee..f874a1d3 100644 --- a/src/main/java/com/moabam/global/error/model/ErrorMessage.java +++ b/src/main/java/com/moabam/global/error/model/ErrorMessage.java @@ -8,13 +8,15 @@ public enum ErrorMessage { INVALID_REQUEST_FIELD("올바른 요청 정보가 아닙니다."), + ROOM_NOT_FOUND("존재하지 않는 방 입니다."), ROOM_MAX_USER_COUNT_MODIFY_FAIL("잘못된 최대 인원수 설정입니다."), ROOM_MODIFY_UNAUTHORIZED_REQUEST("방장이 아닌 사용자는 방을 수정할 수 없습니다."), PARTICIPANT_NOT_FOUND("방에 대한 참여자의 정보가 없습니다."), - LOGIN_FAILED("로그인에 실패했습니다."), - REQUEST_FAILD("네트우크 접근 실패입니다."), + LOGIN_FAILED("로그인에 실패했습니다."), + REQUEST_FAILED("네트워크 접근 실패입니다."), + GRANT_FAILED("인가 코드 실패"), MEMBER_NOT_FOUND("존재하지 않는 회원입니다."), INVALID_BUG_COUNT("벌레 개수는 0 이상이어야 합니다."), diff --git a/src/test/java/com/moabam/api/application/AuthenticationServiceTest.java b/src/test/java/com/moabam/api/application/AuthenticationServiceTest.java index 20fea0af..744b466f 100644 --- a/src/test/java/com/moabam/api/application/AuthenticationServiceTest.java +++ b/src/test/java/com/moabam/api/application/AuthenticationServiceTest.java @@ -3,9 +3,8 @@ import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; +import static org.mockito.BDDMockito.*; -import java.io.IOException; import java.util.List; import org.assertj.core.api.Assertions; @@ -14,26 +13,32 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; -import org.mockito.Mockito; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.http.MediaType; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.util.ReflectionTestUtils; import com.moabam.api.dto.AuthorizationCodeRequest; +import com.moabam.api.dto.AuthorizationCodeResponse; +import com.moabam.api.dto.AuthorizationTokenRequest; +import com.moabam.api.dto.AuthorizationTokenResponse; import com.moabam.api.dto.OAuthMapper; -import com.moabam.global.common.util.GlobalConstant; +import com.moabam.fixture.AuthorizationTokenResponseFixture; import com.moabam.global.config.OAuthConfig; import com.moabam.global.error.exception.BadRequestException; import com.moabam.global.error.model.ErrorMessage; -import jakarta.servlet.http.HttpServletResponse; - @ExtendWith(MockitoExtension.class) class AuthenticationServiceTest { @InjectMocks AuthenticationService authenticationService; + + @Mock + OAuth2AuthorizationServerRequestService oAuth2AuthorizationServerRequestService; + OAuthConfig oauthConfig; AuthenticationService noPropertyService; OAuthConfig noOAuthConfig; @@ -41,17 +46,17 @@ class AuthenticationServiceTest { @BeforeEach public void initParams() { oauthConfig = new OAuthConfig( - new OAuthConfig.Provider("https://authorization/url", "http://redirect/url"), - new OAuthConfig.Client("provider", "testtestetsttest", "authorization_code", + new OAuthConfig.Provider("https://authorization/url", "http://redirect/url", "http://token/url"), + new OAuthConfig.Client("provider", "testtestetsttest", "testtesttest", "authorization_code", List.of("profile_nickname", "profile_image")) ); ReflectionTestUtils.setField(authenticationService, "oAuthConfig", oauthConfig); noOAuthConfig = new OAuthConfig( - new OAuthConfig.Provider(null, null), - new OAuthConfig.Client(null, null, null, null) + new OAuthConfig.Provider(null, null, null), + new OAuthConfig.Client(null, null, null, null, null) ); - noPropertyService = new AuthenticationService(noOAuthConfig); + noPropertyService = new AuthenticationService(noOAuthConfig, oAuth2AuthorizationServerRequestService); } @@ -77,42 +82,79 @@ void authorization_code_request_mapping_success() { ); } - @DisplayName("인가코드 URI 생성 성공") + @DisplayName("redirect 로그인페이지 성공") @Test - void authorization_code_uri_generate_success() throws IOException { + void redirect_loginPage_success() { // given - String uri = "https://authorization/url?" - + "response_type=code&" - + "client_id=testtestetsttest&" - + "redirect_uri=http://redirect/url&scope=profile_nickname,profile_image"; - - MockHttpServletResponse mockHttpServletResponse = mockHttpServletResponse = new MockHttpServletResponse(); + MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse(); // when authenticationService.redirectToLoginPage(mockHttpServletResponse); // then - assertThat(mockHttpServletResponse.getContentType()) - .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED + GlobalConstant.CHARSET_UTF_8); - assertThat(mockHttpServletResponse.getRedirectedUrl()).isEqualTo(uri); + verify(oAuth2AuthorizationServerRequestService).loginRequest(eq(mockHttpServletResponse), anyString()); } - @DisplayName("redirect 실패 테스트") + @DisplayName("인가코드 반환 실패") @Test - void redirect_fail_test() { - // given - HttpServletResponse mockHttpServletResponse = Mockito.mock(HttpServletResponse.class); + void authorization_grant_fail() { + // Given + AuthorizationCodeResponse authorizationCodeResponse = new AuthorizationCodeResponse(null, "error", + "errorDescription", null); + + // When + Then + assertThatThrownBy(() -> authenticationService.requestToken(authorizationCodeResponse)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.GRANT_FAILED.getMessage()); + } + + @DisplayName("인가코드 반환 성공") + @Test + void authorization_grant_success() { + // Given + AuthorizationCodeResponse authorizationCodeResponse = new AuthorizationCodeResponse("test", null, + null, null); + AuthorizationTokenResponse authorizationTokenResponse = + AuthorizationTokenResponseFixture.authorizationTokenResponse(); + + // When + when(oAuth2AuthorizationServerRequestService.requestAuthorizationServer(anyString(), any())).thenReturn( + new ResponseEntity<>(authorizationTokenResponse, HttpStatus.OK)); + + // When + Then + assertThatNoException().isThrownBy(() -> authenticationService.requestToken(authorizationCodeResponse)); + } - try { - doThrow(IOException.class).when(mockHttpServletResponse).sendRedirect(any(String.class)); + @DisplayName("토큰 요청 매퍼 실패 - code null") + @Test + void token_request_mapping_failBy_code() { + // When + Then + Assertions.assertThatThrownBy(() -> OAuthMapper.toAuthorizationTokenRequest(oauthConfig, null)) + .isInstanceOf(NullPointerException.class); + } - assertThatThrownBy(() -> { - // When + Then - authenticationService.redirectToLoginPage(mockHttpServletResponse); - }).isExactlyInstanceOf(BadRequestException.class) - .hasMessage(ErrorMessage.REQUEST_FAILD.getMessage()); - } catch (Exception ignored) { + @DisplayName("토큰 요청 매퍼 실패 - config 에러") + @Test + void token_request_mapping_failBy_config() { + // When + Then + Assertions.assertThatThrownBy(() -> OAuthMapper.toAuthorizationTokenRequest(noOAuthConfig, "Test")) + .isInstanceOf(NullPointerException.class); + } + + @DisplayName("토큰 요청 매퍼 성공") + @Test + void token_request_mapping_success() { + // Given + String code = "Test"; + AuthorizationTokenRequest authorizationTokenRequest = OAuthMapper.toAuthorizationTokenRequest(oauthConfig, + code); - } + // When + Then + assertThat(authorizationTokenRequest).isNotNull(); + assertAll( + () -> assertThat(authorizationTokenRequest.clientId()).isEqualTo(oauthConfig.client().clientId()), + () -> assertThat(authorizationTokenRequest.redirectUri()).isEqualTo(oauthConfig.provider().redirectUri()), + () -> assertThat(authorizationTokenRequest.code()).isEqualTo(code) + ); } } diff --git a/src/test/java/com/moabam/api/application/OAuth2AuthorizationServerRequestServiceTest.java b/src/test/java/com/moabam/api/application/OAuth2AuthorizationServerRequestServiceTest.java new file mode 100644 index 00000000..305aa885 --- /dev/null +++ b/src/test/java/com/moabam/api/application/OAuth2AuthorizationServerRequestServiceTest.java @@ -0,0 +1,150 @@ +package com.moabam.api.application; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.io.IOException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +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.HttpMethod; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import com.moabam.api.dto.AuthorizationTokenResponse; +import com.moabam.global.common.util.GlobalConstant; +import com.moabam.global.error.exception.BadRequestException; +import com.moabam.global.error.model.ErrorMessage; + +import jakarta.servlet.http.HttpServletResponse; + +@ExtendWith(MockitoExtension.class) +public class OAuth2AuthorizationServerRequestServiceTest { + + @InjectMocks + OAuth2AuthorizationServerRequestService oAuth2AuthorizationServerRequestService; + + @Mock + RestTemplate restTemplate; + + String uri = "https://authorization/url?" + + "response_type=code&" + + "client_id=testtestetsttest&" + + "redirect_uri=http://redirect/url&scope=profile_nickname,profile_image"; + + @BeforeEach + void initField() { + ReflectionTestUtils.setField(oAuth2AuthorizationServerRequestService, "restTemplate", restTemplate); + } + + @DisplayName("로그인 페이지 접근 요청") + @Nested + class LoginPage { + + @DisplayName("로그인 페이지 접근 요청 성공") + @Test + void authorization_code_uri_generate_success() throws IOException { + // given + MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse(); + + // when + oAuth2AuthorizationServerRequestService.loginRequest(mockHttpServletResponse, uri); + + // then + assertThat(mockHttpServletResponse.getContentType()) + .isEqualTo(MediaType.APPLICATION_FORM_URLENCODED + GlobalConstant.CHARSET_UTF_8); + assertThat(mockHttpServletResponse.getRedirectedUrl()).isEqualTo(uri); + } + + @DisplayName("redirect 실패 테스트") + @Test + void redirect_fail_test() { + // given + HttpServletResponse mockHttpServletResponse = Mockito.mock(HttpServletResponse.class); + + try { + doThrow(IOException.class).when(mockHttpServletResponse).sendRedirect(any(String.class)); + + assertThatThrownBy(() -> { + // When + Then + oAuth2AuthorizationServerRequestService.loginRequest(mockHttpServletResponse, uri); + }).isExactlyInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.REQUEST_FAILED.getMessage()); + } catch (Exception ignored) { + + } + } + } + + @DisplayName("Authorization Server에 토큰 발급 요청") + @Nested + class TokenRequest { + + @DisplayName("토큰 발급 요청 성공") + @Test + void toekn_issue_request_success() { + // given + String tokenUri = "test"; + MultiValueMap uriParams = new LinkedMultiValueMap<>(); + ResponseEntity authorizationTokenResponse = mock(ResponseEntity.class); + + // when + when(restTemplate.exchange( + eq(tokenUri), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(AuthorizationTokenResponse.class)) + ).thenReturn(authorizationTokenResponse); + + // When + when(authorizationTokenResponse.getStatusCode()).thenReturn(HttpStatusCode.valueOf(200)); + + // Then + assertThatNoException().isThrownBy( + () -> oAuth2AuthorizationServerRequestService.requestAuthorizationServer(tokenUri, uriParams)); + } + + @DisplayName("토큰 발급 요청 실패") + @ParameterizedTest + @ValueSource(ints = {400, 401, 403, 429, 500, 502, 503}) + void token_issue_request_fail(int code) { + // Given + String tokenUri = "test"; + MultiValueMap uriParams = new LinkedMultiValueMap<>(); + + ResponseEntity authorizationTokenResponse = mock(ResponseEntity.class); + + when(restTemplate.exchange( + eq(tokenUri), + eq(HttpMethod.POST), + any(HttpEntity.class), + eq(AuthorizationTokenResponse.class)) + ).thenReturn(authorizationTokenResponse); + + // When + when(authorizationTokenResponse.getStatusCode()).thenReturn(HttpStatusCode.valueOf(code)); + + // Then + assertThatThrownBy( + () -> oAuth2AuthorizationServerRequestService.requestAuthorizationServer(tokenUri, uriParams)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ErrorMessage.REQUEST_FAILED.getMessage()); + } + } +} diff --git a/src/test/java/com/moabam/api/domain/entity/MemberTest.java b/src/test/java/com/moabam/api/domain/entity/MemberTest.java index 8255d843..1a947ac8 100644 --- a/src/test/java/com/moabam/api/domain/entity/MemberTest.java +++ b/src/test/java/com/moabam/api/domain/entity/MemberTest.java @@ -4,9 +4,11 @@ import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import com.moabam.api.domain.entity.enums.Role; +import com.moabam.fixture.MemberFixture; import com.moabam.global.common.util.BaseImageUrl; class MemberTest { @@ -70,4 +72,42 @@ void create_member_failBy_nickname() { .socialId(socialId)::build) .isInstanceOf(NullPointerException.class); } + + @DisplayName("멤버 방 출입 기능 테스트") + @Nested + class MemberRoomInOut { + + @DisplayName("회원 방 입장 성공") + @Test + void member_room_enter_success() { + // given + Member member = MemberFixture.member(); + + // when + int beforeMorningCount = member.getCurrentMorningCount(); + member.enterMorningRoom(); + + int beforeNightCount = member.getCurrentNightCount(); + member.enterNightRoom(); + + // then + assertThat(member.getCurrentMorningCount()).isEqualTo(beforeMorningCount + 1); + assertThat(member.getCurrentMorningCount()).isEqualTo(beforeNightCount + 1); + } + + @DisplayName("회원 방 탈출 성공") + @Test + void member_room_exit_success() { + // given + Member member = MemberFixture.member(); + + // when + member.exitMorningRoom(); + member.exitNightRoom(); + + // then + assertThat(member.getCurrentMorningCount()).isZero(); + assertThat(member.getCurrentMorningCount()).isZero(); + } + } } diff --git a/src/test/java/com/moabam/api/presentation/MemberControllerTest.java b/src/test/java/com/moabam/api/presentation/MemberControllerTest.java index cd4da732..39742fae 100644 --- a/src/test/java/com/moabam/api/presentation/MemberControllerTest.java +++ b/src/test/java/com/moabam/api/presentation/MemberControllerTest.java @@ -1,34 +1,50 @@ package com.moabam.api.presentation; -import static com.moabam.global.common.util.OAuthParameterNames.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static com.moabam.global.common.util.OAuthParameterNames.CLIENT_ID; +import static com.moabam.global.common.util.OAuthParameterNames.CLIENT_SECRET; +import static com.moabam.global.common.util.OAuthParameterNames.CODE; +import static com.moabam.global.common.util.OAuthParameterNames.GRANT_TYPE; +import static com.moabam.global.common.util.OAuthParameterNames.REDIRECT_URI; +import static com.moabam.global.common.util.OAuthParameterNames.RESPONSE_TYPE; +import static com.moabam.global.common.util.OAuthParameterNames.SCOPE; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.test.web.client.match.MockRestRequestMatchers; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import com.fasterxml.jackson.databind.ObjectMapper; -import com.moabam.api.application.AuthenticationService; +import com.moabam.api.application.OAuth2AuthorizationServerRequestService; +import com.moabam.api.dto.AuthorizationCodeResponse; +import com.moabam.fixture.AuthorizationTokenResponseFixture; import com.moabam.global.common.util.GlobalConstant; import com.moabam.global.config.OAuthConfig; -@SpringBootTest( - properties = { - "oauth2.provider.authorization-uri=https://kauth.kakao.com/oauth/authorize", - "oauth2.provider.redirect-uri=http://localhost:8080/members/login/kakao/oauth", - "oauth2.client.client-id=testtesttesttesttesttesttesttesttest", - "oauth2.client.authorization-grant-type=authorization_code", - "oauth2.client.scope=profile_nickname,profile_image" - } -) +@SpringBootTest @AutoConfigureMockMvc +@ActiveProfiles("test") class MemberControllerTest { @Autowired @@ -38,11 +54,27 @@ class MemberControllerTest { ObjectMapper objectMapper; @Autowired - AuthenticationService authenticationService; + OAuth2AuthorizationServerRequestService oAuth2AuthorizationServerRequestService; @Autowired OAuthConfig oAuthConfig; + static RestTemplate restTemplate; + + MockRestServiceServer mockRestServiceServer; + + @BeforeAll + static void allSetUp() { + restTemplate = new RestTemplate(); + } + + @BeforeEach + void setUp() { + // TODO 추후 RestTemplate -> REstTemplateBuilder & Bean등록하여 테스트 코드도 일부 변경됨 + ReflectionTestUtils.setField(oAuth2AuthorizationServerRequestService, "restTemplate", restTemplate); + mockRestServiceServer = MockRestServiceServer.createServer(restTemplate); + } + @DisplayName("인가 코드 받기 위한 로그인 페이지 요청") @Test void authorization_code_request_success() throws Exception { @@ -61,6 +93,33 @@ void authorization_code_request_success() throws Exception { result.andExpect(status().is3xxRedirection()) .andExpect(header().string("Content-type", MediaType.APPLICATION_FORM_URLENCODED_VALUE + GlobalConstant.CHARSET_UTF_8)) - .andExpect(redirectedUrl(uri)); + .andExpect(MockMvcResultMatchers.redirectedUrl(uri)); + } + + @DisplayName("Authorization Server에 토큰 발급 요청") + @Test + void authorization_token_request_success() throws Exception { + // given + MultiValueMap contentParams = new LinkedMultiValueMap<>(); + contentParams.add(GRANT_TYPE, oAuthConfig.client().authorizationGrantType()); + contentParams.add(CLIENT_ID, oAuthConfig.client().clientId()); + contentParams.add(REDIRECT_URI, oAuthConfig.provider().redirectUri()); + contentParams.add(CODE, "test"); + contentParams.add(CLIENT_SECRET, oAuthConfig.client().clientSecret()); + + String response = objectMapper.writeValueAsString( + AuthorizationTokenResponseFixture.authorizationTokenResponse()); + AuthorizationCodeResponse authorizationCodeResponse = new AuthorizationCodeResponse("test", null, null, null); + + mockRestServiceServer.expect(requestTo(oAuthConfig.provider().tokenUri())) + .andExpect(MockRestRequestMatchers.content().formData(contentParams)) + .andExpect(MockRestRequestMatchers.content().contentType("application/x-www-form-urlencoded;charset=UTF-8")) + .andExpect(method(HttpMethod.POST)) + .andRespond(withSuccess(response, MediaType.APPLICATION_JSON)); + + // expected + ResultActions result = mockMvc.perform(get("/members/login/kakao/oauth") + .flashAttr("authorizationCodeResponse", authorizationCodeResponse)) + .andExpect(status().isOk()); } } diff --git a/src/test/java/com/moabam/fixture/AuthorizationTokenResponseFixture.java b/src/test/java/com/moabam/fixture/AuthorizationTokenResponseFixture.java new file mode 100644 index 00000000..ecfa1506 --- /dev/null +++ b/src/test/java/com/moabam/fixture/AuthorizationTokenResponseFixture.java @@ -0,0 +1,19 @@ +package com.moabam.fixture; + +import com.moabam.api.dto.AuthorizationTokenResponse; + +public class AuthorizationTokenResponseFixture { + + static final String tokenType = "tokenType"; + static final String accessToken = "accessToken"; + static final String idToken = "id"; + static final String expiresin = "exp"; + static final String refreshToken = "ref"; + static final String refreshTokenExpiresIn = "refs"; + static final String scope = "scope"; + + public static AuthorizationTokenResponse authorizationTokenResponse() { + return new AuthorizationTokenResponse(tokenType, accessToken, idToken, + expiresin, refreshToken, refreshTokenExpiresIn, scope); + } +} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index b149d76f..94864159 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -9,6 +9,6 @@ oauth2: - profile_image provider: - authorization_uri: https://test.com/test/test - redirect_uri: http://test:8080/test - token_uri: https://test.test.com/test/test + authorization_uri: https://authorization.com/test/test + redirect_uri: http://redirect:8080/test + token_uri: https://token.com/test/test