Skip to content

Commit d4cfebd

Browse files
committed
[Refactor] 예외A처AiServiceImpl 예외처리수정
1 parent f8bcdaf commit d4cfebd

File tree

2 files changed

+43
-22
lines changed

2 files changed

+43
-22
lines changed

back/src/main/java/com/back/global/ai/service/AiServiceImpl.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import com.back.global.ai.dto.AiRequest;
1515
import com.back.global.ai.dto.result.BaseScenarioResult;
1616
import com.back.global.ai.dto.result.DecisionScenarioResult;
17+
import com.back.global.ai.exception.AiParsingException;
1718
import com.back.global.ai.exception.AiServiceException;
1819
import com.back.global.ai.prompt.BaseScenarioPrompt;
1920
import com.back.global.ai.prompt.DecisionScenarioPrompt;
@@ -71,13 +72,17 @@ public CompletableFuture<BaseScenarioResult> generateBaseScenario(BaseLine baseL
7172
} catch (Exception e) {
7273
log.error("Failed to parse BaseScenario AI response for BaseLine ID: {}, error: {}",
7374
baseLine.getId(), e.getMessage(), e);
74-
throw new AiServiceException(com.back.global.exception.ErrorCode.AI_RESPONSE_PARSING_ERROR,
75-
"Failed to parse BaseScenario response: " + e.getMessage());
75+
throw new AiParsingException("Failed to parse BaseScenario response: " + e.getMessage());
7676
}
7777
})
7878
.exceptionally(e -> {
7979
log.error("AI generation failed for BaseLine ID: {}, error: {}",
8080
baseLine.getId(), e.getMessage(), e);
81+
// AiParsingException은 그대로 전파, 나머지만 AiServiceException으로 감쌈
82+
Throwable cause = e.getCause() != null ? e.getCause() : e;
83+
if (cause instanceof AiParsingException) {
84+
throw (AiParsingException) cause;
85+
}
8186
throw new AiServiceException(com.back.global.exception.ErrorCode.AI_GENERATION_FAILED,
8287
"Failed to generate base scenario: " + e.getMessage());
8388
});
@@ -120,13 +125,17 @@ public CompletableFuture<DecisionScenarioResult> generateDecisionScenario(Decisi
120125
} catch (Exception e) {
121126
log.error("Failed to parse DecisionScenario AI response for DecisionLine ID: {}, error: {}",
122127
decisionLine.getId(), e.getMessage(), e);
123-
throw new AiServiceException(com.back.global.exception.ErrorCode.AI_RESPONSE_PARSING_ERROR,
124-
"Failed to parse DecisionScenario response: " + e.getMessage());
128+
throw new AiParsingException("Failed to parse DecisionScenario response: " + e.getMessage());
125129
}
126130
})
127131
.exceptionally(e -> {
128132
log.error("AI generation failed for DecisionLine ID: {}, error: {}",
129133
decisionLine.getId(), e.getMessage(), e);
134+
// AiParsingException은 그대로 전파, 나머지만 AiServiceException으로 감쌈
135+
Throwable cause = e.getCause() != null ? e.getCause() : e;
136+
if (cause instanceof AiParsingException) {
137+
throw (AiParsingException) cause;
138+
}
130139
throw new AiServiceException(com.back.global.exception.ErrorCode.AI_GENERATION_FAILED,
131140
"Failed to generate decision scenario: " + e.getMessage());
132141
});
@@ -196,13 +205,17 @@ public CompletableFuture<String> generateSituation(List<DecisionNode> previousNo
196205
} catch (Exception e) {
197206
log.error("Failed to parse situation AI response, error: {}",
198207
e.getMessage(), e);
199-
throw new AiServiceException(com.back.global.exception.ErrorCode.AI_RESPONSE_PARSING_ERROR,
200-
"Failed to parse situation response: " + e.getMessage());
208+
throw new AiParsingException("Failed to parse situation response: " + e.getMessage());
201209
}
202210
})
203211
.exceptionally(e -> {
204212
log.error("AI generation failed for situation, error: {}",
205213
e.getMessage(), e);
214+
// AiParsingException은 그대로 전파, 나머지만 AiServiceException으로 감쌈
215+
Throwable cause = e.getCause() != null ? e.getCause() : e;
216+
if (cause instanceof AiParsingException) {
217+
throw (AiParsingException) cause;
218+
}
206219
throw new AiServiceException(com.back.global.exception.ErrorCode.AI_GENERATION_FAILED,
207220
"Failed to generate situation: " + e.getMessage());
208221
});
@@ -224,9 +237,7 @@ public CompletableFuture<String> generateImage(String prompt) {
224237

225238
if (prompt == null || prompt.trim().isEmpty()) {
226239
log.warn("Image prompt is empty. Returning placeholder.");
227-
return CompletableFuture.failedFuture(
228-
new AiServiceException(com.back.global.exception.ErrorCode.AI_INVALID_REQUEST,
229-
"Image prompt cannot be null or empty"));
240+
return CompletableFuture.completedFuture("placeholder-image-url");
230241
}
231242

232243
log.info("Generating image with prompt: {} (Storage: {})", prompt, storageService.getStorageType());
@@ -247,15 +258,12 @@ public CompletableFuture<String> generateImage(String prompt) {
247258
return storageService.uploadBase64Image(base64Data);
248259
})
249260
.exceptionally(e -> {
250-
log.error("Failed to generate or upload image: {}", e.getMessage(), e);
251-
throw new AiServiceException(com.back.global.exception.ErrorCode.AI_GENERATION_FAILED,
252-
"Failed to generate or upload image: " + e.getMessage());
261+
log.warn("Failed to generate or upload image, returning placeholder: {}", e.getMessage());
262+
return "placeholder-image-url";
253263
});
254264
} catch (Exception e) {
255-
log.error("Error in generateImage, error: {}", e.getMessage(), e);
256-
return CompletableFuture.failedFuture(
257-
new AiServiceException(com.back.global.exception.ErrorCode.AI_GENERATION_FAILED,
258-
"Unexpected error in image generation: " + e.getMessage()));
265+
log.warn("Error in generateImage, returning placeholder: {}", e.getMessage());
266+
return CompletableFuture.completedFuture("placeholder-image-url");
259267
}
260268
}
261269
}

back/src/test/java/com/back/global/ai/service/AiServiceImplTest.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.back.global.ai.dto.result.BaseScenarioResult;
1818
import com.back.global.ai.dto.result.DecisionScenarioResult;
1919
import com.back.global.ai.exception.AiParsingException;
20+
import com.back.global.ai.exception.AiServiceException;
2021
import com.back.global.baseentity.BaseEntity;
2122
import com.fasterxml.jackson.databind.ObjectMapper;
2223
import org.junit.jupiter.api.BeforeEach;
@@ -55,6 +56,8 @@ class AiServiceImplTest {
5556
@Mock
5657
private ObjectMapper objectMapper;
5758

59+
private final ObjectMapper realObjectMapper = new ObjectMapper();
60+
5861
@Mock
5962
private SceneTypeRepository sceneTypeRepository;
6063

@@ -212,7 +215,7 @@ void generateBaseScenario_Fail_NullBaseLine() {
212215
// when & then
213216
assertThatThrownBy(() -> aiService.generateBaseScenario(null).get())
214217
.isInstanceOf(ExecutionException.class)
215-
.hasCauseInstanceOf(AiParsingException.class)
218+
.hasCauseInstanceOf(AiServiceException.class)
216219
.hasMessageContaining("BaseLine cannot be null");
217220

218221
verify(textAiClient, never()).generateText(any(AiRequest.class));
@@ -306,7 +309,7 @@ void generateDecisionScenario_Fail_NullDecisionLine() {
306309
// when & then
307310
assertThatThrownBy(() -> aiService.generateDecisionScenario(null, testScenario).get())
308311
.isInstanceOf(ExecutionException.class)
309-
.hasCauseInstanceOf(AiParsingException.class)
312+
.hasCauseInstanceOf(AiServiceException.class)
310313
.hasMessageContaining("DecisionLine cannot be null");
311314

312315
verify(textAiClient, never()).generateText(any(AiRequest.class));
@@ -318,7 +321,7 @@ void generateDecisionScenario_Fail_NullBaseScenario() {
318321
// when & then
319322
assertThatThrownBy(() -> aiService.generateDecisionScenario(testDecisionLine, null).get())
320323
.isInstanceOf(ExecutionException.class)
321-
.hasCauseInstanceOf(AiParsingException.class)
324+
.hasCauseInstanceOf(AiServiceException.class)
322325
.hasMessageContaining("BaseScenario cannot be null");
323326

324327
verify(textAiClient, never()).generateText(any(AiRequest.class));
@@ -383,6 +386,8 @@ class GenerateSituationTests {
383386
@DisplayName("성공 - 유효한 이전 선택들로 새로운 상황 생성")
384387
void generateSituation_Success() throws Exception {
385388
// given
389+
setField(aiService, "objectMapper", realObjectMapper); // 실제 ObjectMapper 사용
390+
386391
List<DecisionNode> previousNodes = testDecisionLine.getDecisionNodes();
387392
String mockAiResponse = """
388393
{
@@ -406,13 +411,19 @@ void generateSituation_Success() throws Exception {
406411
verify(textAiClient, times(1)).generateText(any(AiRequest.class));
407412
}
408413

414+
private void setField(Object target, String fieldName, Object value) throws Exception {
415+
var field = AiServiceImpl.class.getDeclaredField(fieldName);
416+
field.setAccessible(true);
417+
field.set(target, value);
418+
}
419+
409420
@Test
410421
@DisplayName("실패 - previousNodes가 null인 경우")
411422
void generateSituation_Fail_NullPreviousNodes() {
412423
// when & then
413424
assertThatThrownBy(() -> aiService.generateSituation(null).get())
414425
.isInstanceOf(ExecutionException.class)
415-
.hasCauseInstanceOf(AiParsingException.class)
426+
.hasCauseInstanceOf(AiServiceException.class)
416427
.hasMessageContaining("Previous nodes cannot be null or empty");
417428

418429
verify(textAiClient, never()).generateText(any(AiRequest.class));
@@ -424,7 +435,7 @@ void generateSituation_Fail_EmptyPreviousNodes() {
424435
// when & then
425436
assertThatThrownBy(() -> aiService.generateSituation(List.of()).get())
426437
.isInstanceOf(ExecutionException.class)
427-
.hasCauseInstanceOf(AiParsingException.class)
438+
.hasCauseInstanceOf(AiServiceException.class)
428439
.hasMessageContaining("Previous nodes cannot be null or empty");
429440

430441
verify(textAiClient, never()).generateText(any(AiRequest.class));
@@ -446,7 +457,7 @@ void generateSituation_Fail_InvalidNodeData() {
446457
// when & then
447458
assertThatThrownBy(() -> aiService.generateSituation(invalidNodes).get())
448459
.isInstanceOf(ExecutionException.class)
449-
.hasCauseInstanceOf(AiParsingException.class)
460+
.hasCauseInstanceOf(AiServiceException.class)
450461
.hasMessageContaining("Previous nodes contain invalid data");
451462

452463
verify(textAiClient, never()).generateText(any(AiRequest.class));
@@ -456,6 +467,8 @@ void generateSituation_Fail_InvalidNodeData() {
456467
@DisplayName("성공 - AI 응답에서 상황 추출 (JSON 형식)")
457468
void generateSituation_Success_ExtractSituationFromJson() throws Exception {
458469
// given
470+
setField(aiService, "objectMapper", realObjectMapper); // 실제 ObjectMapper 사용
471+
459472
List<DecisionNode> previousNodes = testDecisionLine.getDecisionNodes();
460473
String mockAiResponse = """
461474
{

0 commit comments

Comments
 (0)