diff --git a/build.gradle b/build.gradle index 27ea0df..9f8618d 100644 --- a/build.gradle +++ b/build.gradle @@ -2,8 +2,44 @@ plugins { id 'java' id 'org.springframework.boot' version '3.3.2' id 'io.spring.dependency-management' version '1.1.6' + id 'jacoco' } +jacoco { + toolVersion = "0.8.9" +} + +/** Jacoco start **/ +test { + finalizedBy jacocoTestReport +} + +jacocoTestReport { + dependsOn test + + reports { + xml.required.set(true) + html.required.set(true) + + // QueryDSL Q클래스 제외 + def Qdomains = [] + for (qPattern in "**/QA".."**/QZ") { + Qdomains.add(qPattern + "*") + } + + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, + exclude: [] + Qdomains) + })) + } + + xml.destination file("${buildDir}/jacoco/index.xml") + html.destination file("${buildDir}/jacoco/index.html") + } +} +/** Jacoco end **/ + group = 'com.softeer' version = '0.0.1-SNAPSHOT' @@ -47,8 +83,11 @@ dependencies { implementation 'com.nimbusds:nimbus-jose-jwt:9.37.3' // lombok annotationProcessor 'org.projectlombok:lombok' + // junit testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + // test h2 database + testRuntimeOnly 'com.h2database:h2' //redission implementation 'org.redisson:redisson-spring-boot-starter:3.33.0' implementation "org.redisson:redisson-spring-data-27:3.33.0" diff --git a/src/main/java/com/softeer/podoarrival/event/controller/ArrivalEventController.java b/src/main/java/com/softeer/podoarrival/event/controller/ArrivalEventController.java index 63b0b72..a61d0ca 100644 --- a/src/main/java/com/softeer/podoarrival/event/controller/ArrivalEventController.java +++ b/src/main/java/com/softeer/podoarrival/event/controller/ArrivalEventController.java @@ -2,10 +2,9 @@ import com.softeer.podoarrival.common.response.CommonResponse; -import com.softeer.podoarrival.common.response.ErrorCode; import com.softeer.podoarrival.event.exception.AsyncRequestExecuteException; +import com.softeer.podoarrival.event.exception.ExistingUserException; import com.softeer.podoarrival.event.model.dto.ArrivalApplicationResponseDto; -import com.softeer.podoarrival.event.service.ArrivalEventReleaseService; import com.softeer.podoarrival.event.service.ArrivalEventService; import com.softeer.podoarrival.security.Auth; import com.softeer.podoarrival.security.AuthInfo; @@ -33,10 +32,11 @@ public CompletableFuture> arrivalE return arrivalEventService.applyEvent(authInfo) .thenApply(result -> new CommonResponse<>(result)) .exceptionally(ex -> { - log.error("비동기 처리 중 오류 발생", ex); - // 예외 발생 시 적절한 오류 응답을 반환 - throw new AsyncRequestExecuteException("선착순 요청 중 서버 오류 발생"); + if(ex.getCause() instanceof ExistingUserException) { + throw new ExistingUserException("[비동기 에러] 유저가 이미 존재합니다."); + } else { + throw new AsyncRequestExecuteException("[비동기 에러] 선착순 요청 중 서버 오류 발생"); + } }); } - } diff --git a/src/main/java/com/softeer/podoarrival/event/model/entity/EventType.java b/src/main/java/com/softeer/podoarrival/event/model/entity/EventType.java index 35e01ad..35e5a80 100644 --- a/src/main/java/com/softeer/podoarrival/event/model/entity/EventType.java +++ b/src/main/java/com/softeer/podoarrival/event/model/entity/EventType.java @@ -4,12 +4,10 @@ import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; -import lombok.Data; import lombok.NoArgsConstructor; @Entity @Table(name = "event_types") -@Data @Builder @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/com/softeer/podoarrival/event/model/entity/Quiz.java b/src/main/java/com/softeer/podoarrival/event/model/entity/Quiz.java index 657ea38..a35fb42 100644 --- a/src/main/java/com/softeer/podoarrival/event/model/entity/Quiz.java +++ b/src/main/java/com/softeer/podoarrival/event/model/entity/Quiz.java @@ -2,6 +2,7 @@ import jakarta.persistence.*; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -10,6 +11,7 @@ @Entity @Table(name = "quizzes") @Getter +@Builder @AllArgsConstructor @NoArgsConstructor public class Quiz { diff --git a/src/main/java/com/softeer/podoarrival/event/scheduler/scheduler/ArrivalEventMaxArrivalScheduler.java b/src/main/java/com/softeer/podoarrival/event/scheduler/ArrivalEventMaxArrivalScheduler.java similarity index 97% rename from src/main/java/com/softeer/podoarrival/event/scheduler/scheduler/ArrivalEventMaxArrivalScheduler.java rename to src/main/java/com/softeer/podoarrival/event/scheduler/ArrivalEventMaxArrivalScheduler.java index ae43f56..f6154a0 100644 --- a/src/main/java/com/softeer/podoarrival/event/scheduler/scheduler/ArrivalEventMaxArrivalScheduler.java +++ b/src/main/java/com/softeer/podoarrival/event/scheduler/ArrivalEventMaxArrivalScheduler.java @@ -1,4 +1,4 @@ -package com.softeer.podoarrival.event.scheduler.scheduler; +package com.softeer.podoarrival.event.scheduler; import com.softeer.podoarrival.event.exception.EventTypeNotExistsException; import com.softeer.podoarrival.event.model.entity.Event; diff --git a/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventReleaseService.java b/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventReleaseService.java index 3918e9e..52b161f 100644 --- a/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventReleaseService.java +++ b/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventReleaseService.java @@ -29,7 +29,7 @@ public class ArrivalEventReleaseService { private final String ARRIVAL_SET = "arrivalset"; private boolean CHECK = false; - private static int MAX_ARRIVAL = 0; + private static int MAX_ARRIVAL = 100; // default /** * 비동기로 Redis 호출하는 메서드 @@ -38,16 +38,15 @@ public class ArrivalEventReleaseService { @Async("arrivalExecutor") public CompletableFuture applyEvent(AuthInfo authInfo) { return CompletableFuture.supplyAsync(() -> { - LocalDate now = LocalDate.now(); + String redisKey = LocalDate.now() + ARRIVAL_SET; if(CHECK){ return new ArrivalApplicationResponseDto("선착순 응모에 실패했습니다.", -1); } - log.info("전화번호 = {}", authInfo.getPhoneNum()); RBatch batch = redissonClient.createBatch(); - batch.getSet(now + ARRIVAL_SET).addAsync(authInfo.getPhoneNum()); - batch.getSet(now + ARRIVAL_SET).sizeAsync(); + batch.getSet(redisKey).addAsync(authInfo.getPhoneNum()); + batch.getSet(redisKey).sizeAsync(); BatchResult res = batch.execute(); //첫번째 응답 @@ -66,6 +65,7 @@ public CompletableFuture applyEvent(AuthInfo auth .arrivalRank(grade) .build() ); + log.info("전화번호 = {}", authInfo.getPhoneNum()); return new ArrivalApplicationResponseDto("선착순 응모에 성공했습니다.", grade); } else { CHECK = true; @@ -77,4 +77,8 @@ public CompletableFuture applyEvent(AuthInfo auth public static void setMaxArrival(int val) { MAX_ARRIVAL = val; } + + public static int getMaxArrival() { + return MAX_ARRIVAL; + } } diff --git a/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventService.java b/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventService.java index 4b7e4b5..2a6f99d 100644 --- a/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventService.java +++ b/src/main/java/com/softeer/podoarrival/event/service/ArrivalEventService.java @@ -1,25 +1,22 @@ package com.softeer.podoarrival.event.service; -import com.softeer.podoarrival.common.response.CommonResponse; -import com.softeer.podoarrival.event.exception.AsyncRequestExecuteException; import com.softeer.podoarrival.event.model.dto.ArrivalApplicationResponseDto; import com.softeer.podoarrival.security.AuthInfo; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; @Slf4j @Service @RequiredArgsConstructor public class ArrivalEventService { - private final ExecutorService executorService; private final ArrivalEventReleaseService arrivalEventReleaseService; + @Transactional public CompletableFuture applyEvent(AuthInfo authInfo) { return arrivalEventReleaseService.applyEvent(authInfo); } diff --git a/src/main/java/com/softeer/podoarrival/security/Auth.java b/src/main/java/com/softeer/podoarrival/security/Auth.java index ff80c9e..ea0f466 100644 --- a/src/main/java/com/softeer/podoarrival/security/Auth.java +++ b/src/main/java/com/softeer/podoarrival/security/Auth.java @@ -1,10 +1,13 @@ package com.softeer.podoarrival.security; +import io.swagger.v3.oas.annotations.Parameter; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +@Parameter(hidden = true) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Auth { diff --git a/src/test/java/com/softeer/podoarrival/event/service/ArrivalEventServiceTest.java b/src/test/java/com/softeer/podoarrival/integration/event/ArrivalEventServiceTest.java similarity index 88% rename from src/test/java/com/softeer/podoarrival/event/service/ArrivalEventServiceTest.java rename to src/test/java/com/softeer/podoarrival/integration/event/ArrivalEventServiceTest.java index f94df03..3043d57 100644 --- a/src/test/java/com/softeer/podoarrival/event/service/ArrivalEventServiceTest.java +++ b/src/test/java/com/softeer/podoarrival/integration/event/ArrivalEventServiceTest.java @@ -1,23 +1,22 @@ -package com.softeer.podoarrival.event.service; +package com.softeer.podoarrival.integration.event; import com.softeer.podoarrival.PodoArrivalApplication; import com.softeer.podoarrival.event.model.dto.ArrivalApplicationResponseDto; -import com.softeer.podoarrival.security.AuthInfo; import com.softeer.podoarrival.event.model.entity.Role; +import com.softeer.podoarrival.event.service.ArrivalEventService; +import com.softeer.podoarrival.security.AuthInfo; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; -import static org.junit.jupiter.api.Assertions.*; - +import static org.junit.jupiter.api.Assertions.assertEquals; @SpringBootTest @ContextConfiguration(classes = PodoArrivalApplication.class) @@ -31,12 +30,11 @@ class ArrivalEventServiceTest { @AfterEach void tearDown() { - redissonClient.getKeys().flushdb(); + redissonClient.getKeys().deleteByPattern("*arrivalset"); redissonClient.shutdown(); } - @Value("${MAX_COUNT}") - private int MAX_COUNT; + private int MAX_COUNT = 100; @Test @DisplayName("선착순 api 정확도 테스트") @@ -70,7 +68,6 @@ void applicationTest() throws InterruptedException { countDownLatch.await(); //then - redissonClient.getSet("arrivalset").clear(); assertEquals(MAX_COUNT, count.get()); } } \ No newline at end of file diff --git a/src/test/java/com/softeer/podoarrival/unit/base/ArrivalEventBase.java b/src/test/java/com/softeer/podoarrival/unit/base/ArrivalEventBase.java new file mode 100644 index 0000000..454a0da --- /dev/null +++ b/src/test/java/com/softeer/podoarrival/unit/base/ArrivalEventBase.java @@ -0,0 +1,45 @@ +package com.softeer.podoarrival.unit.base; + +import com.softeer.podoarrival.event.model.entity.Role; +import com.softeer.podoarrival.event.repository.ArrivalUserRepository; +import com.softeer.podoarrival.event.service.ArrivalEventReleaseService; +import com.softeer.podoarrival.security.AuthInfo; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.redisson.api.BatchResult; +import org.redisson.api.RBatch; +import org.redisson.api.RSetAsync; +import org.redisson.api.RedissonClient; + +@ExtendWith(MockitoExtension.class) +public class ArrivalEventBase { + + @Mock + protected RedissonClient redissonClient; + + @Mock + protected ArrivalUserRepository arrivalUserRepository; + + @Mock + protected RBatch batch; + + @Mock + protected RSetAsync setAsync; + + @Mock + protected BatchResult batchResult; + + @InjectMocks + protected ArrivalEventReleaseService arrivalEventReleaseService; + + protected AuthInfo authInfo; + + @BeforeEach + public void setUp() { + authInfo = new AuthInfo("user", "01012345678", Role.ROLE_USER); + ArrivalEventReleaseService.setMaxArrival(5); + } +} diff --git a/src/test/java/com/softeer/podoarrival/unit/base/ArrivalEventMaxCountSchedulerBase.java b/src/test/java/com/softeer/podoarrival/unit/base/ArrivalEventMaxCountSchedulerBase.java new file mode 100644 index 0000000..d92ea90 --- /dev/null +++ b/src/test/java/com/softeer/podoarrival/unit/base/ArrivalEventMaxCountSchedulerBase.java @@ -0,0 +1,36 @@ +package com.softeer.podoarrival.unit.base; + +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 org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class ArrivalEventMaxCountSchedulerBase { + + @Mock + protected EventRepository eventRepository; + + @Mock + protected EventRewardRepository eventRewardRepository; + + @Mock + protected EventTypeRepository eventTypeRepository; + + @InjectMocks + protected ArrivalEventMaxArrivalScheduler arrivalEventMaxArrivalScheduler; + + @BeforeEach + public void setUp() { + } + + @AfterEach + public void tearDown() { + } +} diff --git a/src/test/java/com/softeer/podoarrival/unit/base/DailyQuizSchedulerBase.java b/src/test/java/com/softeer/podoarrival/unit/base/DailyQuizSchedulerBase.java new file mode 100644 index 0000000..94ff9bb --- /dev/null +++ b/src/test/java/com/softeer/podoarrival/unit/base/DailyQuizSchedulerBase.java @@ -0,0 +1,32 @@ +package com.softeer.podoarrival.unit.base; + +import com.softeer.podoarrival.event.repository.QuizRepository; +import com.softeer.podoarrival.event.repository.RedisRepository; +import com.softeer.podoarrival.event.scheduler.DailyQuizScheduler; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class DailyQuizSchedulerBase { + + @Mock + protected RedisRepository redisRepository; + + @Mock + protected QuizRepository quizRepository; + + @InjectMocks + protected DailyQuizScheduler quizScheduler; + + @BeforeEach + public void setUp() { + } + + @AfterEach + public void tearDown() { + } +} diff --git a/src/test/java/com/softeer/podoarrival/unit/base/QuizServiceBase.java b/src/test/java/com/softeer/podoarrival/unit/base/QuizServiceBase.java new file mode 100644 index 0000000..28d5527 --- /dev/null +++ b/src/test/java/com/softeer/podoarrival/unit/base/QuizServiceBase.java @@ -0,0 +1,53 @@ +package com.softeer.podoarrival.unit.base; + +import com.softeer.podoarrival.event.repository.QuizRepository; +import com.softeer.podoarrival.event.repository.RedisRepository; +import com.softeer.podoarrival.event.service.QuizService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDate; + +import static org.mockito.Mockito.mockStatic; + +@ExtendWith(MockitoExtension.class) +public class QuizServiceBase { + + protected String QUESTION_KEY; + protected String[] CHOICE_KEYS; + protected String ANSWER_KEYS; + + @Mock + protected QuizRepository quizRepository; + + @Mock + protected RedisRepository redisRepository; + + @InjectMocks + protected QuizService quizService; + + protected MockedStatic localDateMock; + + @BeforeEach + public void setUp() { + QUESTION_KEY = "quiz::question"; + CHOICE_KEYS = new String[]{"quiz::choice1", "quiz::choice2", "quiz::choice3", "quiz::choice4"}; + ANSWER_KEYS = "quiz::answer"; + +// localDateMock = mockStatic(LocalDate.class); +// localDateMock.when(LocalDate::now).thenReturn(LocalDate.of(2023, 12, 2)); + } + + @AfterEach + public void tearDown() { + // Close the mocked static after each test to avoid side effects + if (localDateMock != null) { + localDateMock.close(); + } + } +} diff --git a/src/test/java/com/softeer/podoarrival/unit/event/ApplyArrivalEventTest.java b/src/test/java/com/softeer/podoarrival/unit/event/ApplyArrivalEventTest.java new file mode 100644 index 0000000..d75a503 --- /dev/null +++ b/src/test/java/com/softeer/podoarrival/unit/event/ApplyArrivalEventTest.java @@ -0,0 +1,99 @@ +package com.softeer.podoarrival.unit.event; + +import com.softeer.podoarrival.unit.base.ArrivalEventBase; +import com.softeer.podoarrival.event.model.dto.ArrivalApplicationResponseDto; +import com.softeer.podoarrival.event.service.ArrivalEventReleaseService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.stubbing.Answer; +import org.redisson.api.BatchResult; +import org.redisson.api.RSetAsync; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ApplyArrivalEventTest extends ArrivalEventBase { + + @Test + @DisplayName("선착순 이벤트 응모 (성공)") + public void applyArrivalEventSuccess() throws ExecutionException, InterruptedException { + // given + when(redissonClient.createBatch()).thenReturn(batch); + + when(batch.getSet(anyString())).thenAnswer((Answer) invocation -> { + RSetAsync result = mock(RSetAsync.class); + when(result.addAsync(anyString())).thenReturn(null); + when(result.sizeAsync()).thenReturn(null); + return result; + }); + + when(batch.execute()).thenAnswer((Answer>) invocation -> { + BatchResult result = mock(BatchResult.class); + when(result.getResponses()).thenReturn(Arrays.asList(true, 1)); + return result; + }); + + // when + CompletableFuture responseFuture = arrivalEventReleaseService.applyEvent(authInfo); + ArrivalApplicationResponseDto response = responseFuture.get(); // 비동기 결과를 기다림 + + // then + assertThat(response.getResponse()).isEqualTo("선착순 응모에 성공했습니다."); + assertThat(response.getGrade()).isEqualTo(1); + } + + @Test + @DisplayName("선착순 이벤트 응모 (실패 - 선착순 순위 안에 들지 못했을 경우)") + public void applyArrivalEventFail_Late() throws ExecutionException, InterruptedException { + // given + try { + java.lang.reflect.Field checkField = ArrivalEventReleaseService.class.getDeclaredField("CHECK"); + checkField.setAccessible(true); // private 필드 접근 허용 + checkField.set(arrivalEventReleaseService, true); // 값 설정 + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + + // when + CompletableFuture responseFuture = arrivalEventReleaseService.applyEvent(authInfo); + ArrivalApplicationResponseDto response = responseFuture.get(); // 비동기 결과를 기다림 + + // then + assertThat(response.getResponse()).isEqualTo("선착순 응모에 실패했습니다."); + assertThat(response.getGrade()).isEqualTo(-1); + } + + @Test + @DisplayName("선착순 이벤트 응모 (실패 - 중복 참여)") + public void applyArrivalEventFail_Duplicate() { + // given + when(redissonClient.createBatch()).thenReturn(batch); + + when(batch.getSet(anyString())).thenAnswer((Answer) invocation -> { + RSetAsync result = mock(RSetAsync.class); + when(result.addAsync(anyString())).thenReturn(null); + when(result.sizeAsync()).thenReturn(null); + return result; + }); + + when(batch.execute()).thenAnswer((Answer>) invocation -> { + BatchResult result = mock(BatchResult.class); + when(result.getResponses()).thenReturn(Arrays.asList(false, 1)); + return result; + }); + + // when + CompletableFuture responseFuture = arrivalEventReleaseService.applyEvent(authInfo); + + // then + assertThatThrownBy(() -> responseFuture.get()) + .isInstanceOf(ExecutionException.class); + } +} diff --git a/src/test/java/com/softeer/podoarrival/unit/quiz/GetQuizListTest.java b/src/test/java/com/softeer/podoarrival/unit/quiz/GetQuizListTest.java new file mode 100644 index 0000000..0d772a1 --- /dev/null +++ b/src/test/java/com/softeer/podoarrival/unit/quiz/GetQuizListTest.java @@ -0,0 +1,91 @@ +package com.softeer.podoarrival.unit.quiz; + +import com.softeer.podoarrival.unit.base.QuizServiceBase; +import com.softeer.podoarrival.event.model.dto.GetQuizResponseDto; +import com.softeer.podoarrival.event.model.entity.Quiz; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.when; + +public class GetQuizListTest extends QuizServiceBase{ + + @Test + @DisplayName("퀴즈 리스트 불러오기 (성공 - 레디스에 정보가 존재한 경우)") + public void getQuizListSuccess_CacheHit() { + // given + when(redisRepository.getValues(QUESTION_KEY)).thenReturn("질문입니다"); + when(redisRepository.getValues(CHOICE_KEYS[0])).thenReturn("선택지 1"); + when(redisRepository.getValues(CHOICE_KEYS[1])).thenReturn("선택지 2"); + when(redisRepository.getValues(CHOICE_KEYS[2])).thenReturn("선택지 3"); + when(redisRepository.getValues(CHOICE_KEYS[3])).thenReturn("선택지 4"); + when(redisRepository.getValues(ANSWER_KEYS)).thenReturn("A"); + + // when + GetQuizResponseDto result = quizService.getQuizInfo(); + + // then + assertThat(result.getQuestion()).isEqualTo("질문입니다"); + assertThat(result.getChoice1()).isEqualTo("선택지 1"); + assertThat(result.getChoice2()).isEqualTo("선택지 2"); + assertThat(result.getChoice3()).isEqualTo("선택지 3"); + assertThat(result.getChoice4()).isEqualTo("선택지 4"); + assertThat(result.getAnswer()).isEqualTo("A"); + } + + @Test + @DisplayName("퀴즈 리스트 불러오기 (성공 - 레디스에 정보가 존재하지 않은 경우)") + public void getQuizListSuccess_CacheMiss() { + // given + when(redisRepository.getValues(QUESTION_KEY)).thenReturn(null); + when(redisRepository.getValues(CHOICE_KEYS[0])).thenReturn(null); + when(redisRepository.getValues(CHOICE_KEYS[1])).thenReturn(null); + when(redisRepository.getValues(CHOICE_KEYS[2])).thenReturn(null); + when(redisRepository.getValues(CHOICE_KEYS[3])).thenReturn(null); + when(redisRepository.getValues(ANSWER_KEYS)).thenReturn(null); + when(quizRepository.findByEventDate(LocalDate.now())) + .thenReturn(Quiz.builder() + .question("질문입니다") + .choice1("선택지 1") + .choice2("선택지 2") + .choice3("선택지 3") + .choice4("선택지 4") + .answer("A") + .build() + ); + + // when + GetQuizResponseDto result = quizService.getQuizInfo(); + + // then + assertThat(result.getQuestion()).isEqualTo("질문입니다"); + assertThat(result.getChoice1()).isEqualTo("선택지 1"); + assertThat(result.getChoice2()).isEqualTo("선택지 2"); + assertThat(result.getChoice3()).isEqualTo("선택지 3"); + assertThat(result.getChoice4()).isEqualTo("선택지 4"); + assertThat(result.getAnswer()).isEqualTo("A"); + } + + @Test + @DisplayName("퀴즈 리스트 불러오기 (실패 - 데이터베이스에도 정보가 존재하지 않는 경우)") + public void getQuizListFail_CacheMiss() { + // given + when(redisRepository.getValues(QUESTION_KEY)).thenReturn(null); + when(redisRepository.getValues(CHOICE_KEYS[0])).thenReturn(null); + when(redisRepository.getValues(CHOICE_KEYS[1])).thenReturn(null); + when(redisRepository.getValues(CHOICE_KEYS[2])).thenReturn(null); + when(redisRepository.getValues(CHOICE_KEYS[3])).thenReturn(null); + when(redisRepository.getValues(ANSWER_KEYS)).thenReturn(null); + when(quizRepository.findByEventDate(LocalDate.now())) + .thenReturn(null); + + // when-then + assertThatThrownBy(() -> quizService.getQuizInfo()) + .isInstanceOf(NullPointerException.class); + } + +} diff --git a/src/test/java/com/softeer/podoarrival/unit/scheduler/ArrivalEventMaxCountSchedulerTest.java b/src/test/java/com/softeer/podoarrival/unit/scheduler/ArrivalEventMaxCountSchedulerTest.java new file mode 100644 index 0000000..d61547c --- /dev/null +++ b/src/test/java/com/softeer/podoarrival/unit/scheduler/ArrivalEventMaxCountSchedulerTest.java @@ -0,0 +1,76 @@ +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.ArrivalEventReleaseService; +import com.softeer.podoarrival.unit.base.ArrivalEventMaxCountSchedulerBase; +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.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +public class ArrivalEventMaxCountSchedulerTest extends ArrivalEventMaxCountSchedulerBase { + + @Test + @DisplayName("이벤트 최대인원 세팅 스케줄러 동작 테스트 (성공 - Mysql에 데이터가 존재하는 경우)") + public void setArrivalEventCountSuccess_Data_Exists() { + LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); + LocalDateTime endOfDay = startOfDay.plusDays(1).minusNanos(1); + // given + EventType arrivalType = EventType.builder() + .id(1L) + .type("arrival") + .build(); + + when(eventTypeRepository.findById(1L)) + .thenReturn(Optional.ofNullable(arrivalType)); + when(eventRepository.findFirstByEventTypeAndStartAtBetween(arrivalType, startOfDay, endOfDay)) + .thenReturn(Event.builder() + .id(1L) + .title("셀토스 선착순 이벤트") + .description("The 2025 셀토스 출시 기념 선착순 이벤트") + .startAt(LocalDateTime.now()) + .endAt(LocalDateTime.now().plusDays(5)) + .build() + ); + when(eventRewardRepository.countByEvent(any())) + .thenReturn(50); + + // when + arrivalEventMaxArrivalScheduler.setEventArrivalCount(); + + // then + Assertions.assertThat(ArrivalEventReleaseService.getMaxArrival()) + .isEqualTo(50); + } + + @Test + @DisplayName("이벤트 최대인원 세팅 스케줄러 동작 테스트 (성공 - Mysql에 데이터가 존재하지 않는 경우)") + public void setArrivalEventCountSuccess_Data_Not_Exists() { + // given + EventType arrivalType = EventType.builder() + .id(1L) + .type("arrival") + .build(); + + when(eventTypeRepository.findById(1L)) + .thenReturn(Optional.ofNullable(arrivalType)); + when(eventRepository.findFirstByEventTypeAndStartAtBetween(any(), any(), any())) + .thenReturn(null); + when(eventRewardRepository.countByEvent(any())) + .thenReturn(0); + + // when + arrivalEventMaxArrivalScheduler.setEventArrivalCount(); + + // then + Assertions.assertThat(ArrivalEventReleaseService.getMaxArrival()) + .isEqualTo(0); + } +} diff --git a/src/test/java/com/softeer/podoarrival/unit/scheduler/DailyQuizSchedulerTest.java b/src/test/java/com/softeer/podoarrival/unit/scheduler/DailyQuizSchedulerTest.java new file mode 100644 index 0000000..7305892 --- /dev/null +++ b/src/test/java/com/softeer/podoarrival/unit/scheduler/DailyQuizSchedulerTest.java @@ -0,0 +1,44 @@ +package com.softeer.podoarrival.unit.scheduler; + +import com.softeer.podoarrival.event.model.entity.Quiz; +import com.softeer.podoarrival.unit.base.DailyQuizSchedulerBase; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.mockito.Mockito.when; + +public class DailyQuizSchedulerTest extends DailyQuizSchedulerBase { + + @Test + @DisplayName("퀴즈 스케줄러 동작 테스트 (성공 - Mysql에 데이터가 존재하는 경우)") + public void getQuizListSuccess_CacheHit() { + // given + when(quizRepository.findByEventDate(LocalDate.now())) + .thenReturn(Quiz.builder() + .id(1L) + .question("질문입니다.") + .choice1("선택지 1") + .choice2("선택지 2") + .choice3("선택지 3") + .choice4("선택지 4") + .answer("A") + .build() + ); + + // when + quizScheduler.setDailyQuiz(); + } + + @Test + @DisplayName("퀴즈 스케줄러 동작 테스트 (성공 - Mysql에 데이터가 존재하지 않은 경우)") + public void getQuizListFail_CacheMiss() { + // given + when(quizRepository.findByEventDate(LocalDate.now())) + .thenReturn(null); + + // when + quizScheduler.setDailyQuiz(); + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 0000000..d5622a4 --- /dev/null +++ b/src/test/resources/application.yml @@ -0,0 +1,17 @@ +spring: + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:test;MODE=MYSQL;DATABASE_TO_UPPER=false + username: sa + password: + jpa: + open-in-view: true + hibernate: + ddl-auto: create + show-sql: true + database: h2 + database-platform: org.hibernate.dialect.H2Dialect + +secret: + jwt: ${SECRET_JWT} + redis-url: ${REDIS_URL} \ No newline at end of file