generated from pricelees/issue-pr-template
feat: 모든 스케쥴 API 테스트 추가
This commit is contained in:
parent
9fdbcba85a
commit
5d23216e17
569
src/test/kotlin/roomescape/schedule/ScheduleApiTest.kt
Normal file
569
src/test/kotlin/roomescape/schedule/ScheduleApiTest.kt
Normal file
@ -0,0 +1,569 @@
|
||||
package roomescape.schedule
|
||||
|
||||
import io.kotest.matchers.date.shouldBeAfter
|
||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.restassured.module.kotlin.extensions.Extract
|
||||
import io.restassured.module.kotlin.extensions.Given
|
||||
import io.restassured.module.kotlin.extensions.When
|
||||
import io.restassured.response.ValidatableResponse
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.hamcrest.CoreMatchers.notNullValue
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import roomescape.auth.exception.AuthErrorCode
|
||||
import roomescape.member.infrastructure.persistence.Role
|
||||
import roomescape.schedule.exception.ScheduleErrorCode
|
||||
import roomescape.schedule.infrastructure.persistence.ScheduleEntity
|
||||
import roomescape.schedule.infrastructure.persistence.ScheduleRepository
|
||||
import roomescape.schedule.infrastructure.persistence.ScheduleStatus
|
||||
import roomescape.schedule.web.ScheduleCreateRequest
|
||||
import roomescape.schedule.web.ScheduleUpdateRequest
|
||||
import roomescape.util.*
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
class ScheduleApiTest(
|
||||
private val scheduleRepository: ScheduleRepository
|
||||
) : FunSpecSpringbootTest() {
|
||||
|
||||
init {
|
||||
context("관리자가 아니면 접근할 수 없다.") {
|
||||
lateinit var token: String
|
||||
|
||||
beforeTest {
|
||||
token = loginUtil.loginAsUser()
|
||||
}
|
||||
|
||||
val commonAssertion: ValidatableResponse.() -> Unit = {
|
||||
statusCode(HttpStatus.FORBIDDEN.value())
|
||||
body(
|
||||
"code",
|
||||
equalTo(AuthErrorCode.ACCESS_DENIED.errorCode)
|
||||
)
|
||||
}
|
||||
|
||||
test("일정 상세: GET /schedules/{id}") {
|
||||
runTest(
|
||||
token = token,
|
||||
on = {
|
||||
get("/schedules/1")
|
||||
},
|
||||
expect = commonAssertion
|
||||
)
|
||||
}
|
||||
|
||||
test("일정 생성: POST /schedules") {
|
||||
runTest(
|
||||
token = token,
|
||||
using = {
|
||||
body(ScheduleFixture.createRequest)
|
||||
},
|
||||
on = {
|
||||
get("/schedules/1")
|
||||
},
|
||||
expect = commonAssertion
|
||||
)
|
||||
}
|
||||
|
||||
test("일정 수정: PATCH /schedules/{id}") {
|
||||
val createdSchedule: ScheduleEntity =
|
||||
createDummySchedule(
|
||||
ScheduleFixture.createRequest
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = token,
|
||||
using = {
|
||||
body(ScheduleFixture.createRequest)
|
||||
},
|
||||
on = {
|
||||
patch("/schedules/${createdSchedule.id}")
|
||||
},
|
||||
expect = commonAssertion
|
||||
)
|
||||
}
|
||||
|
||||
test("일정 삭제: DELETE /schedules/{id}") {
|
||||
val createdSchedule: ScheduleEntity =
|
||||
createDummySchedule(
|
||||
ScheduleFixture.createRequest
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = token,
|
||||
on = {
|
||||
delete("/schedules/${createdSchedule.id}")
|
||||
},
|
||||
expect = commonAssertion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("일반 회원도 접근할 수 있다.") {
|
||||
lateinit var token: String
|
||||
|
||||
beforeTest {
|
||||
token = loginUtil.loginAsUser()
|
||||
}
|
||||
|
||||
test("예약 가능 테마 조회: GET /schedules/themes?date={date}") {
|
||||
val date = LocalDate.now().plusDays(1)
|
||||
val time = LocalTime.now()
|
||||
val createdSchedule: ScheduleEntity =
|
||||
createDummySchedule(
|
||||
ScheduleFixture.createRequest.copy(
|
||||
date = date,
|
||||
time = time
|
||||
)
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = token,
|
||||
on = {
|
||||
get("/schedules/themes?date=$date")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
body("data.themeIds.size()", equalTo(1))
|
||||
body("data.themeIds[0]", equalTo(createdSchedule.themeId))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
test("동일한 날짜, 테마에 대한 시간 조회: GET /schedules?date={date}&themeId={themeId}") {
|
||||
val date = LocalDate.now().plusDays(1)
|
||||
val time = LocalTime.now()
|
||||
|
||||
val createdSchedule: ScheduleEntity =
|
||||
createDummySchedule(
|
||||
ScheduleFixture.createRequest.copy(
|
||||
date = date,
|
||||
time = time
|
||||
)
|
||||
)
|
||||
createDummySchedule(
|
||||
ScheduleFixture.createRequest.copy(
|
||||
date = date.plusDays(1L),
|
||||
time = time,
|
||||
themeId = createdSchedule.themeId
|
||||
)
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = token,
|
||||
on = {
|
||||
get("/schedules?date=$date&themeId=${createdSchedule.themeId}")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
body("data.schedules.size()", equalTo(1))
|
||||
body("data.schedules[0].id", equalTo(createdSchedule.id))
|
||||
assertProperties(
|
||||
props = setOf("id", "time", "status"),
|
||||
propsNameIfList = "schedules"
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("특정 날짜에 예약 가능한 테마 목록을 조회한다.") {
|
||||
test("정상 응답") {
|
||||
val date = LocalDate.now().plusDays(1)
|
||||
for (i in 1..10) {
|
||||
createDummySchedule(
|
||||
ScheduleFixture.createRequest.copy(
|
||||
date = date,
|
||||
time = LocalTime.now().plusMinutes(i.toLong())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
on = {
|
||||
get("/schedules/themes?date=$date")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
body("data.themeIds.size()", equalTo(10))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("동일한 날짜, 테마에 대한 모든 시간을 조회한다.") {
|
||||
test("정상 응답") {
|
||||
val date = LocalDate.now().plusDays(1)
|
||||
val createdSchedule = createDummySchedule(
|
||||
ScheduleFixture.createRequest.copy(
|
||||
date = date,
|
||||
time = LocalTime.now()
|
||||
)
|
||||
)
|
||||
for (i in 1..10) {
|
||||
createDummySchedule(
|
||||
ScheduleFixture.createRequest.copy(
|
||||
date = date,
|
||||
time = LocalTime.now().plusMinutes(i.toLong()),
|
||||
themeId = createdSchedule.themeId
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsUser(),
|
||||
on = {
|
||||
get("/schedules?date=$date&themeId=${createdSchedule.themeId}")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
body("data.schedules.size()", equalTo(11))
|
||||
assertProperties(
|
||||
props = setOf("id", "time", "status"),
|
||||
propsNameIfList = "schedules"
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("관리자 페이지에서 특정 일정의 감사 정보를 조회한다.") {
|
||||
test("정상 응답") {
|
||||
val createdSchedule = createDummySchedule(ScheduleFixture.createRequest)
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsAdmin(),
|
||||
on = {
|
||||
get("/schedules/${createdSchedule.id}")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
body("data.id", equalTo(createdSchedule.id))
|
||||
assertProperties(
|
||||
props = setOf(
|
||||
"id", "date", "time", "status",
|
||||
"createdAt", "createdBy", "updatedAt", "updatedBy",
|
||||
)
|
||||
)
|
||||
}
|
||||
).also {
|
||||
it.extract().path<String>("data.createdAt") shouldNotBeNull {}
|
||||
it.extract().path<String>("data.createdBy") shouldNotBeNull {}
|
||||
it.extract().path<String>("data.createdAt") shouldNotBeNull {}
|
||||
it.extract().path<String>("data.createdAt") shouldNotBeNull {}
|
||||
}
|
||||
}
|
||||
|
||||
test("일정이 없으면 실패한다.") {
|
||||
runTest(
|
||||
token = loginUtil.loginAsAdmin(),
|
||||
on = {
|
||||
get("/schedules/1")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.NOT_FOUND.value())
|
||||
body(
|
||||
"code",
|
||||
equalTo(ScheduleErrorCode.SCHEDULE_NOT_FOUND.errorCode)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("일정을 생성한다.") {
|
||||
lateinit var token: String
|
||||
|
||||
beforeTest {
|
||||
token = loginUtil.loginAsAdmin()
|
||||
}
|
||||
|
||||
test("정상 생성 및 감사 정보 확인") {
|
||||
/**
|
||||
* FK 제약조건 해소를 위한 테마 생성 API 호출 및 ID 획득
|
||||
*/
|
||||
val themeId: Long = Given {
|
||||
contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
header("Authorization", "Bearer $token")
|
||||
body(ThemeFixtureV2.createRequest.copy(name = "theme-${System.currentTimeMillis()}"))
|
||||
} When {
|
||||
post("/admin/themes")
|
||||
} Extract {
|
||||
path("data.id")
|
||||
}
|
||||
|
||||
/**
|
||||
* 생성 테스트
|
||||
*/
|
||||
runTest(
|
||||
token = token,
|
||||
using = {
|
||||
body(ScheduleFixture.createRequest.copy(themeId = themeId))
|
||||
},
|
||||
on = {
|
||||
post("/schedules")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
body("data.id", notNullValue())
|
||||
}
|
||||
).also {
|
||||
val createdScheduleId: Long = it.extract().path("data.id")
|
||||
val createdSchedule: ScheduleEntity = scheduleRepository.findByIdOrNull(createdScheduleId)
|
||||
?: throw AssertionError("Unexpected Exception Occurred.")
|
||||
|
||||
createdSchedule.date shouldBe ScheduleFixture.createRequest.date
|
||||
createdSchedule.time.hour shouldBe ScheduleFixture.createRequest.time.hour
|
||||
createdSchedule.time.minute shouldBe ScheduleFixture.createRequest.time.minute
|
||||
createdSchedule.createdAt shouldNotBeNull {}
|
||||
createdSchedule.createdBy shouldNotBeNull {}
|
||||
createdSchedule.updatedAt shouldNotBeNull {}
|
||||
createdSchedule.updatedBy shouldNotBeNull {}
|
||||
}
|
||||
}
|
||||
|
||||
test("이미 동일한 날짜, 시간, 테마인 일정이 있으면 실패한다.") {
|
||||
val date = LocalDate.now().plusDays(1)
|
||||
val time = LocalTime.of(10, 0)
|
||||
|
||||
val alreadyCreated: ScheduleEntity = createDummySchedule(
|
||||
ScheduleFixture.createRequest.copy(
|
||||
date = date, time = time
|
||||
)
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = token,
|
||||
using = {
|
||||
body(
|
||||
ScheduleFixture.createRequest.copy(
|
||||
date = date, time = time, themeId = alreadyCreated.themeId
|
||||
)
|
||||
)
|
||||
},
|
||||
on = {
|
||||
post("/schedules")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.CONFLICT.value())
|
||||
body("code", equalTo(ScheduleErrorCode.SCHEDULE_ALREADY_EXISTS.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
test("입력된 날짜 + 시간이 현재 시간 이전이면 실패한다.") {
|
||||
runTest(
|
||||
token = token,
|
||||
using = {
|
||||
body(
|
||||
ScheduleFixture.createRequest.copy(
|
||||
date = LocalDate.now(),
|
||||
time = LocalTime.now().minusMinutes(1)
|
||||
)
|
||||
)
|
||||
},
|
||||
on = {
|
||||
post("/schedules")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.BAD_REQUEST.value())
|
||||
body("code", equalTo(ScheduleErrorCode.PAST_DATE_TIME.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("일정을 수정한다.") {
|
||||
val updateRequest = ScheduleUpdateRequest(
|
||||
time = LocalTime.now().plusHours(1),
|
||||
status = ScheduleStatus.BLOCKED
|
||||
)
|
||||
|
||||
test("정상 수정 및 감사 정보 변경 확인") {
|
||||
val createdSchedule: ScheduleEntity = createDummySchedule(
|
||||
ScheduleFixture.createRequest.copy(
|
||||
date = LocalDate.now().plusDays(1),
|
||||
time = LocalTime.now().plusMinutes(1),
|
||||
)
|
||||
)
|
||||
val otherAdminToken = loginUtil.login("admin1@admin.com", "admin1", Role.ADMIN)
|
||||
|
||||
runTest(
|
||||
token = otherAdminToken,
|
||||
using = {
|
||||
body(updateRequest)
|
||||
},
|
||||
on = {
|
||||
patch("/schedules/${createdSchedule.id}")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
}
|
||||
).also {
|
||||
val updatedSchedule = scheduleRepository.findByIdOrNull(createdSchedule.id)!!
|
||||
|
||||
updatedSchedule.id shouldBe createdSchedule.id
|
||||
updatedSchedule.time.hour shouldBe updateRequest.time!!.hour
|
||||
updatedSchedule.time.minute shouldBe updateRequest.time.minute
|
||||
updatedSchedule.status shouldBe updateRequest.status
|
||||
updatedSchedule.updatedBy shouldNotBe createdSchedule.updatedBy
|
||||
updatedSchedule.updatedAt shouldBeAfter createdSchedule.updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
test("입력값이 없으면 수정하지 않는다.") {
|
||||
val createdSchedule: ScheduleEntity = createDummySchedule(ScheduleFixture.createRequest)
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsAdmin(),
|
||||
using = {
|
||||
body(ScheduleUpdateRequest())
|
||||
},
|
||||
on = {
|
||||
patch("/schedules/${createdSchedule.id}")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
}
|
||||
).also {
|
||||
val updatedSchedule = scheduleRepository.findByIdOrNull(createdSchedule.id)!!
|
||||
|
||||
updatedSchedule.id shouldBe createdSchedule.id
|
||||
updatedSchedule.updatedAt shouldBe createdSchedule.updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
test("일정이 없으면 실패한다.") {
|
||||
runTest(
|
||||
token = loginUtil.loginAsAdmin(),
|
||||
using = {
|
||||
body(updateRequest)
|
||||
},
|
||||
on = {
|
||||
patch("/schedules/1")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.NOT_FOUND.value())
|
||||
body("code", equalTo(ScheduleErrorCode.SCHEDULE_NOT_FOUND.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
test("입력된 날짜 + 시간이 현재 시간 이전이면 실패한다.") {
|
||||
val createdSchedule: ScheduleEntity = createDummySchedule(
|
||||
ScheduleFixture.createRequest.copy(date = LocalDate.now(), time = LocalTime.now().plusMinutes(1))
|
||||
)
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsAdmin(),
|
||||
using = {
|
||||
body(
|
||||
updateRequest.copy(
|
||||
time = LocalTime.now().minusMinutes(1)
|
||||
)
|
||||
)
|
||||
},
|
||||
on = {
|
||||
patch("/schedules/${createdSchedule.id}")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.BAD_REQUEST.value())
|
||||
body("code", equalTo(ScheduleErrorCode.PAST_DATE_TIME.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
context("일정을 삭제한다.") {
|
||||
test("정상 삭제") {
|
||||
val createdSchedule: ScheduleEntity = createDummySchedule(ScheduleFixture.createRequest)
|
||||
|
||||
runTest(
|
||||
token = loginUtil.loginAsAdmin(),
|
||||
on = {
|
||||
delete("/schedules/${createdSchedule.id}")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.NO_CONTENT.value())
|
||||
}
|
||||
).also {
|
||||
scheduleRepository.findByIdOrNull(createdSchedule.id) shouldBe null
|
||||
}
|
||||
}
|
||||
|
||||
test("예약 중이거나 예약이 완료된 일정이면 실패한다.") {
|
||||
val createdSchedule: ScheduleEntity = createDummySchedule(ScheduleFixture.createRequest)
|
||||
|
||||
/*
|
||||
* 테스트를 위해 수정 API를 호출하여 상태를 예약 중 상태로 변경
|
||||
* 생성 API에서는 일정 생성 시 AVAILABLE을 기본 상태로 지정하기 때문.
|
||||
*/
|
||||
runTest(
|
||||
token = loginUtil.loginAsAdmin(),
|
||||
using = {
|
||||
body(
|
||||
ScheduleUpdateRequest(
|
||||
status = ScheduleStatus.RESERVED
|
||||
)
|
||||
)
|
||||
},
|
||||
on = {
|
||||
patch("/schedules/${createdSchedule.id}")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.OK.value())
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 삭제 테스트
|
||||
*/
|
||||
runTest(
|
||||
token = loginUtil.loginAsAdmin(),
|
||||
on = {
|
||||
delete("/schedules/${createdSchedule.id}")
|
||||
},
|
||||
expect = {
|
||||
statusCode(HttpStatus.CONFLICT.value())
|
||||
body("code", equalTo(ScheduleErrorCode.SCHEDULE_IN_USE.errorCode))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createDummySchedule(request: ScheduleCreateRequest): ScheduleEntity {
|
||||
val token = loginUtil.loginAsAdmin()
|
||||
|
||||
val themeId: Long = if (request.themeId > 1L) {
|
||||
request.themeId
|
||||
} else {
|
||||
Given {
|
||||
contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
header("Authorization", "Bearer $token")
|
||||
body(ThemeFixtureV2.createRequest.copy(name = "theme-${System.currentTimeMillis()}"))
|
||||
} When {
|
||||
post("/admin/themes")
|
||||
} Extract {
|
||||
path("data.id")
|
||||
}
|
||||
}
|
||||
|
||||
val createdScheduleId: Long = Given {
|
||||
contentType(MediaType.APPLICATION_JSON_VALUE)
|
||||
header("Authorization", "Bearer $token")
|
||||
body(request.copy(themeId = themeId))
|
||||
} When {
|
||||
post("/schedules")
|
||||
} Extract {
|
||||
path("data.id")
|
||||
}
|
||||
|
||||
return scheduleRepository.findByIdOrNull(createdScheduleId)
|
||||
?: throw RuntimeException("unreachable line")
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user