|
|
|
@@ -0,0 +1,219 @@
|
|
|
|
|
package kr.co.vividnext.sodalive.member
|
|
|
|
|
|
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper
|
|
|
|
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
|
|
|
|
import kr.co.vividnext.sodalive.creator.admin.member.CreatorAdminMemberService
|
|
|
|
|
import kr.co.vividnext.sodalive.marketing.AdTrackingHistoryType
|
|
|
|
|
import kr.co.vividnext.sodalive.marketing.AdTrackingService
|
|
|
|
|
import kr.co.vividnext.sodalive.member.login.LoginRequest
|
|
|
|
|
import kr.co.vividnext.sodalive.member.login.LoginResponse
|
|
|
|
|
import kr.co.vividnext.sodalive.member.signUp.SignUpRequestV2
|
|
|
|
|
import kr.co.vividnext.sodalive.member.signUp.SignUpResponse
|
|
|
|
|
import kr.co.vividnext.sodalive.member.social.SocialAuthService
|
|
|
|
|
import kr.co.vividnext.sodalive.member.social.SocialAuthServiceResolver
|
|
|
|
|
import kr.co.vividnext.sodalive.member.social.SocialLoginResponse
|
|
|
|
|
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
|
|
|
|
import kr.co.vividnext.sodalive.useraction.ActionType
|
|
|
|
|
import kr.co.vividnext.sodalive.useraction.UserActionService
|
|
|
|
|
import org.junit.jupiter.api.Assertions.assertEquals
|
|
|
|
|
import org.junit.jupiter.api.Assertions.assertFalse
|
|
|
|
|
import org.junit.jupiter.api.Assertions.assertTrue
|
|
|
|
|
import org.junit.jupiter.api.DisplayName
|
|
|
|
|
import org.junit.jupiter.api.Test
|
|
|
|
|
import org.junit.jupiter.params.ParameterizedTest
|
|
|
|
|
import org.junit.jupiter.params.provider.CsvSource
|
|
|
|
|
import org.junit.jupiter.params.provider.ValueSource
|
|
|
|
|
import org.mockito.Mockito
|
|
|
|
|
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.boot.test.mock.mockito.MockBean
|
|
|
|
|
import org.springframework.http.MediaType
|
|
|
|
|
import org.springframework.mock.web.MockMultipartFile
|
|
|
|
|
import org.springframework.test.context.ContextConfiguration
|
|
|
|
|
import org.springframework.test.web.servlet.MockMvc
|
|
|
|
|
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder
|
|
|
|
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart
|
|
|
|
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
|
|
|
|
|
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
|
|
|
|
|
|
|
|
|
@SpringBootTest
|
|
|
|
|
@AutoConfigureMockMvc
|
|
|
|
|
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
|
|
|
|
|
class MemberLoginResponseContractTest @Autowired constructor(
|
|
|
|
|
private val mockMvc: MockMvc,
|
|
|
|
|
private val objectMapper: ObjectMapper
|
|
|
|
|
) {
|
|
|
|
|
@MockBean
|
|
|
|
|
private lateinit var memberService: MemberService
|
|
|
|
|
|
|
|
|
|
@MockBean
|
|
|
|
|
private lateinit var creatorAdminMemberService: CreatorAdminMemberService
|
|
|
|
|
|
|
|
|
|
@MockBean
|
|
|
|
|
private lateinit var socialAuthServiceResolver: SocialAuthServiceResolver
|
|
|
|
|
|
|
|
|
|
@MockBean
|
|
|
|
|
private lateinit var trackingService: AdTrackingService
|
|
|
|
|
|
|
|
|
|
@MockBean
|
|
|
|
|
private lateinit var userActionService: UserActionService
|
|
|
|
|
|
|
|
|
|
private val loginResponse = LoginResponse(42L, "issued-token", "member", "", "profile-url")
|
|
|
|
|
|
|
|
|
|
@ParameterizedTest(name = "{0} isNew={1}")
|
|
|
|
|
@CsvSource(
|
|
|
|
|
"GOOGLE, false",
|
|
|
|
|
"GOOGLE, true",
|
|
|
|
|
"KAKAO, false",
|
|
|
|
|
"KAKAO, true",
|
|
|
|
|
"APPLE, false",
|
|
|
|
|
"APPLE, true",
|
|
|
|
|
"LINE, false",
|
|
|
|
|
"LINE, true"
|
|
|
|
|
)
|
|
|
|
|
@DisplayName("모든 소셜 로그인은 내부 가입 여부와 기존 JSON 계약 및 부수 동작을 유지한다")
|
|
|
|
|
fun shouldExposeSocialIsNewAsBooleanForEveryProvider(provider: MemberProvider, isNew: Boolean) {
|
|
|
|
|
val authService = Mockito.mock(SocialAuthService::class.java)
|
|
|
|
|
val nonce = if (provider == MemberProvider.APPLE || provider == MemberProvider.LINE) "login-nonce" else null
|
|
|
|
|
Mockito.`when`(socialAuthServiceResolver.resolve(provider)).thenReturn(authService)
|
|
|
|
|
Mockito.`when`(authService.authenticate("provider-token", "web", "campaign-pid", "push-token", nonce))
|
|
|
|
|
.thenReturn(SocialLoginResponse(42L, "campaign-pid", loginResponse, isNew))
|
|
|
|
|
|
|
|
|
|
val request = post("/member/login/${provider.name.lowercase()}")
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
|
|
|
.content(
|
|
|
|
|
"""{
|
|
|
|
|
"container":"web", "marketingPid":"campaign-pid", "pushToken":"push-token",
|
|
|
|
|
"identityToken":"provider-token", "nonce":"login-nonce"
|
|
|
|
|
}"""
|
|
|
|
|
)
|
|
|
|
|
if (nonce == null) request.header("Authorization", "Bearer provider-token")
|
|
|
|
|
|
|
|
|
|
val response = perform(request)
|
|
|
|
|
|
|
|
|
|
Mockito.verify(authService).authenticate("provider-token", "web", "campaign-pid", "push-token", nonce)
|
|
|
|
|
Mockito.verify(trackingService).saveTrackingHistory("campaign-pid", AdTrackingHistoryType.SIGNUP, 42L)
|
|
|
|
|
Mockito.verify(userActionService, Mockito.times(if (isNew) 1 else 0))
|
|
|
|
|
.recordAction(42L, false, ActionType.SIGN_UP)
|
|
|
|
|
Mockito.verifyNoMoreInteractions(trackingService, userActionService)
|
|
|
|
|
assertEnvelope(response, "Congratulations on your sign up.")
|
|
|
|
|
assertLoginData(response.path("data"), isNew)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ParameterizedTest
|
|
|
|
|
@ValueSource(strings = ["/member/signup", "/member/signup/v2"])
|
|
|
|
|
@DisplayName("두 이메일 가입 응답은 서비스 DTO 기본값과 무관하게 신규 회원 여부를 true로 반환한다")
|
|
|
|
|
fun shouldReturnNewForBothEmailSignupEndpoints(path: String) {
|
|
|
|
|
val signupResponse = SignUpResponse(42L, "campaign-pid", loginResponse)
|
|
|
|
|
val request = if (path == "/member/signup") {
|
|
|
|
|
val body = """{
|
|
|
|
|
"email":"member@test.com", "password":"test-password", "nickname":"member", "gender":"NONE",
|
|
|
|
|
"marketingPid":"campaign-pid", "isAgreeTermsOfService":true, "isAgreePrivacyPolicy":true, "container":"web"
|
|
|
|
|
}"""
|
|
|
|
|
Mockito.`when`(memberService.signUp(null, body)).thenReturn(signupResponse)
|
|
|
|
|
multipart(path).file(MockMultipartFile("request", "", MediaType.TEXT_PLAIN_VALUE, body.toByteArray()))
|
|
|
|
|
} else {
|
|
|
|
|
Mockito.`when`(
|
|
|
|
|
memberService.signUpV2(
|
|
|
|
|
SignUpRequestV2(
|
|
|
|
|
email = "member@test.com",
|
|
|
|
|
password = "test-password",
|
|
|
|
|
marketingPid = "campaign-pid",
|
|
|
|
|
isAgreeTermsOfService = true,
|
|
|
|
|
isAgreePrivacyPolicy = true,
|
|
|
|
|
container = "web"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
).thenReturn(signupResponse)
|
|
|
|
|
post(path).contentType(MediaType.APPLICATION_JSON).content(
|
|
|
|
|
"""{
|
|
|
|
|
"email":"member@test.com", "password":"test-password", "marketingPid":"campaign-pid",
|
|
|
|
|
"isAgreeTermsOfService":true, "isAgreePrivacyPolicy":true, "container":"web"
|
|
|
|
|
}"""
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val response = perform(request)
|
|
|
|
|
|
|
|
|
|
Mockito.verify(trackingService).saveTrackingHistory("campaign-pid", AdTrackingHistoryType.SIGNUP, 42L)
|
|
|
|
|
Mockito.verify(userActionService, Mockito.times(if (path == "/member/signup/v2") 1 else 0))
|
|
|
|
|
.recordAction(42L, false, ActionType.SIGN_UP)
|
|
|
|
|
Mockito.verifyNoMoreInteractions(trackingService, userActionService)
|
|
|
|
|
assertEnvelope(response, "Congratulations on your sign up.")
|
|
|
|
|
assertLoginData(response.path("data"), true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ParameterizedTest(name = "isAdmin={0}, isCreator={1}")
|
|
|
|
|
@CsvSource("false, false", "true, false", "false, true")
|
|
|
|
|
@DisplayName("일반 및 관리자 및 크리에이터 회원 로그인은 신규 회원 여부를 false로 반환한다")
|
|
|
|
|
fun shouldReturnFalseForEveryMemberLoginBranch(isAdmin: Boolean, isCreator: Boolean) {
|
|
|
|
|
val request = LoginRequest("member@test.com", "test-password", isAdmin, isCreator)
|
|
|
|
|
Mockito.`when`(memberService.login(request))
|
|
|
|
|
.thenReturn(ApiResponse.ok(data = loginResponse, message = "You are logged in."))
|
|
|
|
|
|
|
|
|
|
val response = perform(
|
|
|
|
|
post("/member/login").contentType(MediaType.APPLICATION_JSON).content(
|
|
|
|
|
"""{"email":"member@test.com","password":"test-password","isAdmin":$isAdmin,"isCreator":$isCreator}"""
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Mockito.verify(memberService).login(request)
|
|
|
|
|
assertEnvelope(response, "You are logged in.")
|
|
|
|
|
assertLoginData(response.path("data"), false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@DisplayName("크리에이터 관리자 로그인은 공유 응답의 신규 회원 여부를 false로 반환한다")
|
|
|
|
|
fun shouldReturnFalseForCreatorAdminLogin() {
|
|
|
|
|
val request = LoginRequest("member@test.com", "test-password")
|
|
|
|
|
Mockito.`when`(creatorAdminMemberService.login(request))
|
|
|
|
|
.thenReturn(ApiResponse.ok(data = loginResponse, message = "Logged in."))
|
|
|
|
|
|
|
|
|
|
val response = perform(
|
|
|
|
|
post("/creator-admin/member/login").contentType(MediaType.APPLICATION_JSON)
|
|
|
|
|
.content("""{"email":"member@test.com","password":"test-password"}""")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Mockito.verify(creatorAdminMemberService).login(request)
|
|
|
|
|
assertEnvelope(response, "Logged in.")
|
|
|
|
|
assertLoginData(response.path("data"), false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
@DisplayName("애플리케이션 Jackson은 기본 LoginResponse의 isNew false를 생략하지 않는다")
|
|
|
|
|
fun shouldSerializeDefaultLoginResponseWithIsNewFalse() {
|
|
|
|
|
assertLoginData(objectMapper.readTree(objectMapper.writeValueAsString(loginResponse)), false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun perform(request: MockHttpServletRequestBuilder): JsonNode {
|
|
|
|
|
val response = mockMvc.perform(request.header("Accept-Language", "en"))
|
|
|
|
|
.andExpect(status().isOk)
|
|
|
|
|
.andReturn().response
|
|
|
|
|
return objectMapper.readTree(response.contentAsByteArray)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun assertEnvelope(response: JsonNode, message: String) {
|
|
|
|
|
assertEquals(setOf("success", "message", "data", "errorProperty"), response.fieldNames().asSequence().toSet())
|
|
|
|
|
assertTrue(response.path("success").isBoolean)
|
|
|
|
|
assertTrue(response.path("success").booleanValue())
|
|
|
|
|
assertEquals(message, response.path("message").textValue())
|
|
|
|
|
assertTrue(response.path("errorProperty").isNull)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun assertLoginData(data: JsonNode, isNew: Boolean) {
|
|
|
|
|
assertTrue(data.path("isNew").isBoolean, "isNew must be a present JSON boolean: $data")
|
|
|
|
|
assertEquals(isNew, data.path("isNew").booleanValue())
|
|
|
|
|
assertFalse(data.has("new"))
|
|
|
|
|
assertEquals(
|
|
|
|
|
setOf("userId", "token", "nickname", "email", "profileImage", "isNew"),
|
|
|
|
|
data.fieldNames().asSequence().toSet()
|
|
|
|
|
)
|
|
|
|
|
assertEquals(42L, data.path("userId").longValue())
|
|
|
|
|
assertEquals("issued-token", data.path("token").textValue())
|
|
|
|
|
assertEquals("member", data.path("nickname").textValue())
|
|
|
|
|
assertEquals("", data.path("email").textValue())
|
|
|
|
|
assertEquals("profile-url", data.path("profileImage").textValue())
|
|
|
|
|
}
|
|
|
|
|
}
|