Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.bcsdlab.bcsdinternalapiv2.global.config;

import java.time.Clock;
import java.time.ZoneId;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* 서비스의 "오늘"과 "지금"(회비 학기, 예약 가능 시각 등)은 한국 시간으로 정한다. 운영 컨테이너의
* 시간대(UTC일 수 있음)에 기대지 않으려고 시스템 기본 시간대 대신 Asia/Seoul 고정 Clock을 주입한다.
* 테스트는 이 빈을 바꿔 시각을 고정한다.
*/
@Configuration
public class TimeConfig {

public static final ZoneId SEOUL = ZoneId.of("Asia/Seoul");

@Bean
public Clock clock() {
return Clock.system(SEOUL);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
import com.bcsdlab.bcsdinternalapiv2.reservation.model.RepeatFrequency;
import com.bcsdlab.bcsdinternalapiv2.reservation.repository.ReservationGroupRepository;
import com.bcsdlab.bcsdinternalapiv2.reservation.repository.ReservationRepository;
import java.time.Clock;
import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashSet;
Expand All @@ -47,10 +47,11 @@ public class ReservationService {
private final ReservationRepository reservationRepository;
private final ReservationGroupRepository reservationGroupRepository;
private final MemberRepository memberRepository;
private final Clock clock;

@Transactional
public ReservationCreateResponse create(Long memberId, ReservationCreateRequest request) {
Instant now = Instant.now();
Instant now = Instant.now(clock);
validateCommon(request, now);

if (!request.isRepeat()) {
Expand Down Expand Up @@ -89,7 +90,7 @@ public MonthlyOccupancyResponse getMyMonthlyOccupancy(Long memberId, YearMonth m

@Transactional(readOnly = true)
public MyReservationResponse getMyReservations(Long memberId, String status) {
LocalDate today = LocalDate.now();
LocalDate today = LocalDate.now(clock);
List<Reservation> reservations = "past".equalsIgnoreCase(status)
? reservationRepository
.findByMemberIdAndCancelledAtIsNullAndReservationDateLessThanOrderByReservationDateDescStartMinuteDesc(
Expand All @@ -116,7 +117,7 @@ public ReservationDetailResponse getDetail(Long memberId, Long reservationId) {

@Transactional
public void cancel(Long memberId, Long reservationId) {
Instant now = Instant.now();
Instant now = Instant.now(clock);
Reservation reservation = getOwnedReservation(memberId, reservationId);
if (reservation.isCancelled()) {
throw new ReservationException(ReservationExceptionType.ALREADY_CANCELLED);
Expand All @@ -129,7 +130,7 @@ public void cancel(Long memberId, Long reservationId) {

@Transactional
public void cancelGroup(Long memberId, Long groupId) {
Instant now = Instant.now();
Instant now = Instant.now(clock);
ReservationGroup group = reservationGroupRepository.findById(groupId)
.orElseThrow(() -> new ReservationException(ReservationExceptionType.RESERVATION_GROUP_NOT_FOUND));
if (!group.getMemberId().equals(memberId)) {
Expand Down Expand Up @@ -200,11 +201,11 @@ private void validateCommon(ReservationCreateRequest request, Instant now) {
throw new ReservationException(ReservationExceptionType.INVALID_HEADCOUNT);
}
LocalDateTime startDateTime = request.date().atStartOfDay().plusMinutes(start);
if (!startDateTime.isAfter(LocalDateTime.ofInstant(now, ZoneId.systemDefault()))) {
if (!startDateTime.isAfter(LocalDateTime.ofInstant(now, clock.getZone()))) {
throw new ReservationException(ReservationExceptionType.PAST_TIME);
}

LocalDate today = LocalDate.now();
LocalDate today = LocalDate.now(clock);
if (!request.isRepeat()) {
if (request.date().isAfter(today.plusDays(SINGLE_BOOKING_WINDOW_DAYS))) {
throw new ReservationException(ReservationExceptionType.OUT_OF_SINGLE_BOOKING_WINDOW);
Expand Down Expand Up @@ -291,7 +292,7 @@ private Reservation getOwnedReservation(Long memberId, Long reservationId) {
private boolean hasStarted(Reservation reservation, Instant now) {
LocalDateTime startDateTime = reservation.getReservationDate().atStartOfDay()
.plusMinutes(reservation.getStartMinute());
return !startDateTime.isAfter(LocalDateTime.ofInstant(now, ZoneId.systemDefault()));
return !startDateTime.isAfter(LocalDateTime.ofInstant(now, clock.getZone()));
}

private List<Reservation> pickGroupRepresentatives(List<Reservation> reservations) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.bcsdlab.bcsdinternalapiv2.ledger;
package com.bcsdlab.bcsdinternalapiv2;

import com.bcsdlab.bcsdinternalapiv2.ledger.config.LedgerTimeConfig;
import com.bcsdlab.bcsdinternalapiv2.global.config.TimeConfig;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
Expand All @@ -12,7 +12,7 @@ public class MutableClock extends Clock {
private volatile Instant instant = Instant.now();

public void setToday(LocalDate date) {
this.instant = date.atTime(12, 0).atZone(LedgerTimeConfig.SEOUL).toInstant();
this.instant = date.atTime(12, 0).atZone(TimeConfig.SEOUL).toInstant();
}

public void setInstant(Instant instant) {
Expand All @@ -21,7 +21,7 @@ public void setInstant(Instant instant) {

@Override
public ZoneId getZone() {
return LedgerTimeConfig.SEOUL;
return TimeConfig.SEOUL;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.bcsdlab.bcsdinternalapiv2.IntegrationTestSupport;
import com.bcsdlab.bcsdinternalapiv2.MutableClock;
import com.bcsdlab.bcsdinternalapiv2.auth.repository.RefreshTokenRepository;
import com.bcsdlab.bcsdinternalapiv2.ledger.model.EntryType;
import com.bcsdlab.bcsdinternalapiv2.ledger.model.LedgerCategory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.bcsdlab.bcsdinternalapiv2.global.config.TimeConfig;
import com.bcsdlab.bcsdinternalapiv2.member.model.Member;
import com.bcsdlab.bcsdinternalapiv2.reservation.model.Reservation;
import java.time.DayOfWeek;
Expand All @@ -17,7 +18,7 @@ class MyReservationIntegrationTest extends ReservationIntegrationTestSupport {
@Test
void 예정된_예약_목록을_조회할_수_있다() throws Exception {
String token = createActiveMemberAndLogin("20250401");
LocalDate date = LocalDate.now().plusDays(3);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(3);
mockMvc.perform(post("/v1/reservations")
.header("Authorization", "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON)
Expand All @@ -38,7 +39,7 @@ class MyReservationIntegrationTest extends ReservationIntegrationTestSupport {
String token = login("20250402");
reservationRepository.save(Reservation.builder()
.memberId(member.getId())
.reservationDate(LocalDate.now().minusDays(3))
.reservationDate(LocalDate.now(TimeConfig.SEOUL).minusDays(3))
.startMinute((short) 600)
.endMinute((short) 660)
.purpose("지난스터디")
Expand Down Expand Up @@ -82,7 +83,7 @@ class MyReservationIntegrationTest extends ReservationIntegrationTestSupport {
void 다른_회원의_예약은_조회되지_않는다() throws Exception {
String ownerToken = createActiveMemberAndLogin("20250404");
String otherToken = createActiveMemberAndLogin("20250405");
LocalDate date = LocalDate.now().plusDays(3);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(3);

mockMvc.perform(post("/v1/reservations")
.header("Authorization", "Bearer " + ownerToken)
Expand All @@ -98,7 +99,7 @@ class MyReservationIntegrationTest extends ReservationIntegrationTestSupport {
}

private LocalDate nextDateWithWeekday(DayOfWeek dayOfWeek, int minDaysAhead) {
LocalDate date = LocalDate.now().plusDays(minDaysAhead);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(minDaysAhead);
while (date.getDayOfWeek() != dayOfWeek) {
date = date.plusDays(1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.bcsdlab.bcsdinternalapiv2.global.config.TimeConfig;
import com.bcsdlab.bcsdinternalapiv2.member.model.Member;
import com.bcsdlab.bcsdinternalapiv2.reservation.model.Reservation;
import com.bcsdlab.bcsdinternalapiv2.reservation.model.ReservationGroup;
Expand All @@ -20,7 +21,7 @@ class ReservationCancelIntegrationTest extends ReservationIntegrationTestSupport
@Test
void 본인_예약을_취소할_수_있다() throws Exception {
String token = createActiveMemberAndLogin("20250601");
LocalDate date = LocalDate.now().plusDays(3);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(3);
Long reservationId = createSingleReservationAndGetId(token, date);

mockMvc.perform(delete("/v1/reservations/{id}", reservationId)
Expand All @@ -33,7 +34,7 @@ class ReservationCancelIntegrationTest extends ReservationIntegrationTestSupport
@Test
void 이미_취소된_예약을_다시_취소하면_409가_반환된다() throws Exception {
String token = createActiveMemberAndLogin("20250602");
LocalDate date = LocalDate.now().plusDays(3);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(3);
Long reservationId = createSingleReservationAndGetId(token, date);

mockMvc.perform(delete("/v1/reservations/{id}", reservationId)
Expand All @@ -49,7 +50,7 @@ class ReservationCancelIntegrationTest extends ReservationIntegrationTestSupport
void 타인의_예약을_취소하면_403이_반환된다() throws Exception {
String ownerToken = createActiveMemberAndLogin("20250603");
String otherToken = createActiveMemberAndLogin("20250604");
LocalDate date = LocalDate.now().plusDays(3);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(3);
Long reservationId = createSingleReservationAndGetId(ownerToken, date);

mockMvc.perform(delete("/v1/reservations/{id}", reservationId)
Expand All @@ -63,7 +64,7 @@ class ReservationCancelIntegrationTest extends ReservationIntegrationTestSupport
String token = login("20250605");
Reservation started = reservationRepository.save(Reservation.builder()
.memberId(member.getId())
.reservationDate(LocalDate.now().minusDays(1))
.reservationDate(LocalDate.now(TimeConfig.SEOUL).minusDays(1))
.startMinute((short) 600)
.endMinute((short) 660)
.purpose("지난예약")
Expand All @@ -79,9 +80,9 @@ class ReservationCancelIntegrationTest extends ReservationIntegrationTestSupport
void 반복_예약_그룹을_전체_취소하면_미래_회차만_취소된다() throws Exception {
Member member = createActiveMember("20250606");
String token = login("20250606");
LocalDate pastDate = LocalDate.now().minusDays(7);
LocalDate futureDate1 = LocalDate.now().plusDays(3);
LocalDate futureDate2 = LocalDate.now().plusDays(10);
LocalDate pastDate = LocalDate.now(TimeConfig.SEOUL).minusDays(7);
LocalDate futureDate1 = LocalDate.now(TimeConfig.SEOUL).plusDays(3);
LocalDate futureDate2 = LocalDate.now(TimeConfig.SEOUL).plusDays(10);

ReservationGroup group = reservationGroupRepository.save(ReservationGroup.builder()
.memberId(member.getId())
Expand Down Expand Up @@ -134,7 +135,7 @@ class ReservationCancelIntegrationTest extends ReservationIntegrationTestSupport
.weekdays(List.of(DayOfWeek.MONDAY))
.startMinute((short) 600)
.endMinute((short) 660)
.repeatEndDate(LocalDate.now().plusWeeks(2))
.repeatEndDate(LocalDate.now(TimeConfig.SEOUL).plusWeeks(2))
.createdAt(java.time.Instant.now())
.build());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.assertj.core.api.Assertions.assertThat;

import com.bcsdlab.bcsdinternalapiv2.global.config.TimeConfig;
import java.time.LocalDate;
import java.util.List;
import java.util.concurrent.Callable;
Expand All @@ -20,7 +21,7 @@ class ReservationConcurrencyTest extends ReservationIntegrationTestSupport {
void 동시에_같은_시간대를_예약하면_정확히_1건만_성공한다() throws Exception {
String firstToken = createActiveMemberAndLogin("20250701");
String secondToken = createActiveMemberAndLogin("20250702");
LocalDate date = LocalDate.now().plusDays(3);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(3);
String body = """
{"date":"%s","start":600,"end":660,"purpose":"동시성테스트","headcount":2}
""".formatted(date);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.bcsdlab.bcsdinternalapiv2.global.config.TimeConfig;
import java.time.LocalDate;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
Expand All @@ -14,7 +15,7 @@ class ReservationCreateIntegrationTest extends ReservationIntegrationTestSupport
@Test
void 로그인한_회원은_단건_예약을_생성할_수_있다() throws Exception {
String token = createActiveMemberAndLogin("20250201");
LocalDate date = LocalDate.now().plusDays(3);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(3);

createReservation(token, date, 600, 660, "스터디", 4)
.andExpect(status().isOk())
Expand All @@ -26,7 +27,7 @@ class ReservationCreateIntegrationTest extends ReservationIntegrationTestSupport
@Test
void 시간이_30분_단위가_아니면_예약이_거부된다() throws Exception {
String token = createActiveMemberAndLogin("20250202");
LocalDate date = LocalDate.now().plusDays(3);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(3);

createReservation(token, date, 601, 660, "스터디", 4)
.andExpect(status().isBadRequest());
Expand All @@ -35,7 +36,7 @@ class ReservationCreateIntegrationTest extends ReservationIntegrationTestSupport
@Test
void 시작시간이_종료시간보다_늦으면_예약이_거부된다() throws Exception {
String token = createActiveMemberAndLogin("20250203");
LocalDate date = LocalDate.now().plusDays(3);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(3);

createReservation(token, date, 660, 600, "스터디", 4)
.andExpect(status().isBadRequest());
Expand All @@ -44,7 +45,7 @@ class ReservationCreateIntegrationTest extends ReservationIntegrationTestSupport
@Test
void 이미_지난_시간은_예약할_수_없다() throws Exception {
String token = createActiveMemberAndLogin("20250204");
LocalDate date = LocalDate.now().minusDays(1);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).minusDays(1);

createReservation(token, date, 600, 660, "스터디", 4)
.andExpect(status().isBadRequest());
Expand All @@ -53,7 +54,7 @@ class ReservationCreateIntegrationTest extends ReservationIntegrationTestSupport
@Test
void 이주를_초과한_날짜는_예약할_수_없다() throws Exception {
String token = createActiveMemberAndLogin("20250205");
LocalDate date = LocalDate.now().plusDays(20);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(20);

createReservation(token, date, 600, 660, "스터디", 4)
.andExpect(status().isBadRequest());
Expand All @@ -62,7 +63,7 @@ class ReservationCreateIntegrationTest extends ReservationIntegrationTestSupport
@Test
void 인원수가_한도를_초과하면_예약이_거부된다() throws Exception {
String token = createActiveMemberAndLogin("20250206");
LocalDate date = LocalDate.now().plusDays(3);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(3);

createReservation(token, date, 600, 660, "스터디", 13)
.andExpect(status().isBadRequest());
Expand All @@ -72,7 +73,7 @@ class ReservationCreateIntegrationTest extends ReservationIntegrationTestSupport
void 겹치는_시간대는_예약할_수_없다() throws Exception {
String firstToken = createActiveMemberAndLogin("20250207");
String secondToken = createActiveMemberAndLogin("20250208");
LocalDate date = LocalDate.now().plusDays(3);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(3);

createReservation(firstToken, date, 600, 660, "스터디", 4)
.andExpect(status().isOk());
Expand All @@ -84,7 +85,7 @@ class ReservationCreateIntegrationTest extends ReservationIntegrationTestSupport
@Test
void 하루_3시간_한도를_초과하면_예약할_수_없다() throws Exception {
String token = createActiveMemberAndLogin("20250209");
LocalDate date = LocalDate.now().plusDays(3);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(3);

createReservation(token, date, 540, 600, "스터디1", 2).andExpect(status().isOk());
createReservation(token, date, 600, 660, "스터디2", 2).andExpect(status().isOk());
Expand All @@ -96,7 +97,7 @@ class ReservationCreateIntegrationTest extends ReservationIntegrationTestSupport

@Test
void 비로그인_요청은_401이_반환된다() throws Exception {
LocalDate date = LocalDate.now().plusDays(3);
LocalDate date = LocalDate.now(TimeConfig.SEOUL).plusDays(3);
mockMvc.perform(post("/v1/reservations")
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody(date, 600, 660, "스터디", 4)))
Expand Down
Loading
Loading