diff --git a/service/src/main/kotlin/com/sangdol/roomescape/schedule/business/ScheduleService.kt b/service/src/main/kotlin/com/sangdol/roomescape/schedule/business/ScheduleService.kt index ff6c11ea..685b33a6 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/schedule/business/ScheduleService.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/schedule/business/ScheduleService.kt @@ -1,6 +1,8 @@ package com.sangdol.roomescape.schedule.business import com.sangdol.common.persistence.IDGenerator +import com.sangdol.common.utils.KoreaDate +import com.sangdol.common.utils.KoreaTime import com.sangdol.roomescape.admin.business.AdminService import com.sangdol.roomescape.common.types.AuditingInfo import com.sangdol.roomescape.common.types.Auditor @@ -44,7 +46,9 @@ class ScheduleService( @Transactional(readOnly = true) fun getStoreScheduleByDate(storeId: Long, date: LocalDate): ScheduleWithThemeListResponse { log.info { "[getStoreScheduleByDate] 매장 일정 조회: storeId=${storeId}, date=$date" } - val currentDate = LocalDate.now() + + val currentDate: LocalDate = KoreaDate.today() + val currentTime: LocalTime = KoreaTime.now() if (date.isBefore(currentDate)) { log.warn { "[getStoreScheduleByDate] 이전 날짜 선택으로 인한 실패: date=${date}" } @@ -53,7 +57,7 @@ class ScheduleService( val schedules: List = scheduleRepository.findStoreSchedulesWithThemeByDate(storeId, date) - .filter { it.date.isAfter(date) || (it.date.isEqual(date) && it.time.isAfter(LocalTime.now())) } + .filter { it.time.isAfter(currentTime) } return schedules.toResponse() .also { @@ -89,7 +93,7 @@ class ScheduleService( fun searchSchedules(storeId: Long, date: LocalDate?, themeId: Long?): AdminScheduleSummaryListResponse { log.info { "[searchSchedules] 일정 검색 시작: storeId=$storeId, date=$date, themeId=$themeId" } - val searchDate = date ?: LocalDate.now() + val searchDate = date ?: KoreaDate.today() val schedules: List = scheduleRepository.findStoreSchedulesWithThemeByDate(storeId, searchDate) diff --git a/service/src/main/kotlin/com/sangdol/roomescape/schedule/business/ScheduleValidator.kt b/service/src/main/kotlin/com/sangdol/roomescape/schedule/business/ScheduleValidator.kt index 4b79cd1b..a9387348 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/schedule/business/ScheduleValidator.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/schedule/business/ScheduleValidator.kt @@ -1,5 +1,6 @@ package com.sangdol.roomescape.schedule.business +import com.sangdol.common.utils.KoreaDateTime import com.sangdol.roomescape.schedule.exception.ScheduleErrorCode import com.sangdol.roomescape.schedule.exception.ScheduleException import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity @@ -13,6 +14,7 @@ import org.springframework.stereotype.Component import java.time.LocalDate import java.time.LocalDateTime import java.time.LocalTime +import java.time.temporal.ChronoUnit private val log: KLogger = KotlinLogging.logger {} @@ -56,9 +58,10 @@ class ScheduleValidator( } private fun validateNotInPast(date: LocalDate, time: LocalTime) { - val dateTime = LocalDateTime.of(date, time) + val now = KoreaDateTime.now().truncatedTo(ChronoUnit.MINUTES) + val inputDateTime = LocalDateTime.of(date, time).truncatedTo(ChronoUnit.MINUTES) - if (dateTime.isBefore(LocalDateTime.now())) { + if (inputDateTime.isBefore(now)) { log.info { "[ScheduleValidator.validateDateTime] 이전 시간 선택으로 인한 실패: date=${date} / time=${time}" } diff --git a/service/src/test/kotlin/com/sangdol/roomescape/schedule/AdminScheduleApiTest.kt b/service/src/test/kotlin/com/sangdol/roomescape/schedule/AdminScheduleApiTest.kt index 1576bbca..b24fa2cc 100644 --- a/service/src/test/kotlin/com/sangdol/roomescape/schedule/AdminScheduleApiTest.kt +++ b/service/src/test/kotlin/com/sangdol/roomescape/schedule/AdminScheduleApiTest.kt @@ -1,10 +1,13 @@ package com.sangdol.roomescape.schedule -import com.sangdol.roomescape.common.types.Auditor import com.sangdol.common.types.web.HttpStatus +import com.sangdol.common.utils.KoreaDate +import com.sangdol.common.utils.KoreaDateTime +import com.sangdol.common.utils.KoreaTime import com.sangdol.roomescape.admin.infrastructure.persistence.AdminPermissionLevel import com.sangdol.roomescape.admin.infrastructure.persistence.AdminType import com.sangdol.roomescape.auth.exception.AuthErrorCode +import com.sangdol.roomescape.common.types.Auditor import com.sangdol.roomescape.schedule.exception.ScheduleErrorCode import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleEntity import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleRepository @@ -54,7 +57,8 @@ class AdminScheduleApiTest( lateinit var token: String beforeTest { - val today = LocalDate.now() + val now = KoreaDateTime.now() + val today = now.toLocalDate() store = dummyInitializer.createStore() val admin = AdminFixture.createStoreAdmin(storeId = store.id) token = testAuthUtil.adminLogin(admin).second @@ -66,21 +70,21 @@ class AdminScheduleApiTest( storeId = store.id, request = ScheduleFixture.createRequest.copy( date = today, - time = LocalTime.now().plusHours(2) + time = now.toLocalTime().plusHours(2) ) ), dummyInitializer.createSchedule( storeId = store.id, request = ScheduleFixture.createRequest.copy( date = today, - time = LocalTime.now().plusHours(1) + time = now.toLocalTime().plusHours(1) ) ), dummyInitializer.createSchedule( storeId = store.id, request = ScheduleFixture.createRequest.copy( date = today.plusDays(1), - time = LocalTime.of(11, 0) + time = LocalTime.of(10, 0) ) ) ) @@ -95,7 +99,7 @@ class AdminScheduleApiTest( }, expect = { statusCode(HttpStatus.OK.value()) - body("data.schedules.size()", equalTo(schedules.filter { it.date.isEqual(LocalDate.now()) }.size)) + body("data.schedules.size()", equalTo(schedules.filter { it.date.isEqual(KoreaDate.today()) }.size)) assertProperties( props = setOf("id", "themeName", "startFrom", "endAt", "status"), propsNameIfList = "schedules" @@ -386,8 +390,9 @@ class AdminScheduleApiTest( test("과거 시간을 선택하면 실패한다.") { val (admin, token) = testAuthUtil.defaultStoreAdminLogin() - val date = LocalDate.now() - val time = LocalTime.now().minusMinutes(1) + val now = KoreaDateTime.now() + val date = now.toLocalDate() + val time = now.toLocalTime().minusMinutes(1) val theme = dummyInitializer.createTheme() val request = ScheduleFixture.createRequest.copy(date = date, time = time, themeId = theme.id) @@ -490,29 +495,50 @@ class AdminScheduleApiTest( } context("정상 응답") { - test("시간만 변경한다.") { - val (admin, token) = testAuthUtil.defaultStoreAdminLogin() - val schedule = initialize("수정을 위한 일정 생성") { - dummyInitializer.createSchedule() - } - val updateTime = schedule.time.plusHours(1) - - runTest( - token = token, - using = { - body(ScheduleUpdateRequest(time = updateTime)) - }, - on = { - patch("/admin/schedules/${schedule.id}") - }, - expect = { - statusCode(HttpStatus.OK.value()) + context("시간만 변경한다.") { + test("성공") { + val (admin, token) = testAuthUtil.defaultStoreAdminLogin() + val schedule = initialize("수정을 위한 일정 생성") { + dummyInitializer.createSchedule() } - ).also { - val updated = scheduleRepository.findByIdOrNull(schedule.id)!! - updated.time shouldBe updateTime - updated.status shouldBe schedule.status - updated.updatedAt shouldNotBe schedule.updatedAt + val updateTime = schedule.time.plusHours(1) + + runTest( + token = token, + using = { + body(ScheduleUpdateRequest(time = updateTime)) + }, + on = { + patch("/admin/schedules/${schedule.id}") + }, + expect = { + statusCode(HttpStatus.OK.value()) + } + ).also { + val updated = scheduleRepository.findByIdOrNull(schedule.id)!! + updated.time shouldBe updateTime + updated.status shouldBe schedule.status + updated.updatedAt shouldNotBe schedule.updatedAt + } + } + + test("지난 시간을 선택하면 실패한다.") { + val (admin, token) = testAuthUtil.defaultStoreAdminLogin() + val schedule = initialize("수정을 위한 일정 생성") { + val request = ScheduleFixture.createRequest.copy( + date = KoreaDate.today(), + time = KoreaTime.now().plusHours(1) + ) + dummyInitializer.createSchedule(request = request) + } + + runExceptionTest( + token = token, + method = HttpMethod.PATCH, + endpoint = "/admin/schedules/${schedule.id}", + requestBody = ScheduleUpdateRequest(time = KoreaTime.now().minusMinutes(1)), + expectedErrorCode = ScheduleErrorCode.PAST_DATE_TIME + ) } } diff --git a/service/src/test/kotlin/com/sangdol/roomescape/schedule/ScheduleApiTest.kt b/service/src/test/kotlin/com/sangdol/roomescape/schedule/ScheduleApiTest.kt index 96453cff..b2532cf8 100644 --- a/service/src/test/kotlin/com/sangdol/roomescape/schedule/ScheduleApiTest.kt +++ b/service/src/test/kotlin/com/sangdol/roomescape/schedule/ScheduleApiTest.kt @@ -1,6 +1,8 @@ package com.sangdol.roomescape.schedule import com.sangdol.common.types.web.HttpStatus +import com.sangdol.common.utils.KoreaDate +import com.sangdol.common.utils.KoreaTime import com.sangdol.roomescape.admin.infrastructure.persistence.AdminPermissionLevel import com.sangdol.roomescape.admin.infrastructure.persistence.AdminType import com.sangdol.roomescape.auth.exception.AuthErrorCode @@ -13,16 +15,18 @@ import org.hamcrest.CoreMatchers.equalTo import org.springframework.data.repository.findByIdOrNull import org.springframework.http.HttpMethod import java.time.LocalDate -import java.time.LocalTime class ScheduleApiTest( private val scheduleRepository: ScheduleRepository ) : FunSpecSpringbootTest() { init { context("특정 매장 + 날짜의 일정 및 테마 정보를 조회한다.") { + /** + * @throws 23시 57분 ~ 59분에 실행하면 실패함. + */ test("날짜가 당일이면 현재 시간 이후의 정보만 조회된다.") { val size = 3 - val date = LocalDate.now() + val date = KoreaDate.today() val store = dummyInitializer.createStore() initialize("조회를 위한 오늘 날짜의 현재 시간 이후인 ${size}개의 일정, 현재 시간 이전인 1개의 일정 생성") { @@ -31,7 +35,7 @@ class ScheduleApiTest( storeId = store.id, request = ScheduleFixture.createRequest.copy( date = date, - time = LocalTime.now().plusMinutes(i.toLong()) + time = KoreaTime.now().plusMinutes(i.toLong()) ) ) } @@ -40,22 +44,18 @@ class ScheduleApiTest( storeId = store.id, request = ScheduleFixture.createRequest.copy( date = date, - time = LocalTime.now().minusMinutes(1) + time = KoreaTime.now().minusMinutes(1) ) ) } - val expectedSize = scheduleRepository.findAll().takeIf { it.isNotEmpty() } - ?.let { it.count { schedule -> schedule.date.isEqual(date) && schedule.time.isAfter(LocalTime.now()) } } - ?: throw AssertionError("initialize 작업에서 레코드가 저장되지 않음.") - runTest( on = { get("/stores/${store.id}/schedules?date=${date}") }, expect = { statusCode(HttpStatus.OK.value()) - body("data.schedules.size()", equalTo(expectedSize)) + body("data.schedules.size()", equalTo(size)) assertProperties( props = setOf("id", "startFrom", "endAt", "themeId", "themeName", "themeDifficulty", "status"), propsNameIfList = "schedules"