diff --git a/src/main/java/app/nook/user/domain/User.java b/src/main/java/app/nook/user/domain/User.java index 3c62e5a..a8471d6 100644 --- a/src/main/java/app/nook/user/domain/User.java +++ b/src/main/java/app/nook/user/domain/User.java @@ -101,7 +101,7 @@ public void updateGoal(short goal) { this.goal = goal; } - public boolean needsOnboarding() { - return this.onboardingCompletedAt == null; + public boolean isOnboardingCompleted() { + return this.onboardingCompletedAt != null; } } diff --git a/src/main/java/app/nook/user/dto/UserDTO.java b/src/main/java/app/nook/user/dto/UserDTO.java index 391811f..a6c183d 100644 --- a/src/main/java/app/nook/user/dto/UserDTO.java +++ b/src/main/java/app/nook/user/dto/UserDTO.java @@ -21,6 +21,7 @@ public static class LoginResponse { private String nickName; private String accessToken; private String refreshToken; + private boolean onboardingCompleted; } @AllArgsConstructor diff --git a/src/main/java/app/nook/user/oauth/OAuthService.java b/src/main/java/app/nook/user/oauth/OAuthService.java index df7a20e..8da76ad 100644 --- a/src/main/java/app/nook/user/oauth/OAuthService.java +++ b/src/main/java/app/nook/user/oauth/OAuthService.java @@ -94,6 +94,7 @@ public UserDTO.LoginResponse login( .nickName(user.getNickName()) .accessToken(accessToken) .refreshToken(refreshToken) + .onboardingCompleted(user.isOnboardingCompleted()) .build(); } diff --git a/src/main/java/app/nook/user/service/OnboardingService.java b/src/main/java/app/nook/user/service/OnboardingService.java index 6615b77..6a04b1a 100644 --- a/src/main/java/app/nook/user/service/OnboardingService.java +++ b/src/main/java/app/nook/user/service/OnboardingService.java @@ -115,7 +115,7 @@ public OnboardingDto.CompleteResponse completeOnboarding( public OnboardingDto.StatusResponse getOnboardingStatus(Long userId) { User user = getUser(userId); return new OnboardingDto.StatusResponse( - user.needsOnboarding(), + !user.isOnboardingCompleted(), user.getOnboardingCompletedAt() ); } diff --git a/src/main/java/app/nook/user/service/UserService.java b/src/main/java/app/nook/user/service/UserService.java index c8a7e42..c9a208e 100644 --- a/src/main/java/app/nook/user/service/UserService.java +++ b/src/main/java/app/nook/user/service/UserService.java @@ -71,6 +71,7 @@ public UserDTO.LoginResponse devSignUp(UserDTO.DevSignUpRequest request) { .id(savedUser.getId()) .email(savedUser.getEmail()) .nickName(savedUser.getNickName()) + .onboardingCompleted(savedUser.isOnboardingCompleted()) .build(); } @@ -80,6 +81,7 @@ public UserDTO.LoginResponse getThisUser(User user) { .id(user.getId()) .email(user.getEmail()) .nickName(user.getNickName()) + .onboardingCompleted(user.isOnboardingCompleted()) .build(); } @@ -140,6 +142,7 @@ private UserDTO.LoginResponse issueTokens(User user) { .nickName(user.getNickName()) .accessToken(accessToken) .refreshToken(refreshToken) + .onboardingCompleted(user.isOnboardingCompleted()) .build(); } } diff --git a/src/test/java/app/nook/controller/user/AuthControllerTest.java b/src/test/java/app/nook/controller/user/AuthControllerTest.java index 8f07eeb..fce1f8d 100644 --- a/src/test/java/app/nook/controller/user/AuthControllerTest.java +++ b/src/test/java/app/nook/controller/user/AuthControllerTest.java @@ -32,6 +32,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest( @@ -69,6 +70,7 @@ class AuthControllerTest extends AbstractWebMvcRestDocsTests { .nickName("jiwon") .accessToken("test-access-token") .refreshToken("test-refresh-token") + .onboardingCompleted(false) .build(); given(oAuthService.login(any(), any())) @@ -81,6 +83,7 @@ class AuthControllerTest extends AbstractWebMvcRestDocsTests { .content(objectMapper.writeValueAsString(request)) ) .andExpect(status().isOk()) + .andExpect(jsonPath("$.result.onboardingCompleted").value(false)) .andDo(restDocs.document( requestFields( fieldWithPath("code") @@ -94,7 +97,8 @@ class AuthControllerTest extends AbstractWebMvcRestDocsTests { fieldWithPath("result.email").description("이메일"), fieldWithPath("result.nickName").description("닉네임"), fieldWithPath("result.accessToken").description("엑세스 토큰"), - fieldWithPath("result.refreshToken").description("리프레시 토큰") + fieldWithPath("result.refreshToken").description("리프레시 토큰"), + fieldWithPath("result.onboardingCompleted").description("온보딩 완료 여부") ) ) )); @@ -113,6 +117,7 @@ class AuthControllerTest extends AbstractWebMvcRestDocsTests { .nickName("DEV_USER") .accessToken("test-access-token") .refreshToken("test-refresh-token") + .onboardingCompleted(false) .build(); given(userService.devLogin(any())) @@ -125,6 +130,7 @@ class AuthControllerTest extends AbstractWebMvcRestDocsTests { .content(objectMapper.writeValueAsString(request)) ) .andExpect(status().isOk()) + .andExpect(jsonPath("$.result.onboardingCompleted").value(false)) .andDo(restDocs.document( requestFields( fieldWithPath("email") @@ -138,7 +144,8 @@ class AuthControllerTest extends AbstractWebMvcRestDocsTests { fieldWithPath("result.email").description("이메일"), fieldWithPath("result.nickName").description("닉네임"), fieldWithPath("result.accessToken").description("엑세스 토큰"), - fieldWithPath("result.refreshToken").description("리프레시 토큰") + fieldWithPath("result.refreshToken").description("리프레시 토큰"), + fieldWithPath("result.onboardingCompleted").description("온보딩 완료 여부") ) ) )); @@ -154,6 +161,7 @@ class AuthControllerTest extends AbstractWebMvcRestDocsTests { .id(3L) .email("new@test.com") .nickName("NEW_USER") + .onboardingCompleted(false) .build(); given(userService.devSignUp(any())) @@ -165,6 +173,7 @@ class AuthControllerTest extends AbstractWebMvcRestDocsTests { .content(objectMapper.writeValueAsString(request)) ) .andExpect(status().isOk()) + .andExpect(jsonPath("$.result.onboardingCompleted").value(false)) .andDo(restDocs.document( requestFields( fieldWithPath("email").description("DEV 유저 이메일"), @@ -174,7 +183,8 @@ class AuthControllerTest extends AbstractWebMvcRestDocsTests { ApiResponseSnippet.withResult( fieldWithPath("result.id").description("사용자ID"), fieldWithPath("result.email").description("이메일"), - fieldWithPath("result.nickName").description("닉네임") + fieldWithPath("result.nickName").description("닉네임"), + fieldWithPath("result.onboardingCompleted").description("온보딩 완료 여부") ) ) )); @@ -288,6 +298,7 @@ class AuthControllerTest extends AbstractWebMvcRestDocsTests { .id(2L) .email("jiwon@kakao.com") .nickName("jiwon") + .onboardingCompleted(false) .build(); given(userService.getThisUser(any())) @@ -300,13 +311,15 @@ class AuthControllerTest extends AbstractWebMvcRestDocsTests { .with(user(userDetails)) ) .andExpect(status().isOk()) + .andExpect(jsonPath("$.result.onboardingCompleted").value(false)) .andDo(documentWithAuth( "{class-name}/{method-name}", responseFields( ApiResponseSnippet.withResult( fieldWithPath("result.id").description("사용자ID"), fieldWithPath("result.email").description("이메일"), - fieldWithPath("result.nickName").description("닉네임") + fieldWithPath("result.nickName").description("닉네임"), + fieldWithPath("result.onboardingCompleted").description("온보딩 완료 여부") ) ) )); diff --git a/src/test/java/app/nook/user/oauth/OAuthServiceTest.java b/src/test/java/app/nook/user/oauth/OAuthServiceTest.java index a3583ca..c7deedd 100644 --- a/src/test/java/app/nook/user/oauth/OAuthServiceTest.java +++ b/src/test/java/app/nook/user/oauth/OAuthServiceTest.java @@ -21,6 +21,7 @@ import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestClient; +import java.time.LocalDateTime; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -121,6 +122,7 @@ void setUp() { assertThat(result.getEmail()).isEqualTo("user@test.com"); assertThat(result.getNickName()).isEqualTo("user"); assertThat(result.getAccessToken()).isEqualTo("app-access"); + assertThat(result.isOnboardingCompleted()).isFalse(); ArgumentCaptor tokenCaptor = ArgumentCaptor.forClass(TokenRedis.class); verify(tokenRedisRepository).save(tokenCaptor.capture()); assertThat(tokenCaptor.getValue().getId()).isEqualTo(10L); @@ -181,6 +183,7 @@ void setUp() { assertThat(result.getEmail()).isEqualTo("new@test.com"); assertThat(result.getNickName()).isEqualTo("new-user"); assertThat(result.getAccessToken()).isEqualTo("app-access-new"); + assertThat(result.isOnboardingCompleted()).isFalse(); ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class); verify(userRepository).save(userCaptor.capture()); @@ -188,4 +191,55 @@ void setUp() { assertThat(userCaptor.getValue().getProviderId()).isEqualTo("google-sub-new"); server.verify(); } + + @Test + void 소셜로그인_온보딩완료된_기존유저_onboardingCompleted_true() { + String tokenJson = """ + { + "access_token": "google-access", + "expires_in": 3600, + "scope": "email profile", + "token_type": "Bearer", + "id_token": "id-token", + "refresh_token": "google-refresh" + } + """; + + String userInfoJson = """ + { + "sub": "google-sub-done", + "email": "done@test.com", + "name": "done-user", + "picture": "https://img.test/done.png" + } + """; + + server.expect(once(), requestTo("https://google.test/token")) + .andExpect(method(HttpMethod.POST)) + .andRespond(withSuccess(tokenJson, MediaType.APPLICATION_JSON)); + + server.expect(once(), requestTo("https://google.test/userinfo")) + .andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess(userInfoJson, MediaType.APPLICATION_JSON)); + + User user = User.builder() + .email("done@test.com") + .nickName("done-user") + .role(UserRole.USER) + .provider("GOOGLE") + .providerId("google-sub-done") + .build(); + ReflectionTestUtils.setField(user, "id", 30L); + ReflectionTestUtils.setField(user, "onboardingCompletedAt", LocalDateTime.now()); + + given(userRepository.findByEmail(eq("done@test.com"))) + .willReturn(Optional.of(user)); + given(jwtProvider.createAccessToken(user)).willReturn("app-access-done"); + given(jwtProvider.createRefreshToken()).willReturn("app-refresh-done"); + + UserDTO.LoginResponse result = oAuthService.login("google", "auth-code"); + + assertThat(result.isOnboardingCompleted()).isTrue(); + server.verify(); + } } diff --git a/src/test/java/app/nook/user/service/UserServiceTest.java b/src/test/java/app/nook/user/service/UserServiceTest.java index 3fa64e9..45f6c7e 100644 --- a/src/test/java/app/nook/user/service/UserServiceTest.java +++ b/src/test/java/app/nook/user/service/UserServiceTest.java @@ -17,6 +17,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; +import java.time.LocalDateTime; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -65,6 +66,7 @@ class UserServiceTest { assertThat(response.getNickName()).isEqualTo("DEV_USER"); assertThat(response.getAccessToken()).isEqualTo("access-token"); assertThat(response.getRefreshToken()).isEqualTo("refresh-token"); + assertThat(response.isOnboardingCompleted()).isFalse(); ArgumentCaptor tokenCaptor = ArgumentCaptor.forClass(TokenRedis.class); verify(tokenRedisRepository).save(tokenCaptor.capture()); @@ -124,6 +126,7 @@ class UserServiceTest { assertThat(response.getEmail()).isEqualTo("new@test.com"); assertThat(response.getNickName()).isEqualTo("NEW_USER"); assertThat(response.getAccessToken()).isNull(); + assertThat(response.isOnboardingCompleted()).isFalse(); ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class); verify(userRepository).save(userCaptor.capture()); @@ -131,6 +134,28 @@ class UserServiceTest { assertThat(userCaptor.getValue().getProvider()).isEqualTo("DEV"); } + @Test + void devLogin_온보딩완료된유저_onboardingCompleted_true() { + User user = User.builder() + .email("done@test.com") + .nickName("DONE_USER") + .role(UserRole.USER) + .provider("DEV") + .providerId("dev-done") + .build(); + ReflectionTestUtils.setField(user, "id", 2L); + ReflectionTestUtils.setField(user, "onboardingCompletedAt", LocalDateTime.now()); + + given(userRepository.findByEmail(eq("done@test.com"))) + .willReturn(Optional.of(user)); + given(jwtProvider.createAccessToken(user)).willReturn("access-token"); + given(jwtProvider.createRefreshToken()).willReturn("refresh-token"); + + UserDTO.LoginResponse response = userService.devLogin("done@test.com"); + + assertThat(response.isOnboardingCompleted()).isTrue(); + } + @Test void devSignUp_이메일중복_예외() { UserDTO.DevSignUpRequest request = new UserDTO.DevSignUpRequest("dup@test.com", "DUP_USER");