740 lines
32 KiB
Kotlin

package roomescape.theme
import io.kotest.matchers.date.shouldBeAfter
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import org.hamcrest.CoreMatchers.equalTo
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus
import roomescape.admin.infrastructure.persistence.AdminPermissionLevel
import roomescape.admin.infrastructure.persistence.AdminType
import roomescape.auth.exception.AuthErrorCode
import roomescape.supports.*
import roomescape.supports.ThemeFixture.createRequest
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.ThemeEntity
import roomescape.theme.infrastructure.persistence.ThemeRepository
import roomescape.theme.web.ThemeUpdateRequest
class HQAdminThemeApiTest(
private val themeRepository: ThemeRepository
) : FunSpecSpringbootTest() {
init {
context("테마를 생성한다.") {
val endpoint = "/admin/themes"
context("권한이 없으면 접근할 수 없다.") {
test("비회원") {
runExceptionTest(
method = HttpMethod.POST,
requestBody = createRequest,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
)
}
test("회원") {
runExceptionTest(
token = testAuthUtil.defaultUserLogin(),
method = HttpMethod.POST,
requestBody = createRequest,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
AdminPermissionLevel.entries.forEach {
test("관리자: Type=${AdminType.STORE} / Permission=${it}") {
val admin = AdminFixture.createStoreAdmin(permissionLevel = it)
runExceptionTest(
token = testAuthUtil.adminLogin(admin),
method = HttpMethod.POST,
requestBody = createRequest,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
if (it == AdminPermissionLevel.READ_ALL || it == AdminPermissionLevel.READ_SUMMARY) {
test("관리자: Type=${AdminType.HQ} / Permission=${it}") {
val admin = AdminFixture.createHqAdmin(permissionLevel = it)
runExceptionTest(
token = testAuthUtil.adminLogin(admin),
method = HttpMethod.POST,
requestBody = createRequest,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
}
}
}
test("정상 생성 및 감사 정보 확인") {
runTest(
token = testAuthUtil.defaultHqAdminLogin(),
using = {
body(createRequest)
},
on = {
post(endpoint)
},
expect = {
statusCode(HttpStatus.CREATED.value())
}
).also {
val createdThemeId: Long = it.extract().path("data.id")
val createdTheme: ThemeEntity = themeRepository.findByIdOrNull(createdThemeId)
?: throw AssertionError("Unexpected Exception Occurred.")
createdTheme.name shouldBe createRequest.name
createdTheme.createdAt shouldNotBeNull {}
createdTheme.createdBy shouldNotBeNull {}
createdTheme.updatedAt shouldNotBeNull {}
createdTheme.updatedBy shouldNotBeNull {}
}
}
test("이미 동일한 이름의 테마가 있으면 실패한다.") {
val token = testAuthUtil.defaultHqAdminLogin()
val alreadyExistsName: String = initialize("테스트를 위한 테마 생성 및 이름 반환") {
dummyInitializer.createTheme(token, createRequest).name
}
runTest(
token = token,
using = {
body(createRequest.copy(name = alreadyExistsName))
},
on = {
post(endpoint)
},
expect = {
statusCode(HttpStatus.BAD_REQUEST.value())
body("code", equalTo(ThemeErrorCode.THEME_NAME_DUPLICATED.errorCode))
}
)
}
test("금액이 ${MIN_PRICE}원 미만이면 실패한다.") {
val token = testAuthUtil.defaultHqAdminLogin()
runTest(
token = token,
using = {
body(createRequest.copy(price = (MIN_PRICE - 1)))
},
on = {
post(endpoint)
},
expect = {
statusCode(HttpStatus.BAD_REQUEST.value())
body("code", equalTo(ThemeErrorCode.PRICE_BELOW_MINIMUM.errorCode))
}
)
}
context("입력된 시간이 ${MIN_DURATION}분 미만이면 실패한다.") {
test("field: availableMinutes") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin(),
method = HttpMethod.POST,
endpoint = endpoint,
requestBody = createRequest.copy(availableMinutes = (MIN_DURATION - 1).toShort()),
expectedErrorCode = ThemeErrorCode.DURATION_BELOW_MINIMUM
)
}
test("field: expectedMinutesFrom") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin(),
method = HttpMethod.POST,
endpoint = endpoint,
requestBody = createRequest.copy(expectedMinutesFrom = (MIN_DURATION - 1).toShort()),
expectedErrorCode = ThemeErrorCode.DURATION_BELOW_MINIMUM
)
}
test("field: expectedMinutesTo") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin(),
method = HttpMethod.POST,
endpoint = endpoint,
requestBody = createRequest.copy(expectedMinutesTo = (MIN_DURATION - 1).toShort()),
expectedErrorCode = ThemeErrorCode.DURATION_BELOW_MINIMUM
)
}
}
context("시간 범위가 잘못 지정되면 실패한다.") {
test("최소 예상 시간 > 최대 예상 시간") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin(),
method = HttpMethod.POST,
endpoint = endpoint,
requestBody = createRequest.copy(expectedMinutesFrom = 100, expectedMinutesTo = 99),
expectedErrorCode = ThemeErrorCode.MIN_EXPECTED_TIME_EXCEEDS_MAX_EXPECTED_TIME
)
}
test("최대 예상 시간 > 이용 가능 시간") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin(),
method = HttpMethod.POST,
endpoint = endpoint,
requestBody = createRequest.copy(
availableMinutes = 100,
expectedMinutesFrom = 101,
expectedMinutesTo = 101
),
expectedErrorCode = ThemeErrorCode.EXPECTED_TIME_EXCEEDS_AVAILABLE_TIME
)
}
}
context("입력된 인원이 ${MIN_PARTICIPANTS}명 미만이면 실패한다.") {
test("field: minParticipants") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin(),
method = HttpMethod.POST,
endpoint = endpoint,
requestBody = createRequest.copy(minParticipants = (MIN_PARTICIPANTS - 1).toShort()),
expectedErrorCode = ThemeErrorCode.PARTICIPANT_BELOW_MINIMUM
)
}
test("field: maxParticipants") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin(),
method = HttpMethod.POST,
endpoint = endpoint,
requestBody = createRequest.copy(maxParticipants = (MIN_PARTICIPANTS - 1).toShort()),
expectedErrorCode = ThemeErrorCode.PARTICIPANT_BELOW_MINIMUM
)
}
}
context("인원 범위가 잘못 지정되면 실패한다.") {
test("최소 인원 > 최대 인원") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin(),
method = HttpMethod.POST,
endpoint = endpoint,
requestBody = createRequest.copy(minParticipants = 10, maxParticipants = 9),
expectedErrorCode = ThemeErrorCode.MIN_PARTICIPANT_EXCEEDS_MAX_PARTICIPANT
)
}
}
}
context("테마 요약 목록을 조회한다.") {
val endpoint = "/admin/themes"
context("권한이 없으면 접근할 수 없다.") {
test("비회원") {
runExceptionTest(
method = HttpMethod.GET,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
)
}
test("회원") {
runExceptionTest(
token = testAuthUtil.defaultUserLogin(),
method = HttpMethod.GET,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
AdminPermissionLevel.entries.forEach {
test("관리자: Type=${AdminType.STORE} / Permission=${it}") {
val admin = AdminFixture.createStoreAdmin(permissionLevel = it)
runExceptionTest(
token = testAuthUtil.adminLogin(admin),
method = HttpMethod.POST,
requestBody = createRequest,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
}
}
test("정상 응답") {
val token = testAuthUtil.defaultHqAdminLogin()
val themes: List<ThemeEntity> = initialize("Active 상태인 테마 1개 / Inactive 상태인 테마 2개 생성") {
listOf(
dummyInitializer.createTheme(token, createRequest.copy(name = "active-1", isActive = true)),
dummyInitializer.createTheme(token, createRequest.copy(name = "inactive-1", isActive = false)),
dummyInitializer.createTheme(token, createRequest.copy(name = "inactive-2", isActive = false))
)
}
runTest(
token = token,
on = {
get(endpoint)
},
expect = {
body("data.themes.size()", equalTo(themes.size))
assertProperties(
props = setOf("id", "name", "difficulty", "price", "isActive"),
propsNameIfList = "themes",
)
}
)
}
}
context("관리자 페이지에서 특정 테마의 상세 정보를 조회한다.") {
context("권한이 없으면 접근할 수 없다.") {
val endpoint = "/admin/themes/$INVALID_PK"
test("비회원") {
runExceptionTest(
method = HttpMethod.GET,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
)
}
test("회원") {
runExceptionTest(
token = testAuthUtil.defaultUserLogin(),
method = HttpMethod.GET,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
AdminPermissionLevel.entries.forEach {
test("관리자: Type=${AdminType.STORE} / Permission=${it}") {
val admin = AdminFixture.createStoreAdmin(permissionLevel = it)
runExceptionTest(
token = testAuthUtil.adminLogin(admin),
method = HttpMethod.GET,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
if (it == AdminPermissionLevel.READ_SUMMARY) {
test("관리자: Type=${AdminType.HQ} / Permission=${it}") {
val admin = AdminFixture.createHqAdmin(permissionLevel = it)
runExceptionTest(
token = testAuthUtil.adminLogin(admin),
method = HttpMethod.GET,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
}
}
}
test("정상 응답") {
val token = testAuthUtil.defaultHqAdminLogin()
val createdTheme = initialize("테스트를 위한 테마 생성") {
dummyInitializer.createTheme(token, createRequest)
}
runTest(
token = token,
on = {
get("/admin/themes/${createdTheme.id}")
},
expect = {
statusCode(HttpStatus.OK.value())
body("data.id", equalTo(createdTheme.id))
assertProperties(
props = setOf(
"id", "name", "description", "thumbnailUrl", "difficulty", "price", "isActive",
"minParticipants", "maxParticipants",
"availableMinutes", "expectedMinutesFrom", "expectedMinutesTo",
"createdAt", "createdBy", "updatedAt", "updatedBy"
)
)
}
)
}
test("테마가 없으면 실패한다.") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin(),
method = HttpMethod.GET,
endpoint = "/admin/themes/$INVALID_PK",
expectedErrorCode = ThemeErrorCode.THEME_NOT_FOUND
)
}
}
context("테마를 삭제한다.") {
context("권한이 없으면 접근할 수 없다.") {
val endpoint = "/admin/themes/${INVALID_PK}"
test("비회원") {
runExceptionTest(
method = HttpMethod.DELETE,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
)
}
test("회원") {
runExceptionTest(
token = testAuthUtil.defaultUserLogin(),
method = HttpMethod.DELETE,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
AdminPermissionLevel.entries.forEach {
test("관리자: Type=${AdminType.STORE} / Permission=${it}") {
val admin = AdminFixture.createStoreAdmin(permissionLevel = it)
runExceptionTest(
token = testAuthUtil.adminLogin(admin),
method = HttpMethod.DELETE,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
if (it == AdminPermissionLevel.READ_ALL || it == AdminPermissionLevel.READ_SUMMARY) {
test("관리자: Type=${AdminType.HQ} / Permission=${it}") {
val admin = AdminFixture.createHqAdmin(permissionLevel = it)
runExceptionTest(
token = testAuthUtil.adminLogin(admin),
method = HttpMethod.DELETE,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
}
}
}
test("정상 삭제") {
val token = testAuthUtil.defaultHqAdminLogin()
val createdTheme = initialize("테스트를 위한 테마 생성") {
dummyInitializer.createTheme(token, createRequest)
}
runTest(
token = token,
on = {
delete("/admin/themes/${createdTheme.id}")
},
expect = {
statusCode(HttpStatus.NO_CONTENT.value())
}
).also {
themeRepository.findByIdOrNull(createdTheme.id) shouldBe null
}
}
test("테마가 없으면 실패한다.") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin(),
method = HttpMethod.DELETE,
endpoint = "/admin/themes/$INVALID_PK",
expectedErrorCode = ThemeErrorCode.THEME_NOT_FOUND
)
}
}
context("테마를 수정한다.") {
context("권한이 없으면 접근할 수 없다.") {
val endpoint = "/admin/themes/${INVALID_PK}"
val request = ThemeUpdateRequest(name = "hello")
test("비회원") {
runExceptionTest(
method = HttpMethod.PATCH,
endpoint = endpoint,
requestBody = request,
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
)
}
test("회원") {
runExceptionTest(
token = testAuthUtil.defaultUserLogin(),
method = HttpMethod.PATCH,
endpoint = endpoint,
requestBody = request,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
AdminPermissionLevel.entries.forEach {
test("관리자: Type=${AdminType.STORE} / Permission=${it}") {
val admin = AdminFixture.createStoreAdmin(permissionLevel = it)
runExceptionTest(
token = testAuthUtil.adminLogin(admin),
method = HttpMethod.PATCH,
endpoint = endpoint,
requestBody = request,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
if (it == AdminPermissionLevel.READ_ALL || it == AdminPermissionLevel.READ_SUMMARY) {
test("관리자: Type=${AdminType.HQ} / Permission=${it}") {
val admin = AdminFixture.createHqAdmin(permissionLevel = it)
runExceptionTest(
token = testAuthUtil.adminLogin(admin),
method = HttpMethod.PATCH,
endpoint = endpoint,
requestBody = request,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
}
}
}
val updateRequest = ThemeUpdateRequest(name = "modified")
test("정상 수정 및 감사 정보 변경 확인") {
val createdTheme = initialize("테스트를 위한 관리자1의 테마 생성") {
dummyInitializer.createTheme(testAuthUtil.defaultHqAdminLogin(), createRequest)
}
val otherAdminToken: String = initialize("감사 정보 변경 확인을 위한 관리자2 로그인") {
testAuthUtil.adminLogin(
AdminFixture.createHqAdmin(permissionLevel = AdminPermissionLevel.WRITABLE)
)
}
runTest(
token = otherAdminToken,
using = {
body(updateRequest)
},
on = {
patch("/admin/themes/${createdTheme.id}")
},
expect = {
statusCode(HttpStatus.OK.value())
}
).also {
val updatedTheme = themeRepository.findByIdOrNull(createdTheme.id)!!
updatedTheme.id shouldBe createdTheme.id
updatedTheme.name shouldBe updateRequest.name
updatedTheme.updatedBy shouldNotBe createdTheme.updatedBy
updatedTheme.updatedAt shouldBeAfter createdTheme.updatedAt
}
}
test("입력값이 없으면 수정하지 않는다.") {
val token = testAuthUtil.defaultHqAdminLogin()
val createdTheme = initialize("테스트를 위한 테마 생성") {
dummyInitializer.createTheme(token, createRequest)
}
runTest(
token = token,
using = {
body(ThemeUpdateRequest())
},
on = {
patch("/admin/themes/${createdTheme.id}")
},
expect = {
statusCode(HttpStatus.OK.value())
}
).also {
val updatedTheme = themeRepository.findByIdOrNull(createdTheme.id)!!
updatedTheme.id shouldBe createdTheme.id
updatedTheme.updatedAt shouldBe createdTheme.updatedAt
}
}
test("테마가 없으면 실패한다.") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin(),
method = HttpMethod.PATCH,
endpoint = "/admin/themes/$INVALID_PK",
requestBody = updateRequest,
expectedErrorCode = ThemeErrorCode.THEME_NOT_FOUND
)
}
test("금액이 ${MIN_PRICE}원 미만이면 실패한다.") {
val token = testAuthUtil.defaultHqAdminLogin()
val createdTheme = initialize("테스트를 위한 테마 생성") {
dummyInitializer.createTheme(token, createRequest)
}
runExceptionTest(
token = token,
method = HttpMethod.PATCH,
endpoint = "/admin/themes/${createdTheme.id}",
requestBody = updateRequest.copy(price = (MIN_PRICE - 1)),
expectedErrorCode = ThemeErrorCode.PRICE_BELOW_MINIMUM
)
}
context("입력된 시간이 ${MIN_DURATION}분 미만이면 실패한다.") {
test("field: availableMinutes") {
val token = testAuthUtil.defaultHqAdminLogin()
val createdTheme = initialize("테스트를 위한 테마 생성") {
dummyInitializer.createTheme(token, createRequest)
}
runExceptionTest(
token = token,
method = HttpMethod.PATCH,
endpoint = "/admin/themes/${createdTheme.id}",
requestBody = updateRequest.copy(availableMinutes = (MIN_DURATION - 1).toShort()),
expectedErrorCode = ThemeErrorCode.DURATION_BELOW_MINIMUM
)
}
test("field: expectedMinutesFrom") {
val token = testAuthUtil.defaultHqAdminLogin()
val createdTheme = initialize("테스트를 위한 테마 생성") {
dummyInitializer.createTheme(token, createRequest)
}
runExceptionTest(
token = token,
method = HttpMethod.PATCH,
endpoint = "/admin/themes/${createdTheme.id}",
requestBody = updateRequest.copy(expectedMinutesFrom = (MIN_DURATION - 1).toShort()),
expectedErrorCode = ThemeErrorCode.DURATION_BELOW_MINIMUM
)
}
test("field: expectedMinutesTo") {
val token = testAuthUtil.defaultHqAdminLogin()
val createdTheme = initialize("테스트를 위한 테마 생성") {
dummyInitializer.createTheme(token, createRequest)
}
runExceptionTest(
token = token,
method = HttpMethod.PATCH,
endpoint = "/admin/themes/${createdTheme.id}",
requestBody = updateRequest.copy(expectedMinutesTo = (MIN_DURATION - 1).toShort()),
expectedErrorCode = ThemeErrorCode.DURATION_BELOW_MINIMUM
)
}
}
context("시간 범위가 잘못 지정되면 실패한다.") {
test("최소 예상 시간 > 최대 예상 시간") {
val token = testAuthUtil.defaultHqAdminLogin()
val createdTheme = initialize("테스트를 위한 테마 생성") {
dummyInitializer.createTheme(token, createRequest)
}
runExceptionTest(
token = token,
method = HttpMethod.PATCH,
endpoint = "/admin/themes/${createdTheme.id}",
requestBody = updateRequest.copy(expectedMinutesFrom = 100, expectedMinutesTo = 99),
expectedErrorCode = ThemeErrorCode.MIN_EXPECTED_TIME_EXCEEDS_MAX_EXPECTED_TIME
)
}
test("최대 예상 시간 > 이용 가능 시간") {
val token = testAuthUtil.defaultHqAdminLogin()
val createdTheme = initialize("테스트를 위한 테마 생성") {
dummyInitializer.createTheme(token, createRequest)
}
val requestBody = updateRequest.copy(
availableMinutes = 100,
expectedMinutesFrom = 101,
expectedMinutesTo = 101
)
runExceptionTest(
token = token,
method = HttpMethod.PATCH,
endpoint = "/admin/themes/${createdTheme.id}",
requestBody = requestBody,
expectedErrorCode = ThemeErrorCode.EXPECTED_TIME_EXCEEDS_AVAILABLE_TIME
)
}
}
context("입력된 인원이 ${MIN_PARTICIPANTS}명 미만이면 실패한다.") {
test("field: minParticipants") {
val token = testAuthUtil.defaultHqAdminLogin()
val createdTheme = initialize("테스트를 위한 테마 생성") {
dummyInitializer.createTheme(token, createRequest)
}
runExceptionTest(
token = token,
method = HttpMethod.PATCH,
endpoint = "/admin/themes/${createdTheme.id}",
requestBody = updateRequest.copy(minParticipants = (MIN_PARTICIPANTS - 1).toShort()),
expectedErrorCode = ThemeErrorCode.PARTICIPANT_BELOW_MINIMUM
)
}
test("field: maxParticipants") {
val token = testAuthUtil.defaultHqAdminLogin()
val createdTheme = initialize("테스트를 위한 테마 생성") {
dummyInitializer.createTheme(token, createRequest)
}
runExceptionTest(
token = token,
method = HttpMethod.PATCH,
endpoint = "/admin/themes/${createdTheme.id}",
requestBody = updateRequest.copy(maxParticipants = (MIN_PARTICIPANTS - 1).toShort()),
expectedErrorCode = ThemeErrorCode.PARTICIPANT_BELOW_MINIMUM
)
}
}
context("인원 범위가 잘못 지정되면 실패한다.") {
test("최소 인원 > 최대 인원") {
val token = testAuthUtil.defaultHqAdminLogin()
val createdTheme = initialize("테스트를 위한 테마 생성") {
dummyInitializer.createTheme(token, createRequest)
}
runExceptionTest(
token = token,
method = HttpMethod.PATCH,
endpoint = "/admin/themes/${createdTheme.id}",
requestBody = updateRequest.copy(minParticipants = 10, maxParticipants = 9),
expectedErrorCode = ThemeErrorCode.MIN_PARTICIPANT_EXCEEDS_MAX_PARTICIPANT
)
}
}
}
}
}