diff --git a/src/main/java/com/softeer/podoarrival/common/response/ErrorCode.java b/src/main/java/com/softeer/podoarrival/common/response/ErrorCode.java index a9946b4..5d78868 100644 --- a/src/main/java/com/softeer/podoarrival/common/response/ErrorCode.java +++ b/src/main/java/com/softeer/podoarrival/common/response/ErrorCode.java @@ -24,7 +24,8 @@ public enum ErrorCode { //event_arrival PHONENUM_EXISTS_ERROR(false, HttpStatus.BAD_REQUEST.value(),"이미 응모한 전화번호입니다."), - QUIZ_NOT_FOUND(false, HttpStatus.NOT_FOUND.value(), "오늘 날짜에 퀴즈가 없습니다.") + QUIZ_NOT_FOUND(false, HttpStatus.NOT_FOUND.value(), "오늘 날짜에 퀴즈가 없습니다."), + EVENT_CLOSED(false, HttpStatus.FORBIDDEN.value(), "이벤트 시간이 아닙니다.") ; diff --git a/src/main/java/com/softeer/podoarrival/event/exception/ArrivalEventExceptionHandler.java b/src/main/java/com/softeer/podoarrival/event/exception/ArrivalEventExceptionHandler.java index 5d7cbb8..32acad6 100644 --- a/src/main/java/com/softeer/podoarrival/event/exception/ArrivalEventExceptionHandler.java +++ b/src/main/java/com/softeer/podoarrival/event/exception/ArrivalEventExceptionHandler.java @@ -34,4 +34,11 @@ public CommonResponse dailyQuizNotFoundException(DailyQuizNotExistsException log.warn("ARRIVAL-003> 요청 URI: " + request.getRequestURI() + ", 에러 메세지: " + e.getMessage()); return new CommonResponse<>(ErrorCode.QUIZ_NOT_FOUND); } + + @ExceptionHandler(EventClosedException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public CommonResponse eventClosedException(EventClosedException e, HttpServletRequest request) { + log.warn("ARRIVAL-004> 요청 URI: " + request.getRequestURI() + ", 에러 메세지: " + e.getMessage()); + return new CommonResponse<>(ErrorCode.EVENT_CLOSED); + } } diff --git a/src/main/java/com/softeer/podoarrival/event/exception/EventClosedException.java b/src/main/java/com/softeer/podoarrival/event/exception/EventClosedException.java new file mode 100644 index 0000000..c30ce88 --- /dev/null +++ b/src/main/java/com/softeer/podoarrival/event/exception/EventClosedException.java @@ -0,0 +1,8 @@ +package com.softeer.podoarrival.event.exception; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class EventClosedException extends RuntimeException { + private String message; +} diff --git a/src/main/java/com/softeer/podoarrival/event/scheduler/ArrivalEventMaxArrivalScheduler.java b/src/main/java/com/softeer/podoarrival/event/scheduler/ArrivalEventInformationScheduler.java similarity index 63% rename from src/main/java/com/softeer/podoarrival/event/scheduler/ArrivalEventMaxArrivalScheduler.java rename to src/main/java/com/softeer/podoarrival/event/scheduler/ArrivalEventInformationScheduler.java index d383e97..f23a693 100644 --- a/src/main/java/com/softeer/podoarrival/event/scheduler/ArrivalEventMaxArrivalScheduler.java +++ b/src/main/java/com/softeer/podoarrival/event/scheduler/ArrivalEventInformationScheduler.java @@ -16,6 +16,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.List; /** @@ -24,18 +25,19 @@ @Slf4j @Component @RequiredArgsConstructor -public class ArrivalEventMaxArrivalScheduler { +public class ArrivalEventInformationScheduler { private final EventRepository eventRepository; private final EventTypeRepository eventTypeRepository; private final EventRewardRepository eventRewardRepository; /** - * 특정 시간에 Mysql에서 금일 진행될 선착순 이벤트의 당첨자 수를 읽어옴 + * 특정 시간에 Mysql에서 금일 진행될 선착순 이벤트의 절보를 세팅함. + * 당첨자 수, 이벤트 시작 시간, 이벤트 요일 설정 * */ @Scheduled(cron = "0 25 03 * * *") - public void setEventArrivalCount() { + public void setArrivalEventInformation() { // 시작일자, 이벤트 종류만 고려하여 이벤트 추출 LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); LocalDateTime endOfDay = startOfDay.plusDays(1).minusNanos(1); @@ -44,6 +46,13 @@ public void setEventArrivalCount() { EventType eventType = eventTypeRepository.findById(1L).orElseThrow(() -> new EventTypeNotExistsException("이벤트 타입이 존재하지 않습니다.")); Event findEvent = eventRepository.findFirstByEventTypeAndStartAtBetween(eventType, startOfDay, endOfDay); + if(findEvent == null) { + log.warn("오늘 날짜에 이벤트가 없습니다."); + ArrivalEventReleaseServiceRedisImpl.setMaxArrival(0); + ArrivalEventReleaseServiceJavaImpl.setMaxArrival(0); + return; + } + // 찾은 이벤트에 해당하는 reward개수 조회 int rewardCount = 0; List eventRewards = eventRewardRepository.findAllByEvent(findEvent); @@ -51,7 +60,25 @@ public void setEventArrivalCount() { rewardCount += eventReward.getNumWinners(); } + // 찾은 이벤트에 해당하는 반복 시간 확인 + LocalTime repeatTime = findEvent.getRepeatTime(); + + // 찾은 이벤트에 해당하는 반복 요일 확인 및 저장 + String repeatDate = findEvent.getRepeatDay(); + int today = startOfDay.getDayOfWeek().getValue(); + if(repeatDate.length() >= 7 && repeatDate.charAt(today - 1) == '1') { + ArrivalEventReleaseServiceRedisImpl.setStartDate(true); + ArrivalEventReleaseServiceJavaImpl.setStartDate(true); + }else{ + ArrivalEventReleaseServiceRedisImpl.setStartDate(false); + ArrivalEventReleaseServiceJavaImpl.setStartDate(false); + } + + // service에 이벤트 내용 저장 ArrivalEventReleaseServiceRedisImpl.setMaxArrival(rewardCount); ArrivalEventReleaseServiceJavaImpl.setMaxArrival(rewardCount); + + ArrivalEventReleaseServiceRedisImpl.setStartTime(repeatTime); + ArrivalEventReleaseServiceJavaImpl.setStartTime(repeatTime); } } diff --git a/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventReleaseServiceJavaImpl.java b/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventReleaseServiceJavaImpl.java index f1de729..9c76729 100644 --- a/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventReleaseServiceJavaImpl.java +++ b/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventReleaseServiceJavaImpl.java @@ -1,5 +1,6 @@ package com.softeer.podoarrival.event.service; +import com.softeer.podoarrival.event.exception.EventClosedException; import com.softeer.podoarrival.event.exception.ExistingUserException; import com.softeer.podoarrival.event.model.dto.ArrivalApplicationResponseDto; import com.softeer.podoarrival.event.model.entity.ArrivalUser; @@ -13,6 +14,7 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import java.time.LocalTime; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -26,6 +28,8 @@ public class ArrivalEventReleaseServiceJavaImpl implements ArrivalEventReleaseSe private static int MAX_ARRIVAL = 100; // default private boolean CHECK = false; + private static LocalTime START_TIME = LocalTime.of(0, 0); + private static boolean START_DATE = true; private static AtomicInteger count = new AtomicInteger(1); private static ConcurrentHashMap hashMap = new ConcurrentHashMap<>(); @@ -38,6 +42,8 @@ public class ArrivalEventReleaseServiceJavaImpl implements ArrivalEventReleaseSe @Override public CompletableFuture applyEvent(AuthInfo authInfo) { return CompletableFuture.supplyAsync(() -> { + if(!START_DATE) throw new EventClosedException("이벤트 요일이 아닙니다."); + if(LocalTime.now().isBefore(START_TIME)) throw new EventClosedException("이벤트 시간이 아닙니다."); if(CHECK){ return new ArrivalApplicationResponseDto(false, authInfo.getName(), authInfo.getPhoneNum(), -1); @@ -72,7 +78,24 @@ public static void setMaxArrival(int val) { MAX_ARRIVAL = val; } + public static void setStartTime(LocalTime val) { + START_TIME = val; + } + + public static void setStartDate(Boolean val) { + START_DATE = val; + } + public static int getMaxArrival() { return MAX_ARRIVAL; } + + + public static LocalTime getStartTime() { + return START_TIME; + } + + public static boolean getStartDate() { + return START_DATE; + } } diff --git a/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventReleaseServiceRedisImpl.java b/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventReleaseServiceRedisImpl.java index b250df2..872b08f 100644 --- a/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventReleaseServiceRedisImpl.java +++ b/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventReleaseServiceRedisImpl.java @@ -1,5 +1,6 @@ package com.softeer.podoarrival.event.service; +import com.softeer.podoarrival.event.exception.EventClosedException; import com.softeer.podoarrival.event.exception.ExistingUserException; import com.softeer.podoarrival.event.model.dto.ArrivalApplicationResponseDto; import com.softeer.podoarrival.event.model.entity.ArrivalUser; @@ -17,6 +18,7 @@ import org.springframework.stereotype.Service; import java.time.LocalDate; +import java.time.LocalTime; import java.util.concurrent.CompletableFuture; @Slf4j @@ -32,6 +34,8 @@ public class ArrivalEventReleaseServiceRedisImpl implements ArrivalEventReleaseS private final String ARRIVAL_SET = "arrivalset"; private boolean CHECK = false; private static int MAX_ARRIVAL = 100; // default + private static LocalTime START_TIME = LocalTime.of(0, 0); + private static boolean START_DATE = true; /** * 비동기로 Redis 호출하는 메서드 @@ -44,6 +48,9 @@ public CompletableFuture applyEvent(AuthInfo auth return CompletableFuture.supplyAsync(() -> { String redisKey = LocalDate.now() + ARRIVAL_SET; + if(!START_DATE) throw new EventClosedException("이벤트 요일이 아닙니다."); + if(LocalTime.now().isBefore(START_TIME)) throw new EventClosedException("이벤트 시간이 아닙니다."); + if(CHECK){ return new ArrivalApplicationResponseDto(false, authInfo.getName(), authInfo.getPhoneNum(), -1); } @@ -58,9 +65,6 @@ public CompletableFuture applyEvent(AuthInfo auth throw new ExistingUserException("이미 응모한 전화번호입니다."); } - // 로깅 추가 - specialLogger.info("[응모] 유저 전화번호: {}", authInfo.getPhoneNum()); - int grade = (int) res.getResponses().get(1); // 선착순 순위에 들었다면 if(grade <= MAX_ARRIVAL){ @@ -85,7 +89,23 @@ public static void setMaxArrival(int val) { MAX_ARRIVAL = val; } + public static void setStartTime(LocalTime val) { + START_TIME = val; + } + + public static void setStartDate(Boolean val) { + START_DATE = val; + } + public static int getMaxArrival() { return MAX_ARRIVAL; } + + public static LocalTime getStartTime() { + return START_TIME; + } + + public static boolean getStartDate() { + return START_DATE; + } } diff --git a/src/test/java/com/softeer/podoarrival/integration/event/ArrivalEventServiceTest.java b/src/test/java/com/softeer/podoarrival/integration/event/ArrivalEventServiceTest.java index b54e64c..6a840ea 100644 --- a/src/test/java/com/softeer/podoarrival/integration/event/ArrivalEventServiceTest.java +++ b/src/test/java/com/softeer/podoarrival/integration/event/ArrivalEventServiceTest.java @@ -1,5 +1,6 @@ package com.softeer.podoarrival.integration.event; +import com.softeer.podoarrival.event.exception.EventClosedException; import com.softeer.podoarrival.event.model.dto.ArrivalApplicationResponseDto; import com.softeer.podoarrival.event.model.entity.Role; import com.softeer.podoarrival.event.repository.ArrivalUserRepository; @@ -10,6 +11,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; import org.mockito.Mockito; import org.redisson.Redisson; import org.redisson.api.RedissonClient; @@ -19,15 +21,22 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; +import java.lang.reflect.Field; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mockStatic; @SpringBootTest @ContextConfiguration(classes = {ArrivalEventServiceTest.TestConfig.class}) @@ -48,6 +57,9 @@ class ArrivalEventServiceTest { @Autowired private ArrivalUserRepository arrivalUserRepository; + @MockBean + private LocalTime localTime; + @AfterEach void tearDown() { redissonClient.getKeys().deleteByPattern("*arrivalset"); @@ -73,7 +85,7 @@ void redisApplyTest() throws InterruptedException { try { CompletableFuture futureResponse = redisEventService.applyEvent( new AuthInfo( - "teat" + userId, + "test" + userId, "010-1234-5678-" + userId, Role.ROLE_USER ) @@ -108,7 +120,7 @@ void javaApplyTest() throws InterruptedException { try { CompletableFuture futureResponse = javaEventService.applyEvent( new AuthInfo( - "teat" + userId, + "test" + userId, "010-1234-5678-" + userId, Role.ROLE_USER ) @@ -127,6 +139,30 @@ void javaApplyTest() throws InterruptedException { assertEquals(MAX_COUNT, count.get()); } + @Test + @DisplayName("선착순 api 시간 외 오류 테스트") + void eventOutOfTimeTest() throws NoSuchFieldException, IllegalAccessException { + //given + Field startDate = ArrivalEventReleaseServiceRedisImpl.class.getDeclaredField("START_DATE"); + Field startTime = ArrivalEventReleaseServiceRedisImpl.class.getDeclaredField("START_TIME"); + startDate.setAccessible(true); // private 필드를 접근 가능하도록 설정 + startTime.setAccessible(true); + startDate.set(redisEventService, true); // private 필드 값을 변경 + startTime.set(redisEventService, LocalTime.now().plusHours(1)); + + //when + CompletableFuture futureResponse = redisEventService.applyEvent( + new AuthInfo( + "test", + "010-1234-5678-", + Role.ROLE_USER + ) + ); + + //then + assertThrows(ExecutionException.class, futureResponse::get); + } + @Configuration static class TestConfig { diff --git a/src/test/java/com/softeer/podoarrival/unit/base/ArrivalEventMaxCountSchedulerBase.java b/src/test/java/com/softeer/podoarrival/unit/base/ArrivalEventInformationBase.java similarity index 83% rename from src/test/java/com/softeer/podoarrival/unit/base/ArrivalEventMaxCountSchedulerBase.java rename to src/test/java/com/softeer/podoarrival/unit/base/ArrivalEventInformationBase.java index 654a4f7..dcede5e 100644 --- a/src/test/java/com/softeer/podoarrival/unit/base/ArrivalEventMaxCountSchedulerBase.java +++ b/src/test/java/com/softeer/podoarrival/unit/base/ArrivalEventInformationBase.java @@ -5,7 +5,7 @@ import com.softeer.podoarrival.event.repository.EventRepository; import com.softeer.podoarrival.event.repository.EventRewardRepository; import com.softeer.podoarrival.event.repository.EventTypeRepository; -import com.softeer.podoarrival.event.scheduler.ArrivalEventMaxArrivalScheduler; +import com.softeer.podoarrival.event.scheduler.ArrivalEventInformationScheduler; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; @@ -13,10 +13,12 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; @ExtendWith(MockitoExtension.class) -public class ArrivalEventMaxCountSchedulerBase { +public class ArrivalEventInformationBase { @Mock protected EventRepository eventRepository; @@ -28,7 +30,7 @@ public class ArrivalEventMaxCountSchedulerBase { protected EventTypeRepository eventTypeRepository; @InjectMocks - protected ArrivalEventMaxArrivalScheduler arrivalEventMaxArrivalScheduler; + protected ArrivalEventInformationScheduler arrivalEventInformationScheduler; protected Event eventSample; protected EventReward eventReward1; @@ -42,6 +44,8 @@ public void setUp() { .description("The 2025 셀토스 출시 기념 선착순 이벤트") .startAt(LocalDateTime.now()) .endAt(LocalDateTime.now().plusDays(5)) + .repeatTime(LocalTime.of(15, 0)) + .repeatDay("1111111") .build(); eventReward1 = EventReward.builder() diff --git a/src/test/java/com/softeer/podoarrival/unit/event/ArrivalEventComplexServiceTest.java b/src/test/java/com/softeer/podoarrival/unit/event/ArrivalEventComplexServiceTest.java index 01ce89f..1187078 100644 --- a/src/test/java/com/softeer/podoarrival/unit/event/ArrivalEventComplexServiceTest.java +++ b/src/test/java/com/softeer/podoarrival/unit/event/ArrivalEventComplexServiceTest.java @@ -28,10 +28,10 @@ public class ArrivalEventComplexServiceTest { private ArrivalEventService arrivalEventService; @Mock - private ArrivalEventReleaseServiceJavaImpl redisService; + private ArrivalEventReleaseServiceJavaImpl javaService; @Mock - private ArrivalEventReleaseServiceRedisImpl javaService; + private ArrivalEventReleaseServiceRedisImpl redisService; @Mock private RedissonClient redissonClient; diff --git a/src/test/java/com/softeer/podoarrival/unit/scheduler/ArrivalEventMaxCountSchedulerTest.java b/src/test/java/com/softeer/podoarrival/unit/scheduler/ArrivalEventInformationTest.java similarity index 71% rename from src/test/java/com/softeer/podoarrival/unit/scheduler/ArrivalEventMaxCountSchedulerTest.java rename to src/test/java/com/softeer/podoarrival/unit/scheduler/ArrivalEventInformationTest.java index e3de0ce..37644c6 100644 --- a/src/test/java/com/softeer/podoarrival/unit/scheduler/ArrivalEventMaxCountSchedulerTest.java +++ b/src/test/java/com/softeer/podoarrival/unit/scheduler/ArrivalEventInformationTest.java @@ -1,26 +1,28 @@ package com.softeer.podoarrival.unit.scheduler; -import com.softeer.podoarrival.event.model.entity.Event; import com.softeer.podoarrival.event.model.entity.EventType; import com.softeer.podoarrival.event.service.ArrivalEventReleaseServiceJavaImpl; import com.softeer.podoarrival.event.service.ArrivalEventReleaseServiceRedisImpl; -import com.softeer.podoarrival.unit.base.ArrivalEventMaxCountSchedulerBase; +import com.softeer.podoarrival.unit.base.ArrivalEventInformationBase; +import jakarta.transaction.Transactional; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.List; import java.util.Optional; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; -public class ArrivalEventMaxCountSchedulerTest extends ArrivalEventMaxCountSchedulerBase { +public class ArrivalEventInformationTest extends ArrivalEventInformationBase { @Test - @DisplayName("이벤트 최대인원 세팅 스케줄러 동작 테스트 (성공 - Mysql에 데이터가 존재하는 경우)") + @Transactional + @DisplayName("이벤트 정보 세팅 스케줄러 동작 테스트 (성공 - Mysql에 데이터가 존재하는 경우)") public void setArrivalEventCountSuccess_Data_Exists() { LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); LocalDateTime endOfDay = startOfDay.plusDays(1).minusNanos(1); @@ -39,17 +41,22 @@ public void setArrivalEventCountSuccess_Data_Exists() { // when - arrivalEventMaxArrivalScheduler.setEventArrivalCount(); + arrivalEventInformationScheduler.setArrivalEventInformation(); // then Assertions.assertThat(ArrivalEventReleaseServiceRedisImpl.getMaxArrival()) .isEqualTo(60); Assertions.assertThat(ArrivalEventReleaseServiceJavaImpl.getMaxArrival()) .isEqualTo(60); + Assertions.assertThat(ArrivalEventReleaseServiceRedisImpl.getStartTime()) + .isEqualTo(LocalTime.of(15, 0)); + Assertions.assertThat(ArrivalEventReleaseServiceJavaImpl.getStartTime()) + .isEqualTo(LocalTime.of(15, 0)); } @Test - @DisplayName("이벤트 최대인원 세팅 스케줄러 동작 테스트 (성공 - Mysql에 데이터가 존재하지 않는 경우)") + @Transactional + @DisplayName("이벤트 정보 세팅 스케줄러 동작 테스트 (성공 - Mysql에 데이터가 존재하지 않는 경우)") public void setArrivalEventCountSuccess_Data_Not_Exists() { // given EventType arrivalType = EventType.builder() @@ -63,7 +70,7 @@ public void setArrivalEventCountSuccess_Data_Not_Exists() { .thenReturn(null); // when - arrivalEventMaxArrivalScheduler.setEventArrivalCount(); + arrivalEventInformationScheduler.setArrivalEventInformation(); // then Assertions.assertThat(ArrivalEventReleaseServiceRedisImpl.getMaxArrival())