generated from pricelees/issue-pr-template
[#41] 예약 스키마 재정의 #42
@ -1,82 +0,0 @@
|
||||
package roomescape.auth.business
|
||||
|
||||
import io.kotest.assertions.assertSoftly
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.BehaviorSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import roomescape.auth.exception.AuthErrorCode
|
||||
import roomescape.auth.exception.AuthException
|
||||
import roomescape.auth.infrastructure.jwt.JwtHandler
|
||||
import roomescape.member.exception.MemberErrorCode
|
||||
import roomescape.member.exception.MemberException
|
||||
import roomescape.member.implement.MemberFinder
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.util.JwtFixture
|
||||
import roomescape.util.MemberFixture
|
||||
|
||||
class AuthServiceTest : BehaviorSpec({
|
||||
val memberFinder: MemberFinder = mockk()
|
||||
val jwtHandler: JwtHandler = JwtFixture.create()
|
||||
|
||||
val authService = AuthService(memberFinder, jwtHandler)
|
||||
val user: MemberEntity = MemberFixture.user()
|
||||
|
||||
Given("로그인 요청을 받으면") {
|
||||
When("이메일과 비밀번호로 회원을 찾고") {
|
||||
val request = MemberFixture.userLoginRequest()
|
||||
|
||||
Then("회원이 있다면 JWT 토큰을 생성한 뒤 반환한다.") {
|
||||
every {
|
||||
memberFinder.findByEmailAndPassword(request.email, request.password)
|
||||
} returns user
|
||||
|
||||
val accessToken: String = authService.login(request).accessToken
|
||||
|
||||
accessToken.isNotBlank() shouldBe true
|
||||
jwtHandler.getMemberIdFromToken(accessToken) shouldBe user.id
|
||||
}
|
||||
|
||||
Then("회원이 없다면 예외를 던진다.") {
|
||||
every {
|
||||
memberFinder.findByEmailAndPassword(request.email, request.password)
|
||||
} throws MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
|
||||
|
||||
val exception = shouldThrow<AuthException> {
|
||||
authService.login(request)
|
||||
}
|
||||
|
||||
exception.errorCode shouldBe AuthErrorCode.LOGIN_FAILED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Given("로그인 확인 요청을 받으면") {
|
||||
When("회원 ID로 회원을 찾고") {
|
||||
val userId: Long = user.id!!
|
||||
|
||||
Then("회원이 있다면 회원의 이름을 반환한다.") {
|
||||
every { memberFinder.findById(userId) } returns user
|
||||
|
||||
val response = authService.checkLogin(userId)
|
||||
|
||||
assertSoftly(response) {
|
||||
this.name shouldBe user.name
|
||||
}
|
||||
}
|
||||
|
||||
Then("회원이 없다면 예외를 던진다.") {
|
||||
every {
|
||||
memberFinder.findById(userId)
|
||||
} throws MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
|
||||
|
||||
val exception = shouldThrow<AuthException> {
|
||||
authService.checkLogin(userId)
|
||||
}
|
||||
|
||||
exception.errorCode shouldBe AuthErrorCode.MEMBER_NOT_FOUND
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1,59 +0,0 @@
|
||||
package roomescape.auth.infrastructure.jwt
|
||||
|
||||
import io.jsonwebtoken.Jwts
|
||||
import io.jsonwebtoken.security.Keys
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import roomescape.auth.exception.AuthErrorCode
|
||||
import roomescape.auth.exception.AuthException
|
||||
import roomescape.util.JwtFixture
|
||||
import java.util.*
|
||||
import kotlin.random.Random
|
||||
|
||||
class JwtHandlerTest : FunSpec({
|
||||
|
||||
context("JWT 토큰 조회") {
|
||||
val memberId = Random.nextLong()
|
||||
val jwtHandler: JwtHandler = JwtFixture.create()
|
||||
|
||||
test("토큰에서 멤버 ID를 올바르게 추출한다.") {
|
||||
val token = jwtHandler.createToken(memberId)
|
||||
val extractedMemberId = jwtHandler.getMemberIdFromToken(token)
|
||||
|
||||
extractedMemberId shouldBe memberId
|
||||
}
|
||||
|
||||
test("만료된 토큰이면 예외를 던진다.") {
|
||||
val expirationTime = 0L
|
||||
val shortExpirationTimeJwtHandler: JwtHandler = JwtFixture.create(expirationTime = expirationTime)
|
||||
val token = shortExpirationTimeJwtHandler.createToken(memberId)
|
||||
|
||||
Thread.sleep(expirationTime) // 만료 시간 이후로 대기
|
||||
|
||||
shouldThrow<AuthException> {
|
||||
shortExpirationTimeJwtHandler.getMemberIdFromToken(token)
|
||||
}.errorCode shouldBe AuthErrorCode.EXPIRED_TOKEN
|
||||
}
|
||||
|
||||
test("토큰이 빈 값이면 예외를 던진다.") {
|
||||
shouldThrow<AuthException> {
|
||||
jwtHandler.getMemberIdFromToken("")
|
||||
}.errorCode shouldBe AuthErrorCode.TOKEN_NOT_FOUND
|
||||
}
|
||||
|
||||
test("시크릿 키가 잘못된 경우 예외를 던진다.") {
|
||||
val now = Date()
|
||||
val invalidSignatureToken: String = Jwts.builder()
|
||||
.claim("memberId", memberId)
|
||||
.issuedAt(now)
|
||||
.expiration(Date(now.time + JwtFixture.EXPIRATION_TIME))
|
||||
.signWith(Keys.hmacShaKeyFor(JwtFixture.SECRET_KEY_STRING.substring(1).toByteArray()))
|
||||
.compact()
|
||||
|
||||
shouldThrow<AuthException> {
|
||||
jwtHandler.getMemberIdFromToken(invalidSignatureToken)
|
||||
}.errorCode shouldBe AuthErrorCode.INVALID_TOKEN
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1,151 +0,0 @@
|
||||
package roomescape.auth.web
|
||||
|
||||
import com.ninjasquad.springmockk.MockkBean
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import roomescape.auth.business.AuthService
|
||||
import roomescape.auth.exception.AuthErrorCode
|
||||
import roomescape.auth.exception.AuthException
|
||||
import roomescape.common.exception.CommonErrorCode
|
||||
import roomescape.common.exception.ErrorCode
|
||||
import roomescape.util.MemberFixture
|
||||
import roomescape.util.RoomescapeApiTest
|
||||
|
||||
@WebMvcTest(controllers = [AuthController::class])
|
||||
class AuthControllerTest(val mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
|
||||
@MockkBean
|
||||
private lateinit var authService: AuthService
|
||||
|
||||
val userRequest: LoginRequest = MemberFixture.userLoginRequest()
|
||||
|
||||
init {
|
||||
Given("로그인 요청을 보낼 때") {
|
||||
val endpoint = "/login"
|
||||
|
||||
When("로그인에 성공하면") {
|
||||
val expectedToken = "expectedToken"
|
||||
|
||||
every {
|
||||
authService.login(userRequest)
|
||||
} returns LoginResponse(expectedToken)
|
||||
|
||||
Then("토큰을 반환한다.") {
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = userRequest,
|
||||
) {
|
||||
status { isOk() }
|
||||
jsonPath("$.data.accessToken", equalTo(expectedToken))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
When("회원을 찾지 못하면") {
|
||||
val expectedError = AuthErrorCode.LOGIN_FAILED
|
||||
|
||||
every {
|
||||
authService.login(userRequest)
|
||||
} throws AuthException(expectedError)
|
||||
|
||||
Then("에러 응답을 받는다.") {
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = userRequest,
|
||||
) {
|
||||
status { isEqualTo(expectedError.httpStatus.value()) }
|
||||
jsonPath("$.code", equalTo(expectedError.errorCode))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
When("입력 값이 잘못되면") {
|
||||
val expectedErrorCode: ErrorCode = CommonErrorCode.INVALID_INPUT_VALUE
|
||||
|
||||
Then("400 에러를 응답한다") {
|
||||
listOf(
|
||||
userRequest.copy(email = "invalid"),
|
||||
userRequest.copy(password = " "),
|
||||
"{\"email\": \"null\", \"password\": \"null\"}"
|
||||
).forEach {
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = it,
|
||||
) {
|
||||
status { isEqualTo(expectedErrorCode.httpStatus.value()) }
|
||||
jsonPath("$.code", equalTo(expectedErrorCode.errorCode))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Given("로그인 상태를 확인할 때") {
|
||||
val endpoint = "/login/check"
|
||||
|
||||
When("로그인된 회원의 ID로 요청하면") {
|
||||
loginAsUser()
|
||||
|
||||
Then("회원의 이름과 권한을 응답한다") {
|
||||
every {
|
||||
authService.checkLogin(user.id!!)
|
||||
} returns LoginCheckResponse(user.name, user.role.name)
|
||||
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
) {
|
||||
status { isOk() }
|
||||
jsonPath("$.data.name", equalTo(user.name))
|
||||
jsonPath("$.data.role", equalTo(user.role.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
When("토큰은 있지만 회원을 찾을 수 없으면") {
|
||||
val invalidMemberId: Long = -1L
|
||||
val expectedError = AuthErrorCode.MEMBER_NOT_FOUND
|
||||
|
||||
every { jwtHandler.getMemberIdFromToken(any()) } returns invalidMemberId
|
||||
every { authService.checkLogin(invalidMemberId) } throws AuthException(expectedError)
|
||||
|
||||
Then("에러 응답을 받는다.") {
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
) {
|
||||
status { isEqualTo(expectedError.httpStatus.value()) }
|
||||
jsonPath("$.code", equalTo(expectedError.errorCode))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Given("로그아웃 요청을 보낼 때") {
|
||||
val endpoint = "/logout"
|
||||
|
||||
When("토큰으로 memberId 조회가 가능하면") {
|
||||
loginAsUser()
|
||||
|
||||
every {
|
||||
authService.logout(user.id!!)
|
||||
} just Runs
|
||||
|
||||
Then("정상 응답한다.") {
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
) {
|
||||
status { isNoContent() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
package roomescape.auth.web.support
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
|
||||
class CookieUtilsTest : FunSpec({
|
||||
context("accessToken 쿠키를 가져온다.") {
|
||||
val httpServletRequest: HttpServletRequest = mockk()
|
||||
|
||||
test("accessToken이 있으면 해당 쿠키를 반환한다.") {
|
||||
val token = "test-token"
|
||||
every { httpServletRequest.getHeader("Authorization") } returns "Bearer $token"
|
||||
|
||||
httpServletRequest.accessToken() shouldBe token
|
||||
}
|
||||
|
||||
test("accessToken이 없으면 null을 반환한다.") {
|
||||
every { httpServletRequest.getHeader("Authorization") } returns null
|
||||
|
||||
httpServletRequest.accessToken() shouldBe null
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1,100 +0,0 @@
|
||||
package roomescape.member.business
|
||||
|
||||
import io.kotest.assertions.assertSoftly
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldContainExactly
|
||||
import io.kotest.matchers.collections.shouldHaveSize
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import roomescape.member.exception.MemberErrorCode
|
||||
import roomescape.member.exception.MemberException
|
||||
import roomescape.member.implement.MemberFinder
|
||||
import roomescape.member.implement.MemberWriter
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.member.infrastructure.persistence.Role
|
||||
import roomescape.member.web.SignupRequest
|
||||
import roomescape.util.MemberFixture
|
||||
|
||||
class MemberServiceTest : FunSpec({
|
||||
val memberWriter: MemberWriter = mockk()
|
||||
val memberFinder: MemberFinder = mockk()
|
||||
|
||||
val memberService = MemberService(memberWriter, memberFinder)
|
||||
|
||||
context("findMembers") {
|
||||
test("정상 응답") {
|
||||
val members: List<MemberEntity> = listOf(
|
||||
MemberFixture.create(name = "user1"),
|
||||
MemberFixture.create(name = "user2"),
|
||||
)
|
||||
|
||||
every { memberFinder.findAll() } returns members
|
||||
|
||||
val response = memberService.findMembers()
|
||||
|
||||
// then
|
||||
assertSoftly(response.members) {
|
||||
it shouldHaveSize 2
|
||||
it.map { member -> member.name } shouldContainExactly listOf("user1", "user2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("findById") {
|
||||
val id = 1L
|
||||
|
||||
test("정상 응답") {
|
||||
every {
|
||||
memberFinder.findById(id)
|
||||
} returns MemberFixture.create(id = id)
|
||||
|
||||
memberService.findById(id).id shouldBe id
|
||||
}
|
||||
|
||||
test("회원을 찾을 수 없으면 예외 응답") {
|
||||
every {
|
||||
memberFinder.findById(id)
|
||||
} throws MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
|
||||
|
||||
shouldThrow<MemberException> {
|
||||
memberService.findById(id)
|
||||
}.also {
|
||||
it.errorCode shouldBe MemberErrorCode.MEMBER_NOT_FOUND
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("createMember") {
|
||||
val request = SignupRequest(name = "new-user", email = "new@test.com", password = "password")
|
||||
|
||||
test("정상 저장") {
|
||||
val member = MemberFixture.create(
|
||||
name = request.name,
|
||||
account = request.email,
|
||||
password = request.password
|
||||
)
|
||||
|
||||
every {
|
||||
memberWriter.create(request.name, request.email, request.password, Role.MEMBER)
|
||||
} returns member
|
||||
|
||||
val response = memberService.createMember(request)
|
||||
|
||||
response.id shouldBe member.id
|
||||
}
|
||||
|
||||
test("중복된 이메일이 있으면 예외 응답") {
|
||||
every {
|
||||
memberWriter.create(request.name, request.email, request.password, Role.MEMBER)
|
||||
} throws MemberException(MemberErrorCode.DUPLICATE_EMAIL)
|
||||
|
||||
shouldThrow<MemberException> {
|
||||
memberService.createMember(request)
|
||||
}.also {
|
||||
it.errorCode shouldBe MemberErrorCode.DUPLICATE_EMAIL
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1,136 +0,0 @@
|
||||
package roomescape.member.controller
|
||||
|
||||
import com.ninjasquad.springmockk.MockkBean
|
||||
import io.kotest.assertions.assertSoftly
|
||||
import io.kotest.matchers.collections.shouldContainAll
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import roomescape.auth.exception.AuthErrorCode
|
||||
import roomescape.member.business.MemberService
|
||||
import roomescape.member.exception.MemberErrorCode
|
||||
import roomescape.member.exception.MemberException
|
||||
import roomescape.member.infrastructure.persistence.Role
|
||||
import roomescape.member.web.*
|
||||
import roomescape.util.MemberFixture
|
||||
import roomescape.util.RoomescapeApiTest
|
||||
import kotlin.random.Random
|
||||
|
||||
@WebMvcTest(controllers = [MemberController::class])
|
||||
class MemberControllerTest(val mockMvc: MockMvc) : RoomescapeApiTest() {
|
||||
@MockkBean
|
||||
private lateinit var memberService: MemberService
|
||||
|
||||
init {
|
||||
given("GET /members 요청을") {
|
||||
val endpoint = "/members"
|
||||
val response = listOf(
|
||||
MemberFixture.create(id = Random.nextLong(), name = "name1"),
|
||||
MemberFixture.create(id = Random.nextLong(), name = "name2"),
|
||||
MemberFixture.create(id = Random.nextLong(), name = "name3"),
|
||||
).toRetrieveListResponse()
|
||||
|
||||
every { memberService.findMembers() } returns response
|
||||
|
||||
`when`("관리자가 보내면") {
|
||||
loginAsAdmin()
|
||||
|
||||
then("성공한다.") {
|
||||
val result: MemberRetrieveListResponse = runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
) {
|
||||
status { isOk() }
|
||||
}.andReturn().readValue(MemberRetrieveListResponse::class.java)
|
||||
|
||||
|
||||
assertSoftly(result.members) {
|
||||
it.size shouldBe response.members.size
|
||||
it.map { m -> m.name } shouldContainAll response.members.map { m -> m.name }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
`when`("관리자가 아니면 에러 응답을 받는다.") {
|
||||
then("비회원") {
|
||||
doNotLogin()
|
||||
val expectedError = AuthErrorCode.MEMBER_NOT_FOUND
|
||||
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
) {
|
||||
status { isEqualTo(expectedError.httpStatus.value()) }
|
||||
}.andExpect {
|
||||
jsonPath("$.code") { value(expectedError.errorCode) }
|
||||
}
|
||||
}
|
||||
|
||||
then("일반 회원") {
|
||||
loginAsUser()
|
||||
val expectedError = AuthErrorCode.ACCESS_DENIED
|
||||
|
||||
runGetTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
) {
|
||||
status { isEqualTo(expectedError.httpStatus.value()) }
|
||||
}.andExpect {
|
||||
jsonPath("$.code") { value(expectedError.errorCode) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
given("POST /members") {
|
||||
val endpoint = "/members"
|
||||
val request = SignupRequest(
|
||||
name = "name",
|
||||
email = "email@email.com",
|
||||
password = "password"
|
||||
)
|
||||
`when`("같은 이메일이 없으면") {
|
||||
every {
|
||||
memberService.createMember(request)
|
||||
} returns MemberFixture.create(
|
||||
id = 1,
|
||||
name = request.name,
|
||||
account = request.email,
|
||||
password = request.password,
|
||||
role = Role.MEMBER
|
||||
).toSignupResponse()
|
||||
|
||||
then("id과 이름을 담아 성공 응답") {
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = request
|
||||
) {
|
||||
status { isCreated() }
|
||||
jsonPath("$.data.name") { value(request.name) }
|
||||
jsonPath("$.data.id") { value(1) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
`when`("같은 이메일이 있으면") {
|
||||
val expectedError = MemberErrorCode.DUPLICATE_EMAIL
|
||||
every {
|
||||
memberService.createMember(request)
|
||||
} throws MemberException(expectedError)
|
||||
|
||||
then("에러 응답") {
|
||||
runPostTest(
|
||||
mockMvc = mockMvc,
|
||||
endpoint = endpoint,
|
||||
body = request
|
||||
) {
|
||||
status { isEqualTo(expectedError.httpStatus.value()) }
|
||||
jsonPath("$.code") { value(expectedError.errorCode) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,85 +0,0 @@
|
||||
package roomescape.member.implement
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldHaveSize
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import roomescape.member.exception.MemberErrorCode
|
||||
import roomescape.member.exception.MemberException
|
||||
import roomescape.member.infrastructure.persistence.MemberRepository
|
||||
|
||||
class MemberFinderTest : FunSpec({
|
||||
|
||||
val memberRepository: MemberRepository = mockk()
|
||||
val memberFinder = MemberFinder(memberRepository)
|
||||
|
||||
context("findAll") {
|
||||
test("모든 회원을 조회한다.") {
|
||||
every {
|
||||
memberRepository.findAll()
|
||||
} returns listOf(mockk(), mockk(), mockk())
|
||||
|
||||
memberFinder.findAll() shouldHaveSize 3
|
||||
}
|
||||
}
|
||||
|
||||
context("findById") {
|
||||
val memberId = 1L
|
||||
test("동일한 ID인 회원을 찾아 응답한다.") {
|
||||
every {
|
||||
memberRepository.findByIdOrNull(memberId)
|
||||
} returns mockk()
|
||||
|
||||
memberFinder.findById(memberId)
|
||||
|
||||
verify(exactly = 1) {
|
||||
memberRepository.findByIdOrNull(memberId)
|
||||
}
|
||||
}
|
||||
|
||||
test("동일한 ID인 회원이 없으면 실패한다.") {
|
||||
every {
|
||||
memberRepository.findByIdOrNull(memberId)
|
||||
} returns null
|
||||
|
||||
shouldThrow<MemberException> {
|
||||
memberFinder.findById(memberId)
|
||||
}.also {
|
||||
it.errorCode shouldBe MemberErrorCode.MEMBER_NOT_FOUND
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("findByEmailAndPassword") {
|
||||
val email = "email"
|
||||
val password = "password"
|
||||
|
||||
test("동일한 이메일과 비밀번호를 가진 회원을 찾아 응답한다.") {
|
||||
every {
|
||||
memberRepository.findByEmailAndPassword(email, password)
|
||||
} returns mockk()
|
||||
|
||||
memberFinder.findByEmailAndPassword(email, password)
|
||||
|
||||
verify(exactly = 1) {
|
||||
memberRepository.findByEmailAndPassword(email, password)
|
||||
}
|
||||
}
|
||||
|
||||
test("동일한 이메일과 비밀번호를 가진 회원이 없으면 실패한다.") {
|
||||
every {
|
||||
memberRepository.findByEmailAndPassword(email, password)
|
||||
} returns null
|
||||
|
||||
shouldThrow<MemberException> {
|
||||
memberFinder.findByEmailAndPassword(email, password)
|
||||
}.also {
|
||||
it.errorCode shouldBe MemberErrorCode.MEMBER_NOT_FOUND
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1,44 +0,0 @@
|
||||
package roomescape.member.implement
|
||||
|
||||
import io.kotest.assertions.throwables.shouldNotThrow
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.core.spec.style.StringSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import roomescape.member.exception.MemberErrorCode
|
||||
import roomescape.member.exception.MemberException
|
||||
import roomescape.member.infrastructure.persistence.MemberRepository
|
||||
|
||||
class MemberValidatorTest : FunSpec({
|
||||
|
||||
val memberRepository: MemberRepository = mockk()
|
||||
val memberValidator = MemberValidator(memberRepository)
|
||||
|
||||
context("validateCanSignup") {
|
||||
val email = "email@email.com"
|
||||
|
||||
test("같은 이메일을 가진 회원이 있으면 예외를 던진다.") {
|
||||
every {
|
||||
memberRepository.existsByEmail(email)
|
||||
} returns true
|
||||
|
||||
shouldThrow<MemberException> {
|
||||
memberValidator.validateCanSignup(email)
|
||||
}.also {
|
||||
it.errorCode shouldBe MemberErrorCode.DUPLICATE_EMAIL
|
||||
}
|
||||
}
|
||||
|
||||
test("같은 이메일을 가진 회원이 없으면 종료한다.") {
|
||||
every {
|
||||
memberRepository.existsByEmail(email)
|
||||
} returns false
|
||||
|
||||
shouldNotThrow<MemberException> {
|
||||
memberValidator.validateCanSignup(email)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1,65 +0,0 @@
|
||||
package roomescape.member.implement
|
||||
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.core.spec.style.StringSpec
|
||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import roomescape.member.exception.MemberErrorCode
|
||||
import roomescape.member.exception.MemberException
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.member.infrastructure.persistence.MemberRepository
|
||||
import roomescape.member.infrastructure.persistence.Role
|
||||
import roomescape.util.MemberFixture
|
||||
import roomescape.util.TsidFactory
|
||||
|
||||
class MemberWriterTest : FunSpec({
|
||||
val memberRepository: MemberRepository = mockk()
|
||||
val memberValidator = MemberValidator(memberRepository)
|
||||
val memberWriter = MemberWriter(TsidFactory, memberValidator, memberRepository)
|
||||
|
||||
context("create") {
|
||||
val name = "name"
|
||||
val email = "email"
|
||||
val password = "password"
|
||||
val role = Role.MEMBER
|
||||
|
||||
test("중복된 이메일이 있으면 실패한다.") {
|
||||
every {
|
||||
memberRepository.existsByEmail(any())
|
||||
} returns true
|
||||
|
||||
shouldThrow<MemberException> {
|
||||
memberWriter.create(name, email, password, role)
|
||||
}.also {
|
||||
it.errorCode shouldBe MemberErrorCode.DUPLICATE_EMAIL
|
||||
}
|
||||
}
|
||||
|
||||
test("중복된 이메일이 없으면 저장한다.") {
|
||||
every {
|
||||
memberRepository.existsByEmail(any())
|
||||
} returns false
|
||||
|
||||
every {
|
||||
memberRepository.save(any())
|
||||
} returns MemberFixture.create(
|
||||
name = name,
|
||||
account = email,
|
||||
password = password,
|
||||
role = role
|
||||
)
|
||||
|
||||
memberWriter.create(name, email, password, role)
|
||||
|
||||
verify(exactly = 1) {
|
||||
memberRepository.save(any())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1,63 +0,0 @@
|
||||
package roomescape.member.infrastructure.persistence
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||
import io.kotest.matchers.shouldBe
|
||||
import jakarta.persistence.EntityManager
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||
import roomescape.util.MemberFixture
|
||||
|
||||
@DataJpaTest(showSql = true)
|
||||
class MemberRepositoryTest(
|
||||
val entityManager: EntityManager,
|
||||
val memberRepository: MemberRepository
|
||||
) : FunSpec({
|
||||
context("existsByEmail") {
|
||||
val account = "email"
|
||||
val email = "$account@email.com"
|
||||
|
||||
beforeTest {
|
||||
entityManager.persist(MemberFixture.create(account = account))
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
}
|
||||
|
||||
test("동일한 이메일이 있으면 true 반환") {
|
||||
val result = memberRepository.existsByEmail(email)
|
||||
result shouldBe true
|
||||
}
|
||||
|
||||
test("동일한 이메일이 없으면 false 반환") {
|
||||
memberRepository.existsByEmail(email.substring(email.length - 1)) shouldBe false
|
||||
}
|
||||
}
|
||||
|
||||
context("findByEmailAndPassword") {
|
||||
val account = "email"
|
||||
val email = "$account@email.com"
|
||||
val password = "password123"
|
||||
|
||||
beforeTest {
|
||||
entityManager.persist(MemberFixture.create(account = account, password = password))
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
}
|
||||
|
||||
test("둘다 일치하면 정상 반환") {
|
||||
memberRepository.findByEmailAndPassword(email, password) shouldNotBeNull {
|
||||
this.email shouldBe email
|
||||
this.password shouldBe password
|
||||
}
|
||||
}
|
||||
|
||||
test("이메일이 틀리면 null 반환") {
|
||||
val invalidMail = email.substring(email.length - 1)
|
||||
memberRepository.findByEmailAndPassword(invalidMail, password) shouldBe null
|
||||
}
|
||||
|
||||
test("비밀번호가 틀리면 null 반환") {
|
||||
val invalidPassword = password.substring(password.length - 1)
|
||||
memberRepository.findByEmailAndPassword(email, invalidPassword) shouldBe null
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -290,7 +290,7 @@ class ScheduleApiTest(
|
||||
val themeId: Long = Given {
|
||||
contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
header("Authorization", "Bearer $token")
|
||||
body(ThemeFixtureV2.createRequest.copy(name = "theme-${System.currentTimeMillis()}"))
|
||||
body(ThemeFixture.createRequest.copy(name = "theme-${System.currentTimeMillis()}"))
|
||||
} When {
|
||||
post("/admin/themes")
|
||||
} Extract {
|
||||
@ -616,7 +616,7 @@ class ScheduleApiTest(
|
||||
Given {
|
||||
contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
header("Authorization", "Bearer $token")
|
||||
body(ThemeFixtureV2.createRequest.copy(name = "theme-${System.currentTimeMillis()}"))
|
||||
body(ThemeFixture.createRequest.copy(name = "theme-${System.currentTimeMillis()}"))
|
||||
} When {
|
||||
post("/admin/themes")
|
||||
} Extract {
|
||||
|
||||
@ -19,18 +19,18 @@ import roomescape.theme.business.MIN_DURATION
|
||||
import roomescape.theme.business.MIN_PARTICIPANTS
|
||||
import roomescape.theme.business.MIN_PRICE
|
||||
import roomescape.theme.exception.ThemeErrorCode
|
||||
import roomescape.theme.infrastructure.persistence.v2.ThemeEntityV2
|
||||
import roomescape.theme.infrastructure.persistence.v2.ThemeRepositoryV2
|
||||
import roomescape.theme.web.ThemeCreateRequestV2
|
||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||
import roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||
import roomescape.theme.web.ThemeCreateRequest
|
||||
import roomescape.theme.web.ThemeUpdateRequest
|
||||
import roomescape.util.FunSpecSpringbootTest
|
||||
import roomescape.util.ThemeFixtureV2.createRequest
|
||||
import roomescape.util.ThemeFixture.createRequest
|
||||
import roomescape.util.assertProperties
|
||||
import roomescape.util.runTest
|
||||
import kotlin.random.Random
|
||||
|
||||
class ThemeApiTest(
|
||||
private val themeRepository: ThemeRepositoryV2
|
||||
private val themeRepository: ThemeRepository
|
||||
) : FunSpecSpringbootTest() {
|
||||
|
||||
|
||||
@ -146,7 +146,7 @@ class ThemeApiTest(
|
||||
}
|
||||
).also {
|
||||
val createdThemeId: Long = it.extract().path("data.id")
|
||||
val createdTheme: ThemeEntityV2 = themeRepository.findByIdOrNull(createdThemeId)
|
||||
val createdTheme: ThemeEntity = themeRepository.findByIdOrNull(createdThemeId)
|
||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||
|
||||
createdTheme.name shouldBe createRequest.name
|
||||
@ -376,7 +376,7 @@ class ThemeApiTest(
|
||||
|
||||
context("관리자 페이지에서 특정 테마의 상세 정보를 조회한다.") {
|
||||
test("정상 응답") {
|
||||
val createdTheme: ThemeEntityV2 = createDummyTheme(createRequest)
|
||||
val createdTheme: ThemeEntity = createDummyTheme(createRequest)
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsAdmin(),
|
||||
@ -445,7 +445,7 @@ class ThemeApiTest(
|
||||
|
||||
context("테마를 수정한다.") {
|
||||
lateinit var token: String
|
||||
lateinit var createdTheme: ThemeEntityV2
|
||||
lateinit var createdTheme: ThemeEntity
|
||||
lateinit var apiPath: String
|
||||
|
||||
val updateRequest = ThemeUpdateRequest(name = "modified")
|
||||
@ -671,7 +671,7 @@ class ThemeApiTest(
|
||||
}
|
||||
}
|
||||
|
||||
fun createDummyTheme(request: ThemeCreateRequestV2): ThemeEntityV2 {
|
||||
fun createDummyTheme(request: ThemeCreateRequest): ThemeEntity {
|
||||
val createdThemeId: Long = Given {
|
||||
contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
header("Authorization", "Bearer ${loginUtil.loginAsAdmin()}")
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
package roomescape.util
|
||||
|
||||
import com.github.f4b6a3.tsid.TsidFactory
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.member.infrastructure.persistence.Role
|
||||
import roomescape.schedule.web.ScheduleCreateRequest
|
||||
import roomescape.theme.infrastructure.persistence.v2.Difficulty
|
||||
import roomescape.theme.web.ThemeCreateRequestV2
|
||||
import roomescape.theme.infrastructure.persistence.Difficulty
|
||||
import roomescape.theme.web.ThemeCreateRequest
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
object MemberFixtureV2 {
|
||||
val tsidFactory = TsidFactory(0)
|
||||
|
||||
object MemberFixture {
|
||||
val admin: MemberEntity = MemberEntity(
|
||||
_id = 9304,
|
||||
name = "ADMIN",
|
||||
@ -26,8 +29,8 @@ object MemberFixtureV2 {
|
||||
)
|
||||
}
|
||||
|
||||
object ThemeFixtureV2 {
|
||||
val createRequest: ThemeCreateRequestV2 = ThemeCreateRequestV2(
|
||||
object ThemeFixture {
|
||||
val createRequest: ThemeCreateRequest = ThemeCreateRequest(
|
||||
name = "Matilda Green",
|
||||
description = "constituto",
|
||||
thumbnailUrl = "https://duckduckgo.com/?q=mediocrem",
|
||||
|
||||
@ -21,7 +21,7 @@ class LoginUtil(
|
||||
if (!memberRepository.existsByEmail(email)) {
|
||||
memberRepository.save(
|
||||
MemberEntity(
|
||||
_id = TsidFactory.next(),
|
||||
_id = tsidFactory.next(),
|
||||
email = email,
|
||||
password = password,
|
||||
name = email.split("@").first(),
|
||||
@ -43,11 +43,11 @@ class LoginUtil(
|
||||
}
|
||||
|
||||
fun loginAsAdmin(): String {
|
||||
return login(MemberFixtureV2.admin.email, MemberFixtureV2.admin.password, Role.ADMIN)
|
||||
return login(MemberFixture.admin.email, MemberFixture.admin.password, Role.ADMIN)
|
||||
}
|
||||
|
||||
fun loginAsUser(): String {
|
||||
return login(MemberFixtureV2.user.email, MemberFixtureV2.user.password)
|
||||
return login(MemberFixture.user.email, MemberFixture.user.password)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,145 +0,0 @@
|
||||
package roomescape.util
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.github.f4b6a3.tsid.TsidFactory
|
||||
import com.ninjasquad.springmockk.MockkBean
|
||||
import com.ninjasquad.springmockk.SpykBean
|
||||
import io.kotest.core.spec.style.BehaviorSpec
|
||||
import io.mockk.every
|
||||
import org.springframework.boot.test.context.TestConfiguration
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Import
|
||||
import org.springframework.context.annotation.Primary
|
||||
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.test.web.servlet.*
|
||||
import roomescape.auth.exception.AuthErrorCode
|
||||
import roomescape.auth.exception.AuthException
|
||||
import roomescape.auth.infrastructure.jwt.JwtHandler
|
||||
import roomescape.auth.web.support.AuthInterceptor
|
||||
import roomescape.auth.web.support.MemberIdResolver
|
||||
import roomescape.common.config.JacksonConfig
|
||||
import roomescape.common.log.ApiLogMessageConverter
|
||||
import roomescape.member.exception.MemberErrorCode
|
||||
import roomescape.member.exception.MemberException
|
||||
import roomescape.member.implement.MemberFinder
|
||||
import roomescape.member.infrastructure.persistence.MemberEntity
|
||||
import roomescape.member.infrastructure.persistence.MemberRepository
|
||||
import roomescape.util.MemberFixture.NOT_LOGGED_IN_USERID
|
||||
|
||||
@Import(TestConfig::class, JacksonConfig::class)
|
||||
@MockkBean(JpaMetamodelMappingContext::class)
|
||||
abstract class RoomescapeApiTest : BehaviorSpec() {
|
||||
|
||||
@SpykBean
|
||||
private lateinit var authInterceptor: AuthInterceptor
|
||||
|
||||
@SpykBean
|
||||
private lateinit var memberIdResolver: MemberIdResolver
|
||||
|
||||
@MockkBean
|
||||
private lateinit var memberRepository: MemberRepository
|
||||
|
||||
@SpykBean
|
||||
lateinit var apiLogMessageConverter: ApiLogMessageConverter
|
||||
|
||||
@SpykBean
|
||||
lateinit var memberFinder: MemberFinder
|
||||
|
||||
@MockkBean
|
||||
lateinit var jwtHandler: JwtHandler
|
||||
|
||||
val objectMapper: ObjectMapper = JacksonConfig().objectMapper()
|
||||
val admin: MemberEntity = MemberFixture.admin()
|
||||
val user: MemberEntity = MemberFixture.user()
|
||||
|
||||
fun runGetTest(
|
||||
mockMvc: MockMvc,
|
||||
endpoint: String,
|
||||
log: Boolean = false,
|
||||
assert: MockMvcResultMatchersDsl.() -> Unit
|
||||
): ResultActionsDsl = mockMvc.get(endpoint) {
|
||||
header(HttpHeaders.AUTHORIZATION, "Bearer token")
|
||||
}.apply {
|
||||
log.takeIf { it }?.let { this.andDo { print() } }
|
||||
}.andExpect(assert)
|
||||
|
||||
fun runPostTest(
|
||||
mockMvc: MockMvc,
|
||||
endpoint: String,
|
||||
body: Any? = null,
|
||||
log: Boolean = false,
|
||||
assert: MockMvcResultMatchersDsl.() -> Unit
|
||||
): ResultActionsDsl = mockMvc.post(endpoint) {
|
||||
this.header(HttpHeaders.AUTHORIZATION, "Bearer token")
|
||||
body?.let {
|
||||
this.contentType = MediaType.APPLICATION_JSON
|
||||
this.content = objectMapper.writeValueAsString(it)
|
||||
}
|
||||
}.apply {
|
||||
log.takeIf { it }?.let { this.andDo { print() } }
|
||||
}.andExpect(assert)
|
||||
|
||||
fun runDeleteTest(
|
||||
mockMvc: MockMvc,
|
||||
endpoint: String,
|
||||
log: Boolean = false,
|
||||
assert: MockMvcResultMatchersDsl.() -> Unit
|
||||
): ResultActionsDsl = mockMvc.delete(endpoint) {
|
||||
header(HttpHeaders.AUTHORIZATION, "Bearer token")
|
||||
}.apply {
|
||||
log.takeIf { it }?.let { this.andDo { print() } }
|
||||
}.andExpect(assert)
|
||||
|
||||
fun loginAsAdmin() {
|
||||
every {
|
||||
jwtHandler.getMemberIdFromToken(any())
|
||||
} returns admin.id!!
|
||||
|
||||
every { memberFinder.findById(admin.id!!) } returns admin
|
||||
}
|
||||
|
||||
fun loginAsUser() {
|
||||
every {
|
||||
jwtHandler.getMemberIdFromToken(any())
|
||||
} returns user.id!!
|
||||
|
||||
every { memberFinder.findById(user.id!!) } returns user
|
||||
}
|
||||
|
||||
fun doNotLogin() {
|
||||
every {
|
||||
jwtHandler.getMemberIdFromToken(any())
|
||||
} throws AuthException(AuthErrorCode.INVALID_TOKEN)
|
||||
|
||||
every {
|
||||
memberFinder.findById(NOT_LOGGED_IN_USERID)
|
||||
} throws MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
|
||||
}
|
||||
|
||||
fun <T> MvcResult.readValue(valueType: Class<T>): T = this.response.contentAsString
|
||||
.takeIf { it.isNotBlank() }
|
||||
?.let { readValue(it, valueType) }
|
||||
?: throw RuntimeException(
|
||||
"""
|
||||
[Test] Exception occurred while reading response json: ${this.response.contentAsString} with value type: $valueType
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
fun <T> readValue(responseJson: String, valueType: Class<T>): T = objectMapper
|
||||
.readTree(responseJson)["data"]
|
||||
?.let { objectMapper.convertValue(it, valueType) }
|
||||
?: throw RuntimeException(
|
||||
"""
|
||||
[Test] Exception occurred while reading response json: $responseJson with value type: $valueType
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
class TestConfig {
|
||||
@Bean
|
||||
@Primary
|
||||
fun tsidFactory(): TsidFactory = TsidFactory
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user