Skip to content

Commit bb06485

Browse files
authored
Merge pull request #115 from Team-StackUp/fix/maxquestions-counts-main-only-v2
fix(session): 꼬리질문을 maxQuestions 카운트에서 제외 — 면접 조기·돌연 종료 방지
2 parents 9297ee2 + b1e6710 commit bb06485

3 files changed

Lines changed: 29 additions & 20 deletions

File tree

backend/src/main/java/com/stackup/stackup/session/application/QuestionsCallbackService.java

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ private void insertGeneralFromPool(InterviewSession session, SessionQuestionPool
129129
pool.getCategory(), pool.getTargetEvidence(), pool.getExpectedSignal()));
130130
session.incrementQuestionCount();
131131
publishQuestionEvents(session, message, reason);
132-
maybeAutoEnd(session);
132+
// 여기서 자동종료하지 않는다 — 방금 던진 메인질문은 답변·꼬리질문을 거쳐야 한다.
133+
// maxQuestions 도달 종료는 꼬리 사이클 후 advanceToNextGeneral 에서 판정한다.
133134
}
134135

135136
private void publishQuestionEvents(InterviewSession session, InterviewMessage message, String reason) {
@@ -140,12 +141,6 @@ private void publishQuestionEvents(InterviewSession session, InterviewMessage me
140141
new SessionMessageNotice(session.getId(), message.getId(), reason)));
141142
}
142143

143-
private void maybeAutoEnd(InterviewSession session) {
144-
if (session.isMaxReached()) {
145-
endSession(session, "MAX_QUESTIONS_REACHED");
146-
}
147-
}
148-
149144
private void endSession(InterviewSession session, String reason) {
150145
try {
151146
session.end();
@@ -205,13 +200,10 @@ private void applyFollowup(InterviewSession session, QuestionsCallbackPayload pa
205200
: InterviewMessage.followup(session, nextSeq, payload.followupQuestion(), parent));
206201
}
207202

208-
if (!clarification) {
209-
session.incrementQuestionCount();
210-
}
203+
// 꼬리질문은 maxQuestions(메인질문 수) 카운트에 포함하지 않고, 여기서 자동종료하지도
204+
// 않는다 — 종료는 메인질문의 꼬리 사이클이 끝나고 advanceToNextGeneral 시점에서만 일어나야
205+
// 답변 직후 생성된 꼬리질문을 버리고 갑자기 피드백으로 튕기는 일이 없다.
211206
publishQuestionEvents(session, message, clarification ? "CLARIFICATION" : "FOLLOWUP_READY");
212-
if (!clarification) {
213-
maybeAutoEnd(session);
214-
}
215207
log.info("callback.questions FOLLOWUP processed. sessionId={}, msg={}, clarification={}",
216208
session.getId(), message.getId(), clarification);
217209
}

backend/src/main/java/com/stackup/stackup/session/domain/InterviewSession.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,16 @@ public void cancel() {
179179
this.status = SessionStatus.CANCELLED;
180180
}
181181

182+
// 메인(일반) 질문만 센다. 꼬리질문은 maxQuestions 한도에 포함하지 않는다
183+
// (꼬리질문은 maxFollowupsPerQuestion 으로 별도 제한).
182184
public void incrementQuestionCount() {
183185
if (totalQuestionCount == null) {
184186
totalQuestionCount = 0;
185187
}
186188
totalQuestionCount++;
187189
}
188190

191+
// 던진 메인질문 수가 maxQuestions 에 도달했는지. 꼬리 사이클 종료 후 advanceToNextGeneral 에서 판정.
189192
public boolean isMaxReached() {
190193
return totalQuestionCount != null
191194
&& maxQuestions != null

backend/src/test/java/com/stackup/stackup/session/application/QuestionsCallbackServiceTest.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,10 @@ void apply_poolCallbackSeedsPoolAndInsertsFirstQuestion() {
9696
}
9797

9898
@Test
99-
void apply_followupAutoEndsSessionAtMaxQuestions() {
99+
void apply_followupDoesNotCountOrAutoEnd() {
100100
InterviewSession session = sessionFixture(11L, SessionStatus.IN_PROGRESS);
101-
// maxQuestions=5; total=4, then one follow-up reaches the limit.
101+
// 꼬리질문은 maxQuestions(메인질문 수) 한도에 포함되지 않으며, 도착해도 세션을
102+
// 끝내지 않는다(종료는 메인질문의 꼬리 사이클 후 advanceToNextGeneral 에서만).
102103
ReflectionTestUtils.setField(session, "totalQuestionCount", 4);
103104

104105
QuestionsCallbackEnvelope env = followupEnvelope(11L, 200L, "Follow-up?");
@@ -114,8 +115,8 @@ void apply_followupAutoEndsSessionAtMaxQuestions() {
114115

115116
service.apply(env);
116117

117-
assertThat(session.getStatus()).isEqualTo(SessionStatus.COMPLETED);
118-
assertThat(session.getTotalQuestionCount()).isEqualTo(5);
118+
assertThat(session.getStatus()).isEqualTo(SessionStatus.IN_PROGRESS);
119+
assertThat(session.getTotalQuestionCount()).isEqualTo(4);
119120
}
120121

121122
@Test
@@ -188,7 +189,7 @@ void apply_clarificationReExplainsWithoutCountingQuestion() {
188189
// ── 새 placeholder 경로 테스트 ─────────────────────────────────────────────
189190

190191
@Test
191-
void apply_followupNormal_updatesPlaceholderInPlaceAndCounts() {
192+
void apply_followupNormal_updatesPlaceholderInPlaceWithoutCounting() {
192193
InterviewSession session = sessionFixture(20L, SessionStatus.IN_PROGRESS);
193194
// placeholder: followupPlaceholder 의 sentinel content
194195
InterviewMessage placeholder = InterviewMessage.followupPlaceholder(
@@ -213,8 +214,8 @@ void apply_followupNormal_updatesPlaceholderInPlaceAndCounts() {
213214
// placeholder 가 in-place 로 업데이트되어야 함
214215
assertThat(placeholder.getContent()).isEqualTo("꼬리질문 내용?");
215216
assertThat(placeholder.getStatus()).isEqualTo(com.stackup.stackup.session.domain.MessageStatus.COMPLETED);
216-
// 질문 수 카운트 증가
217-
assertThat(session.getTotalQuestionCount()).isEqualTo(1);
217+
// 꼬리질문은 메인질문 카운트(maxQuestions 한도)에 포함되지 않는다.
218+
assertThat(session.getTotalQuestionCount()).isEqualTo(0);
218219
// save 는 placeholder 자체에 대해 호출됨 (새 InterviewMessage INSERT 아님)
219220
ArgumentCaptor<InterviewMessage> cap = ArgumentCaptor.forClass(InterviewMessage.class);
220221
verify(messageRepository).save(cap.capture());
@@ -281,6 +282,19 @@ void apply_followupDontKnow_deletesPlaceholderAndAdvances() {
281282
assertThat(session.getStatus()).isEqualTo(SessionStatus.COMPLETED);
282283
}
283284

285+
@Test
286+
void advanceToNextGeneral_endsWithMaxReached_whenMainQuestionsHitLimit() {
287+
InterviewSession session = sessionFixture(33L, SessionStatus.IN_PROGRESS);
288+
// maxQuestions=5; 메인질문 5개를 이미 던진 상태 → 다음 advance 에서 풀을 보지 않고 종료.
289+
ReflectionTestUtils.setField(session, "totalQuestionCount", 5);
290+
when(sessionRepository.findById(33L)).thenReturn(Optional.of(session));
291+
292+
service.advanceToNextGeneral(33L);
293+
294+
assertThat(session.getStatus()).isEqualTo(SessionStatus.COMPLETED);
295+
verify(poolRepository, never()).findFirstBySessionIdAndUsedFalseOrderByIdxAsc(any());
296+
}
297+
284298
private QuestionsCallbackEnvelope poolEnvelope(Long sessionId, List<GeneratedQuestion> questions) {
285299
QuestionsCallbackPayload payload = new QuestionsCallbackPayload(
286300
sessionId, "POOL", questions, null, null, null, null, null, null

0 commit comments

Comments
 (0)