Skip to content

Commit d24d49b

Browse files
committed
feat: 음성 콜백 성공 실패 처리 보강
1 parent 755b866 commit d24d49b

4 files changed

Lines changed: 51 additions & 5 deletions

File tree

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import com.stackup.stackup.session.application.event.AnswerSubmittedEvent;
1212
import com.stackup.stackup.session.domain.InterviewMessage;
1313
import com.stackup.stackup.session.domain.InterviewMessageRepository;
14-
import com.stackup.stackup.session.domain.MessageStatus;
1514
import com.stackup.stackup.session.domain.MessageVoiceAnalysis;
1615
import com.stackup.stackup.session.domain.MessageVoiceAnalysisRepository;
1716
import lombok.RequiredArgsConstructor;
@@ -64,14 +63,19 @@ public void apply(VoiceCallbackEnvelope envelope) {
6463
}
6564

6665
if (p.errorCode() != null && !p.errorCode().isBlank()) {
67-
message.markStatus(MessageStatus.FAILED);
68-
sseEventPublisher.publishToSession(p.sessionId(), SseEventType.SESSION_MESSAGE,
69-
new VoiceFailedNotice(p.sessionId(), message.getId(), p.errorCode()));
66+
failVoiceMessage(p.sessionId(), message, p.errorCode());
7067
markProcessed(envelope.messageId());
7168
log.warn("callback.voice STT failed. sessionId={}, msg={}, code={}",
7269
p.sessionId(), message.getId(), p.errorCode());
7370
return;
7471
}
72+
if (p.transcript() == null || p.transcript().isBlank()) {
73+
failVoiceMessage(p.sessionId(), message, "STT_EMPTY_TRANSCRIPT");
74+
markProcessed(envelope.messageId());
75+
log.warn("callback.voice STT empty transcript. sessionId={}, msg={}",
76+
p.sessionId(), message.getId());
77+
return;
78+
}
7579

7680
message.completeWithTranscript(p.transcript());
7781

@@ -113,7 +117,20 @@ public void apply(VoiceCallbackEnvelope envelope) {
113117
public record VoiceTranscribedNotice(Long sessionId, Long messageId, String transcript) {
114118
}
115119

116-
public record VoiceFailedNotice(Long sessionId, Long messageId, String errorCode) {
120+
public record VoiceFailedNotice(Long sessionId, Long messageId, String errorCode, String fallbackText) {
121+
}
122+
123+
private void failVoiceMessage(Long sessionId, InterviewMessage message, String errorCode) {
124+
message.failVoiceTranscription();
125+
VoiceFailedNotice notice = new VoiceFailedNotice(
126+
sessionId,
127+
message.getId(),
128+
errorCode,
129+
message.getContent()
130+
);
131+
sseEventPublisher.publishToSession(sessionId, SseEventType.SESSION_MESSAGE, notice);
132+
sseEventPublisher.publishToUser(message.getSession().getUser().getId(),
133+
SseEventType.SESSION_MESSAGE, notice);
117134
}
118135

119136
private String fillerToJson(java.util.Map<String, Integer> map) {

backend/src/main/java/com/stackup/stackup/session/application/dto/MessageResult.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public record MessageResult(
1111
Integer sequenceNumber,
1212
MessageRole role,
1313
String content,
14+
String audioFilePath,
1415
Long parentMessageId,
1516
MessageStatus status,
1617
Instant createdAt
@@ -22,6 +23,7 @@ public static MessageResult of(InterviewMessage m) {
2223
m.getSequenceNumber(),
2324
m.getRole(),
2425
m.getContent(),
26+
m.getAudioFilePath(),
2527
m.getParentMessage() == null ? null : m.getParentMessage().getId(),
2628
m.getStatus(),
2729
m.getCreatedAt()

backend/src/main/java/com/stackup/stackup/session/presentation/dto/MessageResponse.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public record MessageResponse(
1111
Integer sequenceNumber,
1212
MessageRole role,
1313
String content,
14+
String audioFilePath,
1415
Long parentMessageId,
1516
MessageStatus status,
1617
Instant createdAt
@@ -22,6 +23,7 @@ public static MessageResponse from(MessageResult r) {
2223
r.sequenceNumber(),
2324
r.role(),
2425
r.content(),
26+
r.audioFilePath(),
2527
r.parentMessageId(),
2628
r.status(),
2729
r.createdAt()

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,31 @@ void apply_marksMessageFailedOnErrorCode() {
8686
service.apply(env);
8787

8888
assertThat(voiceMsg.getStatus()).isEqualTo(MessageStatus.FAILED);
89+
assertThat(voiceMsg.getContent()).isEqualTo(InterviewMessage.VOICE_TRANSCRIPTION_FAILED_TEXT);
90+
verify(voiceAnalysisRepository, never()).save(any(MessageVoiceAnalysis.class));
91+
verify(events, never()).publishEvent(any(AnswerSubmittedEvent.class));
92+
verify(sseEventPublisher).publishToSession(eq(50L), eq(SseEventType.SESSION_MESSAGE), any());
93+
verify(sseEventPublisher).publishToUser(eq(1L), eq(SseEventType.SESSION_MESSAGE), any());
94+
}
95+
96+
@Test
97+
void apply_marksMessageFailedOnBlankTranscript() {
98+
InterviewSession session = sessionFixture(50L);
99+
InterviewMessage voiceMsg = InterviewMessage.voiceInterviewee(session, 2, null, null);
100+
ReflectionTestUtils.setField(voiceMsg, "id", 200L);
101+
102+
VoiceCallbackPayload payload = new VoiceCallbackPayload(50L, 200L, " ", 120.0, 1.0,
103+
Map.of(), 0.8, null);
104+
VoiceCallbackEnvelope env = new VoiceCallbackEnvelope("vc-blank", "callback.voice", "1", "t",
105+
null, "ai", payload, null);
106+
107+
when(processedMessageRepository.existsById("vc-blank")).thenReturn(false);
108+
when(messageRepository.findById(200L)).thenReturn(Optional.of(voiceMsg));
109+
110+
service.apply(env);
111+
112+
assertThat(voiceMsg.getStatus()).isEqualTo(MessageStatus.FAILED);
113+
assertThat(voiceMsg.getContent()).isEqualTo(InterviewMessage.VOICE_TRANSCRIPTION_FAILED_TEXT);
89114
verify(voiceAnalysisRepository, never()).save(any(MessageVoiceAnalysis.class));
90115
verify(events, never()).publishEvent(any(AnswerSubmittedEvent.class));
91116
}

0 commit comments

Comments
 (0)