Skip to content

Commit ce0d50b

Browse files
committed
ASAP-81 feat: 토큰 저장 포트 정의 및 테스트 추가
1 parent 139a2bd commit ce0d50b

File tree

15 files changed

+218
-54
lines changed

15 files changed

+218
-54
lines changed

Application-Module/src/main/kotlin/com/asap/application/user/exception/UserException.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ sealed class UserException(
2727
message = "사용자를 찾을 수 없습니다."
2828
)
2929

30+
class UserPermissionDeniedException(
31+
message: String = "사용자 권한이 없습니다."
32+
): UserException(
33+
errorCode = 4,
34+
message = message
35+
)
36+
3037

3138
companion object{
3239
const val CODE_PREFIX = "USER"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.asap.application.user.port.out
2+
3+
import com.asap.domain.user.entity.UserToken
4+
import com.asap.domain.user.enums.TokenType
5+
6+
interface UserTokenManagementPort {
7+
8+
fun isExistsToken(token: String, tokenType: TokenType): Boolean
9+
10+
fun saveUserToken(userToken: UserToken): UserToken
11+
12+
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.asap.application.user.port.out.memory
2+
3+
import com.asap.application.user.port.out.UserTokenManagementPort
4+
import com.asap.domain.user.entity.UserToken
5+
import com.asap.domain.user.enums.TokenType
6+
import org.springframework.context.annotation.Primary
7+
import org.springframework.stereotype.Component
8+
9+
@Component
10+
@Primary
11+
class MemoryUserTokenManagementAdapter: UserTokenManagementPort {
12+
13+
private val tokens = mutableSetOf<String>()
14+
private val userTokens = mutableSetOf<UserToken>()
15+
16+
override fun isExistsToken(token: String, tokenType: TokenType): Boolean {
17+
return tokens.contains(token)
18+
}
19+
20+
override fun saveUserToken(userToken: UserToken): UserToken {
21+
userTokens.add(userToken)
22+
tokens.add(userToken.token)
23+
return userToken
24+
}
25+
}

Application-Module/src/main/kotlin/com/asap/application/user/service/RegisterUserService.kt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,20 @@ import com.asap.application.user.port.`in`.RegisterUserUsecase
55
import com.asap.application.user.port.out.UserAuthManagementPort
66
import com.asap.application.user.port.out.UserManagementPort
77
import com.asap.application.user.port.out.UserTokenConvertPort
8+
import com.asap.application.user.port.out.UserTokenManagementPort
89
import com.asap.domain.user.entity.User
910
import com.asap.domain.user.entity.UserAuth
11+
import com.asap.domain.user.entity.UserToken
12+
import com.asap.domain.user.enums.TokenType
1013
import com.asap.domain.user.vo.UserPermission
1114
import org.springframework.stereotype.Service
1215

1316
@Service
1417
class RegisterUserService(
1518
private val userTokenConvertPort: UserTokenConvertPort,
1619
private val userAuthManagementPort: UserAuthManagementPort,
17-
private val userManagementPort: UserManagementPort
20+
private val userManagementPort: UserManagementPort,
21+
private val userTokenManagementPort: UserTokenManagementPort
1822
) : RegisterUserUsecase {
1923

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

48-
return RegisterUserUsecase.Response(
49-
userTokenConvertPort.generateAccessToken(registerUser),
50-
userTokenConvertPort.generateRefreshToken(registerUser)
55+
val accessToken = userTokenConvertPort.generateAccessToken(registerUser)
56+
val refreshToken = userTokenConvertPort.generateRefreshToken(registerUser)
57+
58+
userTokenManagementPort.saveUserToken(
59+
UserToken(
60+
token = refreshToken,
61+
type = TokenType.REFRESH
62+
)
5163
)
64+
65+
return RegisterUserUsecase.Response(accessToken, refreshToken)
5266
}
5367
}

Application-Module/src/main/kotlin/com/asap/application/user/service/SocialLoginService.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package com.asap.application.user.service
22

33
import com.asap.application.user.port.`in`.SocialLoginUsecase
4-
import com.asap.application.user.port.out.AuthInfoRetrievePort
5-
import com.asap.application.user.port.out.UserAuthManagementPort
6-
import com.asap.application.user.port.out.UserManagementPort
7-
import com.asap.application.user.port.out.UserTokenConvertPort
4+
import com.asap.application.user.port.out.*
85
import com.asap.common.exception.DefaultException
6+
import com.asap.domain.user.entity.UserToken
97
import com.asap.domain.user.enums.SocialLoginProvider
8+
import com.asap.domain.user.enums.TokenType
109
import org.springframework.stereotype.Service
1110

1211

@@ -15,7 +14,8 @@ class SocialLoginService(
1514
private val userAuthManagementPort: UserAuthManagementPort,
1615
private val authInfoRetrievePort: AuthInfoRetrievePort,
1716
private val userTokenConvertPort: UserTokenConvertPort,
18-
private val userManagementPort: UserManagementPort
17+
private val userManagementPort: UserManagementPort,
18+
private val userTokenManagementPort: UserTokenManagementPort
1919
) : SocialLoginUsecase {
2020

2121
override fun login(command: SocialLoginUsecase.Command): SocialLoginUsecase.Response {
@@ -24,10 +24,10 @@ class SocialLoginService(
2424
val userAuth = userAuthManagementPort.getUserAuth(authInfo.socialId, authInfo.socialLoginProvider)
2525
return userAuth?.let {
2626
userManagementPort.getUser(userAuth.userId)?.let {
27-
SocialLoginUsecase.Success(
28-
userTokenConvertPort.generateAccessToken(it),
29-
userTokenConvertPort.generateRefreshToken(it)
30-
)
27+
val accessToken = userTokenConvertPort.generateAccessToken(it)
28+
val refreshToken = userTokenConvertPort.generateRefreshToken(it)
29+
userTokenManagementPort.saveUserToken(UserToken(token = refreshToken, type = TokenType.REFRESH))
30+
SocialLoginUsecase.Success(accessToken, refreshToken)
3131
} ?: run {
3232
throw DefaultException.InvalidStateException("사용자 인증정보만 존재합니다. - ${userAuth.userId}")
3333
}
@@ -38,7 +38,7 @@ class SocialLoginService(
3838
authInfo.username,
3939
authInfo.profileImage
4040
)
41-
41+
userTokenManagementPort.saveUserToken(UserToken(token = registerToken, type = TokenType.REGISTER))
4242
SocialLoginUsecase.NonRegistered(registerToken)
4343
}
4444
}

Application-Module/src/test/kotlin/com/asap/application/user/service/RegisterUserServiceTest.kt

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import com.asap.application.user.port.`in`.RegisterUserUsecase
55
import com.asap.application.user.port.out.UserAuthManagementPort
66
import com.asap.application.user.port.out.UserManagementPort
77
import com.asap.application.user.port.out.UserTokenConvertPort
8+
import com.asap.application.user.port.out.UserTokenManagementPort
89
import com.asap.application.user.vo.UserClaims
910
import com.asap.common.exception.DefaultException
1011
import com.asap.domain.user.enums.SocialLoginProvider
12+
import com.asap.domain.user.enums.TokenType
1113
import io.kotest.assertions.throwables.shouldThrow
1214
import io.kotest.core.spec.style.BehaviorSpec
1315
import io.kotest.matchers.shouldBe
@@ -17,18 +19,25 @@ import io.mockk.verify
1719
import java.time.LocalDate
1820

1921

20-
class RegisterUserServiceTest: BehaviorSpec({
22+
class RegisterUserServiceTest : BehaviorSpec({
2123

2224
val mockUserManagementPort = mockk<UserManagementPort>(relaxed = true)
23-
val mockUserAuthManagementPort = mockk<UserAuthManagementPort>(relaxed=true)
25+
val mockUserAuthManagementPort = mockk<UserAuthManagementPort>(relaxed = true)
2426
val mockUserTokenConvertPort = mockk<UserTokenConvertPort>()
27+
val mockUserTokenManagementPort = mockk<UserTokenManagementPort>(relaxed = true)
2528

2629

27-
val registerUserService = RegisterUserService(mockUserTokenConvertPort, mockUserAuthManagementPort, mockUserManagementPort)
30+
val registerUserService = RegisterUserService(
31+
mockUserTokenConvertPort,
32+
mockUserAuthManagementPort,
33+
mockUserManagementPort,
34+
mockUserTokenManagementPort
35+
)
2836

2937

3038
given("회원 가입 요청이 들어왔을 때") {
3139
val successCommand = RegisterUserUsecase.Command("valid", true, true, true, LocalDate.now())
40+
every { mockUserTokenManagementPort.isExistsToken("valid", TokenType.REGISTER) } returns true
3241
every { mockUserTokenConvertPort.resolveRegisterToken("valid") } returns UserClaims.Register(
3342
socialId = "123",
3443
socialLoginProvider = SocialLoginProvider.KAKAO,
@@ -52,6 +61,7 @@ class RegisterUserServiceTest: BehaviorSpec({
5261
socialLoginProvider = SocialLoginProvider.KAKAO,
5362
username = "test"
5463
)
64+
every { mockUserTokenManagementPort.isExistsToken("duplicate", TokenType.REGISTER) } returns true
5565
every { mockUserAuthManagementPort.isExistsUserAuth("duplicate", SocialLoginProvider.KAKAO) } returns true
5666
`when`("중복 가입 요청이 들어왔을 때") {
5767
val failCommand = RegisterUserUsecase.Command("duplicate", true, true, true, LocalDate.now())
@@ -62,6 +72,7 @@ class RegisterUserServiceTest: BehaviorSpec({
6272
}
6373
}
6474

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

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

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

92116
`when`("개인정보 동의를 하지 않았다면") {
93-
val failCommandWithoutPrivatePermission = RegisterUserUsecase.Command("valid", true, false, true, LocalDate.now())
117+
val failCommandWithoutPrivatePermission =
118+
RegisterUserUsecase.Command("valid", true, false, true, LocalDate.now())
94119
then("InvalidPropertyException 예외가 발생한다.") {
95120
shouldThrow<DefaultException.InvalidDefaultException> {
96121
registerUserService.registerUser(failCommandWithoutPrivatePermission)

Application-Module/src/test/kotlin/com/asap/application/user/service/SocialLoginServiceTest.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package com.asap.application.user.service
22

33
import com.asap.application.user.port.`in`.SocialLoginUsecase
4-
import com.asap.application.user.port.out.AuthInfoRetrievePort
5-
import com.asap.application.user.port.out.UserAuthManagementPort
6-
import com.asap.application.user.port.out.UserManagementPort
7-
import com.asap.application.user.port.out.UserTokenConvertPort
4+
import com.asap.application.user.port.out.*
85
import com.asap.application.user.vo.AuthInfo
96
import com.asap.common.exception.DefaultException
107
import com.asap.domain.common.DomainId
@@ -18,19 +15,22 @@ import io.kotest.matchers.shouldBe
1815
import io.kotest.matchers.types.shouldBeInstanceOf
1916
import io.mockk.every
2017
import io.mockk.mockk
18+
import io.mockk.verify
2119

2220
class SocialLoginServiceTest : BehaviorSpec({
2321

2422
val mockUserAuthManagementPort = mockk<UserAuthManagementPort>()
2523
val mockAuthInfoRetrievePort = mockk<AuthInfoRetrievePort>()
2624
val mockUserManagementPort = mockk<UserManagementPort>()
2725
val mockUserTokenConvertPort = mockk<UserTokenConvertPort>()
26+
val mockUserTokenManagementPort = mockk<UserTokenManagementPort>(relaxed = true)
2827

2928
val socialLoginService = SocialLoginService(
3029
mockUserAuthManagementPort,
3130
mockAuthInfoRetrievePort,
3231
mockUserTokenConvertPort,
33-
mockUserManagementPort
32+
mockUserManagementPort,
33+
mockUserTokenManagementPort
3434
)
3535

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

@@ -84,6 +85,7 @@ class SocialLoginServiceTest : BehaviorSpec({
8485
then("register token을 반환하는 nonRegistered 인스턴스를 반환한다.") {
8586
response.shouldBeInstanceOf<SocialLoginUsecase.NonRegistered>()
8687
response.registerToken.isNotEmpty() shouldBe true
88+
verify { mockUserTokenManagementPort.saveUserToken(any()) }
8789
}
8890
}
8991
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.asap.application.user
2+
3+
import com.asap.application.user.port.out.UserTokenManagementPort
4+
import org.springframework.boot.test.context.TestConfiguration
5+
import org.springframework.context.annotation.Bean
6+
7+
@TestConfiguration
8+
class UserApplicationConfig(
9+
private val userTokenManagementPort: UserTokenManagementPort
10+
) {
11+
12+
@Bean
13+
fun userMockGenerator(): UserMockManager {
14+
return UserMockManager(userTokenManagementPort)
15+
}
16+
17+
18+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.asap.application.user
2+
3+
import com.asap.application.user.port.out.UserTokenManagementPort
4+
import com.asap.domain.user.entity.UserToken
5+
import com.asap.domain.user.enums.TokenType
6+
7+
8+
class UserMockManager(
9+
private val tokenManagementPort: UserTokenManagementPort
10+
) {
11+
12+
13+
fun settingToken(
14+
token: String
15+
){
16+
tokenManagementPort.saveUserToken(
17+
UserToken(
18+
token = token,
19+
type = TokenType.ACCESS
20+
)
21+
)
22+
}
23+
24+
}

Bootstrap-Module/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ dependencies{
55

66

77
implementation(project(":Application-Module"))
8+
testImplementation(testFixtures(project(":Application-Module")))
9+
810
implementation(project(":Common-Module"))
911

1012
implementation(project(":Infrastructure-Module:Client"))

0 commit comments

Comments
 (0)