+
    ڮZjg  c                      R t ^ RIHt ^ RIt^ RIHu Ht ^ RI	t	^ RI
t
^ RIHt ]
P                  R 4       tRR R lltR tR tR	 tR
 tR tR tR tR tR tR# )u  BDD tests for runtime/api/voice.py.

Given: compiled test-persona fixture + FakeProvider + FakeSTT (FAKE_PROVIDER=1)
When:  WebSocket voice conversation flow
Then:  correct protocol frames in correct order

All offline — no model downloads, no real API calls.
)annotationsN)
TestClientc                ~    VP                  R R4       VP                  R\        V 4      4       ^ RIHp \	        V4      # )FAKE_PROVIDER1MENTORCORE_PERSONAS_ROOT)app)setenvstrruntime.api.appr   r   )examples_persona_rootmonkeypatchr   s   && E/Users/jameslopez/projects/MentorCore/tests/runtime/test_voice_api.pyclientr      s6    ,137L3MN#c?    c                    V ^8  d   QhRRRR/# )   audio_bytesbytesreturnzlist[dict | bytes] )formats   "r   __annotate__r      s      E >P r   c                   V P                  \        P                  ! RR/4      4       V P                  V4       V P                  \        P                  ! RR/4      4       . p V P	                  4       pVP                  R4      '       d   VP                  VR,          4       KB  VP                  R4      '       g   K[  \        P                  ! VR,          4      pVP                  V4       VP                  R4      R8X  g   K   V# )z5Send one voice turn and collect all server responses.typestartstopr   textdone)	send_textjsondumps
send_bytesreceivegetappendloads)wsr   framesdatamsgs   &&   r   _ws_voice_turnr*      s    LLVW-./MM+LLVV,-.!#F
zz|88GMM$w-(XXf**T&\*CMM#wwv&(Mr   c           	     `   V P                  R4      ;_uu_ 4       p\        P                  ! VP                  4       4      pVR,          pRqCV8H  qU'       gy   \        P
                  ! RV3RW434      R\        P                  ! V4      R\        P                  ! V4      /,          pRRV/,          p\        \        P                  ! V4      4      hR;p;rTR	q3V9   qU'       g   \        P
                  ! RV3RW234      R\        P                  ! V4      R
R\        P                  ! 4       9   g   \        P                  ! V4      '       d   \        P                  ! V4      MR/,          pRRV/,          p	\        \        P                  ! V	4      4      hR;r5RRR4       R#   + '       g   i     R# ; i)zDGiven: test-persona. When: WS connect. Then: session frame received./api/voice/test-personar   sessionpy1py4assert %(py6)spy6N
session_idpy3r)   assert %(py5)spy5==z%(py1)s == %(py4)sin)z%(py1)s in %(py3)s)websocket_connectr   r%   receive_text
@pytest_ar_call_reprcompare	_safereprAssertionError_format_explanation@py_builtinslocals_should_repr_global_name)
r   r&   r)   @py_assert0@py_assert3@py_assert2@py_format5@py_format7@py_format4@py_format6s
   &         r   (test_voice_ws_connects_and_sends_sessionrL   1   s    		!	!";	<	<jj*+6{'i'i'''''{'''{'''i''''''''"s"""""|"""|""""""s""""s"""""""" 
=	<	<	<s   D+FAFF-	c           	        V P                  R4      ;_uu_ 4       p\        P                  ! VP                  4       4      pVR,          pRqCV8H  qU'       gy   \        P
                  ! R	V3R
W434      R\        P                  ! V4      R\        P                  ! V4      /,          pRRV/,          p\        \        P                  ! V4      4      hR;p;rTRRR4       R#   + '       g   i     R# ; i)z;Given: unknown slug. When: WS connect. Then: error + close.z/api/voice/nonexistentr   errorr.   r/   r0   r1   Nr6   r8   )	r;   r   r%   r<   r=   r>   r?   r@   rA   )r   r&   r)   rE   rF   rG   rH   rI   s   &       r   $test_voice_ws_unknown_persona_closesrO   9   s    		!	!":	;	;rjj*+6{%g%g%%%%%{%%%{%%%g%%%%%%%% 
<	;	;	;s   B8CC-	c                   V P                  R4      ;_uu_ 4       pVP                  4        \        V4      pV Uu. uF5  p\        V\        4      '       g   K  VP                  R4      R8X  g   K3  VNK7  	  pp\        V4      p^qeV8H  qw'       Eg   \        P                  ! RV3RWV34      RR\        P                  ! 4       9   g!   \        P                  ! \        4      '       d   \        P                  ! \        4      MRRR\        P                  ! 4       9   g   \        P                  ! V4      '       d   \        P                  ! V4      MRR\        P                  ! V4      R	\        P                  ! V4      /,          pR
RV/,          p	\        \        P                  ! V	4      4      hR;p;rvRq^ ,          qV9   qU'       gy   \        P                  ! RV3RW34      R\        P                  ! V
4      R\        P                  ! V4      /,          pRR	V/,          p\        \        P                  ! V4      4      hR;p
;r[RRR4       R# u upi   + '       g   i     R# ; i)z8Given: audio bytes. Then: transcript frame before audio.r,   r   
transcriptpy0lenr.   transcript_framesr3   r1   assert %(py8)spy8Nr   r/   r0   r6   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr9   )z%(py1)s in %(py4)sr;   r<   r*   
isinstancedictr#   rS   r=   r>   rB   rC   rD   r?   r@   rA   )r   r&   r'   frT   rG   @py_assert5@py_assert4rI   @py_format9rE   rF   rH   s   &            r    test_voice_turn_sends_transcriptr_   C   sk   		!	!";	<	<
#
!At!4Av,9VAAv 	 
 $%*******%******s****s*******$****$***%***********-1--------v---v------------ 
=	<
 
=	<	<s7    III/I5A/I%AI7DIII#	c                x   V P                  R4      ;_uu_ 4       pVP                  4        \        V4      pV Uu. uF  p\        V\        4      '       g   K  VNK  	  pp\        V4      p^ qeV8  qw'       Eg   \        P                  ! RV3RWV34      RR\        P                  ! 4       9   g!   \        P                  ! \
        4      '       d   \        P                  ! \
        4      MRRR\        P                  ! 4       9   g   \        P                  ! V4      '       d   \        P                  ! V4      MRR\        P                  ! V4      R\        P                  ! V4      /,          pRR	V/,          p	\        \        P                  ! V	4      4      hR
;p;rvR
R
R
4       R
# u upi   + '       g   i     R
# ; i)zAGiven: non-empty transcript. Then: at least one binary PCM frame.r,   rR   rS   r.   binary_framesr3   r1   rU   rV   N)>)z/%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} > %(py6)s)r;   r<   r*   rY   r   rS   r=   r>   rB   rC   rD   r?   r@   rA   )
r   r&   r'   r[   ra   rG   r\   r]   rI   r^   s
   &         r   "test_voice_turn_sends_binary_audiorc   O   s    		!	!";	<	<
#$*CFqjE.BFC=!%A%A%%%%%!%%%%%%s%%%%s%%%%%%%=%%%%=%%%!%%%A%%%%%%%%	 
=	< D 
=	<	<s1    F(F#F#A/F(AF(A:F(#F((F9	c                   V P                  R4      ;_uu_ 4       pVP                  4        \        V4      pV Uu. uF5  p\        V\        4      '       g   K  VP                  R4      R8X  g   K3  VNK7  	  ppRqUP                  pR V 4       qv! V4      q'       g   RR\        P                  ! V4      R\        P                  ! V4      R	\        P                  ! V4      R
\        P                  ! V4      /,          p	\        \        P                  ! V	4      4      hR;p;p;rxRRR4       R# u upi   + '       g   i     R# ; i)zJGiven a voice turn. Then: the assistant reply is available as text frames.r,   r   r    c              3  2   "   T F  qR ,          x  K  	  R# 5i)r   Nr   ).0r[   s   & r   	<genexpr>9test_voice_turn_streams_assistant_text.<locals>.<genexpr>`   s     6+Qyy+s   zFassert %(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.join
}(%(py5)s)
}r.   r3   r5   py7N)r;   r<   r*   rY   rZ   r#   joinr=   r?   r@   rA   )
r   r&   r'   r[   text_framesrE   rG   r]   @py_assert6@py_format8s
   &         r   &test_voice_turn_streams_assistant_textro   X   s    		!	!";	<	<
#
!At!4Av&9PAAv 	 
 6ww66+66w6666666r666w666666666666666 
=	<
 
=	<	<s)    D1D,D,/D,5B-D1,D11E	c           	     8   V P                  R4      ;_uu_ 4       pVP                  4        \        V4      pV Uu. uF  p\        V\        4      '       g   K  VNK  	  ppVR	,          R,          pRqeV8H  qw'       gy   \
        P                  ! R
V3RWV34      R\
        P                  ! V4      R\
        P                  ! V4      /,          pRRV/,          p	\        \
        P                  ! V	4      4      hR;p;rvRRR4       R# u upi   + '       g   i     R# ; i)z&Then: last text frame is {type: done}.r,   r   r   r.   r/   r0   r1   Nr6   r8   )
r;   r<   r*   rY   rZ   r=   r>   r?   r@   rA   )
r   r&   r'   r[   rl   rE   rF   rG   rH   rI   s
   &         r   test_voice_turn_ends_with_donerr   c   s    		!	!";	<	<
#"(@&QJq$,?qq&@2v&0&0&00000&000&000&00000000	 
=	< A 
=	<	<s#    DDDBDDD	c           
     T   V P                  R4      ;_uu_ 4       pVP                  4        \        V4      p\        V4       UUu. uF   w  r4\	        V\
        4      '       g   K  W43NK"  	  ppp\        R V 4       4      p\        R V 4       4      pWg8  q'       g   \        P                  ! RV3RWg34      RR\        P                  ! 4       9   g   \        P                  ! V4      '       d   \        P                  ! V4      MRRR\        P                  ! 4       9   g   \        P                  ! V4      '       d   \        P                  ! V4      MR/,          p	RR	V	/,          p
\        \        P                  ! V
4      4      hR
pR
R
R
4       R
# u uppi   + '       g   i     R
# ; i)u4   Protocol order: transcript → (binary…) → done.r,   c              3  Z   "   T F!  w  rVP                  R 4      R8X  g   K  Vx  K#  	  R# 5i)r   rQ   Nr#   rg   ir[   s   &  r   rh   )test_voice_frame_order.<locals>.<genexpr>s   s#     YKDA155=L;XaaK   +
+c              3  Z   "   T F!  w  rVP                  R 4      R8X  g   K  Vx  K#  	  R# 5ir   r   Nru   rv   s   &  r   rh   rx   t   s#     MkdaQUU6]f5Lkry   rR   transcript_idxpy2done_idxzassert %(py4)sr/   N)<)z%(py0)s < %(py2)s)r;   r<   r*   	enumeraterY   rZ   nextr=   r>   rB   rC   rD   r?   r@   rA   )r   r&   r'   rw   r[   rl   r|   r~   @py_assert1@py_format3rH   s   &          r   test_voice_frame_orderr   l   s   		!	!";	<	<
#*3F*;S*;$!z!T?Rvv*;SYKYYMkMM(((((~((((((~((((~((((((((((((((((((( 
=	< T	 
=	<	<s2   *FF
#F
*BF-AF;AFFF'	c                   V P                  R4      ;_uu_ 4       pVP                  4        \        V4       \        V4      pV Uu. uF5  p\        V\        4      '       g   K  VP                  R4      R8X  g   K3  VNK7  	  pp\        V4      p^qeV8H  qw'       Eg   \        P                  ! RV3RWV34      RR\        P                  ! 4       9   g!   \        P                  ! \        4      '       d   \        P                  ! \        4      MRRR\        P                  ! 4       9   g   \        P                  ! V4      '       d   \        P                  ! V4      MRR\        P                  ! V4      R	\        P                  ! V4      /,          pR
RV/,          p	\        \        P                  ! V	4      4      hR;p;rvRRR4       R# u upi   + '       g   i     R# ; i)z6Two turns in same WS connection share session history.r,   r   r   rR   rS   r.   done_framesr3   r1   rU   rV   Nr6   rW   rX   )
r   r&   frames2r[   r   rG   r\   r]   rI   r^   s
   &         r   (test_voice_multiturn_accumulates_sessionr   y   s   		!	!";	<	<
r $")]'QZ4-@qQUU6]V\E\qq'];$1$1$$$$$$$$$$$s$$$$s$$$$$$$;$$$$;$$$$$$1$$$$$$$$ 
=	<
 ^ 
=	<	<s8   +GG"G:G A/G0AGA:GGG	c                l   V P                  RRR/R7      p\        R VP                  P                  4        4       4      pV P	                  RV 24      ;_uu_ 4       p\
        P                  ! VP                  4       4      pRRR	V/qTV8H  qf'       g   \        P                  ! RV3RWE34      R
R\        P                  ! 4       9   g   \        P                  ! V4      '       d   \        P                  ! V4      MRR\        P                  ! V4      /,          pRRV/,          p\        \        P                  ! V4      4      hR;re\!        V4      p	R V	 4       p\#        V4      q'       g   RR
R\        P                  ! 4       9   g!   \        P                  ! \"        4      '       d   \        P                  ! \"        4      MRR\        P                  ! V4      R\        P                  ! V
4      /,          p\        \        P                  ! V4      4      hR;rjRRR4       R#   + '       g   i     R# ; i)z?A voice connection can continue a session created by text chat.z/api/chat/test-personamessagezFirst turn.)r   c              3    "   T Fu  pVP                  R 4      '       g   K  \        P                  ! VR,          4      P                  R4      R8X  g   KO  \        P                  ! VR,          4      R,          x  Kw  	  R# 5i)zdata: :   NNr   r-   r2   N)
startswithr   r%   r#   )rg   lines   & r   rh   =test_voice_reuses_an_existing_text_session.<locals>.<genexpr>   s_      3D??8$ 	+)-DH)=)A)A&)IY)V 	+

48\**3s   A?.A?,A?z#/api/voice/test-persona?session_id=r   r-   r2   rR   r)   r3   r4   r5   Nc              3  z   "   T F1  p\        V\        4      ;'       d    VP                  R 4      R8H  x  K3  	  R# 5ir{   )rY   rZ   r#   )rg   frames   & r   rh   r      s2     _X^u:eT*JJuyy/@F/JJX^s   ;;z,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}anyr}   r/   r6   )z%(py0)s == %(py3)s)postr   r   
splitlinesr;   r   r%   r<   r=   r>   rB   rC   rD   r?   r@   rA   r*   r   )r   chat_responser2   r&   r)   rG   r   rJ   rK   r'   rF   rH   s   &           r   *test_voice_reuses_an_existing_text_sessionr      s\   KK 8	=?YKZM !&&113 J 
	!	!$G
|"T	U	UY[jj*+y,
CCCCCCCCsCCCCCCsCCCCsCCCCCCCCCCCC#_X^__s__________s____s______________	 
V	U	U	Us   BH"#CH"'A1H""H3	)sd                                                                                                       )__doc__
__future__r   builtinsrB   _pytest.assertion.rewrite	assertionrewriter=   r   pytestfastapi.testclientr   fixturer   r*   rL   rO   r_   rc   ro   rr   r   r   r   r   r   r   <module>r      sg    #     )  ,#&	.&71
)%`r   