From d679f6ba8c90ad430f238e524f95290d56ca4275 Mon Sep 17 00:00:00 2001 From: jaekwan Date: Sun, 10 May 2026 15:55:15 +0900 Subject: [PATCH 1/4] =?UTF-8?q?test:=20=EC=9E=94=EC=97=AC=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EB=A0=88=EC=9D=B4=EC=96=B4=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9D=BC=EA=B4=84=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DiscountCodeQueryService / SurveyQueryService / SurveyCommandService SurveyGlobalStatsService / ResponseQueryService — 총 36개 테스트 --- gradlew | 0 .../service/DiscountCodeQueryServiceTest.java | 134 +++++++++++ .../response/ResponseQueryServiceTest.java | 79 ++++++ .../service/SurveyGlobalStatsServiceTest.java | 130 ++++++++++ .../command/SurveyCommandServiceTest.java | 226 ++++++++++++++++++ .../service/query/SurveyQueryServiceTest.java | 148 ++++++++++++ 6 files changed, 717 insertions(+) mode change 100644 => 100755 gradlew create mode 100644 src/test/java/OneQ/OnSurvey/domain/discount/service/DiscountCodeQueryServiceTest.java create mode 100644 src/test/java/OneQ/OnSurvey/domain/participation/service/response/ResponseQueryServiceTest.java create mode 100644 src/test/java/OneQ/OnSurvey/domain/survey/service/SurveyGlobalStatsServiceTest.java create mode 100644 src/test/java/OneQ/OnSurvey/domain/survey/service/command/SurveyCommandServiceTest.java create mode 100644 src/test/java/OneQ/OnSurvey/domain/survey/service/query/SurveyQueryServiceTest.java diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/test/java/OneQ/OnSurvey/domain/discount/service/DiscountCodeQueryServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/discount/service/DiscountCodeQueryServiceTest.java new file mode 100644 index 00000000..1cc5cbed --- /dev/null +++ b/src/test/java/OneQ/OnSurvey/domain/discount/service/DiscountCodeQueryServiceTest.java @@ -0,0 +1,134 @@ +package OneQ.OnSurvey.domain.discount.service; + +import OneQ.OnSurvey.domain.discount.DiscountCodeErrorCode; +import OneQ.OnSurvey.domain.discount.entity.DiscountCode; +import OneQ.OnSurvey.domain.discount.model.response.DiscountCodeResponse; +import OneQ.OnSurvey.domain.discount.model.response.ValidateDiscountCodeResponse; +import OneQ.OnSurvey.domain.discount.repository.DiscountCodeRepository; +import OneQ.OnSurvey.global.common.exception.CustomException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class DiscountCodeQueryServiceTest { + + @Mock + private DiscountCodeRepository discountCodeRepository; + + @InjectMocks + private DiscountCodeQueryService discountCodeQueryService; + + private DiscountCode buildCode(String code, LocalDate expiredAt) { + return DiscountCode.of("테스트기업", code, expiredAt); + } + + @Test + @DisplayName("validate - 코드 없으면 DISCOUNT_CODE_NOT_FOUND 예외") + void validate_notFound_throwsException() { + given(discountCodeRepository.findByCode("XXXXXX")).willReturn(Optional.empty()); + + assertThatThrownBy(() -> discountCodeQueryService.validate("XXXXXX")) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(DiscountCodeErrorCode.DISCOUNT_CODE_NOT_FOUND)); + } + + @Test + @DisplayName("validate - 만료된 코드면 DISCOUNT_CODE_EXPIRED 예외") + void validate_expired_throwsException() { + DiscountCode expired = buildCode("AAAAAA", LocalDate.now().minusDays(1)); + given(discountCodeRepository.findByCode("AAAAAA")).willReturn(Optional.of(expired)); + + assertThatThrownBy(() -> discountCodeQueryService.validate("AAAAAA")) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(DiscountCodeErrorCode.DISCOUNT_CODE_EXPIRED)); + } + + @Test + @DisplayName("validate - 유효한 코드면 eligible=true 반환") + void validate_valid_returnsEligibleTrue() { + DiscountCode valid = buildCode("BBBBBB", LocalDate.now().plusDays(30)); + given(discountCodeRepository.findByCode("BBBBBB")).willReturn(Optional.of(valid)); + + ValidateDiscountCodeResponse response = discountCodeQueryService.validate("BBBBBB"); + + assertThat(response.eligible()).isTrue(); + } + + @Test + @DisplayName("getByCode - 코드 없으면 DISCOUNT_CODE_NOT_FOUND 예외") + void getByCode_notFound_throwsException() { + given(discountCodeRepository.findByCode("YYYYYY")).willReturn(Optional.empty()); + + assertThatThrownBy(() -> discountCodeQueryService.getByCode("YYYYYY")) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(DiscountCodeErrorCode.DISCOUNT_CODE_NOT_FOUND)); + } + + @Test + @DisplayName("getByCode - 만료된 코드면 DISCOUNT_CODE_EXPIRED 예외") + void getByCode_expired_throwsException() { + DiscountCode expired = buildCode("CCCCCC", LocalDate.now().minusDays(1)); + given(discountCodeRepository.findByCode("CCCCCC")).willReturn(Optional.of(expired)); + + assertThatThrownBy(() -> discountCodeQueryService.getByCode("CCCCCC")) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(DiscountCodeErrorCode.DISCOUNT_CODE_EXPIRED)); + } + + @Test + @DisplayName("getByCode - 유효한 코드면 엔티티 반환") + void getByCode_valid_returnsEntity() { + DiscountCode valid = buildCode("DDDDDD", LocalDate.now().plusDays(10)); + given(discountCodeRepository.findByCode("DDDDDD")).willReturn(Optional.of(valid)); + + DiscountCode result = discountCodeQueryService.getByCode("DDDDDD"); + + assertThat(result.getCode()).isEqualTo("DDDDDD"); + assertThat(result.getOrganizationName()).isEqualTo("테스트기업"); + } + + @Test + @DisplayName("findAll - 활성 코드가 만료 코드보다 먼저, 각 그룹 내 만료일 오름차순") + void findAll_sortedActiveFirst() { + LocalDate today = LocalDate.now(); + DiscountCode expired1 = buildCode("EXP001", today.minusDays(5)); + DiscountCode expired2 = buildCode("EXP002", today.minusDays(1)); + DiscountCode active1 = buildCode("ACT001", today.plusDays(10)); + DiscountCode active2 = buildCode("ACT002", today.plusDays(3)); + given(discountCodeRepository.findAll()).willReturn(List.of(expired1, active1, expired2, active2)); + + List results = discountCodeQueryService.findAll(); + + assertThat(results).hasSize(4); + assertThat(results.get(0).code()).isEqualTo("ACT002"); + assertThat(results.get(1).code()).isEqualTo("ACT001"); + assertThat(results.get(2).code()).isEqualTo("EXP001"); + assertThat(results.get(3).code()).isEqualTo("EXP002"); + } + + @Test + @DisplayName("findAll - 빈 목록이면 빈 리스트 반환") + void findAll_empty_returnsEmptyList() { + given(discountCodeRepository.findAll()).willReturn(List.of()); + + List results = discountCodeQueryService.findAll(); + + assertThat(results).isEmpty(); + } +} diff --git a/src/test/java/OneQ/OnSurvey/domain/participation/service/response/ResponseQueryServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/participation/service/response/ResponseQueryServiceTest.java new file mode 100644 index 00000000..ed735909 --- /dev/null +++ b/src/test/java/OneQ/OnSurvey/domain/participation/service/response/ResponseQueryServiceTest.java @@ -0,0 +1,79 @@ +package OneQ.OnSurvey.domain.participation.service.response; + +import OneQ.OnSurvey.domain.participation.repository.response.ResponseRepository; +import OneQ.OnSurvey.domain.survey.model.AgeRange; +import OneQ.OnSurvey.domain.survey.model.Gender; +import OneQ.OnSurvey.domain.survey.model.Residence; +import OneQ.OnSurvey.domain.survey.model.SurveyResponseFilterCondition; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.never; + +@ExtendWith(MockitoExtension.class) +class ResponseQueryServiceTest { + + @Mock + private ResponseRepository responseRepository; + + @InjectMocks + private ResponseQueryService responseQueryService; + + @Test + @DisplayName("getResponseCountBySurveyId (필터 없음) - repository에 위임") + void getResponseCount_noFilter_delegatesToRepository() { + given(responseRepository.getResponseCountBySurveyId(1L)).willReturn(50); + + Integer result = responseQueryService.getResponseCountBySurveyId(1L); + + assertThat(result).isEqualTo(50); + verify(responseRepository).getResponseCountBySurveyId(1L); + } + + @Test + @DisplayName("getResponseCountBySurveyId - filter가 null이면 필터 없는 메서드 위임") + void getResponseCount_nullFilter_delegatesToNoFilter() { + given(responseRepository.getResponseCountBySurveyId(1L)).willReturn(30); + + Integer result = responseQueryService.getResponseCountBySurveyId(1L, null); + + assertThat(result).isEqualTo(30); + verify(responseRepository).getResponseCountBySurveyId(1L); + verify(responseRepository, never()).getResponseCountBySurveyId(1L, null); + } + + @Test + @DisplayName("getResponseCountBySurveyId - filter가 비어있으면 필터 없는 메서드 위임") + void getResponseCount_emptyFilter_delegatesToNoFilter() { + SurveyResponseFilterCondition empty = SurveyResponseFilterCondition.empty(); + given(responseRepository.getResponseCountBySurveyId(1L)).willReturn(20); + + Integer result = responseQueryService.getResponseCountBySurveyId(1L, empty); + + assertThat(result).isEqualTo(20); + verify(responseRepository).getResponseCountBySurveyId(1L); + } + + @Test + @DisplayName("getResponseCountBySurveyId - 유효한 filter면 필터 포함 메서드 위임") + void getResponseCount_withFilter_delegatesToFilteredMethod() { + SurveyResponseFilterCondition filter = new SurveyResponseFilterCondition( + List.of(AgeRange.TWENTY), List.of(Gender.MALE), List.of(Residence.SEOUL) + ); + given(responseRepository.getResponseCountBySurveyId(1L, filter)).willReturn(10); + + Integer result = responseQueryService.getResponseCountBySurveyId(1L, filter); + + assertThat(result).isEqualTo(10); + verify(responseRepository).getResponseCountBySurveyId(1L, filter); + } +} diff --git a/src/test/java/OneQ/OnSurvey/domain/survey/service/SurveyGlobalStatsServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/survey/service/SurveyGlobalStatsServiceTest.java new file mode 100644 index 00000000..776fa728 --- /dev/null +++ b/src/test/java/OneQ/OnSurvey/domain/survey/service/SurveyGlobalStatsServiceTest.java @@ -0,0 +1,130 @@ +package OneQ.OnSurvey.domain.survey.service; + +import OneQ.OnSurvey.domain.survey.entity.SurveyGlobalStats; +import OneQ.OnSurvey.domain.survey.model.dto.GlobalStats; +import OneQ.OnSurvey.domain.survey.repository.SurveyGlobalStatsRepository; +import OneQ.OnSurvey.global.infra.redis.RedisAgent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class SurveyGlobalStatsServiceTest { + + @Mock + private SurveyGlobalStatsRepository statsRepository; + + @Mock + private RedisAgent redisAgent; + + @InjectMocks + private SurveyGlobalStatsService surveyGlobalStatsService; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(surveyGlobalStatsService, "dailyUserKey", "daily:user:"); + } + + private SurveyGlobalStats buildStats(long due, long completed, long promotion) { + return SurveyGlobalStats.builder() + .id(1L) + .totalDueCount(due) + .totalCompletedCount(completed) + .totalPromotionCount(promotion) + .build(); + } + + @Test + @DisplayName("addDueCount - 기존 stats에 delta만큼 증가") + void addDueCount_increasesExistingStats() { + SurveyGlobalStats stats = buildStats(1000L, 500L, 200L); + given(statsRepository.findById(1L)).willReturn(Optional.of(stats)); + + surveyGlobalStatsService.addDueCount(100L); + + assertThat(stats.getTotalDueCount()).isEqualTo(1100L); + } + + @Test + @DisplayName("addDueCount - stats 없으면 init 후 증가") + void addDueCount_noStats_initAndIncreases() { + SurveyGlobalStats newStats = SurveyGlobalStats.init(); + given(statsRepository.findById(1L)).willReturn(Optional.empty()); + given(statsRepository.save(any(SurveyGlobalStats.class))).willReturn(newStats); + + surveyGlobalStatsService.addDueCount(50L); + + assertThat(newStats.getTotalDueCount()).isEqualTo(1050L); + } + + @Test + @DisplayName("addCompletedCount - 기존 stats에 delta만큼 증가") + void addCompletedCount_increasesExistingStats() { + SurveyGlobalStats stats = buildStats(1000L, 500L, 200L); + given(statsRepository.findById(1L)).willReturn(Optional.of(stats)); + + surveyGlobalStatsService.addCompletedCount(30L); + + assertThat(stats.getTotalCompletedCount()).isEqualTo(530L); + } + + @Test + @DisplayName("addPromotionCount - 기존 stats에 delta만큼 증가") + void addPromotionCount_increasesExistingStats() { + SurveyGlobalStats stats = buildStats(1000L, 500L, 200L); + given(statsRepository.findById(1L)).willReturn(Optional.of(stats)); + + surveyGlobalStatsService.addPromotionCount(10L); + + assertThat(stats.getTotalPromotionCount()).isEqualTo(210L); + } + + @Test + @DisplayName("getStats - stats 존재 시 dailyUserCount와 함께 반환") + void getStats_existingStats_returnsCorrectValues() { + SurveyGlobalStats stats = buildStats(2000L, 800L, 400L); + given(statsRepository.findById(1L)).willReturn(Optional.of(stats)); + given(redisAgent.getZSetCount(any(), anyLong(), anyLong())).willReturn(42L); + + GlobalStats result = surveyGlobalStatsService.getStats(); + + assertThat(result.totalDueCount()).isEqualTo(2000L); + assertThat(result.totalCompletedCount()).isEqualTo(800L); + assertThat(result.totalPromotionCount()).isEqualTo(400L); + assertThat(result.dailyUserCount()).isEqualTo(42L); + } + + @Test + @DisplayName("getStats - stats 없으면 init 기본값 사용") + void getStats_noStats_usesInitDefaults() { + given(statsRepository.findById(1L)).willReturn(Optional.empty()); + given(redisAgent.getZSetCount(any(), anyLong(), anyLong())).willReturn(0L); + + GlobalStats result = surveyGlobalStatsService.getStats(); + + assertThat(result.totalDueCount()).isEqualTo(1000L); + assertThat(result.totalCompletedCount()).isEqualTo(1000L); + assertThat(result.totalPromotionCount()).isEqualTo(1000L); + } + + @Test + @DisplayName("removeOldDailyUsers - redisAgent.rangeRemoveFromZSet 호출") + void removeOldDailyUsers_callsRedisRangeRemove() { + surveyGlobalStatsService.removeOldDailyUsers(); + + verify(redisAgent).rangeRemoveFromZSet(any(), anyLong(), anyLong()); + } +} diff --git a/src/test/java/OneQ/OnSurvey/domain/survey/service/command/SurveyCommandServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/survey/service/command/SurveyCommandServiceTest.java new file mode 100644 index 00000000..69ca233f --- /dev/null +++ b/src/test/java/OneQ/OnSurvey/domain/survey/service/command/SurveyCommandServiceTest.java @@ -0,0 +1,226 @@ +package OneQ.OnSurvey.domain.survey.service.command; + +import OneQ.OnSurvey.domain.member.Member; +import OneQ.OnSurvey.domain.member.MemberErrorCode; +import OneQ.OnSurvey.domain.member.repository.MemberRepository; +import OneQ.OnSurvey.domain.member.value.MemberStatus; +import OneQ.OnSurvey.domain.member.value.Role; +import OneQ.OnSurvey.domain.question.service.QuestionQueryService; +import OneQ.OnSurvey.domain.discount.service.DiscountCodeQueryService; +import OneQ.OnSurvey.domain.survey.SurveyErrorCode; +import OneQ.OnSurvey.domain.survey.entity.Screening; +import OneQ.OnSurvey.domain.survey.entity.Survey; +import OneQ.OnSurvey.domain.survey.entity.SurveyInfo; +import OneQ.OnSurvey.domain.survey.model.Gender; +import OneQ.OnSurvey.domain.survey.model.SurveyStatus; +import OneQ.OnSurvey.domain.survey.model.response.ScreeningResponse; +import OneQ.OnSurvey.domain.survey.repository.SurveyRepository; +import OneQ.OnSurvey.domain.survey.repository.screening.ScreeningRepository; +import OneQ.OnSurvey.domain.survey.repository.surveyInfo.SurveyInfoRepository; +import OneQ.OnSurvey.domain.survey.service.SurveyGlobalStatsService; +import OneQ.OnSurvey.domain.survey.service.refund.SurveyRefundPolicy; +import OneQ.OnSurvey.global.common.exception.CustomException; +import OneQ.OnSurvey.global.infra.discord.notifier.AlertNotifier; +import OneQ.OnSurvey.global.infra.redis.RedisAgent; +import OneQ.OnSurvey.global.infra.transaction.AfterCommitExecutor; +import OneQ.OnSurvey.global.infra.transaction.TransactionHandler; +import OneQ.OnSurvey.global.promotion.application.PromotionTierResolver; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class SurveyCommandServiceTest { + + @Mock private SurveyRepository surveyRepository; + @Mock private ScreeningRepository screeningRepository; + @Mock private SurveyInfoRepository surveyInfoRepository; + @Mock private MemberRepository memberRepository; + @Mock private SurveyRefundPolicy surveyRefundPolicy; + @Mock private SurveyGlobalStatsService surveyGlobalStatsService; + @Mock private QuestionQueryService questionQueryService; + @Mock private PromotionTierResolver promotionTierResolver; + @Mock private DiscountCodeQueryService discountCodeQueryService; + @Mock private AlertNotifier alertNotifier; + @Mock private AfterCommitExecutor afterCommitExecutor; + @Mock private RedisAgent redisAgent; + @Mock private TransactionHandler transactionHandler; + + @InjectMocks + private SurveyCommandService surveyCommandService; + + private Survey buildSurvey(Long id, Long memberId) { + Survey survey = Survey.of(memberId, "테스트 설문", "설명"); + ReflectionTestUtils.setField(survey, "id", id); + return survey; + } + + private SurveyInfo buildRefundableSurveyInfo(Long surveyId) { + return SurveyInfo.createSurveyInfo( + surveyId, 100, Gender.ALL, Set.of(), Set.of(), 0, 0, 0, 0, 0, null + ); + } + + private Member buildMember(Long userKey) { + Member member = Member.createMember( + userKey, "홍길동", "010-1234-5678", + "19900101", "test@test.com", + OneQ.OnSurvey.domain.survey.model.Gender.MALE, Role.ROLE_MEMBER, MemberStatus.ACTIVE + ); + member.increaseCoin(1000L); + return member; + } + + @Test + @DisplayName("upsertScreening - content가 null이면 기존 스크리닝 삭제") + void upsertScreening_nullContent_deletesExisting() { + Screening existing = Screening.of(1L, "기존 질문", true); + given(screeningRepository.getScreeningBySurveyId(1L)).willReturn(existing); + + ScreeningResponse response = surveyCommandService.upsertScreening(1L, null, null); + + verify(screeningRepository).delete(existing); + assertThat(response.screeningId()).isNull(); + } + + @Test + @DisplayName("upsertScreening - content가 blank이면 기존 스크리닝 삭제") + void upsertScreening_blankContent_deletesExisting() { + Screening existing = Screening.of(1L, "기존 질문", true); + given(screeningRepository.getScreeningBySurveyId(1L)).willReturn(existing); + + ScreeningResponse response = surveyCommandService.upsertScreening(1L, " ", true); + + verify(screeningRepository).delete(existing); + assertThat(response.screeningId()).isNull(); + } + + @Test + @DisplayName("upsertScreening - 스크리닝 없고 유효한 값이면 신규 생성") + void upsertScreening_noExisting_createsNew() { + Screening created = Screening.of(1L, "새 질문", false); + ReflectionTestUtils.setField(created, "id", 99L); + given(screeningRepository.getScreeningBySurveyId(1L)).willReturn(null); + given(screeningRepository.save(any(Screening.class))).willReturn(created); + + ScreeningResponse response = surveyCommandService.upsertScreening(1L, "새 질문", false); + + assertThat(response.content()).isEqualTo("새 질문"); + assertThat(response.answer()).isFalse(); + } + + @Test + @DisplayName("upsertScreening - 기존 스크리닝 있으면 내용 업데이트") + void upsertScreening_existingScreening_updates() { + Screening existing = Screening.of(1L, "기존 질문", true); + ReflectionTestUtils.setField(existing, "id", 5L); + given(screeningRepository.getScreeningBySurveyId(1L)).willReturn(existing); + given(screeningRepository.save(existing)).willReturn(existing); + + ScreeningResponse response = surveyCommandService.upsertScreening(1L, "수정된 질문", false); + + assertThat(response.content()).isEqualTo("수정된 질문"); + assertThat(response.answer()).isFalse(); + } + + @Test + @DisplayName("refundSurvey - 설문 없으면 SURVEY_NOT_FOUND 예외") + void refundSurvey_surveyNotFound_throwsException() { + given(surveyRepository.getSurveyById(999L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> surveyCommandService.refundSurvey(1L, 999L)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(SurveyErrorCode.SURVEY_NOT_FOUND)); + } + + @Test + @DisplayName("refundSurvey - surveyInfo 없으면 SURVEY_INFO_NOT_FOUND 예외") + void refundSurvey_infoNotFound_throwsException() { + Survey survey = buildSurvey(1L, 10L); + given(surveyRepository.getSurveyById(1L)).willReturn(Optional.of(survey)); + given(surveyInfoRepository.findBySurveyId(1L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> surveyCommandService.refundSurvey(1L, 1L)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(SurveyErrorCode.SURVEY_INFO_NOT_FOUND)); + } + + @Test + @DisplayName("refundSurvey - 환불 불가 상태면 SURVEY_NOT_REFUNDABLE 예외") + void refundSurvey_notRefundable_throwsException() { + Survey survey = buildSurvey(1L, 10L); + SurveyInfo info = buildRefundableSurveyInfo(1L); + info.markNonRefundable(); + given(surveyRepository.getSurveyById(1L)).willReturn(Optional.of(survey)); + given(surveyInfoRepository.findBySurveyId(1L)).willReturn(Optional.of(info)); + + assertThatThrownBy(() -> surveyCommandService.refundSurvey(1L, 1L)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(SurveyErrorCode.SURVEY_NOT_REFUNDABLE)); + } + + @Test + @DisplayName("refundSurvey - 환불 금액이 0 이하면 SURVEY_NOT_REFUNDABLE 예외") + void refundSurvey_zeroRefundAmount_throwsException() { + Survey survey = buildSurvey(1L, 10L); + SurveyInfo info = buildRefundableSurveyInfo(1L); + given(surveyRepository.getSurveyById(1L)).willReturn(Optional.of(survey)); + given(surveyInfoRepository.findBySurveyId(1L)).willReturn(Optional.of(info)); + given(surveyRefundPolicy.calculateRefundAmount(survey, info)).willReturn(0); + + assertThatThrownBy(() -> surveyCommandService.refundSurvey(1L, 1L)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(SurveyErrorCode.SURVEY_NOT_REFUNDABLE)); + } + + @Test + @DisplayName("refundSurvey - 환불 성공 시 코인 지급 및 설문 상태 REFUNDED") + void refundSurvey_success_increasesCoinAndUpdatesStatus() { + Survey survey = buildSurvey(1L, 10L); + SurveyInfo info = buildRefundableSurveyInfo(1L); + Member member = buildMember(1L); + given(surveyRepository.getSurveyById(1L)).willReturn(Optional.of(survey)); + given(surveyInfoRepository.findBySurveyId(1L)).willReturn(Optional.of(info)); + given(surveyRefundPolicy.calculateRefundAmount(survey, info)).willReturn(500); + given(memberRepository.findMemberByUserKey(1L)).willReturn(Optional.of(member)); + + Boolean result = surveyCommandService.refundSurvey(1L, 1L); + + assertThat(result).isTrue(); + assertThat(member.getCoin()).isEqualTo(1500L); + assertThat(survey.getStatus()).isEqualTo(SurveyStatus.REFUNDED); + } + + @Test + @DisplayName("refundSurvey - 멤버 없으면 MEMBER_NOT_FOUND 예외") + void refundSurvey_memberNotFound_throwsException() { + Survey survey = buildSurvey(1L, 10L); + SurveyInfo info = buildRefundableSurveyInfo(1L); + given(surveyRepository.getSurveyById(1L)).willReturn(Optional.of(survey)); + given(surveyInfoRepository.findBySurveyId(1L)).willReturn(Optional.of(info)); + given(surveyRefundPolicy.calculateRefundAmount(survey, info)).willReturn(300); + given(memberRepository.findMemberByUserKey(1L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> surveyCommandService.refundSurvey(1L, 1L)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(MemberErrorCode.MEMBER_NOT_FOUND)); + } +} diff --git a/src/test/java/OneQ/OnSurvey/domain/survey/service/query/SurveyQueryServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/survey/service/query/SurveyQueryServiceTest.java new file mode 100644 index 00000000..b1808e14 --- /dev/null +++ b/src/test/java/OneQ/OnSurvey/domain/survey/service/query/SurveyQueryServiceTest.java @@ -0,0 +1,148 @@ +package OneQ.OnSurvey.domain.survey.service.query; + +import OneQ.OnSurvey.domain.member.repository.MemberRepository; +import OneQ.OnSurvey.domain.participation.repository.response.ResponseRepository; +import OneQ.OnSurvey.domain.question.repository.section.SectionRepository; +import OneQ.OnSurvey.domain.question.service.QuestionQueryService; +import OneQ.OnSurvey.domain.survey.SurveyErrorCode; +import OneQ.OnSurvey.domain.survey.entity.Survey; +import OneQ.OnSurvey.domain.survey.entity.SurveyInfo; +import OneQ.OnSurvey.domain.survey.model.Gender; +import OneQ.OnSurvey.domain.survey.model.SurveyStatus; +import OneQ.OnSurvey.domain.survey.model.response.MySurveyListResponse; +import OneQ.OnSurvey.domain.survey.repository.SurveyRepository; +import OneQ.OnSurvey.domain.survey.repository.screening.ScreeningRepository; +import OneQ.OnSurvey.domain.survey.repository.surveyInfo.SurveyInfoRepository; +import OneQ.OnSurvey.global.common.exception.CustomException; +import OneQ.OnSurvey.global.infra.redis.RedisAgent; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class SurveyQueryServiceTest { + + @Mock private SurveyRepository surveyRepository; + @Mock private SurveyInfoRepository surveyInfoRepository; + @Mock private ScreeningRepository screeningRepository; + @Mock private ResponseRepository responseRepository; + @Mock private MemberRepository memberRepository; + @Mock private SectionRepository sectionRepository; + @Mock private RedisAgent redisAgent; + @Mock private QuestionQueryService questionQueryService; + + @InjectMocks + private SurveyQueryService surveyQueryService; + + private Survey buildSurvey(Long id, Long memberId, SurveyStatus status, LocalDateTime createdAt) { + Survey survey = Survey.builder() + .memberId(memberId) + .title("테스트 설문") + .totalCoin(1000) + .deadline(LocalDateTime.now().plusDays(7)) + .build(); + ReflectionTestUtils.setField(survey, "id", id); + ReflectionTestUtils.setField(survey, "status", status); + ReflectionTestUtils.setField(survey, "createdAt", createdAt); + return survey; + } + + @Test + @DisplayName("getSurveyById - 설문 존재 시 반환") + void getSurveyById_found_returnsSurvey() { + Survey survey = buildSurvey(1L, 10L, SurveyStatus.ONGOING, LocalDateTime.now()); + given(surveyRepository.getSurveyById(1L)).willReturn(Optional.of(survey)); + + Survey result = surveyQueryService.getSurveyById(1L); + + assertThat(result).isEqualTo(survey); + } + + @Test + @DisplayName("getSurveyById - 설문 없으면 SURVEY_NOT_FOUND 예외") + void getSurveyById_notFound_throwsException() { + given(surveyRepository.getSurveyById(999L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> surveyQueryService.getSurveyById(999L)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(SurveyErrorCode.SURVEY_NOT_FOUND)); + } + + @Test + @DisplayName("getPromotionAmountBySurveyId - surveyInfo 존재 시 promotionAmount 반환") + void getPromotionAmountBySurveyId_found_returnsAmount() { + SurveyInfo info = SurveyInfo.createSurveyInfo( + 1L, 100, Gender.ALL, Set.of(), Set.of(), 0, 0, 0, 0, 300, null); + given(surveyInfoRepository.findBySurveyId(1L)).willReturn(Optional.of(info)); + + Integer result = surveyQueryService.getPromotionAmountBySurveyId(1L); + + assertThat(result).isEqualTo(300); + } + + @Test + @DisplayName("getPromotionAmountBySurveyId - surveyInfo 없으면 null 반환") + void getPromotionAmountBySurveyId_notFound_returnsNull() { + given(surveyInfoRepository.findBySurveyId(999L)).willReturn(Optional.empty()); + + Integer result = surveyQueryService.getPromotionAmountBySurveyId(999L); + + assertThat(result).isNull(); + } + + @Test + @DisplayName("getMySurveys - ONGOING/CLOSED는 ongoing, REFUNDED는 refunded로 분리") + void getMySurveys_separatesOngoingAndRefunded() { + LocalDateTime base = LocalDateTime.of(2026, 1, 1, 0, 0); + Survey ongoing = buildSurvey(1L, 10L, SurveyStatus.ONGOING, base.plusDays(2)); + Survey closed = buildSurvey(2L, 10L, SurveyStatus.CLOSED, base.plusDays(1)); + Survey refunded = buildSurvey(3L, 10L, SurveyStatus.REFUNDED, base); + given(surveyRepository.getSurveyListByMemberId(10L)).willReturn(List.of(ongoing, closed, refunded)); + + MySurveyListResponse response = surveyQueryService.getMySurveys(10L); + + assertThat(response.totalCount()).isEqualTo(2); + assertThat(response.refundedCount()).isEqualTo(1); + assertThat(response.ongoingSurveys()).hasSize(2); + assertThat(response.refundedSurveys()).hasSize(1); + } + + @Test + @DisplayName("getMySurveys - ongoing 목록은 createdAt 내림차순 정렬") + void getMySurveys_ongoingSortedByCreatedAtDesc() { + LocalDateTime base = LocalDateTime.of(2026, 1, 1, 0, 0); + Survey older = buildSurvey(1L, 10L, SurveyStatus.ONGOING, base); + Survey newer = buildSurvey(2L, 10L, SurveyStatus.ONGOING, base.plusDays(5)); + given(surveyRepository.getSurveyListByMemberId(10L)).willReturn(List.of(older, newer)); + + MySurveyListResponse response = surveyQueryService.getMySurveys(10L); + + assertThat(response.ongoingSurveys().get(0).surveyId()).isEqualTo(2L); + assertThat(response.ongoingSurveys().get(1).surveyId()).isEqualTo(1L); + } + + @Test + @DisplayName("getMySurveys - 빈 목록이면 카운트 0") + void getMySurveys_empty_returnsZeroCounts() { + given(surveyRepository.getSurveyListByMemberId(10L)).willReturn(List.of()); + + MySurveyListResponse response = surveyQueryService.getMySurveys(10L); + + assertThat(response.totalCount()).isZero(); + assertThat(response.refundedCount()).isZero(); + } +} From d0f67fb32fbd0e4f627e45bf432c4fb0afca853d Mon Sep 17 00:00:00 2001 From: jaekwan Date: Sun, 10 May 2026 16:17:15 +0900 Subject: [PATCH 2/4] =?UTF-8?q?test:=20=EC=9E=94=EC=97=AC=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EB=A0=88=EC=9D=B4=EC=96=B4=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(question,=20participation,=20formRequest)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QuestionAnswerCommandServiceTest.java | 71 ++++++++ .../ScreeningAnswerCommandServiceTest.java | 102 +++++++++++ .../service/QuestionCommandServiceTest.java | 126 ++++++++++++++ .../service/QuestionQueryServiceTest.java | 82 +++++++++ .../formRequest/FormCommandServiceTest.java | 162 ++++++++++++++++++ .../formRequest/FormQueryServiceTest.java | 80 +++++++++ 6 files changed, 623 insertions(+) create mode 100644 src/test/java/OneQ/OnSurvey/domain/participation/service/answer/QuestionAnswerCommandServiceTest.java create mode 100644 src/test/java/OneQ/OnSurvey/domain/participation/service/answer/ScreeningAnswerCommandServiceTest.java create mode 100644 src/test/java/OneQ/OnSurvey/domain/question/service/QuestionCommandServiceTest.java create mode 100644 src/test/java/OneQ/OnSurvey/domain/question/service/QuestionQueryServiceTest.java create mode 100644 src/test/java/OneQ/OnSurvey/domain/survey/service/formRequest/FormCommandServiceTest.java create mode 100644 src/test/java/OneQ/OnSurvey/domain/survey/service/formRequest/FormQueryServiceTest.java diff --git a/src/test/java/OneQ/OnSurvey/domain/participation/service/answer/QuestionAnswerCommandServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/participation/service/answer/QuestionAnswerCommandServiceTest.java new file mode 100644 index 00000000..6c099dfe --- /dev/null +++ b/src/test/java/OneQ/OnSurvey/domain/participation/service/answer/QuestionAnswerCommandServiceTest.java @@ -0,0 +1,71 @@ +package OneQ.OnSurvey.domain.participation.service.answer; + +import OneQ.OnSurvey.domain.participation.entity.QuestionAnswer; +import OneQ.OnSurvey.domain.participation.entity.Response; +import OneQ.OnSurvey.domain.participation.repository.answer.AnswerRepository; +import OneQ.OnSurvey.domain.participation.repository.response.ResponseRepository; +import OneQ.OnSurvey.domain.question.repository.question.QuestionRepository; +import OneQ.OnSurvey.global.infra.redis.RedisAgent; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class QuestionAnswerCommandServiceTest { + + @Mock private AnswerRepository answerRepository; + @Mock private ResponseRepository responseRepository; + @Mock private RedisAgent redisAgent; + @Mock private QuestionRepository questionRepository; + + @InjectMocks + private QuestionAnswerCommandService questionAnswerCommandService; + + @Test + @DisplayName("updateResponseAfterQuestionAnswers - 응답 없으면 신규 생성 후 저장") + void updateResponseAfterQuestionAnswers_notFound_createsAndSaves() { + given(responseRepository.findBySurveyIdAndMemberId(1L, 2L)).willReturn(Optional.empty()); + + questionAnswerCommandService.updateResponseAfterQuestionAnswers(1L, 2L); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Response.class); + verify(responseRepository).save(captor.capture()); + assertThat(captor.getValue().getSurveyId()).isEqualTo(1L); + assertThat(captor.getValue().getMemberId()).isEqualTo(2L); + assertThat(captor.getValue().getIsResponded()).isFalse(); + } + + @Test + @DisplayName("updateResponseAfterQuestionAnswers - 미완료 응답이면 저장") + void updateResponseAfterQuestionAnswers_notResponded_saves() { + Response response = Response.of(1L, 2L); + given(responseRepository.findBySurveyIdAndMemberId(1L, 2L)).willReturn(Optional.of(response)); + + questionAnswerCommandService.updateResponseAfterQuestionAnswers(1L, 2L); + + verify(responseRepository).save(response); + } + + @Test + @DisplayName("updateResponseAfterQuestionAnswers - 이미 완료된 응답이면 저장 안함") + void updateResponseAfterQuestionAnswers_alreadyResponded_doesNotSave() { + Response response = Response.of(1L, 2L); + response.markResponded(); + given(responseRepository.findBySurveyIdAndMemberId(1L, 2L)).willReturn(Optional.of(response)); + + questionAnswerCommandService.updateResponseAfterQuestionAnswers(1L, 2L); + + verify(responseRepository, never()).save(response); + } +} diff --git a/src/test/java/OneQ/OnSurvey/domain/participation/service/answer/ScreeningAnswerCommandServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/participation/service/answer/ScreeningAnswerCommandServiceTest.java new file mode 100644 index 00000000..24667aaa --- /dev/null +++ b/src/test/java/OneQ/OnSurvey/domain/participation/service/answer/ScreeningAnswerCommandServiceTest.java @@ -0,0 +1,102 @@ +package OneQ.OnSurvey.domain.participation.service.answer; + +import OneQ.OnSurvey.domain.participation.entity.Response; +import OneQ.OnSurvey.domain.participation.entity.ScreeningAnswer; +import OneQ.OnSurvey.domain.participation.model.dto.AnswerInsertDto; +import OneQ.OnSurvey.domain.participation.repository.answer.AnswerRepository; +import OneQ.OnSurvey.domain.participation.repository.response.ResponseRepository; +import OneQ.OnSurvey.domain.survey.repository.screening.ScreeningRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class ScreeningAnswerCommandServiceTest { + + @Mock private AnswerRepository answerRepository; + @Mock private ResponseRepository responseRepository; + @Mock private ScreeningRepository screeningRepository; + + @InjectMocks + private ScreeningAnswerCommandService screeningAnswerCommandService; + + private AnswerInsertDto.AnswerInfo buildAnswerInfo(Long screeningId, Long memberId, String content) { + return AnswerInsertDto.AnswerInfo.builder() + .id(screeningId) + .memberId(memberId) + .content(content) + .build(); + } + + @Test + @DisplayName("updateResponseAfterScreening - 기댓값과 제출값 같으면 screened=false") + void updateResponseAfterScreening_sameAsExpected_notScreened() { + given(screeningRepository.getScreeningAnswer(10L)).willReturn(true); + Response response = Response.of(5L, 1L); + given(responseRepository.findBySurveyIdAndMemberId(5L, 1L)).willReturn(Optional.of(response)); + + AnswerInsertDto.AnswerInfo info = buildAnswerInfo(10L, 1L, "true"); + screeningAnswerCommandService.updateResponseAfterScreening(5L, info); + + assertThat(response.getIsScreened()).isFalse(); + verify(responseRepository).save(response); + } + + @Test + @DisplayName("updateResponseAfterScreening - 기댓값과 제출값 다르면 screened=true") + void updateResponseAfterScreening_differsFromExpected_screened() { + given(screeningRepository.getScreeningAnswer(10L)).willReturn(true); + Response response = Response.of(5L, 1L); + given(responseRepository.findBySurveyIdAndMemberId(5L, 1L)).willReturn(Optional.of(response)); + + AnswerInsertDto.AnswerInfo info = buildAnswerInfo(10L, 1L, "false"); + screeningAnswerCommandService.updateResponseAfterScreening(5L, info); + + assertThat(response.getIsScreened()).isTrue(); + verify(responseRepository).save(response); + } + + @Test + @DisplayName("updateResponseAfterScreening - 기존 응답 없으면 신규 생성 후 저장") + void updateResponseAfterScreening_noExistingResponse_createsNewAndSaves() { + given(screeningRepository.getScreeningAnswer(10L)).willReturn(false); + given(responseRepository.findBySurveyIdAndMemberId(5L, 1L)).willReturn(Optional.empty()); + + AnswerInsertDto.AnswerInfo info = buildAnswerInfo(10L, 1L, "false"); + screeningAnswerCommandService.updateResponseAfterScreening(5L, info); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Response.class); + verify(responseRepository).save(captor.capture()); + assertThat(captor.getValue().getSurveyId()).isEqualTo(5L); + assertThat(captor.getValue().getMemberId()).isEqualTo(1L); + assertThat(captor.getValue().getIsScreened()).isFalse(); + } + + @Test + @DisplayName("insertAnswer - answerRepository.save 호출 후 updateResponseAfterScreening 진행") + void insertAnswer_savesAnswerAndUpdatesResponse() { + given(screeningRepository.getSurveyId(10L)).willReturn(5L); + given(screeningRepository.getScreeningAnswer(10L)).willReturn(true); + given(answerRepository.save(any())).willReturn(null); + Response response = Response.of(5L, 1L); + given(responseRepository.findBySurveyIdAndMemberId(5L, 1L)).willReturn(Optional.of(response)); + + AnswerInsertDto.AnswerInfo info = buildAnswerInfo(10L, 1L, "true"); + Boolean result = screeningAnswerCommandService.insertAnswer(info); + + assertThat(result).isTrue(); + verify(answerRepository).save(any()); + verify(responseRepository).save(response); + } +} diff --git a/src/test/java/OneQ/OnSurvey/domain/question/service/QuestionCommandServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/question/service/QuestionCommandServiceTest.java new file mode 100644 index 00000000..f5c97bd3 --- /dev/null +++ b/src/test/java/OneQ/OnSurvey/domain/question/service/QuestionCommandServiceTest.java @@ -0,0 +1,126 @@ +package OneQ.OnSurvey.domain.question.service; + +import OneQ.OnSurvey.domain.question.entity.Section; +import OneQ.OnSurvey.domain.question.model.dto.SectionDto; +import OneQ.OnSurvey.domain.question.repository.choiceOption.ChoiceOptionRepository; +import OneQ.OnSurvey.domain.question.repository.gridOption.GridOptionRepository; +import OneQ.OnSurvey.domain.question.repository.question.QuestionRepository; +import OneQ.OnSurvey.domain.question.repository.section.SectionRepository; +import OneQ.OnSurvey.domain.survey.SurveyErrorCode; +import OneQ.OnSurvey.global.common.exception.CustomException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.never; + +@ExtendWith(MockitoExtension.class) +class QuestionCommandServiceTest { + + @Mock private ChoiceOptionRepository choiceOptionRepository; + @Mock private GridOptionRepository gridOptionRepository; + @Mock private SectionRepository sectionRepository; + @Mock private QuestionRepository questionRepository; + + @InjectMocks + private QuestionCommandService questionCommandService; + + private Section buildSection(Long sectionId, Long surveyId, String title, int order, int nextSection) { + Section section = Section.builder() + .surveyId(surveyId) + .title(title) + .sectionOrder(order) + .nextSection(nextSection) + .build(); + if (sectionId != null) { + ReflectionTestUtils.setField(section, "sectionId", sectionId); + } + return section; + } + + @Test + @DisplayName("upsertSections - null 제목이면 SURVEY_FORM_INVALID_SECTION 예외") + void upsertSections_nullTitle_throwsInvalidSection() { + SectionDto invalid = new SectionDto(null, null, null, 1, 2); + + assertThatThrownBy(() -> questionCommandService.upsertSections(1L, List.of(invalid))) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(SurveyErrorCode.SURVEY_FORM_INVALID_SECTION)); + } + + @Test + @DisplayName("upsertSections - 공백 제목이면 SURVEY_FORM_INVALID_SECTION 예외") + void upsertSections_blankTitle_throwsInvalidSection() { + SectionDto invalid = new SectionDto(null, " ", null, 1, 2); + + assertThatThrownBy(() -> questionCommandService.upsertSections(1L, List.of(invalid))) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(SurveyErrorCode.SURVEY_FORM_INVALID_SECTION)); + } + + @Test + @DisplayName("upsertSections - null order이면 SURVEY_FORM_INVALID_SECTION 예외") + void upsertSections_nullOrder_throwsInvalidSection() { + SectionDto invalid = new SectionDto(null, "제목", null, null, 2); + + assertThatThrownBy(() -> questionCommandService.upsertSections(1L, List.of(invalid))) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(SurveyErrorCode.SURVEY_FORM_INVALID_SECTION)); + } + + @Test + @DisplayName("upsertSections - 신규 섹션이면 새 Section 엔티티 생성 및 저장") + void upsertSections_newSection_createsAndSaves() { + SectionDto newDto = new SectionDto(null, "새 섹션", "설명", 1, 2); + Section savedSection = buildSection(100L, 1L, "새 섹션", 1, 2); + given(sectionRepository.findAllSectionBySurveyId(1L)).willReturn(List.of()); + given(sectionRepository.saveAll(any())).willReturn(List.of(savedSection)); + + List result = questionCommandService.upsertSections(1L, List.of(newDto)); + + assertThat(result).hasSize(1); + assertThat(result.get(0).sectionId()).isEqualTo(100L); + assertThat(result.get(0).title()).isEqualTo("새 섹션"); + verify(sectionRepository).saveAll(any()); + } + + @Test + @DisplayName("upsertSections - 기존 섹션이면 내용 업데이트") + void upsertSections_existingSection_updatesSection() { + Section existing = buildSection(50L, 1L, "기존 섹션", 1, 2); + SectionDto updateDto = new SectionDto(50L, "수정된 섹션", "수정 설명", 1, 3); + given(sectionRepository.findAllSectionBySurveyId(1L)).willReturn(List.of(existing)); + given(sectionRepository.saveAll(any())).willReturn(List.of(existing)); + + questionCommandService.upsertSections(1L, List.of(updateDto)); + + assertThat(existing.getTitle()).isEqualTo("수정된 섹션"); + assertThat(existing.getNextSection()).isEqualTo(3); + verify(sectionRepository).saveAll(any()); + } + + @Test + @DisplayName("upsertSections - DB에만 있는 섹션은 삭제") + void upsertSections_extraDbSections_deletesOldSections() { + Section extra = buildSection(50L, 1L, "삭제될 섹션", 1, 2); + given(sectionRepository.findAllSectionBySurveyId(1L)).willReturn(List.of(extra)); + + questionCommandService.upsertSections(1L, List.of()); + + verify(sectionRepository).deleteAll(List.of(50L)); + } +} diff --git a/src/test/java/OneQ/OnSurvey/domain/question/service/QuestionQueryServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/question/service/QuestionQueryServiceTest.java new file mode 100644 index 00000000..97ebf4e7 --- /dev/null +++ b/src/test/java/OneQ/OnSurvey/domain/question/service/QuestionQueryServiceTest.java @@ -0,0 +1,82 @@ +package OneQ.OnSurvey.domain.question.service; + +import OneQ.OnSurvey.domain.question.entity.ChoiceOption; +import OneQ.OnSurvey.domain.question.entity.GridOption; +import OneQ.OnSurvey.domain.question.model.dto.GridOptionDto; +import OneQ.OnSurvey.domain.question.model.dto.OptionDto; +import OneQ.OnSurvey.domain.question.repository.choiceOption.ChoiceOptionRepository; +import OneQ.OnSurvey.domain.question.repository.gridOption.GridOptionRepository; +import OneQ.OnSurvey.domain.question.repository.question.QuestionRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class QuestionQueryServiceTest { + + @Mock private QuestionRepository questionRepository; + @Mock private ChoiceOptionRepository choiceOptionRepository; + @Mock private GridOptionRepository gridOptionRepository; + + @InjectMocks + private QuestionQueryService questionQueryService; + + @Test + @DisplayName("getOptionsByQuestionIdList - 보기를 OptionDto로 변환하여 반환") + void getOptionsByQuestionIdList_returnsOptionDtos() { + ChoiceOption option = ChoiceOption.of(1L, "선택지1", null, null); + ReflectionTestUtils.setField(option, "choiceOptionId", 10L); + given(choiceOptionRepository.getOptionsByQuestionIds(List.of(1L))).willReturn(List.of(option)); + + List result = questionQueryService.getOptionsByQuestionIdList(List.of(1L)); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getOptionId()).isEqualTo(10L); + assertThat(result.get(0).getContent()).isEqualTo("선택지1"); + assertThat(result.get(0).getQuestionId()).isEqualTo(1L); + } + + @Test + @DisplayName("getOptionsByQuestionIdList - 보기 없으면 빈 리스트 반환") + void getOptionsByQuestionIdList_empty_returnsEmpty() { + given(choiceOptionRepository.getOptionsByQuestionIds(List.of(1L))).willReturn(List.of()); + + List result = questionQueryService.getOptionsByQuestionIdList(List.of(1L)); + + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("getGridOptionsByQuestionIdList - 그리드 옵션을 GridOptionDto로 변환하여 반환") + void getGridOptionsByQuestionIdList_returnsGridOptionDtos() { + GridOption gridOption = GridOption.of(2L, true, "행1", 0); + ReflectionTestUtils.setField(gridOption, "gridOptionId", 20L); + given(gridOptionRepository.getGridOptionsByQuestionIds(List.of(2L))).willReturn(List.of(gridOption)); + + List result = questionQueryService.getGridOptionsByQuestionIdList(List.of(2L)); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getGridOptionId()).isEqualTo(20L); + assertThat(result.get(0).getIsRow()).isTrue(); + assertThat(result.get(0).getContent()).isEqualTo("행1"); + } + + @Test + @DisplayName("countQuestionsBySurveyId - repository에 위임") + void countQuestionsBySurveyId_delegatesToRepository() { + given(questionRepository.countBySurveyId(1L)).willReturn(7); + + int result = questionQueryService.countQuestionsBySurveyId(1L); + + assertThat(result).isEqualTo(7); + } +} diff --git a/src/test/java/OneQ/OnSurvey/domain/survey/service/formRequest/FormCommandServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/survey/service/formRequest/FormCommandServiceTest.java new file mode 100644 index 00000000..790191e7 --- /dev/null +++ b/src/test/java/OneQ/OnSurvey/domain/survey/service/formRequest/FormCommandServiceTest.java @@ -0,0 +1,162 @@ +package OneQ.OnSurvey.domain.survey.service.formRequest; + +import OneQ.OnSurvey.domain.member.dto.MemberSearchResult; +import OneQ.OnSurvey.domain.member.service.MemberFinder; +import OneQ.OnSurvey.domain.member.value.MemberStatus; +import OneQ.OnSurvey.domain.survey.SurveyErrorCode; +import OneQ.OnSurvey.domain.survey.entity.FormRequest; +import OneQ.OnSurvey.domain.survey.model.formRequest.FormPublishRequest; +import OneQ.OnSurvey.domain.survey.model.Gender; +import OneQ.OnSurvey.domain.survey.model.response.SurveyFormResponse; +import OneQ.OnSurvey.domain.survey.repository.formRequest.FormRequestRepository; +import OneQ.OnSurvey.domain.survey.service.command.SurveyCommand; +import OneQ.OnSurvey.domain.survey.service.query.SurveyQueryService; +import OneQ.OnSurvey.global.common.exception.CustomException; +import OneQ.OnSurvey.global.infra.redis.RedisCacheAction; +import OneQ.OnSurvey.global.infra.redis.RedisLockAction; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class FormCommandServiceTest { + + @Mock private ApplicationEventPublisher eventPublisher; + @Mock private RedisCacheAction redisCacheAction; + @Mock private RedisLockAction redisLockAction; + @Mock private FormConverter formConverter; + @Mock private FormRequestLambda formRequestLambda; + @Mock private FormRequestRepository formRequestRepository; + @Mock private SurveyQueryService surveyQueryService; + @Mock private MemberFinder memberFinder; + @Mock private SurveyCommand surveyCommand; + + @InjectMocks + private FormCommandService formCommandService; + + private FormRequest buildRegisteredRequest() { + FormRequest request = FormRequest.builder() + .formLink("https://link") + .userKey(1L) + .requesterEmail("test@test.com") + .isRegistered(true) + .registeredSurveyId(10L) + .build(); + return request; + } + + private MemberSearchResult buildMemberResult(Long userKey) { + return new MemberSearchResult(1L, userKey, "홍길동", "test@test.com", + "010-1234-5678", "19900101", Gender.MALE, MemberStatus.ACTIVE, 1000L); + } + + @Test + @DisplayName("markAsRegistered - FormRequest 없으면 FORM_REQUEST_NOT_FOUND 예외") + void markAsRegistered_notFound_throwsException() { + given(formRequestRepository.findById(999L)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> formCommandService.markAsRegistered(999L, 1L, 10)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(SurveyErrorCode.FORM_REQUEST_NOT_FOUND)); + } + + @Test + @DisplayName("markAsRegistered - 성공 시 request에 surveyId와 questionCount 기록") + void markAsRegistered_success_marksRequest() { + FormRequest request = FormRequest.createRequest("https://link", "test@test.com", 100, 1000, 1L); + given(formRequestRepository.findById(1L)).willReturn(Optional.of(request)); + + formCommandService.markAsRegistered(1L, 10L, 5); + + assertThat(request.getIsRegistered()).isTrue(); + assertThat(request.getRegisteredSurveyId()).isEqualTo(10L); + assertThat(request.getQuestionCount()).isEqualTo(5); + } + + @Test + @DisplayName("publishFormRequest - FormRequest 없으면 FORM_REQUEST_NOT_FOUND 예외") + void publishFormRequest_requestNotFound_throwsException() { + given(formRequestRepository.findById(999L)).willReturn(Optional.empty()); + FormPublishRequest publishRequest = new FormPublishRequest(null, null, null); + + assertThatThrownBy(() -> formCommandService.publishFormRequest(999L, publishRequest)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(SurveyErrorCode.FORM_REQUEST_NOT_FOUND)); + } + + @Test + @DisplayName("publishFormRequest - 미등록 상태면 FORM_REQUEST_NOT_YET_REGISTERED 예외") + void publishFormRequest_notRegistered_throwsException() { + FormRequest unregistered = FormRequest.createRequest("https://link", "test@test.com", 100, 1000, 1L); + given(formRequestRepository.findById(1L)).willReturn(Optional.of(unregistered)); + FormPublishRequest publishRequest = new FormPublishRequest(null, null, null); + + assertThatThrownBy(() -> formCommandService.publishFormRequest(1L, publishRequest)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(SurveyErrorCode.FORM_REQUEST_NOT_YET_REGISTERED)); + } + + @Test + @DisplayName("publishFormRequest - 회원 없으면 FORM_REQUEST_MEMBER_NOT_FOUND 예외") + void publishFormRequest_memberNotFound_throwsException() { + FormRequest registered = buildRegisteredRequest(); + given(formRequestRepository.findById(1L)).willReturn(Optional.of(registered)); + given(memberFinder.searchMembers("test@test.com", null, null, null)).willReturn(List.of()); + FormPublishRequest publishRequest = new FormPublishRequest(null, null, null); + + assertThatThrownBy(() -> formCommandService.publishFormRequest(1L, publishRequest)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(SurveyErrorCode.FORM_REQUEST_MEMBER_NOT_FOUND)); + } + + @Test + @DisplayName("publishFormRequest - 회원이 복수면 FORM_REQUEST_MEMBER_NOT_FOUND 예외") + void publishFormRequest_multipleMembers_throwsException() { + FormRequest registered = buildRegisteredRequest(); + given(formRequestRepository.findById(1L)).willReturn(Optional.of(registered)); + given(memberFinder.searchMembers("test@test.com", null, null, null)) + .willReturn(List.of(buildMemberResult(1L), buildMemberResult(2L))); + FormPublishRequest publishRequest = new FormPublishRequest(null, null, null); + + assertThatThrownBy(() -> formCommandService.publishFormRequest(1L, publishRequest)) + .isInstanceOf(CustomException.class) + .satisfies(ex -> assertThat(((CustomException) ex).getErrorCode()) + .isEqualTo(SurveyErrorCode.FORM_REQUEST_MEMBER_NOT_FOUND)); + } + + @Test + @DisplayName("publishFormRequest - 성공 시 surveyCommand.submitSurvey 호출") + void publishFormRequest_success_callsSubmitSurvey() { + FormRequest registered = buildRegisteredRequest(); + given(formRequestRepository.findById(1L)).willReturn(Optional.of(registered)); + given(memberFinder.searchMembers("test@test.com", null, null, null)) + .willReturn(List.of(buildMemberResult(99L))); + SurveyFormResponse mockResponse = SurveyFormResponse.builder().build(); + given(surveyCommand.submitSurvey(anyLong(), anyLong(), isNull())).willReturn(mockResponse); + + FormPublishRequest publishRequest = new FormPublishRequest(null, null, null); + + SurveyFormResponse result = formCommandService.publishFormRequest(1L, publishRequest); + + assertThat(result).isNotNull(); + verify(surveyCommand).submitSurvey(99L, 10L, null); + } +} diff --git a/src/test/java/OneQ/OnSurvey/domain/survey/service/formRequest/FormQueryServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/survey/service/formRequest/FormQueryServiceTest.java new file mode 100644 index 00000000..1a221411 --- /dev/null +++ b/src/test/java/OneQ/OnSurvey/domain/survey/service/formRequest/FormQueryServiceTest.java @@ -0,0 +1,80 @@ +package OneQ.OnSurvey.domain.survey.service.formRequest; + +import OneQ.OnSurvey.domain.survey.entity.FormRequest; +import OneQ.OnSurvey.domain.survey.model.formRequest.FormListResponse; +import OneQ.OnSurvey.domain.survey.model.formRequest.FormValidationEmailQuotaResponse; +import OneQ.OnSurvey.domain.survey.repository.formRequest.FormRequestRepository; +import OneQ.OnSurvey.global.infra.redis.RedisCacheAction; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class FormQueryServiceTest { + + @Mock private RedisCacheAction redisCacheAction; + @Mock private FormRequestRepository formRequestRepository; + + @InjectMocks + private FormQueryService formQueryService; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(formQueryService, "emailQuota", 20); + ReflectionTestUtils.setField(formQueryService, "emailHourUsageKey", "form:email:usage:"); + } + + @Test + @DisplayName("getEmailQuota - 사용량 없으면 전체 한도 반환") + void getEmailQuota_noUsage_returnsFullQuota() { + given(redisCacheAction.getIntValue(anyString())).willReturn(0); + + FormValidationEmailQuotaResponse result = formQueryService.getEmailQuota(1L); + + assertThat(result.quota()).isEqualTo(20); + } + + @Test + @DisplayName("getEmailQuota - 사용량이 있으면 차감된 한도 반환") + void getEmailQuota_withUsage_returnsReducedQuota() { + given(redisCacheAction.getIntValue(anyString())).willReturn(5); + + FormValidationEmailQuotaResponse result = formQueryService.getEmailQuota(1L); + + assertThat(result.quota()).isEqualTo(15); + } + + @Test + @DisplayName("getAllUnregisteredRequests - 미등록 신청 목록 반환") + void getAllUnregisteredRequests_returnsFormListResponse() { + FormRequest request = FormRequest.createRequest("https://link", "email@test.com", 100, 1000, 1L); + given(formRequestRepository.findAllUnregistered()).willReturn(List.of(request)); + + FormListResponse result = formQueryService.getAllUnregisteredRequests(); + + assertThat(result.totalCount()).isEqualTo(1); + assertThat(result.requests()).hasSize(1); + } + + @Test + @DisplayName("getAllUnregisteredRequests - 빈 목록이면 totalCount=0") + void getAllUnregisteredRequests_empty_returnsZeroCount() { + given(formRequestRepository.findAllUnregistered()).willReturn(List.of()); + + FormListResponse result = formQueryService.getAllUnregisteredRequests(); + + assertThat(result.totalCount()).isEqualTo(0); + assertThat(result.requests()).isEmpty(); + } +} From 5acd05f8eb1014ec98ac3f164ec9899c7b68d615 Mon Sep 17 00:00:00 2001 From: jaekwan Date: Sun, 10 May 2026 16:30:10 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20=EC=85=80=ED=94=84=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81=20-=20=EB=AF=B8=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20import=20=EC=A0=95=EB=A6=AC,=20verify=20=EB=B3=B4?= =?UTF-8?q?=EC=99=84,=20DisplayName=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/DiscountCodeQueryServiceTest.java | 12 ++++++------ .../question/service/QuestionCommandServiceTest.java | 3 ++- .../service/command/SurveyCommandServiceTest.java | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/test/java/OneQ/OnSurvey/domain/discount/service/DiscountCodeQueryServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/discount/service/DiscountCodeQueryServiceTest.java index 1cc5cbed..b13a9054 100644 --- a/src/test/java/OneQ/OnSurvey/domain/discount/service/DiscountCodeQueryServiceTest.java +++ b/src/test/java/OneQ/OnSurvey/domain/discount/service/DiscountCodeQueryServiceTest.java @@ -104,14 +104,14 @@ void getByCode_valid_returnsEntity() { } @Test - @DisplayName("findAll - 활성 코드가 만료 코드보다 먼저, 각 그룹 내 만료일 오름차순") + @DisplayName("findAll - 활성 코드 우선, 만료 코드 후순; 각 그룹 내 만료일 오름차순(가까운 날짜 먼저)") void findAll_sortedActiveFirst() { LocalDate today = LocalDate.now(); - DiscountCode expired1 = buildCode("EXP001", today.minusDays(5)); - DiscountCode expired2 = buildCode("EXP002", today.minusDays(1)); - DiscountCode active1 = buildCode("ACT001", today.plusDays(10)); - DiscountCode active2 = buildCode("ACT002", today.plusDays(3)); - given(discountCodeRepository.findAll()).willReturn(List.of(expired1, active1, expired2, active2)); + DiscountCode expiredEarlier = buildCode("EXP001", today.minusDays(5)); + DiscountCode expiredLater = buildCode("EXP002", today.minusDays(1)); + DiscountCode activeNear = buildCode("ACT002", today.plusDays(3)); + DiscountCode activeFar = buildCode("ACT001", today.plusDays(10)); + given(discountCodeRepository.findAll()).willReturn(List.of(expiredEarlier, activeFar, expiredLater, activeNear)); List results = discountCodeQueryService.findAll(); diff --git a/src/test/java/OneQ/OnSurvey/domain/question/service/QuestionCommandServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/question/service/QuestionCommandServiceTest.java index f5c97bd3..d64d07c6 100644 --- a/src/test/java/OneQ/OnSurvey/domain/question/service/QuestionCommandServiceTest.java +++ b/src/test/java/OneQ/OnSurvey/domain/question/service/QuestionCommandServiceTest.java @@ -114,7 +114,7 @@ void upsertSections_existingSection_updatesSection() { } @Test - @DisplayName("upsertSections - DB에만 있는 섹션은 삭제") + @DisplayName("upsertSections - DB에만 있는 섹션은 삭제, saveAll은 호출되지 않음") void upsertSections_extraDbSections_deletesOldSections() { Section extra = buildSection(50L, 1L, "삭제될 섹션", 1, 2); given(sectionRepository.findAllSectionBySurveyId(1L)).willReturn(List.of(extra)); @@ -122,5 +122,6 @@ void upsertSections_extraDbSections_deletesOldSections() { questionCommandService.upsertSections(1L, List.of()); verify(sectionRepository).deleteAll(List.of(50L)); + verify(sectionRepository, never()).saveAll(any()); } } diff --git a/src/test/java/OneQ/OnSurvey/domain/survey/service/command/SurveyCommandServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/survey/service/command/SurveyCommandServiceTest.java index 69ca233f..91076b0d 100644 --- a/src/test/java/OneQ/OnSurvey/domain/survey/service/command/SurveyCommandServiceTest.java +++ b/src/test/java/OneQ/OnSurvey/domain/survey/service/command/SurveyCommandServiceTest.java @@ -78,7 +78,7 @@ private Member buildMember(Long userKey) { Member member = Member.createMember( userKey, "홍길동", "010-1234-5678", "19900101", "test@test.com", - OneQ.OnSurvey.domain.survey.model.Gender.MALE, Role.ROLE_MEMBER, MemberStatus.ACTIVE + Gender.MALE, Role.ROLE_MEMBER, MemberStatus.ACTIVE ); member.increaseCoin(1000L); return member; From 53fc2389be291967472510852959eb3141ba6117 Mon Sep 17 00:00:00 2001 From: jaekwan Date: Tue, 12 May 2026 22:19:36 +0900 Subject: [PATCH 4/4] =?UTF-8?q?test:=20CodeRabbit=20=EB=B0=98=EC=98=81=20-?= =?UTF-8?q?=20=EA=B2=BD=EA=B3=84=20=EC=BC=80=EC=9D=B4=EC=8A=A4,=20never=20?= =?UTF-8?q?matcher=20=EA=B0=9C=EC=84=A0,=20ArgumentCaptor=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/DiscountCodeQueryServiceTest.java | 15 +++++++++ .../QuestionAnswerCommandServiceTest.java | 3 +- .../response/ResponseQueryServiceTest.java | 6 ++-- .../service/SurveyGlobalStatsServiceTest.java | 32 +++++++++++++++++-- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/test/java/OneQ/OnSurvey/domain/discount/service/DiscountCodeQueryServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/discount/service/DiscountCodeQueryServiceTest.java index b13a9054..27bcbeb3 100644 --- a/src/test/java/OneQ/OnSurvey/domain/discount/service/DiscountCodeQueryServiceTest.java +++ b/src/test/java/OneQ/OnSurvey/domain/discount/service/DiscountCodeQueryServiceTest.java @@ -122,6 +122,21 @@ void findAll_sortedActiveFirst() { assertThat(results.get(3).code()).isEqualTo("EXP002"); } + @Test + @DisplayName("findAll - 만료일이 오늘인 코드는 활성으로 분류(isBefore 기준)") + void findAll_todayExpiry_treatedAsActive() { + LocalDate today = LocalDate.now(); + DiscountCode todayCode = buildCode("TODAY1", today); + DiscountCode expiredCode = buildCode("EXP001", today.minusDays(1)); + given(discountCodeRepository.findAll()).willReturn(List.of(expiredCode, todayCode)); + + List results = discountCodeQueryService.findAll(); + + assertThat(results).hasSize(2); + assertThat(results.get(0).code()).isEqualTo("TODAY1"); + assertThat(results.get(1).code()).isEqualTo("EXP001"); + } + @Test @DisplayName("findAll - 빈 목록이면 빈 리스트 반환") void findAll_empty_returnsEmptyList() { diff --git a/src/test/java/OneQ/OnSurvey/domain/participation/service/answer/QuestionAnswerCommandServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/participation/service/answer/QuestionAnswerCommandServiceTest.java index 6c099dfe..d2eabe8c 100644 --- a/src/test/java/OneQ/OnSurvey/domain/participation/service/answer/QuestionAnswerCommandServiceTest.java +++ b/src/test/java/OneQ/OnSurvey/domain/participation/service/answer/QuestionAnswerCommandServiceTest.java @@ -17,6 +17,7 @@ import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -66,6 +67,6 @@ void updateResponseAfterQuestionAnswers_alreadyResponded_doesNotSave() { questionAnswerCommandService.updateResponseAfterQuestionAnswers(1L, 2L); - verify(responseRepository, never()).save(response); + verify(responseRepository, never()).save(any(Response.class)); } } diff --git a/src/test/java/OneQ/OnSurvey/domain/participation/service/response/ResponseQueryServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/participation/service/response/ResponseQueryServiceTest.java index ed735909..dfea8d51 100644 --- a/src/test/java/OneQ/OnSurvey/domain/participation/service/response/ResponseQueryServiceTest.java +++ b/src/test/java/OneQ/OnSurvey/domain/participation/service/response/ResponseQueryServiceTest.java @@ -15,9 +15,11 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class ResponseQueryServiceTest { @@ -48,7 +50,7 @@ void getResponseCount_nullFilter_delegatesToNoFilter() { assertThat(result).isEqualTo(30); verify(responseRepository).getResponseCountBySurveyId(1L); - verify(responseRepository, never()).getResponseCountBySurveyId(1L, null); + verify(responseRepository, never()).getResponseCountBySurveyId(anyLong(), any()); } @Test diff --git a/src/test/java/OneQ/OnSurvey/domain/survey/service/SurveyGlobalStatsServiceTest.java b/src/test/java/OneQ/OnSurvey/domain/survey/service/SurveyGlobalStatsServiceTest.java index 776fa728..ced3b4e3 100644 --- a/src/test/java/OneQ/OnSurvey/domain/survey/service/SurveyGlobalStatsServiceTest.java +++ b/src/test/java/OneQ/OnSurvey/domain/survey/service/SurveyGlobalStatsServiceTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -19,6 +20,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) @@ -61,13 +63,13 @@ void addDueCount_increasesExistingStats() { @Test @DisplayName("addDueCount - stats 없으면 init 후 증가") void addDueCount_noStats_initAndIncreases() { - SurveyGlobalStats newStats = SurveyGlobalStats.init(); given(statsRepository.findById(1L)).willReturn(Optional.empty()); - given(statsRepository.save(any(SurveyGlobalStats.class))).willReturn(newStats); + ArgumentCaptor captor = ArgumentCaptor.forClass(SurveyGlobalStats.class); + given(statsRepository.save(captor.capture())).willAnswer(inv -> inv.getArgument(0)); surveyGlobalStatsService.addDueCount(50L); - assertThat(newStats.getTotalDueCount()).isEqualTo(1050L); + assertThat(captor.getValue().getTotalDueCount()).isEqualTo(1050L); } @Test @@ -81,6 +83,18 @@ void addCompletedCount_increasesExistingStats() { assertThat(stats.getTotalCompletedCount()).isEqualTo(530L); } + @Test + @DisplayName("addCompletedCount - stats 없으면 init 후 증가") + void addCompletedCount_noStats_initAndIncreases() { + given(statsRepository.findById(1L)).willReturn(Optional.empty()); + ArgumentCaptor captor = ArgumentCaptor.forClass(SurveyGlobalStats.class); + given(statsRepository.save(captor.capture())).willAnswer(inv -> inv.getArgument(0)); + + surveyGlobalStatsService.addCompletedCount(30L); + + assertThat(captor.getValue().getTotalCompletedCount()).isEqualTo(1030L); + } + @Test @DisplayName("addPromotionCount - 기존 stats에 delta만큼 증가") void addPromotionCount_increasesExistingStats() { @@ -92,6 +106,18 @@ void addPromotionCount_increasesExistingStats() { assertThat(stats.getTotalPromotionCount()).isEqualTo(210L); } + @Test + @DisplayName("addPromotionCount - stats 없으면 init 후 증가") + void addPromotionCount_noStats_initAndIncreases() { + given(statsRepository.findById(1L)).willReturn(Optional.empty()); + ArgumentCaptor captor = ArgumentCaptor.forClass(SurveyGlobalStats.class); + given(statsRepository.save(captor.capture())).willAnswer(inv -> inv.getArgument(0)); + + surveyGlobalStatsService.addPromotionCount(10L); + + assertThat(captor.getValue().getTotalPromotionCount()).isEqualTo(1010L); + } + @Test @DisplayName("getStats - stats 존재 시 dailyUserCount와 함께 반환") void getStats_existingStats_returnsCorrectValues() {