diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 8d1f14d..deca9b7 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -15,7 +15,20 @@ "Bash(git add:*)", "Bash(git commit:*)", "Bash(git push)", - "Bash(git pull:*)" + "Bash(git pull:*)", + "Bash(./gradlew participation-service:compileJava:*)", + "Bash(find:*)", + "Bash(netstat:*)", + "Bash(findstr:*)", + "Bash(docker-compose up:*)", + "Bash(docker --version:*)", + "Bash(timeout 60 bash:*)", + "Bash(docker ps:*)", + "Bash(docker exec:*)", + "Bash(docker-compose down:*)", + "Bash(git rm:*)", + "Bash(git restore:*)", + "Bash(./gradlew participation-service:test:*)" ], "deny": [], "ask": [] diff --git a/participation-service/src/test/java/com/kt/event/participation/test/integration/DrawLogRepositoryIntegrationTest.java b/participation-service/src/test/java/com/kt/event/participation/test/integration/DrawLogRepositoryIntegrationTest.java new file mode 100644 index 0000000..32881dc --- /dev/null +++ b/participation-service/src/test/java/com/kt/event/participation/test/integration/DrawLogRepositoryIntegrationTest.java @@ -0,0 +1,167 @@ +package com.kt.event.participation.test.integration; + +import com.kt.event.participation.domain.draw.DrawLog; +import com.kt.event.participation.domain.draw.DrawLogRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * DrawLogRepository 통합 테스트 + * + * @author Digital Garage Team + * @since 2025-01-24 + */ +@DataJpaTest +@DisplayName("DrawLogRepository 통합 테스트") +class DrawLogRepositoryIntegrationTest { + + @Autowired + private DrawLogRepository drawLogRepository; + + // 테스트 데이터 상수 + private static final String VALID_EVENT_ID = "evt_20250124_001"; + private static final Integer TOTAL_PARTICIPANTS = 100; + private static final Integer WINNER_COUNT = 10; + private static final String ALGORITHM = "WEIGHTED_RANDOM"; + private static final String DRAWN_BY = "SYSTEM"; + + @BeforeEach + void setUp() { + drawLogRepository.deleteAll(); + } + + @Test + @DisplayName("추첨 로그를 저장하면 정상적으로 조회할 수 있다") + void givenDrawLog_whenSave_thenCanRetrieve() { + // Given + DrawLog drawLog = createDrawLog(VALID_EVENT_ID, true); + + // When + DrawLog saved = drawLogRepository.save(drawLog); + + // Then + assertThat(saved.getId()).isNotNull(); + assertThat(saved.getEventId()).isEqualTo(VALID_EVENT_ID); + assertThat(saved.getTotalParticipants()).isEqualTo(TOTAL_PARTICIPANTS); + assertThat(saved.getWinnerCount()).isEqualTo(WINNER_COUNT); + } + + @Test + @DisplayName("이벤트 ID로 추첨 로그를 조회할 수 있다") + void givenSavedDrawLog_whenFindByEventId_thenReturnDrawLog() { + // Given + DrawLog drawLog = createDrawLog(VALID_EVENT_ID, true); + drawLogRepository.save(drawLog); + + // When + Optional found = drawLogRepository.findByEventId(VALID_EVENT_ID); + + // Then + assertThat(found).isPresent(); + assertThat(found.get().getEventId()).isEqualTo(VALID_EVENT_ID); + assertThat(found.get().getApplyStoreVisitBonus()).isTrue(); + } + + @Test + @DisplayName("존재하지 않는 이벤트 ID로 조회하면 Empty가 반환된다") + void givenNoDrawLog_whenFindByEventId_thenReturnEmpty() { + // Given + String nonExistentEventId = "evt_99999999_999"; + + // When + Optional found = drawLogRepository.findByEventId(nonExistentEventId); + + // Then + assertThat(found).isEmpty(); + } + + @Test + @DisplayName("이벤트 ID로 추첨 여부를 확인할 수 있다") + void givenSavedDrawLog_whenExistsByEventId_thenReturnTrue() { + // Given + DrawLog drawLog = createDrawLog(VALID_EVENT_ID, false); + drawLogRepository.save(drawLog); + + // When + boolean exists = drawLogRepository.existsByEventId(VALID_EVENT_ID); + + // Then + assertThat(exists).isTrue(); + } + + @Test + @DisplayName("추첨이 없는 이벤트 ID로 확인하면 false가 반환된다") + void givenNoDrawLog_whenExistsByEventId_thenReturnFalse() { + // Given + String nonExistentEventId = "evt_99999999_999"; + + // When + boolean exists = drawLogRepository.existsByEventId(nonExistentEventId); + + // Then + assertThat(exists).isFalse(); + } + + @Test + @DisplayName("매장 방문 보너스 미적용 추첨 로그를 저장할 수 있다") + void givenDrawLogWithoutBonus_whenSave_thenCanRetrieve() { + // Given + DrawLog drawLog = createDrawLog(VALID_EVENT_ID, false); + + // When + DrawLog saved = drawLogRepository.save(drawLog); + + // Then + assertThat(saved.getApplyStoreVisitBonus()).isFalse(); + } + + @Test + @DisplayName("추첨 로그의 모든 필드가 정상적으로 저장된다") + void givenCompleteDrawLog_whenSave_thenAllFieldsPersisted() { + // Given + LocalDateTime now = LocalDateTime.now(); + DrawLog drawLog = DrawLog.builder() + .eventId(VALID_EVENT_ID) + .totalParticipants(TOTAL_PARTICIPANTS) + .winnerCount(WINNER_COUNT) + .applyStoreVisitBonus(true) + .algorithm(ALGORITHM) + .drawnAt(now) + .drawnBy(DRAWN_BY) + .build(); + + // When + DrawLog saved = drawLogRepository.save(drawLog); + + // Then + assertThat(saved.getId()).isNotNull(); + assertThat(saved.getEventId()).isEqualTo(VALID_EVENT_ID); + assertThat(saved.getTotalParticipants()).isEqualTo(TOTAL_PARTICIPANTS); + assertThat(saved.getWinnerCount()).isEqualTo(WINNER_COUNT); + assertThat(saved.getApplyStoreVisitBonus()).isTrue(); + assertThat(saved.getAlgorithm()).isEqualTo(ALGORITHM); + assertThat(saved.getDrawnAt()).isEqualToIgnoringNanos(now); + assertThat(saved.getDrawnBy()).isEqualTo(DRAWN_BY); + } + + // 헬퍼 메서드 + private DrawLog createDrawLog(String eventId, boolean applyBonus) { + return DrawLog.builder() + .eventId(eventId) + .totalParticipants(TOTAL_PARTICIPANTS) + .winnerCount(WINNER_COUNT) + .applyStoreVisitBonus(applyBonus) + .algorithm(ALGORITHM) + .drawnAt(LocalDateTime.now()) + .drawnBy(DRAWN_BY) + .build(); + } +} diff --git a/participation-service/src/test/java/com/kt/event/participation/test/integration/ParticipantRepositoryIntegrationTest.java b/participation-service/src/test/java/com/kt/event/participation/test/integration/ParticipantRepositoryIntegrationTest.java new file mode 100644 index 0000000..25c3ea6 --- /dev/null +++ b/participation-service/src/test/java/com/kt/event/participation/test/integration/ParticipantRepositoryIntegrationTest.java @@ -0,0 +1,324 @@ +package com.kt.event.participation.test.integration; + +import com.kt.event.participation.domain.participant.Participant; +import com.kt.event.participation.domain.participant.ParticipantRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * ParticipantRepository 통합 테스트 + * + * @author Digital Garage Team + * @since 2025-01-24 + */ +@DataJpaTest +@DisplayName("ParticipantRepository 통합 테스트") +class ParticipantRepositoryIntegrationTest { + + @Autowired + private ParticipantRepository participantRepository; + + // 테스트 데이터 상수 + private static final String VALID_EVENT_ID = "evt_20250124_001"; + private static final String VALID_NAME = "홍길동"; + private static final String VALID_PHONE = "010-1234-5678"; + private static final String VALID_EMAIL = "hong@test.com"; + + @BeforeEach + void setUp() { + participantRepository.deleteAll(); + } + + @Test + @DisplayName("참여자를 저장하면 정상적으로 조회할 수 있다") + void givenParticipant_whenSave_thenCanRetrieve() { + // Given + Participant participant = createValidParticipant(); + + // When + Participant saved = participantRepository.save(participant); + + // Then + assertThat(saved.getId()).isNotNull(); + assertThat(saved.getParticipantId()).isEqualTo(participant.getParticipantId()); + assertThat(saved.getName()).isEqualTo(VALID_NAME); + } + + @Test + @DisplayName("참여자 ID로 조회하면 해당 참여자가 반환된다") + void givenSavedParticipant_whenFindByParticipantId_thenReturnParticipant() { + // Given + Participant participant = createValidParticipant(); + participantRepository.save(participant); + + // When + Optional found = participantRepository.findByParticipantId(participant.getParticipantId()); + + // Then + assertThat(found).isPresent(); + assertThat(found.get().getName()).isEqualTo(VALID_NAME); + } + + @Test + @DisplayName("이벤트 ID와 전화번호로 중복 참여를 확인할 수 있다") + void givenSavedParticipant_whenExistsByEventIdAndPhoneNumber_thenReturnTrue() { + // Given + Participant participant = createValidParticipant(); + participantRepository.save(participant); + + // When + boolean exists = participantRepository.existsByEventIdAndPhoneNumber(VALID_EVENT_ID, VALID_PHONE); + + // Then + assertThat(exists).isTrue(); + } + + @Test + @DisplayName("이벤트 ID로 참여자 목록을 페이징 조회할 수 있다") + void givenMultipleParticipants_whenFindByEventId_thenReturnPagedList() { + // Given + for (int i = 1; i <= 5; i++) { + Participant participant = Participant.builder() + .participantId("prt_20250124_" + String.format("%03d", i)) + .eventId(VALID_EVENT_ID) + .name("참여자" + i) + .phoneNumber("010-1234-" + String.format("%04d", i)) + .email("test" + i + "@test.com") + .storeVisited(i % 2 == 0) + .bonusEntries(i % 2 == 0 ? 2 : 1) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(false) + .build(); + participantRepository.save(participant); + } + Pageable pageable = PageRequest.of(0, 3); + + // When + Page page = participantRepository.findByEventIdOrderByCreatedAtDesc(VALID_EVENT_ID, pageable); + + // Then + assertThat(page.getContent()).hasSize(3); + assertThat(page.getTotalElements()).isEqualTo(5); + assertThat(page.getTotalPages()).isEqualTo(2); + } + + @Test + @DisplayName("매장 방문 여부로 필터링하여 참여자 목록을 조회할 수 있다") + void givenParticipantsWithStoreVisit_whenFindByStoreVisited_thenReturnFiltered() { + // Given + for (int i = 1; i <= 5; i++) { + Participant participant = Participant.builder() + .participantId("prt_20250124_" + String.format("%03d", i)) + .eventId(VALID_EVENT_ID) + .name("참여자" + i) + .phoneNumber("010-1234-" + String.format("%04d", i)) + .email("test" + i + "@test.com") + .storeVisited(i % 2 == 0) + .bonusEntries(i % 2 == 0 ? 2 : 1) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(false) + .build(); + participantRepository.save(participant); + } + Pageable pageable = PageRequest.of(0, 10); + + // When + Page page = participantRepository + .findByEventIdAndStoreVisitedOrderByCreatedAtDesc(VALID_EVENT_ID, true, pageable); + + // Then + assertThat(page.getContent()).hasSize(2); + assertThat(page.getContent()).allMatch(Participant::getStoreVisited); + } + + @Test + @DisplayName("이벤트 ID로 전체 참여자 수를 조회할 수 있다") + void givenParticipants_whenCountByEventId_thenReturnCount() { + // Given + for (int i = 1; i <= 3; i++) { + Participant participant = Participant.builder() + .participantId("prt_20250124_" + String.format("%03d", i)) + .eventId(VALID_EVENT_ID) + .name("참여자" + i) + .phoneNumber("010-1234-" + String.format("%04d", i)) + .email("test" + i + "@test.com") + .storeVisited(true) + .bonusEntries(2) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(false) + .build(); + participantRepository.save(participant); + } + + // When + long count = participantRepository.countByEventId(VALID_EVENT_ID); + + // Then + assertThat(count).isEqualTo(3); + } + + @Test + @DisplayName("당첨자만 순위 순으로 조회할 수 있다") + void givenWinners_whenFindWinners_thenReturnSortedByRank() { + // Given + for (int i = 1; i <= 3; i++) { + Participant participant = Participant.builder() + .participantId("prt_20250124_" + String.format("%03d", i)) + .eventId(VALID_EVENT_ID) + .name("당첨자" + i) + .phoneNumber("010-1234-" + String.format("%04d", i)) + .email("winner" + i + "@test.com") + .storeVisited(true) + .bonusEntries(2) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(true) + .build(); + participant.markAsWinner(4 - i); // 역순으로 순위 부여 + participantRepository.save(participant); + } + Pageable pageable = PageRequest.of(0, 10); + + // When + Page page = participantRepository + .findByEventIdAndIsWinnerTrueOrderByWinnerRankAsc(VALID_EVENT_ID, pageable); + + // Then + assertThat(page.getContent()).hasSize(3); + assertThat(page.getContent().get(0).getWinnerRank()).isEqualTo(1); + assertThat(page.getContent().get(1).getWinnerRank()).isEqualTo(2); + assertThat(page.getContent().get(2).getWinnerRank()).isEqualTo(3); + } + + @Test + @DisplayName("이벤트 ID로 당첨자 수를 조회할 수 있다") + void givenWinners_whenCountWinners_thenReturnCount() { + // Given + for (int i = 1; i <= 5; i++) { + Participant participant = Participant.builder() + .participantId("prt_20250124_" + String.format("%03d", i)) + .eventId(VALID_EVENT_ID) + .name("참여자" + i) + .phoneNumber("010-1234-" + String.format("%04d", i)) + .email("test" + i + "@test.com") + .storeVisited(true) + .bonusEntries(2) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(i <= 2) + .build(); + if (i <= 2) { + participant.markAsWinner(i); + } + participantRepository.save(participant); + } + + // When + long count = participantRepository.countByEventIdAndIsWinnerTrue(VALID_EVENT_ID); + + // Then + assertThat(count).isEqualTo(2); + } + + @Test + @DisplayName("이벤트 ID로 최대 ID를 조회할 수 있다") + void givenParticipants_whenFindMaxId_thenReturnMaxId() { + // Given + for (int i = 1; i <= 3; i++) { + Participant participant = Participant.builder() + .participantId("prt_20250124_" + String.format("%03d", i)) + .eventId(VALID_EVENT_ID) + .name("참여자" + i) + .phoneNumber("010-1234-" + String.format("%04d", i)) + .email("test" + i + "@test.com") + .storeVisited(true) + .bonusEntries(2) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(false) + .build(); + participantRepository.save(participant); + } + + // When + Optional maxId = participantRepository.findMaxIdByEventId(VALID_EVENT_ID); + + // Then + assertThat(maxId).isPresent(); + assertThat(maxId.get()).isGreaterThan(0); + } + + @Test + @DisplayName("비당첨자 목록만 조회할 수 있다") + void givenMixedParticipants_whenFindNonWinners_thenReturnOnlyNonWinners() { + // Given + for (int i = 1; i <= 5; i++) { + Participant participant = Participant.builder() + .participantId("prt_20250124_" + String.format("%03d", i)) + .eventId(VALID_EVENT_ID) + .name("참여자" + i) + .phoneNumber("010-1234-" + String.format("%04d", i)) + .email("test" + i + "@test.com") + .storeVisited(true) + .bonusEntries(2) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(i <= 2) + .build(); + participantRepository.save(participant); + } + + // When + List nonWinners = participantRepository.findByEventIdAndIsWinnerFalse(VALID_EVENT_ID); + + // Then + assertThat(nonWinners).hasSize(3); + assertThat(nonWinners).allMatch(p -> !p.getIsWinner()); + } + + @Test + @DisplayName("이벤트 ID와 참여자 ID로 조회할 수 있다") + void givenParticipant_whenFindByEventIdAndParticipantId_thenReturnParticipant() { + // Given + Participant participant = createValidParticipant(); + participantRepository.save(participant); + + // When + Optional found = participantRepository + .findByEventIdAndParticipantId(VALID_EVENT_ID, participant.getParticipantId()); + + // Then + assertThat(found).isPresent(); + assertThat(found.get().getName()).isEqualTo(VALID_NAME); + } + + // 헬퍼 메서드 + private Participant createValidParticipant() { + return Participant.builder() + .participantId("prt_20250124_001") + .eventId(VALID_EVENT_ID) + .name(VALID_NAME) + .phoneNumber(VALID_PHONE) + .email(VALID_EMAIL) + .storeVisited(true) + .bonusEntries(2) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(false) + .build(); + } +} diff --git a/participation-service/src/test/java/com/kt/event/participation/test/unit/DrawLogUnitTest.java b/participation-service/src/test/java/com/kt/event/participation/test/unit/DrawLogUnitTest.java new file mode 100644 index 0000000..18e72ee --- /dev/null +++ b/participation-service/src/test/java/com/kt/event/participation/test/unit/DrawLogUnitTest.java @@ -0,0 +1,97 @@ +package com.kt.event.participation.test.unit; + +import com.kt.event.participation.domain.draw.DrawLog; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * DrawLog Entity 단위 테스트 + * + * @author Digital Garage Team + * @since 2025-01-24 + */ +@DisplayName("DrawLog 엔티티 단위 테스트") +class DrawLogUnitTest { + + // 테스트 데이터 상수 + private static final String VALID_EVENT_ID = "evt_20250124_001"; + private static final Integer TOTAL_PARTICIPANTS = 100; + private static final Integer WINNER_COUNT = 10; + private static final String ALGORITHM = "WEIGHTED_RANDOM"; + private static final String DRAWN_BY = "admin"; + + @Test + @DisplayName("빌더로 추첨 로그를 생성하면 필드가 정상 설정된다") + void givenValidData_whenBuild_thenDrawLogCreated() { + // Given + LocalDateTime drawnAt = LocalDateTime.now(); + + // When + DrawLog drawLog = DrawLog.builder() + .eventId(VALID_EVENT_ID) + .totalParticipants(TOTAL_PARTICIPANTS) + .winnerCount(WINNER_COUNT) + .applyStoreVisitBonus(true) + .algorithm(ALGORITHM) + .drawnAt(drawnAt) + .drawnBy(DRAWN_BY) + .build(); + + // Then + assertThat(drawLog.getEventId()).isEqualTo(VALID_EVENT_ID); + assertThat(drawLog.getTotalParticipants()).isEqualTo(TOTAL_PARTICIPANTS); + assertThat(drawLog.getWinnerCount()).isEqualTo(WINNER_COUNT); + assertThat(drawLog.getApplyStoreVisitBonus()).isTrue(); + assertThat(drawLog.getAlgorithm()).isEqualTo(ALGORITHM); + assertThat(drawLog.getDrawnAt()).isEqualTo(drawnAt); + assertThat(drawLog.getDrawnBy()).isEqualTo(DRAWN_BY); + } + + @Test + @DisplayName("매장 방문 보너스 미적용으로 추첨 로그를 생성할 수 있다") + void givenNoBonus_whenBuild_thenDrawLogCreated() { + // Given + LocalDateTime drawnAt = LocalDateTime.now(); + + // When + DrawLog drawLog = DrawLog.builder() + .eventId(VALID_EVENT_ID) + .totalParticipants(TOTAL_PARTICIPANTS) + .winnerCount(WINNER_COUNT) + .applyStoreVisitBonus(false) + .algorithm(ALGORITHM) + .drawnAt(drawnAt) + .drawnBy(DRAWN_BY) + .build(); + + // Then + assertThat(drawLog.getApplyStoreVisitBonus()).isFalse(); + } + + @Test + @DisplayName("당첨자가 없는 경우도 추첨 로그를 생성할 수 있다") + void givenNoWinners_whenBuild_thenDrawLogCreated() { + // Given + LocalDateTime drawnAt = LocalDateTime.now(); + Integer zeroWinners = 0; + + // When + DrawLog drawLog = DrawLog.builder() + .eventId(VALID_EVENT_ID) + .totalParticipants(TOTAL_PARTICIPANTS) + .winnerCount(zeroWinners) + .applyStoreVisitBonus(true) + .algorithm(ALGORITHM) + .drawnAt(drawnAt) + .drawnBy(DRAWN_BY) + .build(); + + // Then + assertThat(drawLog.getWinnerCount()).isZero(); + assertThat(drawLog.getTotalParticipants()).isEqualTo(TOTAL_PARTICIPANTS); + } +} diff --git a/participation-service/src/test/java/com/kt/event/participation/test/unit/ParticipantUnitTest.java b/participation-service/src/test/java/com/kt/event/participation/test/unit/ParticipantUnitTest.java new file mode 100644 index 0000000..cc0f352 --- /dev/null +++ b/participation-service/src/test/java/com/kt/event/participation/test/unit/ParticipantUnitTest.java @@ -0,0 +1,222 @@ +package com.kt.event.participation.test.unit; + +import com.kt.event.participation.domain.participant.Participant; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +/** + * Participant Entity 단위 테스트 + * + * @author Digital Garage Team + * @since 2025-01-24 + */ +@DisplayName("Participant 엔티티 단위 테스트") +class ParticipantUnitTest { + + // 테스트 데이터 상수 + private static final String VALID_EVENT_ID = "evt_20250124_001"; + private static final String VALID_NAME = "홍길동"; + private static final String VALID_PHONE = "010-1234-5678"; + private static final String VALID_EMAIL = "hong@test.com"; + private static final Long VALID_SEQUENCE = 1L; + + @Test + @DisplayName("매장 방문 시 participantId가 정상적으로 생성된다") + void givenStoreVisited_whenGenerateParticipantId_thenSuccess() { + // Given + String eventId = VALID_EVENT_ID; + Long sequenceNumber = VALID_SEQUENCE; + + // When + String participantId = Participant.generateParticipantId(eventId, sequenceNumber); + + // Then + assertThat(participantId).isEqualTo("prt_20250124_001"); + assertThat(participantId).startsWith("prt_"); + assertThat(participantId).hasSize(16); + } + + @Test + @DisplayName("시퀀스 번호가 증가하면 participantId도 증가한다") + void givenLargeSequence_whenGenerateParticipantId_thenIdIncreases() { + // Given + String eventId = VALID_EVENT_ID; + Long sequenceNumber = 999L; + + // When + String participantId = Participant.generateParticipantId(eventId, sequenceNumber); + + // Then + assertThat(participantId).isEqualTo("prt_20250124_999"); + } + + @Test + @DisplayName("매장 방문 시 보너스 응모권이 2개가 된다") + void givenStoreVisited_whenCalculateBonusEntries_thenTwo() { + // Given + Boolean storeVisited = true; + + // When + Integer bonusEntries = Participant.calculateBonusEntries(storeVisited); + + // Then + assertThat(bonusEntries).isEqualTo(2); + } + + @Test + @DisplayName("매장 미방문 시 보너스 응모권이 1개가 된다") + void givenNotVisited_whenCalculateBonusEntries_thenOne() { + // Given + Boolean storeVisited = false; + + // When + Integer bonusEntries = Participant.calculateBonusEntries(storeVisited); + + // Then + assertThat(bonusEntries).isEqualTo(1); + } + + @Test + @DisplayName("당첨자로 표시하면 isWinner가 true가 되고 당첨 정보가 설정된다") + void givenParticipant_whenMarkAsWinner_thenWinnerFieldsSet() { + // Given + Participant participant = createValidParticipant(); + Integer winnerRank = 1; + + // When + participant.markAsWinner(winnerRank); + + // Then + assertThat(participant.getIsWinner()).isTrue(); + assertThat(participant.getWinnerRank()).isEqualTo(1); + assertThat(participant.getWonAt()).isNotNull(); + } + + @Test + @DisplayName("빌더로 참여자를 생성하면 필드가 정상 설정된다") + void givenValidData_whenBuild_thenParticipantCreated() { + // Given & When + Participant participant = Participant.builder() + .participantId("prt_20250124_001") + .eventId(VALID_EVENT_ID) + .name(VALID_NAME) + .phoneNumber(VALID_PHONE) + .email(VALID_EMAIL) + .storeVisited(true) + .bonusEntries(2) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(false) + .build(); + + // Then + assertThat(participant.getParticipantId()).isEqualTo("prt_20250124_001"); + assertThat(participant.getEventId()).isEqualTo(VALID_EVENT_ID); + assertThat(participant.getName()).isEqualTo(VALID_NAME); + assertThat(participant.getPhoneNumber()).isEqualTo(VALID_PHONE); + assertThat(participant.getEmail()).isEqualTo(VALID_EMAIL); + assertThat(participant.getStoreVisited()).isTrue(); + assertThat(participant.getBonusEntries()).isEqualTo(2); + assertThat(participant.getAgreeMarketing()).isTrue(); + assertThat(participant.getAgreePrivacy()).isTrue(); + assertThat(participant.getIsWinner()).isFalse(); + } + + @Test + @DisplayName("prePersist에서 개인정보 동의가 null이면 예외가 발생한다") + void givenNullPrivacyAgree_whenPrePersist_thenThrowException() { + // Given + Participant participant = Participant.builder() + .participantId("prt_20250124_001") + .eventId(VALID_EVENT_ID) + .name(VALID_NAME) + .phoneNumber(VALID_PHONE) + .storeVisited(true) + .agreePrivacy(null) + .build(); + + // When & Then + assertThatThrownBy(participant::prePersist) + .isInstanceOf(IllegalStateException.class) + .hasMessage("개인정보 수집 및 이용 동의는 필수입니다"); + } + + @Test + @DisplayName("prePersist에서 개인정보 동의가 false이면 예외가 발생한다") + void givenFalsePrivacyAgree_whenPrePersist_thenThrowException() { + // Given + Participant participant = Participant.builder() + .participantId("prt_20250124_001") + .eventId(VALID_EVENT_ID) + .name(VALID_NAME) + .phoneNumber(VALID_PHONE) + .storeVisited(true) + .agreePrivacy(false) + .build(); + + // When & Then + assertThatThrownBy(participant::prePersist) + .isInstanceOf(IllegalStateException.class) + .hasMessage("개인정보 수집 및 이용 동의는 필수입니다"); + } + + @Test + @DisplayName("prePersist에서 bonusEntries가 null이면 자동 계산된다") + void givenNullBonusEntries_whenPrePersist_thenCalculated() { + // Given + Participant participant = Participant.builder() + .participantId("prt_20250124_001") + .eventId(VALID_EVENT_ID) + .name(VALID_NAME) + .phoneNumber(VALID_PHONE) + .storeVisited(true) + .agreePrivacy(true) + .bonusEntries(null) + .build(); + + // When + participant.prePersist(); + + // Then + assertThat(participant.getBonusEntries()).isEqualTo(2); + } + + @Test + @DisplayName("prePersist에서 isWinner가 null이면 false로 설정된다") + void givenNullIsWinner_whenPrePersist_thenSetFalse() { + // Given + Participant participant = Participant.builder() + .participantId("prt_20250124_001") + .eventId(VALID_EVENT_ID) + .name(VALID_NAME) + .phoneNumber(VALID_PHONE) + .storeVisited(true) + .agreePrivacy(true) + .isWinner(null) + .build(); + + // When + participant.prePersist(); + + // Then + assertThat(participant.getIsWinner()).isFalse(); + } + + // 헬퍼 메서드 + private Participant createValidParticipant() { + return Participant.builder() + .participantId("prt_20250124_001") + .eventId(VALID_EVENT_ID) + .name(VALID_NAME) + .phoneNumber(VALID_PHONE) + .email(VALID_EMAIL) + .storeVisited(true) + .bonusEntries(2) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(false) + .build(); + } +} diff --git a/participation-service/src/test/java/com/kt/event/participation/test/unit/ParticipationServiceUnitTest.java b/participation-service/src/test/java/com/kt/event/participation/test/unit/ParticipationServiceUnitTest.java new file mode 100644 index 0000000..9fb7f77 --- /dev/null +++ b/participation-service/src/test/java/com/kt/event/participation/test/unit/ParticipationServiceUnitTest.java @@ -0,0 +1,269 @@ +package com.kt.event.participation.test.unit; + +import com.kt.event.common.dto.PageResponse; +import com.kt.event.participation.application.dto.ParticipationRequest; +import com.kt.event.participation.application.dto.ParticipationResponse; +import com.kt.event.participation.application.service.ParticipationService; +import com.kt.event.participation.domain.participant.Participant; +import com.kt.event.participation.domain.participant.ParticipantRepository; +import com.kt.event.participation.exception.ParticipationException.*; +import com.kt.event.participation.infrastructure.kafka.KafkaProducerService; +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.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +/** + * ParticipationService 단위 테스트 + * + * @author Digital Garage Team + * @since 2025-01-24 + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("ParticipationService 단위 테스트") +class ParticipationServiceUnitTest { + + @Mock + private ParticipantRepository participantRepository; + + @Mock + private KafkaProducerService kafkaProducerService; + + @InjectMocks + private ParticipationService participationService; + + // 테스트 데이터 상수 + private static final String VALID_EVENT_ID = "evt_20250124_001"; + private static final String VALID_PARTICIPANT_ID = "prt_20250124_001"; + private static final String VALID_NAME = "홍길동"; + private static final String VALID_PHONE = "010-1234-5678"; + private static final String VALID_EMAIL = "hong@test.com"; + + @Test + @DisplayName("정상적인 참여 요청이면 참여자가 저장되고 Kafka 이벤트가 발행된다") + void givenValidRequest_whenParticipate_thenSaveAndPublishEvent() { + // Given + ParticipationRequest request = createValidRequest(); + Participant savedParticipant = createValidParticipant(); + + given(participantRepository.existsByEventIdAndPhoneNumber(VALID_EVENT_ID, VALID_PHONE)) + .willReturn(false); + given(participantRepository.findMaxIdByEventId(VALID_EVENT_ID)) + .willReturn(Optional.of(0L)); + given(participantRepository.save(any(Participant.class))) + .willReturn(savedParticipant); + willDoNothing().given(kafkaProducerService) + .publishParticipantRegistered(any()); + + // When + ParticipationResponse response = participationService.participate(VALID_EVENT_ID, request); + + // Then + assertThat(response).isNotNull(); + assertThat(response.getParticipantId()).isEqualTo(VALID_PARTICIPANT_ID); + assertThat(response.getName()).isEqualTo(VALID_NAME); + assertThat(response.getPhoneNumber()).isEqualTo(VALID_PHONE); + + then(participantRepository).should(times(1)).save(any(Participant.class)); + then(kafkaProducerService).should(times(1)).publishParticipantRegistered(any()); + } + + @Test + @DisplayName("중복 참여 시 DuplicateParticipationException이 발생한다") + void givenDuplicatePhone_whenParticipate_thenThrowException() { + // Given + ParticipationRequest request = createValidRequest(); + + given(participantRepository.existsByEventIdAndPhoneNumber(VALID_EVENT_ID, VALID_PHONE)) + .willReturn(true); + + // When & Then + assertThatThrownBy(() -> participationService.participate(VALID_EVENT_ID, request)) + .isInstanceOf(DuplicateParticipationException.class) + .hasMessageContaining("이미 참여하신 이벤트입니다"); + + then(participantRepository).should(never()).save(any()); + then(kafkaProducerService).should(never()).publishParticipantRegistered(any()); + } + + @Test + @DisplayName("매장 방문 참여자는 보너스 응모권이 2개가 된다") + void givenStoreVisited_whenParticipate_thenBonusEntriesIsTwo() { + // Given + ParticipationRequest request = ParticipationRequest.builder() + .name(VALID_NAME) + .phoneNumber(VALID_PHONE) + .email(VALID_EMAIL) + .storeVisited(true) + .agreeMarketing(true) + .agreePrivacy(true) + .build(); + + Participant savedParticipant = Participant.builder() + .participantId(VALID_PARTICIPANT_ID) + .eventId(VALID_EVENT_ID) + .name(VALID_NAME) + .phoneNumber(VALID_PHONE) + .email(VALID_EMAIL) + .storeVisited(true) + .bonusEntries(2) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(false) + .build(); + + given(participantRepository.existsByEventIdAndPhoneNumber(VALID_EVENT_ID, VALID_PHONE)) + .willReturn(false); + given(participantRepository.findMaxIdByEventId(VALID_EVENT_ID)) + .willReturn(Optional.of(0L)); + given(participantRepository.save(any(Participant.class))) + .willReturn(savedParticipant); + + // When + ParticipationResponse response = participationService.participate(VALID_EVENT_ID, request); + + // Then + assertThat(response.getBonusEntries()).isEqualTo(2); + assertThat(response.getStoreVisited()).isTrue(); + } + + @Test + @DisplayName("참여자 목록 조회 시 페이징이 적용된다") + void givenPageable_whenGetParticipants_thenReturnPagedList() { + // Given + Pageable pageable = PageRequest.of(0, 10); + List participants = List.of( + createValidParticipant(), + createAnotherParticipant() + ); + Page participantPage = new PageImpl<>(participants, pageable, 2); + + given(participantRepository.findByEventIdOrderByCreatedAtDesc(VALID_EVENT_ID, pageable)) + .willReturn(participantPage); + + // When + PageResponse response = participationService + .getParticipants(VALID_EVENT_ID, null, pageable); + + // Then + assertThat(response.getContent()).hasSize(2); + assertThat(response.getTotalElements()).isEqualTo(2); + assertThat(response.getTotalPages()).isEqualTo(1); + assertThat(response.isFirst()).isTrue(); + assertThat(response.isLast()).isTrue(); + } + + @Test + @DisplayName("매장 방문 필터 적용 시 필터링된 참여자 목록이 조회된다") + void givenStoreVisitedFilter_whenGetParticipants_thenReturnFilteredList() { + // Given + Boolean storeVisited = true; + Pageable pageable = PageRequest.of(0, 10); + List participants = List.of(createValidParticipant()); + Page participantPage = new PageImpl<>(participants, pageable, 1); + + given(participantRepository.findByEventIdAndStoreVisitedOrderByCreatedAtDesc( + VALID_EVENT_ID, storeVisited, pageable)) + .willReturn(participantPage); + + // When + PageResponse response = participationService + .getParticipants(VALID_EVENT_ID, storeVisited, pageable); + + // Then + assertThat(response.getContent()).hasSize(1); + assertThat(response.getContent().get(0).getStoreVisited()).isTrue(); + + then(participantRepository).should(times(1)) + .findByEventIdAndStoreVisitedOrderByCreatedAtDesc(VALID_EVENT_ID, storeVisited, pageable); + } + + @Test + @DisplayName("참여자 상세 조회 시 정상적으로 반환된다") + void givenValidParticipantId_whenGetParticipant_thenReturnParticipant() { + // Given + Participant participant = createValidParticipant(); + + given(participantRepository.findByEventIdAndParticipantId(VALID_EVENT_ID, VALID_PARTICIPANT_ID)) + .willReturn(Optional.of(participant)); + + // When + ParticipationResponse response = participationService + .getParticipant(VALID_EVENT_ID, VALID_PARTICIPANT_ID); + + // Then + assertThat(response).isNotNull(); + assertThat(response.getParticipantId()).isEqualTo(VALID_PARTICIPANT_ID); + assertThat(response.getName()).isEqualTo(VALID_NAME); + } + + @Test + @DisplayName("존재하지 않는 참여자 조회 시 ParticipantNotFoundException이 발생한다") + void givenInvalidParticipantId_whenGetParticipant_thenThrowException() { + // Given + String invalidParticipantId = "prt_20250124_999"; + + given(participantRepository.findByEventIdAndParticipantId(VALID_EVENT_ID, invalidParticipantId)) + .willReturn(Optional.empty()); + + // When & Then + assertThatThrownBy(() -> participationService.getParticipant(VALID_EVENT_ID, invalidParticipantId)) + .isInstanceOf(ParticipantNotFoundException.class) + .hasMessageContaining("참여자를 찾을 수 없습니다"); + } + + // 헬퍼 메서드 + private ParticipationRequest createValidRequest() { + return ParticipationRequest.builder() + .name(VALID_NAME) + .phoneNumber(VALID_PHONE) + .email(VALID_EMAIL) + .storeVisited(true) + .agreeMarketing(true) + .agreePrivacy(true) + .build(); + } + + private Participant createValidParticipant() { + return Participant.builder() + .participantId(VALID_PARTICIPANT_ID) + .eventId(VALID_EVENT_ID) + .name(VALID_NAME) + .phoneNumber(VALID_PHONE) + .email(VALID_EMAIL) + .storeVisited(true) + .bonusEntries(2) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(false) + .build(); + } + + private Participant createAnotherParticipant() { + return Participant.builder() + .participantId("prt_20250124_002") + .eventId(VALID_EVENT_ID) + .name("김철수") + .phoneNumber("010-9876-5432") + .email("kim@test.com") + .storeVisited(false) + .bonusEntries(1) + .agreeMarketing(false) + .agreePrivacy(true) + .isWinner(false) + .build(); + } +} diff --git a/participation-service/src/test/java/com/kt/event/participation/test/unit/WinnerDrawServiceUnitTest.java b/participation-service/src/test/java/com/kt/event/participation/test/unit/WinnerDrawServiceUnitTest.java new file mode 100644 index 0000000..eca7e3d --- /dev/null +++ b/participation-service/src/test/java/com/kt/event/participation/test/unit/WinnerDrawServiceUnitTest.java @@ -0,0 +1,245 @@ +package com.kt.event.participation.test.unit; + +import com.kt.event.common.dto.PageResponse; +import com.kt.event.participation.application.dto.DrawWinnersRequest; +import com.kt.event.participation.application.dto.DrawWinnersResponse; +import com.kt.event.participation.application.dto.ParticipationResponse; +import com.kt.event.participation.application.service.WinnerDrawService; +import com.kt.event.participation.domain.draw.DrawLog; +import com.kt.event.participation.domain.draw.DrawLogRepository; +import com.kt.event.participation.domain.participant.Participant; +import com.kt.event.participation.domain.participant.ParticipantRepository; +import com.kt.event.participation.exception.ParticipationException.*; +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.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +/** + * WinnerDrawService 단위 테스트 + * + * @author Digital Garage Team + * @since 2025-01-24 + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("WinnerDrawService 단위 테스트") +class WinnerDrawServiceUnitTest { + + @Mock + private ParticipantRepository participantRepository; + + @Mock + private DrawLogRepository drawLogRepository; + + @InjectMocks + private WinnerDrawService winnerDrawService; + + // 테스트 데이터 상수 + private static final String VALID_EVENT_ID = "evt_20250124_001"; + private static final Integer WINNER_COUNT = 2; + + @Test + @DisplayName("정상적인 추첨 요청이면 당첨자가 선정되고 로그가 저장된다") + void givenValidRequest_whenDrawWinners_thenWinnersSelectedAndLogSaved() { + // Given + DrawWinnersRequest request = createDrawRequest(WINNER_COUNT, false); + List participants = createParticipantList(5); + + given(drawLogRepository.existsByEventId(VALID_EVENT_ID)).willReturn(false); + given(participantRepository.findByEventIdAndIsWinnerFalse(VALID_EVENT_ID)) + .willReturn(participants); + given(participantRepository.saveAll(anyList())).willAnswer(invocation -> invocation.getArgument(0)); + given(drawLogRepository.save(any(DrawLog.class))).willAnswer(invocation -> invocation.getArgument(0)); + + // When + DrawWinnersResponse response = winnerDrawService.drawWinners(VALID_EVENT_ID, request); + + // Then + assertThat(response).isNotNull(); + assertThat(response.getEventId()).isEqualTo(VALID_EVENT_ID); + assertThat(response.getTotalParticipants()).isEqualTo(5); + assertThat(response.getWinnerCount()).isEqualTo(WINNER_COUNT); + assertThat(response.getWinners()).hasSize(WINNER_COUNT); + assertThat(response.getDrawnAt()).isNotNull(); + + then(participantRepository).should(times(1)).saveAll(anyList()); + then(drawLogRepository).should(times(1)).save(any(DrawLog.class)); + } + + @Test + @DisplayName("이미 추첨이 완료된 이벤트면 AlreadyDrawnException이 발생한다") + void givenAlreadyDrawn_whenDrawWinners_thenThrowException() { + // Given + DrawWinnersRequest request = createDrawRequest(WINNER_COUNT, false); + + given(drawLogRepository.existsByEventId(VALID_EVENT_ID)).willReturn(true); + + // When & Then + assertThatThrownBy(() -> winnerDrawService.drawWinners(VALID_EVENT_ID, request)) + .isInstanceOf(AlreadyDrawnException.class); + + then(participantRepository).should(never()).findByEventIdAndIsWinnerFalse(anyString()); + } + + @Test + @DisplayName("참여자 수가 당첨자 수보다 적으면 InsufficientParticipantsException이 발생한다") + void givenInsufficientParticipants_whenDrawWinners_thenThrowException() { + // Given + DrawWinnersRequest request = createDrawRequest(10, false); + List participants = createParticipantList(5); + + given(drawLogRepository.existsByEventId(VALID_EVENT_ID)).willReturn(false); + given(participantRepository.findByEventIdAndIsWinnerFalse(VALID_EVENT_ID)) + .willReturn(participants); + + // When & Then + assertThatThrownBy(() -> winnerDrawService.drawWinners(VALID_EVENT_ID, request)) + .isInstanceOf(InsufficientParticipantsException.class); + + then(participantRepository).should(never()).saveAll(anyList()); + then(drawLogRepository).should(never()).save(any(DrawLog.class)); + } + + @Test + @DisplayName("매장 방문 보너스 적용 시 가중치가 반영된 추첨이 이루어진다") + void givenApplyBonus_whenDrawWinners_thenWeightedDraw() { + // Given + DrawWinnersRequest request = createDrawRequest(WINNER_COUNT, true); + List participants = createParticipantList(5); + + given(drawLogRepository.existsByEventId(VALID_EVENT_ID)).willReturn(false); + given(participantRepository.findByEventIdAndIsWinnerFalse(VALID_EVENT_ID)) + .willReturn(participants); + given(participantRepository.saveAll(anyList())).willAnswer(invocation -> invocation.getArgument(0)); + given(drawLogRepository.save(any(DrawLog.class))).willAnswer(invocation -> invocation.getArgument(0)); + + // When + DrawWinnersResponse response = winnerDrawService.drawWinners(VALID_EVENT_ID, request); + + // Then + assertThat(response.getWinnerCount()).isEqualTo(WINNER_COUNT); + then(drawLogRepository).should(times(1)).save(argThat(log -> + log.getApplyStoreVisitBonus().equals(true) + )); + } + + @Test + @DisplayName("당첨자 목록 조회 시 순위 순으로 정렬되어 반환된다") + void givenWinnersExist_whenGetWinners_thenReturnSortedByRank() { + // Given + Pageable pageable = PageRequest.of(0, 10); + List winners = createWinnerList(3); + Page winnerPage = new PageImpl<>(winners, pageable, 3); + + given(drawLogRepository.existsByEventId(VALID_EVENT_ID)).willReturn(true); + given(participantRepository.findByEventIdAndIsWinnerTrueOrderByWinnerRankAsc(VALID_EVENT_ID, pageable)) + .willReturn(winnerPage); + + // When + PageResponse response = winnerDrawService.getWinners(VALID_EVENT_ID, pageable); + + // Then + assertThat(response.getContent()).hasSize(3); + assertThat(response.getTotalElements()).isEqualTo(3); + } + + @Test + @DisplayName("추첨이 완료되지 않은 이벤트의 당첨자 조회 시 NoWinnersYetException이 발생한다") + void givenNoDrawYet_whenGetWinners_thenThrowException() { + // Given + Pageable pageable = PageRequest.of(0, 10); + + given(drawLogRepository.existsByEventId(VALID_EVENT_ID)).willReturn(false); + + // When & Then + assertThatThrownBy(() -> winnerDrawService.getWinners(VALID_EVENT_ID, pageable)) + .isInstanceOf(NoWinnersYetException.class); + + then(participantRepository).should(never()) + .findByEventIdAndIsWinnerTrueOrderByWinnerRankAsc(anyString(), any(Pageable.class)); + } + + @Test + @DisplayName("당첨자 추첨 시 모든 참여자에게 순위가 할당된다") + void givenParticipants_whenDrawWinners_thenAllWinnersHaveRank() { + // Given + DrawWinnersRequest request = createDrawRequest(3, false); + List participants = createParticipantList(5); + + given(drawLogRepository.existsByEventId(VALID_EVENT_ID)).willReturn(false); + given(participantRepository.findByEventIdAndIsWinnerFalse(VALID_EVENT_ID)) + .willReturn(participants); + given(participantRepository.saveAll(anyList())).willAnswer(invocation -> invocation.getArgument(0)); + given(drawLogRepository.save(any(DrawLog.class))).willAnswer(invocation -> invocation.getArgument(0)); + + // When + DrawWinnersResponse response = winnerDrawService.drawWinners(VALID_EVENT_ID, request); + + // Then + assertThat(response.getWinners()).allSatisfy(winner -> { + assertThat(winner.getRank()).isNotNull(); + assertThat(winner.getRank()).isBetween(1, 3); + }); + } + + // 헬퍼 메서드 + private DrawWinnersRequest createDrawRequest(Integer winnerCount, Boolean applyBonus) { + return DrawWinnersRequest.builder() + .winnerCount(winnerCount) + .applyStoreVisitBonus(applyBonus) + .build(); + } + + private List createParticipantList(int count) { + List participants = new ArrayList<>(); + for (int i = 1; i <= count; i++) { + participants.add(Participant.builder() + .participantId("prt_20250124_" + String.format("%03d", i)) + .eventId(VALID_EVENT_ID) + .name("참여자" + i) + .phoneNumber("010-" + String.format("%04d", 1000 + i) + "-" + String.format("%04d", i)) + .email("participant" + i + "@test.com") + .storeVisited(i % 2 == 0) + .bonusEntries(i % 2 == 0 ? 2 : 1) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(false) + .build()); + } + return participants; + } + + private List createWinnerList(int count) { + List winners = new ArrayList<>(); + for (int i = 1; i <= count; i++) { + Participant winner = Participant.builder() + .participantId("prt_20250124_" + String.format("%03d", i)) + .eventId(VALID_EVENT_ID) + .name("당첨자" + i) + .phoneNumber("010-" + String.format("%04d", 1000 + i) + "-" + String.format("%04d", i)) + .email("winner" + i + "@test.com") + .storeVisited(true) + .bonusEntries(2) + .agreeMarketing(true) + .agreePrivacy(true) + .isWinner(true) + .build(); + winner.markAsWinner(i); + winners.add(winner); + } + return winners; + } +} diff --git a/participation-service/src/test/resources/application.yml b/participation-service/src/test/resources/application.yml new file mode 100644 index 0000000..3bf6599 --- /dev/null +++ b/participation-service/src/test/resources/application.yml @@ -0,0 +1,35 @@ +spring: + # JPA 설정 + jpa: + hibernate: + ddl-auto: create-drop + show-sql: true + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.H2Dialect + + # H2 인메모리 데이터베이스 설정 + datasource: + url: jdbc:h2:mem:testdb + driver-class-name: org.h2.Driver + username: sa + password: + + # Kafka 자동설정 비활성화 (통합 테스트에서는 불필요) + autoconfigure: + exclude: + - org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration + + # H2 콘솔 활성화 (디버깅용) + h2: + console: + enabled: true + path: /h2-console + +# 로깅 레벨 +logging: + level: + org.hibernate.SQL: DEBUG + org.hibernate.type.descriptor.sql.BasicBinder: TRACE + com.kt.event.participation: DEBUG