[#41] 예약 스키마 재정의 #42

Merged
pricelees merged 41 commits from refactor/#41 into main 2025-09-09 00:43:39 +00:00
15 changed files with 22 additions and 975 deletions
Showing only changes of commit a4f384a242 - Show all commits

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 {
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 {

View File

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

View File

@ -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",

View File

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

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
}