콘텐츠로 이동

테스트·문제 해결

검증 계층

단위·통합 테스트

순수 Go 경로는 기본 테스트로, 실제 Opus encoder 경로는 opusenc tag와 libopus가 있는 환경에서 검증합니다.

go test ./...
go test -tags opusenc ./...

기본 build는 SDP, 인증 선행, ICE, 이벤트, resampler와 lifecycle을 폭넓게 검사하지만 실제 아웃바운드 Opus encode까지 성공한다는 보장은 없습니다. production image와 같은 cgo/libopus 환경에서 tag 빌드 및 테스트가 반드시 통과해야 합니다.

로컬 E2E

examples/local-e2e harness는 mock /sohri, Gateway, Pion probe를 함께 실행해 다음을 검증합니다.

  • 비-트리클 offer/answer와 세션 WSS
  • session.created 데이터채널 왕복
  • TTS raw PCM/WAV → Opus 트랙
  • sohri.tts_stream_start/end/aborted 순서와 상관 ID

Probe에는 유효한 진폭의 sine wave를 사용합니다. 모두 0인 입력이나 channel 설정이 맞지 않는 fixture는 파이프라인 결함처럼 보이는 무음 결과를 만들 수 있습니다.

부하 테스트

sohri-load는 실제 session contract를 따르는 부하 생성기입니다. 단순 SDP 성공률 외에 다음을 함께 수집해야 합니다.

  • session setup 단계별 실패율과 연결 시간
  • RTP packet loss, RTCP RTT/jitter
  • DataChannel event 순서
  • tts_duck_applied latency와 tts_playback_state true/false 균형
  • queue full·stale generation·half-duplex drop 비율
  • 종료 후 active session 회수

증상별 진단

SDP POST가 401

  1. Authorization: Bearer 형식과 토큰 앞뒤 공백을 확인합니다.
  2. API key는 sr_, JWT는 eyJ... 3분절 형태인지 확인합니다.
  3. Gateway의 형태 필터인지 /sohri의 401/403인지 setup label과 bridge 로그로 구분합니다.
  4. JWT issuer·서명키·만료, API key workspace 상태는 socket-backend에서 확인합니다.

Gateway는 credential을 자체 검증하지 않으며 백엔드 거절을 401로 매핑합니다.

브라우저에서 네트워크 오류

curl은 성공하지만 브라우저만 실패하면 CORS를 먼저 확인합니다.

  • Origin의 scheme/host/port가 CORS_ALLOWED_ORIGINS와 정확히 일치하는가
  • OPTIONS가 204인가; 403이면 Origin/요청 메서드 오류, 405이면 CORS 비활성
  • 오류 POST에도 Access-Control-Allow-Origin이 있는가

/v1/realtime/client_secrets는 제거된 경로이므로 404가 정상입니다.

answer는 왔지만 ICE가 연결되지 않음

  1. 양쪽 SDP에 candidate가 포함되었는지 확인합니다. trickle candidate를 나중에 보낼 API는 없습니다.
  2. answer의 host/srflx IP가 외부에서 도달 가능한지 확인합니다.
  3. NAT_1TO1_IP, UDP 포트 범위, LB backend와 방화벽 범위를 대조합니다.
  4. 직접 UDP가 차단된 망이면 클라이언트·Gateway에 실제 relay candidate가 생성되는 coturn 구성을 검증합니다. coturn 배포만으로는 충분하지 않습니다.

연결됐지만 마이크가 백엔드에 도착하지 않음

  • remote audio track codec이 Opus 48 kHz인지 확인합니다.
  • inbound RTP/decoded/PCM byte 메트릭을 차례로 비교합니다.
  • BARGE_IN_ENABLED=false이고 TTS가 재생 중이면 입력 폐기는 정상입니다.
  • 100 ms에 못 미치는 마지막 PCM 조각은 전송하지 않습니다.
  • packet loss, empty payload, timestamp jump와 decode error를 확인합니다.

TTS 텍스트는 오지만 소리가 없음

  1. production binary가 -tags opusenc와 libopus로 빌드되었는지 확인합니다.
  2. WAV header 또는 tts_stream_start.format이 mono s16le, 16/24/48 kHz인지 확인합니다.
  3. 활성 response가 있었는지와 tts_discarded_total{reason}을 봅니다.
  4. stale generation, queue full, pause/duck gain, half-duplex 상태를 확인합니다.
  5. tts_playback_state{active:true}가 없으면 첫 가청 frame까지 도달하지 못한 것입니다.

음성이 끊기거나 이전 답변이 다시 들림

  • stale_generation drop이 없다면 cancel과 enqueue의 세대 상관관계를 확인합니다.
  • streaming 청크 사이에서 playback state가 반복되면 true/false를 청크마다 내는 회귀입니다.
  • queue full과 deadline lateness가 증가하면 backend burst와 20 ms pacer 처리량을 비교합니다.
  • underrun gap은 OpenAI PCM 공급 지연이며 삽입 무음 누계를 함께 확인합니다.

barge-in이 너무 느리거나 봇이 복귀하지 않음

  • speech_candidate 도착 자체가 늦으면 Gateway 전단 EPD 지연입니다.
  • duck ACK latency가 1초 이내인지 확인합니다.
  • 취소된 후보의 duck_duration{reason="cancelled"}만 최소 hold 검증에 사용합니다.
  • pause_duration{end_reason="max"}는 pause 고착 또는 긴 STT 지연 조사 신호입니다.
  • 확정·재생 종료로 짧게 끝난 duck은 정상이며 최소 hold 위반으로 보면 안 됩니다.

일시적 망 전환에서 세션이 종료됨

Peer 상태와 PEER_DISCONNECTED_GRACE를 확인합니다. 기본 10초 안에 Connected로 복귀하면 세션을 유지하지만 Failed·Closed는 즉시 종료합니다. 현재 API에는 ICE restart가 없으므로 유예를 넘긴 연결은 새 SDP 세션을 만들어야 합니다.

배포 전 최소 승인 기준

  1. 유효/무효 credential에서 인증 선행 테스트 통과
  2. 실제 외부 네트워크에서 host/srflx와 필요 시 relay ICE 성공
  3. 마이크 3,200 B/100 ms WSS framing 확인
  4. 16/24/48 kHz 샘플의 Opus 20 ms playout 확인
  5. cancel·duck·pause·half-duplex와 의도적 drop 확인
  6. disconnected 10초 복구와 shutdown terminal event 확인
  7. 부하 종료 후 active session, goroutine, UDP socket 회수 확인