Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ private void insertGeneralFromPool(InterviewSession session, SessionQuestionPool
pool.getCategory(), pool.getTargetEvidence(), pool.getExpectedSignal()));
session.incrementQuestionCount();
publishQuestionEvents(session, message, reason);
maybeAutoEnd(session);
// 여기서 자동종료하지 않는다 — 방금 던진 메인질문은 답변·꼬리질문을 거쳐야 한다.
// maxQuestions 도달 종료는 꼬리 사이클 후 advanceToNextGeneral 에서 판정한다.
}

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

private void maybeAutoEnd(InterviewSession session) {
if (session.isMaxReached()) {
endSession(session, "MAX_QUESTIONS_REACHED");
}
}

private void endSession(InterviewSession session, String reason) {
try {
session.end();
Expand Down Expand Up @@ -205,13 +200,10 @@ private void applyFollowup(InterviewSession session, QuestionsCallbackPayload pa
: InterviewMessage.followup(session, nextSeq, payload.followupQuestion(), parent));
}

if (!clarification) {
session.incrementQuestionCount();
}
// 꼬리질문은 maxQuestions(메인질문 수) 카운트에 포함하지 않고, 여기서 자동종료하지도
// 않는다 — 종료는 메인질문의 꼬리 사이클이 끝나고 advanceToNextGeneral 시점에서만 일어나야
// 답변 직후 생성된 꼬리질문을 버리고 갑자기 피드백으로 튕기는 일이 없다.
publishQuestionEvents(session, message, clarification ? "CLARIFICATION" : "FOLLOWUP_READY");
if (!clarification) {
maybeAutoEnd(session);
}
log.info("callback.questions FOLLOWUP processed. sessionId={}, msg={}, clarification={}",
session.getId(), message.getId(), clarification);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,16 @@ public void cancel() {
this.status = SessionStatus.CANCELLED;
}

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

// 던진 메인질문 수가 maxQuestions 에 도달했는지. 꼬리 사이클 종료 후 advanceToNextGeneral 에서 판정.
public boolean isMaxReached() {
return totalQuestionCount != null
&& maxQuestions != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,10 @@ void apply_poolCallbackSeedsPoolAndInsertsFirstQuestion() {
}

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

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

service.apply(env);

assertThat(session.getStatus()).isEqualTo(SessionStatus.COMPLETED);
assertThat(session.getTotalQuestionCount()).isEqualTo(5);
assertThat(session.getStatus()).isEqualTo(SessionStatus.IN_PROGRESS);
assertThat(session.getTotalQuestionCount()).isEqualTo(4);
}

@Test
Expand Down Expand Up @@ -188,7 +189,7 @@ void apply_clarificationReExplainsWithoutCountingQuestion() {
// ── 새 placeholder 경로 테스트 ─────────────────────────────────────────────

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

@Test
void advanceToNextGeneral_endsWithMaxReached_whenMainQuestionsHitLimit() {
InterviewSession session = sessionFixture(33L, SessionStatus.IN_PROGRESS);
// maxQuestions=5; 메인질문 5개를 이미 던진 상태 → 다음 advance 에서 풀을 보지 않고 종료.
ReflectionTestUtils.setField(session, "totalQuestionCount", 5);
when(sessionRepository.findById(33L)).thenReturn(Optional.of(session));

service.advanceToNextGeneral(33L);

assertThat(session.getStatus()).isEqualTo(SessionStatus.COMPLETED);
verify(poolRepository, never()).findFirstBySessionIdAndUsedFalseOrderByIdxAsc(any());
}

private QuestionsCallbackEnvelope poolEnvelope(Long sessionId, List<GeneratedQuestion> questions) {
QuestionsCallbackPayload payload = new QuestionsCallbackPayload(
sessionId, "POOL", questions, null, null, null, null, null, null
Expand Down