[FEAT] AI 멘토 질문 및 답변 스트리밍 API 구현 - #119
Conversation
|
Warning Review limit reached
Next review available in: 13 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (14)
📝 WalkthroughWalkthrough멘토 질문 POST API를 추가했습니다. 질문을 검증하고 세션 생성 상태를 관리합니다. Gemini 응답을 비동기 SSE로 전달합니다. 응답 완료·실패·타임아웃을 처리하고 사용자 및 어시스턴트 메시지를 저장합니다. Changes멘토 질문 스트리밍
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant MentorController
participant MentorQuestionService
participant MentorStreamingService
participant GeminiStreamingClient
Client->>MentorController: POST 질문 JSON
MentorController->>MentorQuestionService: 질문 준비
MentorQuestionService-->>MentorController: PreparedQuestion
MentorController->>MentorStreamingService: SSE 스트림 시작
MentorStreamingService->>GeminiStreamingClient: Gemini 스트리밍 요청
GeminiStreamingClient-->>MentorStreamingService: 응답 청크
MentorStreamingService-->>Client: SSE 청크 이벤트
MentorStreamingService->>MentorQuestionService: 답변 저장 및 완료
MentorStreamingService-->>Client: SSE 완료 이벤트
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| MentorChatSession session = sessionRepository.findByAnalysisId(analysisId) | ||
| .orElseGet(() -> sessionRepository.save( | ||
| MentorChatSession.createActive(analysis, analysis.getUser()))); |
There was a problem hiding this comment.
레포지토리에 findByIdForUpdate에는 비관적 락 정의 되어있는데 prepare()에는 일부러 findByAnalysisId 사용하신걸까요??
There was a problem hiding this comment.
기존에는 Analysis를 findByIdForUpdate()로 먼저 조회하고 있어 동일 분석에 대한 요청이 직렬화된다고 판단해 세션은 일반 조회를 사용했습니다. 분석 락은 세션이 아직 없는 최초 요청에서 중복 세션 생성을 막기 위해 유지할 필요가 있었습니당...
다만 리뷰 덕분에 기존 세션은 스트리밍 완료/실패 처리에서 findByIdForUpdate()로 직접 갱신되므로, prepare()에서도 동일 세션을 잠그지 않으면 세션 상태 변경이 동시에 진행될 여지가 있다는 걸 확인했습니다. 이에 분석 락은 유지하고, 세션 조회는 findByAnalysisIdForUpdate()로 변경해 생성 경쟁과 세션 상태 경쟁을 모두 방지하도록 수정했습니다!
감사합니닿!!
| public SseEmitter stream(MentorQuestionService.PreparedQuestion prepared) { | ||
| SseEmitter emitter = new SseEmitter(SSE_TIMEOUT_MS); | ||
| AtomicBoolean terminated = new AtomicBoolean(false); | ||
| emitter.onTimeout(() -> recover(prepared.sessionId(), prepared.generationToken(), terminated)); |
There was a problem hiding this comment.
호출자체는 취소하지 않는 처리라고 하는데 취소가능한 구조도 고려해보시면 좋을 것 같습니다
| String generationToken = session.startGenerating(STALE_GENERATION_AFTER); | ||
|
|
||
| String prompt = buildPrompt(analysis, content.trim()); | ||
| MentorMessage userMessage = messageRepository.saveAndFlush( |
There was a problem hiding this comment.
[P2] 질문 저장 및 questionCount 증가 시점의 불일치로 인한 정책 우회 가능성
현재 사용자 질문은 스트리밍 시작 전에 DB에 저장되지만, questionCount는 AI 답변이 정상 완료된 경우에만 증가하고 있습니다.
우려 사항
- 질문 제한 정책 우회 가능성 : 질문 전송 후 SSE 연결을 의도적으로 반복 종료하거나 AI 응답이 실패할 경우, 세션은 ACTIVE로 복구되지만 DB에 사용자 메시지만 쌓이고
questionCount는 증가하지 않을 것 같아요. - 프롬프트 이력 오염: 실패/중단된 사용자 질문 메시지가 DB에 그대로 유지되어, 이후 정상 요청 시 AI 모델의 Context(대화 이력)에 포함될 수 있습니다.
개선안
- 요청 시점 차감: 사용자 질문을 저장하는 시점에
questionCount를 바로 증가시킵니다. (실패해도 질문 권한 소진) → 이 부분은 논의가 필요해 보일 듯 합니다. - 실패 시 보정: 스트리밍 실패/중단 시 해당 사용자 메시지를 DB에서 삭제 or
status = FAILED로 관리 → 이후 대화 이력 및 Count 계산에서 제외합니다.
개인적으로 실패 시 보정을 추가 도입하면 프롬프트 이력 관리도 함께 해결할 수 있어 더 적절한 방향이라고 생각하는데 은우님, 다른 팀원분들 의견도 궁금해요~
There was a problem hiding this comment.
현재 핵심 기능의 동작을 막는 문제는 아니므로 필수 수정 사항은 아닌 것 같습니다.
데이터 정합성과 질문 횟수 정책에 대해서는 팀원들과 별도로 논의한 뒤, 서브 이슈로 관리해도 좋을 것 같아요!
There was a problem hiding this comment.
안녕하세요! 좋은 리뷰 감사합니다. 서브 이슈로 관리하면 좋을 것 같아요!
저는 아래와 같은 로직 + 논의할 사안이라고 생각했는데 어떠실지요...
- 사용자 메시지에 PENDING, COMPLETED, FAILED 상태 추가
- 질문 저장 시 PENDING
- 답변 성공 시 질문을 COMPLETED로 변경하고 count 증가
- 실패 또는 중단 시 질문을 FAILED로 변경
- 프롬프트와 일반 대화 내역에서는 FAILED 질문 제외
- 실패 요청의 횟수 차감 여부만 논의 후 결정
There was a problem hiding this comment.
네 좋습니다! 추후 자세히 정책 세우고 디벨롭하면 좋을 것 같아요.
on1yoneprivate
left a comment
There was a problem hiding this comment.
수고하셨습니다~ 리뷰 확인해 주세요
p1001q
left a comment
There was a problem hiding this comment.
결론:
이거 하나 빼면, 스트리밍 흐름(생성 토큰 기반 CAS로 stale 세션 복구 시 새 요청을 안전하게 보호하는 구조), SSE 에러/타임아웃 시 세션 롤백(recover), 프롬프트에 이전 대화를 "데이터로만 취급"하도록 시스템 프롬프트로 명시해둔 것까지
—> 꽤 꼼꼼하게 짜여 있어서 새로운 P1/P2급 이슈는 못 찾았습니다. kimyw1018님이 물어보신 "세션 락 안 거는 이유" 질문에 대해서는, analysisRepository.findByIdForUpdate가 이미 Analysis 행에 락을 걸고 있고 analysisId↔세션이 1:1이라 사실상 그 락이 세션 동시성도 같이 막아주는 구조로 보이긴 하는데, 이건 은우님이 의도하신 게 맞는지 답변으로 확인해주시면 좋을 것 같습니다!
rkdehdrbs7885-oss
left a comment
There was a problem hiding this comment.
[P5]시스템 프롬프트에 대한 작은 의견입니다. 지금처럼 자바 코드 내부에 프롬프트가 하드코딩되어 있으면 내용을 한 줄만 수정해도 매번 서버를 다시 빌드하고 배포하는 과정이 필요하니, 이미 만들어두신 GeminiProperties나 application.yml로 프롬프트 텍스트를 빼두면, 추후 프롬프트 엔지니어링을 할 때 유지보수에 용이할 것 같습니다. 수고하셨습니다!
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (6)
src/main/java/com/mr/domain/mentor/service/MentorStreamingService.java (1)
94-118: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win타임아웃/오류 시 클라이언트에 error 이벤트가 전달되지 않습니다.
terminateWithError()는safelyFail()다음에sendError()를 호출해 클라이언트에 명확한 에러를 전달합니다. 반면recover()는safelyFail()만 호출합니다. 타임아웃이 발생해도 클라이언트는error이벤트 없이 연결이 끊깁니다.
recover()에서도sendError()를 호출하면 두 경로의 동작이 일관되고, 클라이언트가 타임아웃 사유를 명확히 알 수 있습니다.🔧 제안하는 수정 방향
- emitter.onTimeout(() -> recover(prepared.sessionId(), prepared.generationToken(), terminated)); - emitter.onError(exception -> recover(prepared.sessionId(), prepared.generationToken(), terminated)); + emitter.onTimeout(() -> recover(prepared, emitter, terminated)); + emitter.onError(exception -> recover(prepared, emitter, terminated)); ... - private void recover(Long sessionId, String generationToken, AtomicBoolean terminated) { - if (terminated.compareAndSet(false, true)) { - safelyFail(sessionId, generationToken); - } - } + private void recover(MentorQuestionService.PreparedQuestion prepared, SseEmitter emitter, AtomicBoolean terminated) { + if (terminated.compareAndSet(false, true)) { + safelyFail(prepared.sessionId(), prepared.generationToken()); + sendError(emitter, MentorErrorStatus.MENTOR_RESPONSE_GENERATION_FAILED); + } + }정확한 오류 상태(
MentorErrorStatus)는 세션 계층 파일 기준으로 확인해 주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/mr/domain/mentor/service/MentorStreamingService.java` around lines 94 - 118, Update recover to send an error event after safelyFail, matching terminateWithError; determine and pass the appropriate MentorErrorStatus for the timeout/recovery path so clients receive the correct failure reason before the connection closes.src/test/java/com/mr/domain/mentor/entity/MentorChatSessionTest.java (1)
62-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winstale 상태의 GENERATING 세션 복구 경로가 테스트되지 않았습니다.
이 테스트는
staleAfter이내에 중복 시작을 시도하는 경우만 검증합니다.startGenerating(Duration)(컨텍스트 스니펫)은isStale(staleAfter)가true일 때 새 생성 토큰 발급을 허용합니다. 이 stale 복구 경로는 타임아웃 후 재시도 흐름의 핵심인데 테스트가 없습니다.
Duration.ZERO나 매우 짧은staleAfter값을 사용해 stale 상태에서startGenerating()이 정상적으로 새 토큰을 발급하는 케이스를 추가해 주세요. Gemini 응답이 늦어질 때의 복구 흐름을 확인하는 좋은 안전망이 될 것 같아요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/mr/domain/mentor/entity/MentorChatSessionTest.java` around lines 62 - 71, Add a test alongside startGenerating_inProgress_throwsException covering a GENERATING session whose staleAfter threshold has elapsed. Use Duration.ZERO or another minimal duration, invoke MentorChatSession.startGenerating(Duration) again, and assert that it succeeds with a newly issued generation token rather than throwing MENTOR_RESPONSE_IN_PROGRESS.src/test/java/com/mr/domain/mentor/controller/MentorControllerTest.java (1)
114-135: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win소유권 검증 실패 등 예외 경로 테스트가 빠졌습니다.
이 테스트는
mentorQuestionService.prepare()와mentorStreamingService.stream()이 성공하는 경우만 검증합니다.prepare()가 소유권 위반, 분석 미완료, 질문 한도 초과 등으로GeneralException을 던지는 경우는 다루지 않습니다.PR 목표에는 "사용자 및 분석 소유권 검증"이 명시되어 있습니다.
given(mentorQuestionService.prepare(...)).willThrow(...)로 실패 케이스를 추가하고, 이때Accept: text/event-stream헤더에서 실제 응답 상태와 본문 형식을 확인해 주세요. 이 테스트는MentorController.java에서 제기한 콘텐츠 협상(406) 위험을 조기에 검증하는 데도 도움이 됩니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/mr/domain/mentor/controller/MentorControllerTest.java` around lines 114 - 135, 멘토 질문 처리의 예외 경로 테스트가 없어 소유권·분석 상태·질문 한도 검증과 SSE 콘텐츠 협상을 검증하지 못합니다. MentorControllerTest의 sendQuestion_startsSseStream 주변에 mentorQuestionService.prepare(...)가 GeneralException을 던지는 실패 테스트를 추가하고, Accept: text/event-stream 요청에서 기대하는 실제 HTTP 상태와 오류 본문 형식을 검증하세요. 소유권 위반 등 대표 실패 조건을 포함하되 성공 시나리오와 mentorStreamingService.stream(...) 검증은 유지하세요.src/test/java/com/mr/global/client/gemini/GeminiStreamingClientTest.java (1)
1-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win외부 API 오류 경로에 대한 테스트가 없습니다.
이 테스트 클래스는 정상 SSE 스트림 처리만 검증합니다. 다음 케이스가 빠져 있습니다.
- Gemini가 2xx가 아닌 상태 코드를 반환하는 경우.
- 답변이 비어 있어
IllegalStateException("Gemini returned an empty answer.")이 발생하는 경우.GeminiStreamingClient.java에서 지적한 빈/비-JSONdata:라인이 포함된 경우.
server.expect(...).andRespond(withStatus(...))와assertThatThrownBy(...)를 활용해 이 실패 경로들을 추가하면, PR 목표에 명시된 "외부 API 오류 처리" 검증을 충족할 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/mr/global/client/gemini/GeminiStreamingClientTest.java` around lines 1 - 59, GeminiStreamingClientTest의 정상 스트림 테스트에 외부 API 실패 경로 검증을 추가하세요. 동일한 요청 매칭을 재사용해 비-2xx 응답이 예외로 전파되는지, 빈 응답이 IllegalStateException("Gemini returned an empty answer.")을 발생시키는지, 빈 또는 비-JSON data: 라인이 포함된 SSE에서도 유효한 청크 처리와 오류 동작이 유지되는지를 assertThatThrownBy 및 withStatus로 검증하세요.src/main/java/com/mr/domain/mentor/entity/MentorChatSession.java (1)
160-168: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win시간 의존 로직에
Clock을 주입하면 테스트가 쉬워집니다.
isStale이LocalDateTime.now()를 직접 호출합니다(161-162행). 같은 저장소의AnalysisStateService.now()는Clock을 주입받아 시간을 제어 가능하게 만듭니다. 같은 패턴을 적용하면staleAfter경계값 테스트를 결정적으로 작성할 수 있습니다.지금 당장 급한 문제는 아니지만, 시간 기반 로직은 나중에 플레이키 테스트의 원인이 되기 쉽습니다. 관련 개념: 테스트 가능성을 위한
Clock추상화는 Java 8의java.time.Clock공식 문서에서 권장하는 패턴입니다.♻️ 제안하는 리팩터링
+ private final Clock clock; + private boolean isStale(Duration staleAfter) { return this.generationStartedAt == null - || this.generationStartedAt.isBefore(LocalDateTime.now().minus(staleAfter)); + || this.generationStartedAt.isBefore(LocalDateTime.now(clock).minus(staleAfter)); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/java/com/mr/domain/mentor/entity/MentorChatSession.java` around lines 160 - 168, Update MentorChatSession to receive and retain a java.time.Clock, then use that clock in isStale instead of LocalDateTime.now() while preserving the existing staleAfter boundary behavior. Follow the injectable-clock pattern used by AnalysisStateService.now(), and update construction sites as needed to provide the production clock.src/test/java/com/mr/domain/mentor/service/MentorQuestionServiceTest.java (1)
16-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift
prepare()의 핵심 분기에 대한 테스트를 추가하세요.현재 테스트는 입력 검증(공백, 길이 초과) 두 가지만 다룹니다.
prepare()의 나머지 분기 – 소유권 불일치(MENTOR_ACCESS_DENIED), 분석 미완료(MENTOR_ANALYSIS_NOT_COMPLETED), 질문 한도 초과(MENTOR_QUESTION_LIMIT_EXCEEDED), 기존 세션 재사용 – 는 검증되지 않았습니다. 이 흐름은 PR 목표에 명시된 "서비스 및 SSE 응답 테스트 추가"와 직결됩니다.목(mock) 리포지토리에 시나리오별 스텁을 추가해 각 분기를 검증하는 테스트를 작성하면 회귀를 조기에 잡을 수 있습니다. 원하시면 테스트 코드 초안을 작성해 드릴까요?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/mr/domain/mentor/service/MentorQuestionServiceTest.java` around lines 16 - 44, Expand MentorQuestionServiceTest to cover the remaining prepare() branches: ownership mismatch yielding MENTOR_ACCESS_DENIED, incomplete analysis yielding MENTOR_ANALYSIS_NOT_COMPLETED, exceeding the question limit yielding MENTOR_QUESTION_LIMIT_EXCEEDED, and reuse of an existing mentor session. Stub the relevant mock repositories for each scenario and assert the expected exception code or reused-session result.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/main/java/com/mr/domain/mentor/controller/MentorController.java`:
- Around line 43-58: Update MentorController.sendQuestion and the corresponding
GlobalExceptionHandler exception handler so exceptions from this SSE endpoint
can negotiate an application/json response without producing 406 Not Acceptable.
Prefer declaring produces = MediaType.APPLICATION_JSON_VALUE on the relevant
`@ExceptionHandler` method, or include JSON alongside text/event-stream in
sendQuestion’s producible media types while preserving normal SSE responses.
In `@src/main/java/com/mr/domain/mentor/entity/MentorChatSession.java`:
- Around line 125-139: Update MentorChatSession.startGenerating() and the
surrounding generation flow so a stale generation does not overwrite an active
request’s token while its MentorStreamingService.generate() task may still
complete; either pass a cancellable token to the prior request or track and
cancel the stale generation before issuing a new token, ensuring the old
completion cannot fail token validation or save a response after replacement.
In `@src/main/java/com/mr/domain/mentor/exception/MentorErrorStatus.java`:
- Around line 18-22: Update the error-code values in MentorErrorStatus: assign
MENTOR_RESPONSE_IN_PROGRESS and MENTOR_SESSION_NOT_ACTIVE distinct MENTOR_409_*
codes, change MENTOR_RESPONSE_GENERATION_FAILED to MENTOR_500_01, and shift
MENTOR_MESSAGE_SAVE_FAILED to MENTOR_500_02.
In `@src/main/java/com/mr/domain/mentor/service/MentorStreamingService.java`:
- Around line 58-79: Update the streaming callback in generate to check
terminated.get() before invoking send for each chunk, and skip sending when the
stream has already terminated. Preserve normal chunk delivery while preventing
writes to an already-closed emitter from reaching the existing exception
handler.
- Around line 80-92: In the completion flow around `questionService.complete()`
and `terminateWithError()`, acquire the termination guard before calling
`complete()` so timeout handling cannot run concurrently after token validation
and question-count updates. Make the reservation through
`terminated.compareAndSet(false, true)` and the completion operation atomic, or
otherwise atomically couple generation-token state changes, while preserving
exclusive success and failure paths.
In `@src/main/java/com/mr/global/client/gemini/GeminiStreamingClient.java`:
- Around line 45-68: Update the SSE parsing loop in the exchange handler to skip
blank data payloads after removing the data prefix and to ignore non-JSON
markers such as [DONE] before calling objectMapper.readTree. Preserve normal
JSON event processing and chunkConsumer behavior for valid events.
---
Nitpick comments:
In `@src/main/java/com/mr/domain/mentor/entity/MentorChatSession.java`:
- Around line 160-168: Update MentorChatSession to receive and retain a
java.time.Clock, then use that clock in isStale instead of LocalDateTime.now()
while preserving the existing staleAfter boundary behavior. Follow the
injectable-clock pattern used by AnalysisStateService.now(), and update
construction sites as needed to provide the production clock.
In `@src/main/java/com/mr/domain/mentor/service/MentorStreamingService.java`:
- Around line 94-118: Update recover to send an error event after safelyFail,
matching terminateWithError; determine and pass the appropriate
MentorErrorStatus for the timeout/recovery path so clients receive the correct
failure reason before the connection closes.
In `@src/test/java/com/mr/domain/mentor/controller/MentorControllerTest.java`:
- Around line 114-135: 멘토 질문 처리의 예외 경로 테스트가 없어 소유권·분석 상태·질문 한도 검증과 SSE 콘텐츠 협상을
검증하지 못합니다. MentorControllerTest의 sendQuestion_startsSseStream 주변에
mentorQuestionService.prepare(...)가 GeneralException을 던지는 실패 테스트를 추가하고, Accept:
text/event-stream 요청에서 기대하는 실제 HTTP 상태와 오류 본문 형식을 검증하세요. 소유권 위반 등 대표 실패 조건을 포함하되
성공 시나리오와 mentorStreamingService.stream(...) 검증은 유지하세요.
In `@src/test/java/com/mr/domain/mentor/entity/MentorChatSessionTest.java`:
- Around line 62-71: Add a test alongside
startGenerating_inProgress_throwsException covering a GENERATING session whose
staleAfter threshold has elapsed. Use Duration.ZERO or another minimal duration,
invoke MentorChatSession.startGenerating(Duration) again, and assert that it
succeeds with a newly issued generation token rather than throwing
MENTOR_RESPONSE_IN_PROGRESS.
In `@src/test/java/com/mr/domain/mentor/service/MentorQuestionServiceTest.java`:
- Around line 16-44: Expand MentorQuestionServiceTest to cover the remaining
prepare() branches: ownership mismatch yielding MENTOR_ACCESS_DENIED, incomplete
analysis yielding MENTOR_ANALYSIS_NOT_COMPLETED, exceeding the question limit
yielding MENTOR_QUESTION_LIMIT_EXCEEDED, and reuse of an existing mentor
session. Stub the relevant mock repositories for each scenario and assert the
expected exception code or reused-session result.
In `@src/test/java/com/mr/global/client/gemini/GeminiStreamingClientTest.java`:
- Around line 1-59: GeminiStreamingClientTest의 정상 스트림 테스트에 외부 API 실패 경로 검증을
추가하세요. 동일한 요청 매칭을 재사용해 비-2xx 응답이 예외로 전파되는지, 빈 응답이 IllegalStateException("Gemini
returned an empty answer.")을 발생시키는지, 빈 또는 비-JSON data: 라인이 포함된 SSE에서도 유효한 청크 처리와
오류 동작이 유지되는지를 assertThatThrownBy 및 withStatus로 검증하세요.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c54128fe-5ca6-41d3-b6ac-0f7b8f8bad0e
📒 Files selected for processing (15)
src/main/java/com/mr/domain/mentor/controller/MentorController.javasrc/main/java/com/mr/domain/mentor/dto/req/MentorQuestionRequestDTO.javasrc/main/java/com/mr/domain/mentor/dto/res/MentorStreamEventDTO.javasrc/main/java/com/mr/domain/mentor/entity/MentorChatSession.javasrc/main/java/com/mr/domain/mentor/entity/enums/MentorChatStatus.javasrc/main/java/com/mr/domain/mentor/exception/MentorErrorStatus.javasrc/main/java/com/mr/domain/mentor/repository/MentorChatSessionRepository.javasrc/main/java/com/mr/domain/mentor/repository/MentorMessageRepository.javasrc/main/java/com/mr/domain/mentor/service/MentorQuestionService.javasrc/main/java/com/mr/domain/mentor/service/MentorStreamingService.javasrc/main/java/com/mr/global/client/gemini/GeminiStreamingClient.javasrc/test/java/com/mr/domain/mentor/controller/MentorControllerTest.javasrc/test/java/com/mr/domain/mentor/entity/MentorChatSessionTest.javasrc/test/java/com/mr/domain/mentor/service/MentorQuestionServiceTest.javasrc/test/java/com/mr/global/client/gemini/GeminiStreamingClientTest.java
📍 개요
⛓️💥 관련 이슈
🛠️ 작업 내용
🔥 리뷰 요청 사항
✅ 체크리스트
📎 참고 사항
Summary by CodeRabbit