test: 통합 테스트 전환을 위해 이전의 테스트 전체 제거

This commit is contained in:
이상진 2025-09-07 22:18:45 +09:00
parent e4b9214d75
commit a4f384a242
15 changed files with 22 additions and 975 deletions

View File

@ -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
}
}
}
})

View File

@ -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
}
}
})

View File

@ -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() }
}
}
}
}
}
}

View File

@ -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
}
}
})

View File

@ -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
}
}
}
})

View File

@ -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) }
}
}
}
}
}
}

View File

@ -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
}
}
}
})

View File

@ -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)
}
}
}
})

View File

@ -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())
}
}
}
})

View File

@ -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
}
}
})

View File

@ -290,7 +290,7 @@ class ScheduleApiTest(
val themeId: Long = Given { val themeId: Long = Given {
contentType(MediaType.APPLICATION_JSON_VALUE) contentType(MediaType.APPLICATION_JSON_VALUE)
header("Authorization", "Bearer $token") header("Authorization", "Bearer $token")
body(ThemeFixtureV2.createRequest.copy(name = "theme-${System.currentTimeMillis()}")) body(ThemeFixture.createRequest.copy(name = "theme-${System.currentTimeMillis()}"))
} When { } When {
post("/admin/themes") post("/admin/themes")
} Extract { } Extract {
@ -616,7 +616,7 @@ class ScheduleApiTest(
Given { Given {
contentType(MediaType.APPLICATION_JSON_VALUE) contentType(MediaType.APPLICATION_JSON_VALUE)
header("Authorization", "Bearer $token") header("Authorization", "Bearer $token")
body(ThemeFixtureV2.createRequest.copy(name = "theme-${System.currentTimeMillis()}")) body(ThemeFixture.createRequest.copy(name = "theme-${System.currentTimeMillis()}"))
} When { } When {
post("/admin/themes") post("/admin/themes")
} Extract { } Extract {

View File

@ -19,18 +19,18 @@ import roomescape.theme.business.MIN_DURATION
import roomescape.theme.business.MIN_PARTICIPANTS import roomescape.theme.business.MIN_PARTICIPANTS
import roomescape.theme.business.MIN_PRICE import roomescape.theme.business.MIN_PRICE
import roomescape.theme.exception.ThemeErrorCode import roomescape.theme.exception.ThemeErrorCode
import roomescape.theme.infrastructure.persistence.v2.ThemeEntityV2 import roomescape.theme.infrastructure.persistence.ThemeEntity
import roomescape.theme.infrastructure.persistence.v2.ThemeRepositoryV2 import roomescape.theme.infrastructure.persistence.ThemeRepository
import roomescape.theme.web.ThemeCreateRequestV2 import roomescape.theme.web.ThemeCreateRequest
import roomescape.theme.web.ThemeUpdateRequest import roomescape.theme.web.ThemeUpdateRequest
import roomescape.util.FunSpecSpringbootTest import roomescape.util.FunSpecSpringbootTest
import roomescape.util.ThemeFixtureV2.createRequest import roomescape.util.ThemeFixture.createRequest
import roomescape.util.assertProperties import roomescape.util.assertProperties
import roomescape.util.runTest import roomescape.util.runTest
import kotlin.random.Random import kotlin.random.Random
class ThemeApiTest( class ThemeApiTest(
private val themeRepository: ThemeRepositoryV2 private val themeRepository: ThemeRepository
) : FunSpecSpringbootTest() { ) : FunSpecSpringbootTest() {
@ -146,7 +146,7 @@ class ThemeApiTest(
} }
).also { ).also {
val createdThemeId: Long = it.extract().path("data.id") 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.") ?: throw AssertionError("Unexpected Exception Occurred.")
createdTheme.name shouldBe createRequest.name createdTheme.name shouldBe createRequest.name
@ -376,7 +376,7 @@ class ThemeApiTest(
context("관리자 페이지에서 특정 테마의 상세 정보를 조회한다.") { context("관리자 페이지에서 특정 테마의 상세 정보를 조회한다.") {
test("정상 응답") { test("정상 응답") {
val createdTheme: ThemeEntityV2 = createDummyTheme(createRequest) val createdTheme: ThemeEntity = createDummyTheme(createRequest)
runTest( runTest(
token = loginUtil.loginAsAdmin(), token = loginUtil.loginAsAdmin(),
@ -445,7 +445,7 @@ class ThemeApiTest(
context("테마를 수정한다.") { context("테마를 수정한다.") {
lateinit var token: String lateinit var token: String
lateinit var createdTheme: ThemeEntityV2 lateinit var createdTheme: ThemeEntity
lateinit var apiPath: String lateinit var apiPath: String
val updateRequest = ThemeUpdateRequest(name = "modified") 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 { val createdThemeId: Long = Given {
contentType(MediaType.APPLICATION_JSON_VALUE) contentType(MediaType.APPLICATION_JSON_VALUE)
header("Authorization", "Bearer ${loginUtil.loginAsAdmin()}") header("Authorization", "Bearer ${loginUtil.loginAsAdmin()}")

View File

@ -1,14 +1,17 @@
package roomescape.util package roomescape.util
import com.github.f4b6a3.tsid.TsidFactory
import roomescape.member.infrastructure.persistence.MemberEntity import roomescape.member.infrastructure.persistence.MemberEntity
import roomescape.member.infrastructure.persistence.Role import roomescape.member.infrastructure.persistence.Role
import roomescape.schedule.web.ScheduleCreateRequest import roomescape.schedule.web.ScheduleCreateRequest
import roomescape.theme.infrastructure.persistence.v2.Difficulty import roomescape.theme.infrastructure.persistence.Difficulty
import roomescape.theme.web.ThemeCreateRequestV2 import roomescape.theme.web.ThemeCreateRequest
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
object MemberFixtureV2 { val tsidFactory = TsidFactory(0)
object MemberFixture {
val admin: MemberEntity = MemberEntity( val admin: MemberEntity = MemberEntity(
_id = 9304, _id = 9304,
name = "ADMIN", name = "ADMIN",
@ -26,8 +29,8 @@ object MemberFixtureV2 {
) )
} }
object ThemeFixtureV2 { object ThemeFixture {
val createRequest: ThemeCreateRequestV2 = ThemeCreateRequestV2( val createRequest: ThemeCreateRequest = ThemeCreateRequest(
name = "Matilda Green", name = "Matilda Green",
description = "constituto", description = "constituto",
thumbnailUrl = "https://duckduckgo.com/?q=mediocrem", thumbnailUrl = "https://duckduckgo.com/?q=mediocrem",

View File

@ -21,7 +21,7 @@ class LoginUtil(
if (!memberRepository.existsByEmail(email)) { if (!memberRepository.existsByEmail(email)) {
memberRepository.save( memberRepository.save(
MemberEntity( MemberEntity(
_id = TsidFactory.next(), _id = tsidFactory.next(),
email = email, email = email,
password = password, password = password,
name = email.split("@").first(), name = email.split("@").first(),
@ -43,11 +43,11 @@ class LoginUtil(
} }
fun loginAsAdmin(): String { 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 { fun loginAsUser(): String {
return login(MemberFixtureV2.user.email, MemberFixtureV2.user.password) return login(MemberFixture.user.email, MemberFixture.user.password)
} }
} }

View File

@ -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
}