mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2026-06-13 13:39:12 +00:00
WinnerController Swagger 문서화 추가 및 이벤트/참여자 예외 처리 개선
- WinnerController에 Swagger 어노테이션 추가 (Operation, Parameter, ParameterObject) - 당첨자 목록 조회 API 기본 정렬 설정 (winnerRank ASC, size=20) - ParticipationService에서 이벤트/참여자 구분 로직 개선 - 이벤트 없음: EventNotFoundException 발생 - 참여자 없음: ParticipantNotFoundException 발생 - EventCacheService 제거 (Redis 기반 검증에서 DB 기반 검증으로 변경) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+165
@@ -0,0 +1,165 @@
|
||||
package com.kt.event.participation.test.integration;
|
||||
|
||||
import com.kt.event.participation.application.dto.ParticipationRequest;
|
||||
import com.kt.event.participation.application.service.ParticipationService;
|
||||
import com.kt.event.participation.domain.participant.ParticipantRepository;
|
||||
import com.kt.event.participation.infrastructure.kafka.event.ParticipantRegisteredEvent;
|
||||
import org.apache.kafka.clients.consumer.Consumer;
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
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.context.SpringBootTest;
|
||||
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
|
||||
import org.springframework.kafka.support.serializer.JsonDeserializer;
|
||||
import org.springframework.kafka.test.utils.KafkaTestUtils;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Kafka 이벤트 발행 통합 테스트
|
||||
*
|
||||
* @author Digital Garage Team
|
||||
* @since 2025-01-24
|
||||
*/
|
||||
@SpringBootTest
|
||||
@DisplayName("Kafka 이벤트 발행 통합 테스트")
|
||||
class KafkaEventPublishIntegrationTest {
|
||||
|
||||
private static final String TOPIC = "participant-registered-events";
|
||||
private static final String TEST_EVENT_ID = "EVT-TEST-001";
|
||||
|
||||
@Autowired
|
||||
private ParticipationService participationService;
|
||||
|
||||
@Autowired
|
||||
private ParticipantRepository participantRepository;
|
||||
|
||||
private Consumer<String, ParticipantRegisteredEvent> consumer;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// Kafka Consumer 설정
|
||||
Map<String, Object> consumerProps = KafkaTestUtils.consumerProps(
|
||||
"20.249.182.13:9095", "test-group", "false");
|
||||
consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
|
||||
consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
|
||||
consumerProps.put(JsonDeserializer.TRUSTED_PACKAGES, "*");
|
||||
consumerProps.put(JsonDeserializer.VALUE_DEFAULT_TYPE, ParticipantRegisteredEvent.class);
|
||||
consumerProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
|
||||
|
||||
DefaultKafkaConsumerFactory<String, ParticipantRegisteredEvent> consumerFactory =
|
||||
new DefaultKafkaConsumerFactory<>(consumerProps);
|
||||
consumer = consumerFactory.createConsumer();
|
||||
consumer.subscribe(Collections.singletonList(TOPIC));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (consumer != null) {
|
||||
consumer.close();
|
||||
}
|
||||
// 테스트 데이터 정리
|
||||
participantRepository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("이벤트 참여 시 Kafka 이벤트가 발행되어야 한다")
|
||||
void shouldPublishKafkaEventWhenParticipate() throws Exception {
|
||||
// Given: 참여 요청 데이터
|
||||
ParticipationRequest request = ParticipationRequest.builder()
|
||||
.name("테스트사용자")
|
||||
.phoneNumber("01012345678")
|
||||
.email("test@example.com")
|
||||
.storeVisited(true)
|
||||
.agreeMarketing(true)
|
||||
.agreePrivacy(true)
|
||||
.build();
|
||||
|
||||
// When: 이벤트 참여
|
||||
participationService.participate(TEST_EVENT_ID, request);
|
||||
|
||||
// Then: Kafka 메시지 수신 확인
|
||||
ConsumerRecord<String, ParticipantRegisteredEvent> record =
|
||||
KafkaTestUtils.getSingleRecord(consumer, TOPIC, Duration.ofSeconds(10));
|
||||
|
||||
assertThat(record).isNotNull();
|
||||
assertThat(record.key()).isEqualTo(TEST_EVENT_ID);
|
||||
|
||||
ParticipantRegisteredEvent event = record.value();
|
||||
assertThat(event).isNotNull();
|
||||
assertThat(event.getEventId()).isEqualTo(TEST_EVENT_ID);
|
||||
assertThat(event.getName()).isEqualTo("테스트사용자");
|
||||
assertThat(event.getPhoneNumber()).isEqualTo("01012345678");
|
||||
assertThat(event.getStoreVisited()).isTrue();
|
||||
assertThat(event.getBonusEntries()).isEqualTo(5);
|
||||
assertThat(event.getParticipatedAt()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("매장 미방문 참여자의 이벤트가 발행되어야 한다")
|
||||
void shouldPublishEventForNonStoreVisitor() throws Exception {
|
||||
// Given: 매장 미방문 참여 요청
|
||||
ParticipationRequest request = ParticipationRequest.builder()
|
||||
.name("온라인사용자")
|
||||
.phoneNumber("01098765432")
|
||||
.email("online@example.com")
|
||||
.storeVisited(false)
|
||||
.agreeMarketing(false)
|
||||
.agreePrivacy(true)
|
||||
.build();
|
||||
|
||||
// When: 이벤트 참여
|
||||
participationService.participate(TEST_EVENT_ID, request);
|
||||
|
||||
// Then: Kafka 메시지 수신 확인
|
||||
ConsumerRecord<String, ParticipantRegisteredEvent> record =
|
||||
KafkaTestUtils.getSingleRecord(consumer, TOPIC, Duration.ofSeconds(10));
|
||||
|
||||
assertThat(record).isNotNull();
|
||||
|
||||
ParticipantRegisteredEvent event = record.value();
|
||||
assertThat(event.getStoreVisited()).isFalse();
|
||||
assertThat(event.getBonusEntries()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("여러 참여자의 이벤트가 순차적으로 발행되어야 한다")
|
||||
void shouldPublishMultipleEventsSequentially() throws Exception {
|
||||
// Given: 3명의 참여자
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
ParticipationRequest request = ParticipationRequest.builder()
|
||||
.name("참여자" + i)
|
||||
.phoneNumber("0101234567" + i)
|
||||
.email("user" + i + "@example.com")
|
||||
.storeVisited(i % 2 == 0)
|
||||
.agreeMarketing(true)
|
||||
.agreePrivacy(true)
|
||||
.build();
|
||||
|
||||
// When: 이벤트 참여
|
||||
participationService.participate(TEST_EVENT_ID, request);
|
||||
}
|
||||
|
||||
// Then: 3개의 Kafka 메시지 수신 확인
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
ConsumerRecord<String, ParticipantRegisteredEvent> record =
|
||||
KafkaTestUtils.getSingleRecord(consumer, TOPIC, Duration.ofSeconds(10));
|
||||
|
||||
assertThat(record).isNotNull();
|
||||
|
||||
ParticipantRegisteredEvent event = record.value();
|
||||
assertThat(event.getName()).startsWith("참여자");
|
||||
assertThat(event.getEventId()).isEqualTo(TEST_EVENT_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
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.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.test.context.TestPropertySource;
|
||||
|
||||
/**
|
||||
* Spring Data JPA 메서드의 실제 쿼리 확인용 테스트
|
||||
*
|
||||
* @author Digital Garage Team
|
||||
* @since 2025-01-24
|
||||
*/
|
||||
@DataJpaTest
|
||||
@TestPropertySource(properties = {
|
||||
"spring.jpa.show-sql=true",
|
||||
"spring.jpa.properties.hibernate.format_sql=true",
|
||||
"logging.level.org.hibernate.SQL=DEBUG",
|
||||
"logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE"
|
||||
})
|
||||
@DisplayName("JPA 쿼리 검증 테스트")
|
||||
class QueryVerificationTest {
|
||||
|
||||
@Autowired
|
||||
private ParticipantRepository participantRepository;
|
||||
|
||||
@Test
|
||||
@DisplayName("countByEventIdAndIsWinnerTrue 메서드의 실제 쿼리 확인")
|
||||
void verifyCountByEventIdAndIsWinnerTrueQuery() {
|
||||
// Given
|
||||
String eventId = "evt_test_001";
|
||||
|
||||
// 테스트 데이터 생성
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
Participant participant = Participant.builder()
|
||||
.participantId("prt_test_" + i)
|
||||
.eventId(eventId)
|
||||
.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 - 이 쿼리가 실행되면서 콘솔에 SQL이 출력됨
|
||||
System.out.println("\n========== countByEventIdAndIsWinnerTrue 실행 ==========");
|
||||
long count = participantRepository.countByEventIdAndIsWinnerTrue(eventId);
|
||||
System.out.println("========== 결과: " + count + " ==========\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("findByEventIdAndPhoneNumber 메서드의 실제 쿼리 확인")
|
||||
void verifyExistsByEventIdAndPhoneNumberQuery() {
|
||||
// Given
|
||||
String eventId = "evt_test_002";
|
||||
String phoneNumber = "010-1234-5678";
|
||||
|
||||
Participant participant = Participant.builder()
|
||||
.participantId("prt_test_001")
|
||||
.eventId(eventId)
|
||||
.name("홍길동")
|
||||
.phoneNumber(phoneNumber)
|
||||
.email("hong@test.com")
|
||||
.storeVisited(true)
|
||||
.bonusEntries(2)
|
||||
.agreeMarketing(true)
|
||||
.agreePrivacy(true)
|
||||
.isWinner(false)
|
||||
.build();
|
||||
participantRepository.save(participant);
|
||||
|
||||
// When
|
||||
System.out.println("\n========== existsByEventIdAndPhoneNumber 실행 ==========");
|
||||
boolean exists = participantRepository.existsByEventIdAndPhoneNumber(eventId, phoneNumber);
|
||||
System.out.println("========== 결과: " + exists + " ==========\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("findByEventIdOrderByCreatedAtDesc 메서드의 실제 쿼리 확인")
|
||||
void verifyFindByEventIdOrderByCreatedAtDescQuery() {
|
||||
// Given
|
||||
String eventId = "evt_test_003";
|
||||
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
Participant participant = Participant.builder()
|
||||
.participantId("prt_test_" + i)
|
||||
.eventId(eventId)
|
||||
.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
|
||||
System.out.println("\n========== findByEventIdOrderByCreatedAtDesc 실행 ==========");
|
||||
participantRepository.findByEventIdOrderByCreatedAtDesc(eventId,
|
||||
org.springframework.data.domain.PageRequest.of(0, 10));
|
||||
System.out.println("========== 쿼리 실행 완료 ==========\n");
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,14 @@ spring:
|
||||
username: sa
|
||||
password:
|
||||
|
||||
# Kafka 자동설정 비활성화 (통합 테스트에서는 불필요)
|
||||
autoconfigure:
|
||||
exclude:
|
||||
- org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
|
||||
# Kafka 설정 (통합 테스트용)
|
||||
kafka:
|
||||
bootstrap-servers: 20.249.182.13:9095
|
||||
producer:
|
||||
key-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
|
||||
acks: all
|
||||
retries: 3
|
||||
|
||||
# H2 콘솔 활성화 (디버깅용)
|
||||
h2:
|
||||
@@ -27,9 +31,16 @@ spring:
|
||||
enabled: true
|
||||
path: /h2-console
|
||||
|
||||
# JWT 설정 (테스트용)
|
||||
jwt:
|
||||
secret: test-secret-key-for-testing-only-minimum-256-bits
|
||||
expiration: 86400000
|
||||
|
||||
# 로깅 레벨
|
||||
logging:
|
||||
level:
|
||||
org.hibernate.SQL: DEBUG
|
||||
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
||||
com.kt.event.participation: DEBUG
|
||||
org.springframework.kafka: DEBUG
|
||||
org.apache.kafka: DEBUG
|
||||
|
||||
Reference in New Issue
Block a user