AI 서비스 Kafka/Redis 통합 테스트 및 설정 개선
- Gradle 빌드 캐시 파일 제외 (.gitignore 업데이트) - Kafka 통합 테스트 구현 (AIJobConsumerIntegrationTest) - 단위 테스트 추가 (Controller, Service 레이어) - IntelliJ 실행 프로파일 자동 생성 도구 추가 - Kafka 테스트 배치 스크립트 추가 - Redis 캐시 설정 개선 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+127
@@ -0,0 +1,127 @@
|
||||
package com.kt.ai.test.integration.kafka;
|
||||
|
||||
import com.kt.ai.kafka.message.AIJobMessage;
|
||||
import com.kt.ai.service.CacheService;
|
||||
import com.kt.ai.service.JobStatusService;
|
||||
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.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
/**
|
||||
* AIJobConsumer Kafka 통합 테스트
|
||||
*
|
||||
* 실제 Kafka 브로커가 실행 중이어야 합니다.
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
@DisplayName("AIJobConsumer Kafka 통합 테스트")
|
||||
class AIJobConsumerIntegrationTest {
|
||||
|
||||
@Value("${spring.kafka.bootstrap-servers}")
|
||||
private String bootstrapServers;
|
||||
|
||||
@Value("${kafka.topics.ai-job}")
|
||||
private String aiJobTopic;
|
||||
|
||||
@Autowired
|
||||
private JobStatusService jobStatusService;
|
||||
|
||||
@Autowired
|
||||
private CacheService cacheService;
|
||||
|
||||
private KafkaTestProducer testProducer;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testProducer = new KafkaTestProducer(bootstrapServers, aiJobTopic);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (testProducer != null) {
|
||||
testProducer.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Given valid AI job message, When send to Kafka, Then consumer processes and saves to Redis")
|
||||
void givenValidAIJobMessage_whenSendToKafka_thenConsumerProcessesAndSavesToRedis() {
|
||||
// Given
|
||||
String jobId = "test-job-" + System.currentTimeMillis();
|
||||
String eventId = "test-event-" + System.currentTimeMillis();
|
||||
AIJobMessage message = KafkaTestProducer.createSampleMessage(jobId, eventId);
|
||||
|
||||
// When
|
||||
testProducer.sendAIJobMessage(message);
|
||||
|
||||
// Then - Kafka Consumer가 메시지를 처리하고 Redis에 저장할 때까지 대기
|
||||
await()
|
||||
.atMost(30, TimeUnit.SECONDS)
|
||||
.pollInterval(1, TimeUnit.SECONDS)
|
||||
.untilAsserted(() -> {
|
||||
// Job 상태가 Redis에 저장되었는지 확인
|
||||
Object jobStatus = cacheService.getJobStatus(jobId);
|
||||
assertThat(jobStatus).isNotNull();
|
||||
System.out.println("Job 상태 확인: " + jobStatus);
|
||||
});
|
||||
|
||||
// 최종 상태 확인 (COMPLETED 또는 FAILED)
|
||||
await()
|
||||
.atMost(60, TimeUnit.SECONDS)
|
||||
.pollInterval(2, TimeUnit.SECONDS)
|
||||
.untilAsserted(() -> {
|
||||
Object jobStatus = cacheService.getJobStatus(jobId);
|
||||
assertThat(jobStatus).isNotNull();
|
||||
|
||||
// AI 추천 결과도 저장되었는지 확인 (COMPLETED 상태인 경우)
|
||||
Object recommendation = cacheService.getRecommendation(eventId);
|
||||
System.out.println("AI 추천 결과: " + (recommendation != null ? "있음" : "없음"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Given multiple messages, When send to Kafka, Then all messages are processed")
|
||||
void givenMultipleMessages_whenSendToKafka_thenAllMessagesAreProcessed() {
|
||||
// Given
|
||||
int messageCount = 3;
|
||||
String[] jobIds = new String[messageCount];
|
||||
String[] eventIds = new String[messageCount];
|
||||
|
||||
// When - 여러 메시지 전송
|
||||
for (int i = 0; i < messageCount; i++) {
|
||||
jobIds[i] = "batch-job-" + i + "-" + System.currentTimeMillis();
|
||||
eventIds[i] = "batch-event-" + i + "-" + System.currentTimeMillis();
|
||||
AIJobMessage message = KafkaTestProducer.createSampleMessage(jobIds[i], eventIds[i]);
|
||||
testProducer.sendAIJobMessage(message);
|
||||
}
|
||||
|
||||
// Then - 모든 메시지가 처리되었는지 확인
|
||||
await()
|
||||
.atMost(90, TimeUnit.SECONDS)
|
||||
.pollInterval(2, TimeUnit.SECONDS)
|
||||
.untilAsserted(() -> {
|
||||
int processedCount = 0;
|
||||
for (int i = 0; i < messageCount; i++) {
|
||||
Object jobStatus = cacheService.getJobStatus(jobIds[i]);
|
||||
if (jobStatus != null) {
|
||||
processedCount++;
|
||||
}
|
||||
}
|
||||
assertThat(processedCount).isEqualTo(messageCount);
|
||||
System.out.println("처리된 메시지 수: " + processedCount + "/" + messageCount);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.kt.ai.test.integration.kafka;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.kt.ai.kafka.message.AIJobMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.kafka.clients.producer.KafkaProducer;
|
||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||
import org.apache.kafka.clients.producer.RecordMetadata;
|
||||
import org.apache.kafka.common.serialization.StringSerializer;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* Kafka 테스트용 Producer 유틸리티
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
public class KafkaTestProducer {
|
||||
|
||||
private final KafkaProducer<String, String> producer;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final String topic;
|
||||
|
||||
public KafkaTestProducer(String bootstrapServers, String topic) {
|
||||
this.topic = topic;
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.objectMapper.registerModule(new JavaTimeModule());
|
||||
|
||||
Properties props = new Properties();
|
||||
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
|
||||
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
|
||||
props.put(ProducerConfig.ACKS_CONFIG, "all");
|
||||
props.put(ProducerConfig.RETRIES_CONFIG, 3);
|
||||
|
||||
this.producer = new KafkaProducer<>(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI Job 메시지 전송
|
||||
*/
|
||||
public RecordMetadata sendAIJobMessage(AIJobMessage message) {
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(message);
|
||||
ProducerRecord<String, String> record = new ProducerRecord<>(topic, message.getJobId(), json);
|
||||
|
||||
Future<RecordMetadata> future = producer.send(record);
|
||||
RecordMetadata metadata = future.get();
|
||||
|
||||
log.info("Kafka 메시지 전송 성공: topic={}, partition={}, offset={}, jobId={}",
|
||||
metadata.topic(), metadata.partition(), metadata.offset(), message.getJobId());
|
||||
|
||||
return metadata;
|
||||
} catch (Exception e) {
|
||||
log.error("Kafka 메시지 전송 실패: jobId={}", message.getJobId(), e);
|
||||
throw new RuntimeException("Kafka 메시지 전송 실패", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 테스트용 샘플 메시지 생성
|
||||
*/
|
||||
public static AIJobMessage createSampleMessage(String jobId, String eventId) {
|
||||
return AIJobMessage.builder()
|
||||
.jobId(jobId)
|
||||
.eventId(eventId)
|
||||
.objective("신규 고객 유치")
|
||||
.industry("음식점")
|
||||
.region("강남구")
|
||||
.storeName("테스트 BBQ 레스토랑")
|
||||
.targetAudience("20-30대 직장인")
|
||||
.budget(500000)
|
||||
.requestedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Producer 종료
|
||||
*/
|
||||
public void close() {
|
||||
if (producer != null) {
|
||||
producer.close();
|
||||
log.info("Kafka Producer 종료");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.kt.ai.test.manual;
|
||||
|
||||
import com.kt.ai.kafka.message.AIJobMessage;
|
||||
import com.kt.ai.test.integration.kafka.KafkaTestProducer;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Kafka 수동 테스트
|
||||
*
|
||||
* 이 클래스는 main 메서드를 실행하여 Kafka에 메시지를 직접 전송할 수 있습니다.
|
||||
* IDE에서 직접 실행하거나 Gradle로 실행할 수 있습니다.
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class KafkaManualTest {
|
||||
|
||||
// Kafka 설정 (환경에 맞게 수정)
|
||||
private static final String BOOTSTRAP_SERVERS = "20.249.182.13:9095,4.217.131.59:9095";
|
||||
private static final String TOPIC = "ai-event-generation-job";
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Kafka 수동 테스트 시작 ===");
|
||||
System.out.println("Bootstrap Servers: " + BOOTSTRAP_SERVERS);
|
||||
System.out.println("Topic: " + TOPIC);
|
||||
|
||||
KafkaTestProducer producer = new KafkaTestProducer(BOOTSTRAP_SERVERS, TOPIC);
|
||||
|
||||
try {
|
||||
// 테스트 메시지 1: 기본 메시지
|
||||
AIJobMessage message1 = createTestMessage(
|
||||
"manual-job-001",
|
||||
"manual-event-001",
|
||||
"신규 고객 유치",
|
||||
"음식점",
|
||||
"강남구",
|
||||
"테스트 BBQ 레스토랑",
|
||||
500000
|
||||
);
|
||||
|
||||
System.out.println("\n[메시지 1] 전송 중...");
|
||||
producer.sendAIJobMessage(message1);
|
||||
System.out.println("[메시지 1] 전송 완료");
|
||||
|
||||
// 테스트 메시지 2: 다른 업종
|
||||
AIJobMessage message2 = createTestMessage(
|
||||
"manual-job-002",
|
||||
"manual-event-002",
|
||||
"재방문 유도",
|
||||
"카페",
|
||||
"서초구",
|
||||
"테스트 카페",
|
||||
300000
|
||||
);
|
||||
|
||||
System.out.println("\n[메시지 2] 전송 중...");
|
||||
producer.sendAIJobMessage(message2);
|
||||
System.out.println("[메시지 2] 전송 완료");
|
||||
|
||||
// 테스트 메시지 3: 저예산
|
||||
AIJobMessage message3 = createTestMessage(
|
||||
"manual-job-003",
|
||||
"manual-event-003",
|
||||
"매출 증대",
|
||||
"소매점",
|
||||
"마포구",
|
||||
"테스트 편의점",
|
||||
100000
|
||||
);
|
||||
|
||||
System.out.println("\n[메시지 3] 전송 중...");
|
||||
producer.sendAIJobMessage(message3);
|
||||
System.out.println("[메시지 3] 전송 완료");
|
||||
|
||||
System.out.println("\n=== 모든 메시지 전송 완료 ===");
|
||||
System.out.println("\n다음 API로 결과를 확인하세요:");
|
||||
System.out.println("- Job 상태: GET http://localhost:8083/api/v1/ai-service/internal/jobs/{jobId}/status");
|
||||
System.out.println("- AI 추천: GET http://localhost:8083/api/v1/ai-service/internal/recommendations/{eventId}");
|
||||
System.out.println("\n예시:");
|
||||
System.out.println(" curl http://localhost:8083/api/v1/ai-service/internal/jobs/manual-job-001/status");
|
||||
System.out.println(" curl http://localhost:8083/api/v1/ai-service/internal/recommendations/manual-event-001");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("에러 발생: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
producer.close();
|
||||
System.out.println("\n=== Kafka Producer 종료 ===");
|
||||
}
|
||||
}
|
||||
|
||||
private static AIJobMessage createTestMessage(
|
||||
String jobId,
|
||||
String eventId,
|
||||
String objective,
|
||||
String industry,
|
||||
String region,
|
||||
String storeName,
|
||||
int budget
|
||||
) {
|
||||
return AIJobMessage.builder()
|
||||
.jobId(jobId)
|
||||
.eventId(eventId)
|
||||
.objective(objective)
|
||||
.industry(industry)
|
||||
.region(region)
|
||||
.storeName(storeName)
|
||||
.targetAudience("20-40대 고객")
|
||||
.budget(budget)
|
||||
.requestedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
package com.kt.ai.test.unit.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kt.ai.controller.InternalJobController;
|
||||
import com.kt.ai.exception.JobNotFoundException;
|
||||
import com.kt.ai.model.dto.response.JobStatusResponse;
|
||||
import com.kt.ai.model.enums.JobStatus;
|
||||
import com.kt.ai.service.CacheService;
|
||||
import com.kt.ai.service.JobStatusService;
|
||||
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.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
/**
|
||||
* InternalJobController 단위 테스트
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@WebMvcTest(controllers = InternalJobController.class,
|
||||
excludeAutoConfiguration = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
|
||||
@DisplayName("InternalJobController 단위 테스트")
|
||||
class InternalJobControllerUnitTest {
|
||||
|
||||
// Constants
|
||||
private static final String VALID_JOB_ID = "job-123";
|
||||
private static final String INVALID_JOB_ID = "job-999";
|
||||
private static final String BASE_URL = "/api/v1/ai-service/internal/jobs";
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@MockBean
|
||||
private JobStatusService jobStatusService;
|
||||
|
||||
@MockBean
|
||||
private CacheService cacheService;
|
||||
|
||||
private JobStatusResponse sampleJobStatusResponse;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
sampleJobStatusResponse = JobStatusResponse.builder()
|
||||
.jobId(VALID_JOB_ID)
|
||||
.status(JobStatus.PROCESSING)
|
||||
.progress(50)
|
||||
.message("AI 추천 생성 중 (50%)")
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
// ========== GET /{jobId}/status 테스트 ==========
|
||||
|
||||
@Test
|
||||
@DisplayName("Given existing job, When get status, Then return 200 with job status")
|
||||
void givenExistingJob_whenGetStatus_thenReturn200WithJobStatus() throws Exception {
|
||||
// Given
|
||||
when(jobStatusService.getJobStatus(VALID_JOB_ID)).thenReturn(sampleJobStatusResponse);
|
||||
|
||||
// When & Then
|
||||
mockMvc.perform(get(BASE_URL + "/{jobId}/status", VALID_JOB_ID)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.jobId", is(VALID_JOB_ID)))
|
||||
.andExpect(jsonPath("$.status", is("PROCESSING")))
|
||||
.andExpect(jsonPath("$.progress", is(50)))
|
||||
.andExpect(jsonPath("$.message", is("AI 추천 생성 중 (50%)")))
|
||||
.andExpect(jsonPath("$.createdAt", notNullValue()));
|
||||
|
||||
verify(jobStatusService, times(1)).getJobStatus(VALID_JOB_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Given non-existing job, When get status, Then return 404")
|
||||
void givenNonExistingJob_whenGetStatus_thenReturn404() throws Exception {
|
||||
// Given
|
||||
when(jobStatusService.getJobStatus(INVALID_JOB_ID))
|
||||
.thenThrow(new JobNotFoundException(INVALID_JOB_ID));
|
||||
|
||||
// When & Then
|
||||
mockMvc.perform(get(BASE_URL + "/{jobId}/status", INVALID_JOB_ID)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isNotFound())
|
||||
.andExpect(jsonPath("$.code", is("JOB_NOT_FOUND")))
|
||||
.andExpect(jsonPath("$.message", containsString(INVALID_JOB_ID)));
|
||||
|
||||
verify(jobStatusService, times(1)).getJobStatus(INVALID_JOB_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Given completed job, When get status, Then return COMPLETED status with 100% progress")
|
||||
void givenCompletedJob_whenGetStatus_thenReturnCompletedStatus() throws Exception {
|
||||
// Given
|
||||
JobStatusResponse completedResponse = JobStatusResponse.builder()
|
||||
.jobId(VALID_JOB_ID)
|
||||
.status(JobStatus.COMPLETED)
|
||||
.progress(100)
|
||||
.message("AI 추천 완료")
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
when(jobStatusService.getJobStatus(VALID_JOB_ID)).thenReturn(completedResponse);
|
||||
|
||||
// When & Then
|
||||
mockMvc.perform(get(BASE_URL + "/{jobId}/status", VALID_JOB_ID)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.status", is("COMPLETED")))
|
||||
.andExpect(jsonPath("$.progress", is(100)));
|
||||
|
||||
verify(jobStatusService, times(1)).getJobStatus(VALID_JOB_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Given failed job, When get status, Then return FAILED status")
|
||||
void givenFailedJob_whenGetStatus_thenReturnFailedStatus() throws Exception {
|
||||
// Given
|
||||
JobStatusResponse failedResponse = JobStatusResponse.builder()
|
||||
.jobId(VALID_JOB_ID)
|
||||
.status(JobStatus.FAILED)
|
||||
.progress(0)
|
||||
.message("AI API 호출 실패")
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
when(jobStatusService.getJobStatus(VALID_JOB_ID)).thenReturn(failedResponse);
|
||||
|
||||
// When & Then
|
||||
mockMvc.perform(get(BASE_URL + "/{jobId}/status", VALID_JOB_ID)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.status", is("FAILED")))
|
||||
.andExpect(jsonPath("$.progress", is(0)))
|
||||
.andExpect(jsonPath("$.message", containsString("실패")));
|
||||
|
||||
verify(jobStatusService, times(1)).getJobStatus(VALID_JOB_ID);
|
||||
}
|
||||
|
||||
// ========== 디버그 엔드포인트 테스트 (선택사항) ==========
|
||||
|
||||
@Test
|
||||
@DisplayName("Given valid jobId, When create test job, Then return 200 with test data")
|
||||
void givenValidJobId_whenCreateTestJob_thenReturn200WithTestData() throws Exception {
|
||||
// Given
|
||||
doNothing().when(jobStatusService).updateJobStatus(anyString(), org.mockito.ArgumentMatchers.any(JobStatus.class), anyString());
|
||||
when(cacheService.getJobStatus(VALID_JOB_ID)).thenReturn(sampleJobStatusResponse);
|
||||
|
||||
// When & Then
|
||||
mockMvc.perform(get(BASE_URL + "/debug/create-test-job/{jobId}", VALID_JOB_ID)
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.success", is(true)))
|
||||
.andExpect(jsonPath("$.jobId", is(VALID_JOB_ID)))
|
||||
.andExpect(jsonPath("$.saved", is(true)))
|
||||
.andExpect(jsonPath("$.additionalSamples", notNullValue()));
|
||||
|
||||
// updateJobStatus가 4번 호출되어야 함 (main + 3 additional samples)
|
||||
verify(jobStatusService, times(4)).updateJobStatus(anyString(), org.mockito.ArgumentMatchers.any(JobStatus.class), anyString());
|
||||
verify(cacheService, times(1)).getJobStatus(VALID_JOB_ID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
package com.kt.ai.test.unit.service;
|
||||
|
||||
import com.kt.ai.service.CacheService;
|
||||
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.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
|
||||
/**
|
||||
* CacheService 단위 테스트
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("CacheService 단위 테스트")
|
||||
class CacheServiceUnitTest {
|
||||
|
||||
// Constants
|
||||
private static final String VALID_KEY = "test:key";
|
||||
private static final String VALID_VALUE = "test-value";
|
||||
private static final long VALID_TTL = 3600L;
|
||||
private static final String VALID_JOB_ID = "job-123";
|
||||
private static final String VALID_EVENT_ID = "evt-001";
|
||||
private static final String VALID_INDUSTRY = "음식점";
|
||||
private static final String VALID_REGION = "강남구";
|
||||
|
||||
@Mock
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Mock
|
||||
private ValueOperations<String, Object> valueOperations;
|
||||
|
||||
@InjectMocks
|
||||
private CacheService cacheService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// TTL 값 설정
|
||||
ReflectionTestUtils.setField(cacheService, "recommendationTtl", 86400L);
|
||||
ReflectionTestUtils.setField(cacheService, "jobStatusTtl", 86400L);
|
||||
ReflectionTestUtils.setField(cacheService, "trendTtl", 3600L);
|
||||
|
||||
// RedisTemplate Mock 설정 (lenient를 사용하여 모든 테스트에서 사용하지 않아도 됨)
|
||||
lenient().when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
}
|
||||
|
||||
// ========== set() 메서드 테스트 ==========
|
||||
|
||||
@Test
|
||||
@DisplayName("Given valid key and value, When set, Then success")
|
||||
void givenValidKeyAndValue_whenSet_thenSuccess() {
|
||||
// Given
|
||||
doNothing().when(valueOperations).set(anyString(), any(), anyLong(), any(TimeUnit.class));
|
||||
|
||||
// When
|
||||
cacheService.set(VALID_KEY, VALID_VALUE, VALID_TTL);
|
||||
|
||||
// Then
|
||||
verify(valueOperations, times(1))
|
||||
.set(VALID_KEY, VALID_VALUE, VALID_TTL, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Given Redis exception, When set, Then log error and continue")
|
||||
void givenRedisException_whenSet_thenLogErrorAndContinue() {
|
||||
// Given
|
||||
doThrow(new RuntimeException("Redis connection failed"))
|
||||
.when(valueOperations).set(anyString(), any(), anyLong(), any(TimeUnit.class));
|
||||
|
||||
// When & Then (예외가 전파되지 않아야 함)
|
||||
cacheService.set(VALID_KEY, VALID_VALUE, VALID_TTL);
|
||||
|
||||
verify(valueOperations, times(1))
|
||||
.set(VALID_KEY, VALID_VALUE, VALID_TTL, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// ========== get() 메서드 테스트 ==========
|
||||
|
||||
@Test
|
||||
@DisplayName("Given existing key, When get, Then return value")
|
||||
void givenExistingKey_whenGet_thenReturnValue() {
|
||||
// Given
|
||||
when(valueOperations.get(VALID_KEY)).thenReturn(VALID_VALUE);
|
||||
|
||||
// When
|
||||
Object result = cacheService.get(VALID_KEY);
|
||||
|
||||
// Then
|
||||
assertThat(result).isEqualTo(VALID_VALUE);
|
||||
verify(valueOperations, times(1)).get(VALID_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Given non-existing key, When get, Then return null")
|
||||
void givenNonExistingKey_whenGet_thenReturnNull() {
|
||||
// Given
|
||||
when(valueOperations.get(VALID_KEY)).thenReturn(null);
|
||||
|
||||
// When
|
||||
Object result = cacheService.get(VALID_KEY);
|
||||
|
||||
// Then
|
||||
assertThat(result).isNull();
|
||||
verify(valueOperations, times(1)).get(VALID_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Given Redis exception, When get, Then return null")
|
||||
void givenRedisException_whenGet_thenReturnNull() {
|
||||
// Given
|
||||
when(valueOperations.get(VALID_KEY))
|
||||
.thenThrow(new RuntimeException("Redis connection failed"));
|
||||
|
||||
// When
|
||||
Object result = cacheService.get(VALID_KEY);
|
||||
|
||||
// Then
|
||||
assertThat(result).isNull();
|
||||
verify(valueOperations, times(1)).get(VALID_KEY);
|
||||
}
|
||||
|
||||
// ========== delete() 메서드 테스트 ==========
|
||||
|
||||
@Test
|
||||
@DisplayName("Given valid key, When delete, Then invoke RedisTemplate delete")
|
||||
void givenValidKey_whenDelete_thenInvokeRedisTemplateDelete() {
|
||||
// Given - No specific setup needed
|
||||
|
||||
// When
|
||||
cacheService.delete(VALID_KEY);
|
||||
|
||||
// Then
|
||||
verify(redisTemplate, times(1)).delete(VALID_KEY);
|
||||
}
|
||||
|
||||
// ========== saveJobStatus() 메서드 테스트 ==========
|
||||
|
||||
@Test
|
||||
@DisplayName("Given valid job status, When save, Then success")
|
||||
void givenValidJobStatus_whenSave_thenSuccess() {
|
||||
// Given
|
||||
Object jobStatus = "PROCESSING";
|
||||
doNothing().when(valueOperations).set(anyString(), any(), anyLong(), any(TimeUnit.class));
|
||||
|
||||
// When
|
||||
cacheService.saveJobStatus(VALID_JOB_ID, jobStatus);
|
||||
|
||||
// Then
|
||||
verify(valueOperations, times(1))
|
||||
.set("ai:job:status:" + VALID_JOB_ID, jobStatus, 86400L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// ========== getJobStatus() 메서드 테스트 ==========
|
||||
|
||||
@Test
|
||||
@DisplayName("Given existing job, When get status, Then return status")
|
||||
void givenExistingJob_whenGetStatus_thenReturnStatus() {
|
||||
// Given
|
||||
Object expectedStatus = "COMPLETED";
|
||||
when(valueOperations.get("ai:job:status:" + VALID_JOB_ID)).thenReturn(expectedStatus);
|
||||
|
||||
// When
|
||||
Object result = cacheService.getJobStatus(VALID_JOB_ID);
|
||||
|
||||
// Then
|
||||
assertThat(result).isEqualTo(expectedStatus);
|
||||
verify(valueOperations, times(1)).get("ai:job:status:" + VALID_JOB_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Given non-existing job, When get status, Then return null")
|
||||
void givenNonExistingJob_whenGetStatus_thenReturnNull() {
|
||||
// Given
|
||||
when(valueOperations.get("ai:job:status:" + VALID_JOB_ID)).thenReturn(null);
|
||||
|
||||
// When
|
||||
Object result = cacheService.getJobStatus(VALID_JOB_ID);
|
||||
|
||||
// Then
|
||||
assertThat(result).isNull();
|
||||
verify(valueOperations, times(1)).get("ai:job:status:" + VALID_JOB_ID);
|
||||
}
|
||||
|
||||
// ========== saveRecommendation() 메서드 테스트 ==========
|
||||
|
||||
@Test
|
||||
@DisplayName("Given valid recommendation, When save, Then success")
|
||||
void givenValidRecommendation_whenSave_thenSuccess() {
|
||||
// Given
|
||||
Object recommendation = "recommendation-data";
|
||||
doNothing().when(valueOperations).set(anyString(), any(), anyLong(), any(TimeUnit.class));
|
||||
|
||||
// When
|
||||
cacheService.saveRecommendation(VALID_EVENT_ID, recommendation);
|
||||
|
||||
// Then
|
||||
verify(valueOperations, times(1))
|
||||
.set("ai:recommendation:" + VALID_EVENT_ID, recommendation, 86400L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// ========== getRecommendation() 메서드 테스트 ==========
|
||||
|
||||
@Test
|
||||
@DisplayName("Given existing recommendation, When get, Then return recommendation")
|
||||
void givenExistingRecommendation_whenGet_thenReturnRecommendation() {
|
||||
// Given
|
||||
Object expectedRecommendation = "recommendation-data";
|
||||
when(valueOperations.get("ai:recommendation:" + VALID_EVENT_ID))
|
||||
.thenReturn(expectedRecommendation);
|
||||
|
||||
// When
|
||||
Object result = cacheService.getRecommendation(VALID_EVENT_ID);
|
||||
|
||||
// Then
|
||||
assertThat(result).isEqualTo(expectedRecommendation);
|
||||
verify(valueOperations, times(1)).get("ai:recommendation:" + VALID_EVENT_ID);
|
||||
}
|
||||
|
||||
// ========== saveTrend() 메서드 테스트 ==========
|
||||
|
||||
@Test
|
||||
@DisplayName("Given valid trend, When save, Then success")
|
||||
void givenValidTrend_whenSave_thenSuccess() {
|
||||
// Given
|
||||
Object trend = "trend-data";
|
||||
doNothing().when(valueOperations).set(anyString(), any(), anyLong(), any(TimeUnit.class));
|
||||
|
||||
// When
|
||||
cacheService.saveTrend(VALID_INDUSTRY, VALID_REGION, trend);
|
||||
|
||||
// Then
|
||||
verify(valueOperations, times(1))
|
||||
.set("ai:trend:" + VALID_INDUSTRY + ":" + VALID_REGION, trend, 3600L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
// ========== getTrend() 메서드 테스트 ==========
|
||||
|
||||
@Test
|
||||
@DisplayName("Given existing trend, When get, Then return trend")
|
||||
void givenExistingTrend_whenGet_thenReturnTrend() {
|
||||
// Given
|
||||
Object expectedTrend = "trend-data";
|
||||
when(valueOperations.get("ai:trend:" + VALID_INDUSTRY + ":" + VALID_REGION))
|
||||
.thenReturn(expectedTrend);
|
||||
|
||||
// When
|
||||
Object result = cacheService.getTrend(VALID_INDUSTRY, VALID_REGION);
|
||||
|
||||
// Then
|
||||
assertThat(result).isEqualTo(expectedTrend);
|
||||
verify(valueOperations, times(1))
|
||||
.get("ai:trend:" + VALID_INDUSTRY + ":" + VALID_REGION);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package com.kt.ai.test.unit.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kt.ai.exception.JobNotFoundException;
|
||||
import com.kt.ai.model.dto.response.JobStatusResponse;
|
||||
import com.kt.ai.model.enums.JobStatus;
|
||||
import com.kt.ai.service.CacheService;
|
||||
import com.kt.ai.service.JobStatusService;
|
||||
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.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* JobStatusService 단위 테스트
|
||||
*
|
||||
* @author AI Service Team
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("JobStatusService 단위 테스트")
|
||||
class JobStatusServiceUnitTest {
|
||||
|
||||
// Constants
|
||||
private static final String VALID_JOB_ID = "job-123";
|
||||
private static final String INVALID_JOB_ID = "job-999";
|
||||
private static final String VALID_MESSAGE = "AI 추천 생성 중";
|
||||
|
||||
@Mock
|
||||
private CacheService cacheService;
|
||||
|
||||
@Mock
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@InjectMocks
|
||||
private JobStatusService jobStatusService;
|
||||
|
||||
private JobStatusResponse sampleJobStatusResponse;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
sampleJobStatusResponse = JobStatusResponse.builder()
|
||||
.jobId(VALID_JOB_ID)
|
||||
.status(JobStatus.PROCESSING)
|
||||
.progress(50)
|
||||
.message(VALID_MESSAGE)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
// ========== getJobStatus() 메서드 테스트 ==========
|
||||
|
||||
@Test
|
||||
@DisplayName("Given existing job, When get status, Then return job status")
|
||||
void givenExistingJob_whenGetStatus_thenReturnJobStatus() {
|
||||
// Given
|
||||
Map<String, Object> cachedData = createCachedJobStatusData();
|
||||
when(cacheService.getJobStatus(VALID_JOB_ID)).thenReturn(cachedData);
|
||||
when(objectMapper.convertValue(cachedData, JobStatusResponse.class))
|
||||
.thenReturn(sampleJobStatusResponse);
|
||||
|
||||
// When
|
||||
JobStatusResponse result = jobStatusService.getJobStatus(VALID_JOB_ID);
|
||||
|
||||
// Then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getJobId()).isEqualTo(VALID_JOB_ID);
|
||||
assertThat(result.getStatus()).isEqualTo(JobStatus.PROCESSING);
|
||||
assertThat(result.getProgress()).isEqualTo(50);
|
||||
assertThat(result.getMessage()).isEqualTo(VALID_MESSAGE);
|
||||
|
||||
verify(cacheService, times(1)).getJobStatus(VALID_JOB_ID);
|
||||
verify(objectMapper, times(1)).convertValue(cachedData, JobStatusResponse.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Given non-existing job, When get status, Then throw JobNotFoundException")
|
||||
void givenNonExistingJob_whenGetStatus_thenThrowJobNotFoundException() {
|
||||
// Given
|
||||
when(cacheService.getJobStatus(INVALID_JOB_ID)).thenReturn(null);
|
||||
|
||||
// When & Then
|
||||
assertThatThrownBy(() -> jobStatusService.getJobStatus(INVALID_JOB_ID))
|
||||
.isInstanceOf(JobNotFoundException.class)
|
||||
.hasMessageContaining(INVALID_JOB_ID);
|
||||
|
||||
verify(cacheService, times(1)).getJobStatus(INVALID_JOB_ID);
|
||||
verify(objectMapper, never()).convertValue(any(), eq(JobStatusResponse.class));
|
||||
}
|
||||
|
||||
// ========== updateJobStatus() 메서드 테스트 ==========
|
||||
|
||||
@Test
|
||||
@DisplayName("Given PENDING status, When update, Then save with 0% progress")
|
||||
void givenPendingStatus_whenUpdate_thenSaveWithZeroProgress() {
|
||||
// Given
|
||||
doNothing().when(cacheService).saveJobStatus(eq(VALID_JOB_ID), any(JobStatusResponse.class));
|
||||
|
||||
// When
|
||||
jobStatusService.updateJobStatus(VALID_JOB_ID, JobStatus.PENDING, "대기 중");
|
||||
|
||||
// Then
|
||||
ArgumentCaptor<JobStatusResponse> captor = ArgumentCaptor.forClass(JobStatusResponse.class);
|
||||
verify(cacheService, times(1)).saveJobStatus(eq(VALID_JOB_ID), captor.capture());
|
||||
|
||||
JobStatusResponse saved = captor.getValue();
|
||||
assertThat(saved.getJobId()).isEqualTo(VALID_JOB_ID);
|
||||
assertThat(saved.getStatus()).isEqualTo(JobStatus.PENDING);
|
||||
assertThat(saved.getProgress()).isEqualTo(0);
|
||||
assertThat(saved.getMessage()).isEqualTo("대기 중");
|
||||
assertThat(saved.getCreatedAt()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Given PROCESSING status, When update, Then save with 50% progress")
|
||||
void givenProcessingStatus_whenUpdate_thenSaveWithFiftyProgress() {
|
||||
// Given
|
||||
doNothing().when(cacheService).saveJobStatus(eq(VALID_JOB_ID), any(JobStatusResponse.class));
|
||||
|
||||
// When
|
||||
jobStatusService.updateJobStatus(VALID_JOB_ID, JobStatus.PROCESSING, VALID_MESSAGE);
|
||||
|
||||
// Then
|
||||
ArgumentCaptor<JobStatusResponse> captor = ArgumentCaptor.forClass(JobStatusResponse.class);
|
||||
verify(cacheService, times(1)).saveJobStatus(eq(VALID_JOB_ID), captor.capture());
|
||||
|
||||
JobStatusResponse saved = captor.getValue();
|
||||
assertThat(saved.getJobId()).isEqualTo(VALID_JOB_ID);
|
||||
assertThat(saved.getStatus()).isEqualTo(JobStatus.PROCESSING);
|
||||
assertThat(saved.getProgress()).isEqualTo(50);
|
||||
assertThat(saved.getMessage()).isEqualTo(VALID_MESSAGE);
|
||||
assertThat(saved.getCreatedAt()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Given COMPLETED status, When update, Then save with 100% progress")
|
||||
void givenCompletedStatus_whenUpdate_thenSaveWithHundredProgress() {
|
||||
// Given
|
||||
doNothing().when(cacheService).saveJobStatus(eq(VALID_JOB_ID), any(JobStatusResponse.class));
|
||||
|
||||
// When
|
||||
jobStatusService.updateJobStatus(VALID_JOB_ID, JobStatus.COMPLETED, "AI 추천 완료");
|
||||
|
||||
// Then
|
||||
ArgumentCaptor<JobStatusResponse> captor = ArgumentCaptor.forClass(JobStatusResponse.class);
|
||||
verify(cacheService, times(1)).saveJobStatus(eq(VALID_JOB_ID), captor.capture());
|
||||
|
||||
JobStatusResponse saved = captor.getValue();
|
||||
assertThat(saved.getJobId()).isEqualTo(VALID_JOB_ID);
|
||||
assertThat(saved.getStatus()).isEqualTo(JobStatus.COMPLETED);
|
||||
assertThat(saved.getProgress()).isEqualTo(100);
|
||||
assertThat(saved.getMessage()).isEqualTo("AI 추천 완료");
|
||||
assertThat(saved.getCreatedAt()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Given FAILED status, When update, Then save with 0% progress")
|
||||
void givenFailedStatus_whenUpdate_thenSaveWithZeroProgress() {
|
||||
// Given
|
||||
doNothing().when(cacheService).saveJobStatus(eq(VALID_JOB_ID), any(JobStatusResponse.class));
|
||||
|
||||
// When
|
||||
jobStatusService.updateJobStatus(VALID_JOB_ID, JobStatus.FAILED, "AI API 호출 실패");
|
||||
|
||||
// Then
|
||||
ArgumentCaptor<JobStatusResponse> captor = ArgumentCaptor.forClass(JobStatusResponse.class);
|
||||
verify(cacheService, times(1)).saveJobStatus(eq(VALID_JOB_ID), captor.capture());
|
||||
|
||||
JobStatusResponse saved = captor.getValue();
|
||||
assertThat(saved.getJobId()).isEqualTo(VALID_JOB_ID);
|
||||
assertThat(saved.getStatus()).isEqualTo(JobStatus.FAILED);
|
||||
assertThat(saved.getProgress()).isEqualTo(0);
|
||||
assertThat(saved.getMessage()).isEqualTo("AI API 호출 실패");
|
||||
assertThat(saved.getCreatedAt()).isNotNull();
|
||||
}
|
||||
|
||||
// ========== Helper Methods ==========
|
||||
|
||||
/**
|
||||
* Cache에 저장된 Job 상태 데이터 생성 (LinkedHashMap 형태)
|
||||
*/
|
||||
private Map<String, Object> createCachedJobStatusData() {
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("jobId", VALID_JOB_ID);
|
||||
data.put("status", JobStatus.PROCESSING.name());
|
||||
data.put("progress", 50);
|
||||
data.put("message", VALID_MESSAGE);
|
||||
data.put("createdAt", LocalDateTime.now().toString());
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
spring:
|
||||
application:
|
||||
name: ai-service-test
|
||||
|
||||
# Redis Configuration (테스트용)
|
||||
data:
|
||||
redis:
|
||||
host: ${REDIS_HOST:20.214.210.71}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:Hi5Jessica!}
|
||||
database: ${REDIS_DATABASE:3}
|
||||
timeout: 3000
|
||||
|
||||
# Kafka Configuration (테스트용)
|
||||
kafka:
|
||||
bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:20.249.182.13:9095,4.217.131.59:9095}
|
||||
consumer:
|
||||
group-id: ai-service-test-consumers
|
||||
auto-offset-reset: earliest
|
||||
enable-auto-commit: false
|
||||
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
|
||||
properties:
|
||||
spring.json.trusted.packages: "*"
|
||||
listener:
|
||||
ack-mode: manual
|
||||
|
||||
# Server Configuration
|
||||
server:
|
||||
port: 0 # 랜덤 포트 사용
|
||||
|
||||
# JWT Configuration (테스트용)
|
||||
jwt:
|
||||
secret: test-jwt-secret-key-for-testing-only
|
||||
access-token-validity: 1800
|
||||
refresh-token-validity: 86400
|
||||
|
||||
# Kafka Topics
|
||||
kafka:
|
||||
topics:
|
||||
ai-job: ai-event-generation-job
|
||||
ai-job-dlq: ai-event-generation-job-dlq
|
||||
|
||||
# AI API Configuration (테스트용 - Mock 사용)
|
||||
ai:
|
||||
provider: CLAUDE
|
||||
claude:
|
||||
api-url: ${CLAUDE_API_URL:https://api.anthropic.com/v1/messages}
|
||||
api-key: ${CLAUDE_API_KEY:test-key}
|
||||
anthropic-version: 2023-06-01
|
||||
model: claude-3-5-sonnet-20241022
|
||||
max-tokens: 4096
|
||||
temperature: 0.7
|
||||
timeout: 300000
|
||||
|
||||
# Cache TTL
|
||||
cache:
|
||||
ttl:
|
||||
recommendation: 86400
|
||||
job-status: 86400
|
||||
trend: 3600
|
||||
fallback: 604800
|
||||
|
||||
# Logging
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
com.kt.ai: DEBUG
|
||||
org.springframework.kafka: DEBUG
|
||||
Reference in New Issue
Block a user