Skip to content

Commit

Permalink
Merge pull request #12 from ASAP-Lettering/ASAP-81
Browse files Browse the repository at this point in the history
ASAP-81 feat: 토큰 저장 포트 정의 및 테스트 추가
  • Loading branch information
tlarbals824 authored Aug 31, 2024
2 parents 139a2bd + ce0d50b commit 23077c0
Show file tree
Hide file tree
Showing 15 changed files with 218 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ sealed class UserException(
message = "사용자를 찾을 수 없습니다."
)

class UserPermissionDeniedException(
message: String = "사용자 권한이 없습니다."
): UserException(
errorCode = 4,
message = message
)


companion object{
const val CODE_PREFIX = "USER"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.asap.application.user.port.out

import com.asap.domain.user.entity.UserToken
import com.asap.domain.user.enums.TokenType

interface UserTokenManagementPort {

fun isExistsToken(token: String, tokenType: TokenType): Boolean

fun saveUserToken(userToken: UserToken): UserToken


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.asap.application.user.port.out.memory

import com.asap.application.user.port.out.UserTokenManagementPort
import com.asap.domain.user.entity.UserToken
import com.asap.domain.user.enums.TokenType
import org.springframework.context.annotation.Primary
import org.springframework.stereotype.Component

@Component
@Primary
class MemoryUserTokenManagementAdapter: UserTokenManagementPort {

private val tokens = mutableSetOf<String>()
private val userTokens = mutableSetOf<UserToken>()

override fun isExistsToken(token: String, tokenType: TokenType): Boolean {
return tokens.contains(token)
}

override fun saveUserToken(userToken: UserToken): UserToken {
userTokens.add(userToken)
tokens.add(userToken.token)
return userToken
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ import com.asap.application.user.port.`in`.RegisterUserUsecase
import com.asap.application.user.port.out.UserAuthManagementPort
import com.asap.application.user.port.out.UserManagementPort
import com.asap.application.user.port.out.UserTokenConvertPort
import com.asap.application.user.port.out.UserTokenManagementPort
import com.asap.domain.user.entity.User
import com.asap.domain.user.entity.UserAuth
import com.asap.domain.user.entity.UserToken
import com.asap.domain.user.enums.TokenType
import com.asap.domain.user.vo.UserPermission
import org.springframework.stereotype.Service

@Service
class RegisterUserService(
private val userTokenConvertPort: UserTokenConvertPort,
private val userAuthManagementPort: UserAuthManagementPort,
private val userManagementPort: UserManagementPort
private val userManagementPort: UserManagementPort,
private val userTokenManagementPort: UserTokenManagementPort
) : RegisterUserUsecase {

/**
Expand All @@ -24,6 +28,9 @@ class RegisterUserService(
* 4. 사용자 정보 저장 및 jwt 토큰 반환
*/
override fun registerUser(command: RegisterUserUsecase.Command): RegisterUserUsecase.Response {
if (!userTokenManagementPort.isExistsToken(command.registerToken, TokenType.REGISTER)) {
throw UserException.UserPermissionDeniedException("존재하지 않는 가입 토큰입니다.")
}
val userClaims = userTokenConvertPort.resolveRegisterToken(command.registerToken)
if (userAuthManagementPort.isExistsUserAuth(userClaims.socialId, userClaims.socialLoginProvider)) {
throw UserException.UserAlreadyRegisteredException()
Expand All @@ -45,9 +52,16 @@ class RegisterUserService(
userManagementPort.saveUser(registerUser)
userAuthManagementPort.saveUserAuth(userAuth)

return RegisterUserUsecase.Response(
userTokenConvertPort.generateAccessToken(registerUser),
userTokenConvertPort.generateRefreshToken(registerUser)
val accessToken = userTokenConvertPort.generateAccessToken(registerUser)
val refreshToken = userTokenConvertPort.generateRefreshToken(registerUser)

userTokenManagementPort.saveUserToken(
UserToken(
token = refreshToken,
type = TokenType.REFRESH
)
)

return RegisterUserUsecase.Response(accessToken, refreshToken)
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package com.asap.application.user.service

import com.asap.application.user.port.`in`.SocialLoginUsecase
import com.asap.application.user.port.out.AuthInfoRetrievePort
import com.asap.application.user.port.out.UserAuthManagementPort
import com.asap.application.user.port.out.UserManagementPort
import com.asap.application.user.port.out.UserTokenConvertPort
import com.asap.application.user.port.out.*
import com.asap.common.exception.DefaultException
import com.asap.domain.user.entity.UserToken
import com.asap.domain.user.enums.SocialLoginProvider
import com.asap.domain.user.enums.TokenType
import org.springframework.stereotype.Service


Expand All @@ -15,7 +14,8 @@ class SocialLoginService(
private val userAuthManagementPort: UserAuthManagementPort,
private val authInfoRetrievePort: AuthInfoRetrievePort,
private val userTokenConvertPort: UserTokenConvertPort,
private val userManagementPort: UserManagementPort
private val userManagementPort: UserManagementPort,
private val userTokenManagementPort: UserTokenManagementPort
) : SocialLoginUsecase {

override fun login(command: SocialLoginUsecase.Command): SocialLoginUsecase.Response {
Expand All @@ -24,10 +24,10 @@ class SocialLoginService(
val userAuth = userAuthManagementPort.getUserAuth(authInfo.socialId, authInfo.socialLoginProvider)
return userAuth?.let {
userManagementPort.getUser(userAuth.userId)?.let {
SocialLoginUsecase.Success(
userTokenConvertPort.generateAccessToken(it),
userTokenConvertPort.generateRefreshToken(it)
)
val accessToken = userTokenConvertPort.generateAccessToken(it)
val refreshToken = userTokenConvertPort.generateRefreshToken(it)
userTokenManagementPort.saveUserToken(UserToken(token = refreshToken, type = TokenType.REFRESH))
SocialLoginUsecase.Success(accessToken, refreshToken)
} ?: run {
throw DefaultException.InvalidStateException("사용자 인증정보만 존재합니다. - ${userAuth.userId}")
}
Expand All @@ -38,7 +38,7 @@ class SocialLoginService(
authInfo.username,
authInfo.profileImage
)

userTokenManagementPort.saveUserToken(UserToken(token = registerToken, type = TokenType.REGISTER))
SocialLoginUsecase.NonRegistered(registerToken)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import com.asap.application.user.port.`in`.RegisterUserUsecase
import com.asap.application.user.port.out.UserAuthManagementPort
import com.asap.application.user.port.out.UserManagementPort
import com.asap.application.user.port.out.UserTokenConvertPort
import com.asap.application.user.port.out.UserTokenManagementPort
import com.asap.application.user.vo.UserClaims
import com.asap.common.exception.DefaultException
import com.asap.domain.user.enums.SocialLoginProvider
import com.asap.domain.user.enums.TokenType
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
Expand All @@ -17,18 +19,25 @@ import io.mockk.verify
import java.time.LocalDate


class RegisterUserServiceTest: BehaviorSpec({
class RegisterUserServiceTest : BehaviorSpec({

val mockUserManagementPort = mockk<UserManagementPort>(relaxed = true)
val mockUserAuthManagementPort = mockk<UserAuthManagementPort>(relaxed=true)
val mockUserAuthManagementPort = mockk<UserAuthManagementPort>(relaxed = true)
val mockUserTokenConvertPort = mockk<UserTokenConvertPort>()
val mockUserTokenManagementPort = mockk<UserTokenManagementPort>(relaxed = true)


val registerUserService = RegisterUserService(mockUserTokenConvertPort, mockUserAuthManagementPort, mockUserManagementPort)
val registerUserService = RegisterUserService(
mockUserTokenConvertPort,
mockUserAuthManagementPort,
mockUserManagementPort,
mockUserTokenManagementPort
)


given("회원 가입 요청이 들어왔을 때") {
val successCommand = RegisterUserUsecase.Command("valid", true, true, true, LocalDate.now())
every { mockUserTokenManagementPort.isExistsToken("valid", TokenType.REGISTER) } returns true
every { mockUserTokenConvertPort.resolveRegisterToken("valid") } returns UserClaims.Register(
socialId = "123",
socialLoginProvider = SocialLoginProvider.KAKAO,
Expand All @@ -52,6 +61,7 @@ class RegisterUserServiceTest: BehaviorSpec({
socialLoginProvider = SocialLoginProvider.KAKAO,
username = "test"
)
every { mockUserTokenManagementPort.isExistsToken("duplicate", TokenType.REGISTER) } returns true
every { mockUserAuthManagementPort.isExistsUserAuth("duplicate", SocialLoginProvider.KAKAO) } returns true
`when`("중복 가입 요청이 들어왔을 때") {
val failCommand = RegisterUserUsecase.Command("duplicate", true, true, true, LocalDate.now())
Expand All @@ -62,6 +72,7 @@ class RegisterUserServiceTest: BehaviorSpec({
}
}

every { mockUserTokenManagementPort.isExistsToken("invalid", TokenType.REGISTER) } returns true
every { mockUserTokenConvertPort.resolveRegisterToken("invalid") } throws IllegalArgumentException("Invalid token")
`when`("register token이 유요하지 않다면") {
val failCommandWithoutRegisterToken =
Expand All @@ -73,15 +84,28 @@ class RegisterUserServiceTest: BehaviorSpec({
}
}

every { mockUserTokenManagementPort.isExistsToken("non-saved", TokenType.REGISTER) } returns false
`when`("register token이 존재하지 않는다면") {
val failCommandWithoutRegisterToken =
RegisterUserUsecase.Command("non-saved", true, true, true, LocalDate.now())
then("UserPermissionDeniedException 예외가 발생한다.") {
shouldThrow<UserException.UserPermissionDeniedException> {
registerUserService.registerUser(failCommandWithoutRegisterToken)
}
}
}


every { mockUserTokenConvertPort.resolveRegisterToken("valid") } returns UserClaims.Register(
socialId = "123",
socialLoginProvider = SocialLoginProvider.KAKAO,
username = "test"
)
every{ mockUserAuthManagementPort.isExistsUserAuth("123", SocialLoginProvider.KAKAO) } returns false
every { mockUserTokenManagementPort.isExistsToken("valid", TokenType.REGISTER) } returns true
every { mockUserAuthManagementPort.isExistsUserAuth("123", SocialLoginProvider.KAKAO) } returns false
`when`("서비스 동의를 하지 않았다면") {
val failCommandWithoutServicePermission = RegisterUserUsecase.Command("valid", false, true, true, LocalDate.now())
val failCommandWithoutServicePermission =
RegisterUserUsecase.Command("valid", false, true, true, LocalDate.now())
then("InvalidPropertyException 예외가 발생한다.") {
shouldThrow<DefaultException.InvalidDefaultException> {
registerUserService.registerUser(failCommandWithoutServicePermission)
Expand All @@ -90,7 +114,8 @@ class RegisterUserServiceTest: BehaviorSpec({
}

`when`("개인정보 동의를 하지 않았다면") {
val failCommandWithoutPrivatePermission = RegisterUserUsecase.Command("valid", true, false, true, LocalDate.now())
val failCommandWithoutPrivatePermission =
RegisterUserUsecase.Command("valid", true, false, true, LocalDate.now())
then("InvalidPropertyException 예외가 발생한다.") {
shouldThrow<DefaultException.InvalidDefaultException> {
registerUserService.registerUser(failCommandWithoutPrivatePermission)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.asap.application.user.service

import com.asap.application.user.port.`in`.SocialLoginUsecase
import com.asap.application.user.port.out.AuthInfoRetrievePort
import com.asap.application.user.port.out.UserAuthManagementPort
import com.asap.application.user.port.out.UserManagementPort
import com.asap.application.user.port.out.UserTokenConvertPort
import com.asap.application.user.port.out.*
import com.asap.application.user.vo.AuthInfo
import com.asap.common.exception.DefaultException
import com.asap.domain.common.DomainId
Expand All @@ -18,19 +15,22 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.shouldBeInstanceOf
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify

class SocialLoginServiceTest : BehaviorSpec({

val mockUserAuthManagementPort = mockk<UserAuthManagementPort>()
val mockAuthInfoRetrievePort = mockk<AuthInfoRetrievePort>()
val mockUserManagementPort = mockk<UserManagementPort>()
val mockUserTokenConvertPort = mockk<UserTokenConvertPort>()
val mockUserTokenManagementPort = mockk<UserTokenManagementPort>(relaxed = true)

val socialLoginService = SocialLoginService(
mockUserAuthManagementPort,
mockAuthInfoRetrievePort,
mockUserTokenConvertPort,
mockUserManagementPort
mockUserManagementPort,
mockUserTokenManagementPort
)

given("소셜 로그인에서 요청한 사용자가") {
Expand Down Expand Up @@ -62,6 +62,7 @@ class SocialLoginServiceTest : BehaviorSpec({
response.shouldBeInstanceOf<SocialLoginUsecase.Success>()
response.accessToken.isNotEmpty() shouldBe true
response.refreshToken.isNotEmpty() shouldBe true
verify { mockUserTokenManagementPort.saveUserToken(any()) }
}
}

Expand All @@ -84,6 +85,7 @@ class SocialLoginServiceTest : BehaviorSpec({
then("register token을 반환하는 nonRegistered 인스턴스를 반환한다.") {
response.shouldBeInstanceOf<SocialLoginUsecase.NonRegistered>()
response.registerToken.isNotEmpty() shouldBe true
verify { mockUserTokenManagementPort.saveUserToken(any()) }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.asap.application.user

import com.asap.application.user.port.out.UserTokenManagementPort
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean

@TestConfiguration
class UserApplicationConfig(
private val userTokenManagementPort: UserTokenManagementPort
) {

@Bean
fun userMockGenerator(): UserMockManager {
return UserMockManager(userTokenManagementPort)
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.asap.application.user

import com.asap.application.user.port.out.UserTokenManagementPort
import com.asap.domain.user.entity.UserToken
import com.asap.domain.user.enums.TokenType


class UserMockManager(
private val tokenManagementPort: UserTokenManagementPort
) {


fun settingToken(
token: String
){
tokenManagementPort.saveUserToken(
UserToken(
token = token,
type = TokenType.ACCESS
)
)
}

}
2 changes: 2 additions & 0 deletions Bootstrap-Module/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ dependencies{


implementation(project(":Application-Module"))
testImplementation(testFixtures(project(":Application-Module")))

implementation(project(":Common-Module"))

implementation(project(":Infrastructure-Module:Client"))
Expand Down
Loading

0 comments on commit 23077c0

Please sign in to comment.