
    ڮZjg                        d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlmZ e
j        d             ZdddZd Zd Zd Zd Zd Zd Zd Zd Zd ZdS )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                    |                     dd           |                     dt          |                      ddlm} t	          |          S )NFAKE_PROVIDER1MENTORCORE_PERSONAS_ROOTr   )app)setenvstrruntime.api.appr	   r   )examples_persona_rootmonkeypatchr	   s      E/Users/jameslopez/projects/MentorCore/tests/runtime/test_voice_api.pyclientr      sV    ,,,137L3M3MNNN######c??    d                                                                                                       audio_bytesbytesreturnlist[dict | bytes]c                "   |                      t          j        ddi                     |                     |           |                      t          j        ddi                     g }	 |                                 }|                    d          r|                    |d                    n^|                    d          rIt          j        |d                   }|                    |           |                    d          dk    rn|S )z5Send one voice turn and collect all server responses.typestartstopTr   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                   |                      d          5 }t          j        |                                          }|d         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d	x}x}}d
}||v }|st	          j        d|fd||f          t	          j        |          dt          j
                    v st	          j        |          rt	          j        |          nddz  }dd|iz  }	t          t	          j        |	                    d	x}}d	d	d	           d	S # 1 swxY w Y   d	S )zDGiven: test-persona. When: WS connect. Then: session frame received./api/voice/test-personar   session==z%(py1)s == %(py4)spy1py4assert %(py6)spy6N
session_idin)z%(py1)s in %(py3)sr(   )r1   py3assert %(py5)spy5)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   		!	!";	<	< #j**++6{'i'{i''''''''''{i'''''{''''i'''''''''''''''''''''''''''"|s""""""""""|s"""""|"""""""""""s"""""s"""""""""""""""""""""""""""# # # # # # # # # # # # # # # # # #s   EE11E58E5c                   |                      d          5 }t          j        |                                          }|d         }d}||k    }|slt	          j        d|fd||f          t	          j        |          t	          j        |          dz  }dd|iz  }t          t	          j        |                    d	x}x}}d	d	d	           d	S # 1 swxY w Y   d	S )
z;Given: unknown slug. When: WS connect. Then: error + close.z/api/voice/nonexistentr   errorr-   r/   r0   r3   r4   N)	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   s6   		!	!":	;	; &rj**++6{%g%{g%%%%%%%%%%{g%%%%%{%%%%g%%%%%%%%%%%%%%%%%%%%%%%%%%%& & & & & & & & & & & & & & & & & &s   B+CCCc                   |                      d          5 }|                                 t          |          }d |D             }t          |          }d}||k    }|st	          j        d|fd||f          dt          j                    v st	          j        t                    rt	          j	        t                    nddt          j                    v st	          j        |          rt	          j	        |          ndt	          j	        |          t	          j	        |          dz  }d	d
|iz  }t          t	          j        |                    dx}x}}d}	|d         }
|	|
v }|slt	          j        d|fd|	|
f          t	          j	        |	          t	          j	        |
          dz  }dd|iz  }t          t	          j        |                    dx}	x}}
ddd           dS # 1 swxY w Y   dS )z8Given: audio bytes. Then: transcript frame before audio.r+   c                n    g | ]2}t          |t                    r|                    d           dk    0|3S )r   
transcript
isinstancedictr"   .0fs     r   
<listcomp>z4test_voice_turn_sends_transcript.<locals>.<listcomp>H   sE     
 
 
At!4!4
9:v,9V9VA9V9V9Vr      r-   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)slentranscript_framespy0r1   r8   r4   assert %(py8)spy8Nr   r   r6   )z%(py1)s in %(py4)sr0   r3   r4   r;   r<   r)   r\   r=   r>   rB   rC   rD   r?   r@   rA   )r   r%   r&   r]   rG   @py_assert5@py_assert4rI   @py_format9rE   rF   rH   s               r    test_voice_turn_sends_transcriptrf   C   s   		!	!";	<	< .
##
 

 
 
 $%%**%**********%************s*****s*************$*****$******%*******************************-*1--v-----------v------v--------------------------------. . . . . . . . . . . . . . . . . .s   GG&&G*-G*c                   |                      d          5 }|                                 t          |          }d |D             }t          |          }d}||k    }|st	          j        d|fd||f          dt          j                    v st	          j        t                    rt	          j	        t                    nddt          j                    v st	          j        |          rt	          j	        |          ndt	          j	        |          t	          j	        |          dz  }d	d
|iz  }t          t	          j        |                    dx}x}}ddd           dS # 1 swxY w Y   dS )zAGiven: non-empty transcript. Then: at least one binary PCM frame.r+   c                <    g | ]}t          |t                    |S  )rT   r   rV   s     r   rY   z6test_voice_turn_sends_binary_audio.<locals>.<listcomp>T   s'    CCCqjE.B.BCCCCr   r   )>)z/%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} > %(py6)sr\   binary_framesr^   r`   ra   Nrb   )	r   r%   r&   rk   rG   rc   rd   rI   re   s	            r   "test_voice_turn_sends_binary_audiorl   O   s   		!	!";	<	< &
##CCFCCC=!!%A%!A%%%%%%%%%%!A%%%%%%%%%%%%s%%%%%s%%%%%%%%%%%%%=%%%%%=%%%%%%!%%%%A%%%%%%%%%%%%%%%%%%%%%%%%%%%	& & & & & & & & & & & & & & & & & &s   EE$$E(+E(c                   |                      d          5 }|                                 t          |          }d |D             }d}|j        }d |D             } ||          }|ssdt	          j        |          t	          j        |          t	          j        |          t	          j        |          dz  }t          t	          j        |                    dx}x}x}}ddd           dS # 1 swxY w Y   dS )zJGiven a voice turn. Then: the assistant reply is available as text frames.r+   c                n    g | ]2}t          |t                    r|                    d           dk    0|3S )r   r   rS   rV   s     r   rY   z:test_voice_turn_streams_assistant_text.<locals>.<listcomp>]   sE     
 
 
At!4!4
9:v&9P9PA9P9P9Pr    c              3  &   K   | ]}|d          V  dS )r   Nri   rV   s     r   	<genexpr>z9test_voice_turn_streams_assistant_text.<locals>.<genexpr>`   s&      66Qqy666666r   zFassert %(py7)s
{%(py7)s = %(py3)s
{%(py3)s = %(py1)s.join
}(%(py5)s)
})r1   r8   r:   py7N)r;   r<   r)   joinr=   r?   r@   rA   )	r   r%   r&   text_framesrE   rG   rd   @py_assert6@py_format8s	            r   &test_voice_turn_streams_assistant_textrw   X   sh   		!	!";	<	< 7
##
 

 
 
 6rw666+6666ww666666666r6666w6666666666666666666666666666666667 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7s   CC00C47C4c                   |                      d          5 }|                                 t          |          }d |D             }|d         d         }d}||k    }|slt          j        d|fd||f          t          j        |          t          j        |          dz  }d	d
|iz  }t          t          j        |                    dx}x}}ddd           dS # 1 swxY w Y   dS )z&Then: last text frame is {type: done}.r+   c                <    g | ]}t          |t                    |S ri   rT   rU   rV   s     r   rY   z2test_voice_turn_ends_with_done.<locals>.<listcomp>h   s'    @@@QJq$,?,?@q@@@r   r   r   r-   r/   r0   r3   r4   N)r;   r<   r)   r=   r>   r?   r@   rA   )	r   r%   r&   rt   rE   rF   rG   rH   rI   s	            r   test_voice_turn_ends_with_doner|   c   sU   		!	!";	<	< 1
##@@&@@@2v&0&0&&0000000000&&00000&0000&000000000000000000000000000	1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1s   B:CC!$C!c                   |                      d          5 }|                                 t          |          }d t          |          D             }t	          d |D                       }t	          d |D                       }||k     }|st          j        d|fd||f          dt          j                    v st          j	        |          rt          j
        |          nddt          j                    v st          j	        |          rt          j
        |          ndd	z  }d
d|iz  }t          t          j        |                    d}ddd           dS # 1 swxY w Y   dS )u4   Protocol order: transcript → (binary…) → done.r+   c                F    g | ]\  }}t          |t                    ||fS ri   rz   rW   irX   s      r   rY   z*test_voice_frame_order.<locals>.<listcomp>r   s/    SSS$!Qz!T?R?RS1vSSSr   c              3  R   K   | ]"\  }}|                     d           dk    |V  #dS )r   rR   Nr"   r   s      r   rq   z)test_voice_frame_order.<locals>.<genexpr>s   s:      YYDAq155==L;X;Xa;X;X;X;XYYr   c              3  R   K   | ]"\  }}|                     d           dk    |V  #dS r   r   Nr   r   s      r   rq   z)test_voice_frame_order.<locals>.<genexpr>t   s:      MMdaQUU6]]f5L5L5L5L5L5LMMr   )<)z%(py0)s < %(py2)stranscript_idxdone_idx)r_   py2zassert %(py4)sr2   N)r;   r<   r)   	enumeratenextr=   r>   rB   rC   rD   r?   r@   rA   )	r   r%   r&   rt   r   r   @py_assert1@py_format3rH   s	            r   test_voice_frame_orderr   l   s   		!	!";	<	< )
##SS)F*;*;SSSYYKYYYYYMMkMMMMM((((((((((~((((((((((((~(((((~((((((((((((((((((((((((((((((((((((((((((() ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )s   D;EE"%E"c                    |                      d          5 }|                                 t          |           t          |          }d |D             }t          |          }d}||k    }|st	          j        d|fd||f          dt          j                    v st	          j        t                    rt	          j	        t                    nddt          j                    v st	          j        |          rt	          j	        |          ndt	          j	        |          t	          j	        |          dz  }d	d
|iz  }t          t	          j        |                    dx}x}}ddd           dS # 1 swxY w Y   dS )z6Two turns in same WS connection share session history.r+   c                n    g | ]2}t          |t                    |                    d           dk    0|3S )r   r   rS   rV   s     r   rY   z<test_voice_multiturn_accumulates_session.<locals>.<listcomp>   s<    ]]]QZ4-@-@]QUU6]]V\E\E\qE\E\E\r   rZ   r-   r[   r\   done_framesr^   r`   ra   Nrb   )	r   r%   frames2r   rG   rc   rd   rI   re   s	            r   (test_voice_multiturn_accumulates_sessionr   y   s   		!	!";	<	< %
r $$]]']]];$1$1$$$$$$$$$$1$$$$$$$$$$$$s$$$$$s$$$$$$$$$$$$$;$$$$$;$$$$$$$$$$1$$$$$$$$$$$$$$$$$$$$$$$$$$$% % % % % % % % % % % % % % % % % %s   EE33E7:E7c                    |                      dddi          }t          d |j                                        D                       }|                     d|           5 }t          j        |                                          }d|d}||k    }|st          j	        d	|fd
||f          dt          j                    v st          j        |          rt          j        |          ndt          j        |          dz  }dd|iz  }t          t          j        |                    dx}}t!          |          }	d |	D             }t#          |          }
|
sddt          j                    v st          j        t"                    rt          j        t"                    ndt          j        |          t          j        |
          dz  }t          t          j        |                    dx}}
ddd           dS # 1 swxY w Y   dS )z?A voice connection can continue a session created by text chat.z/api/chat/test-personamessagezFirst turn.)r   c              3     K   | ]n}|                     d           rWt          j        |dd                                       d          dk    Jt          j        |dd                   d         V  odS )zdata:    Nr   r,   r5   )
startswithr   r$   r"   )rW   lines     r   rq   z=test_voice_reuses_an_existing_text_session.<locals>.<genexpr>   s        ??8$$ *.DH)=)=)A)A&)I)IY)V)V 	
48\*)V)V)V)V r   z#/api/voice/test-persona?session_id=r,   )r   r5   r-   )z%(py0)s == %(py3)sr(   )r_   r8   r9   r:   Nc              3  r   K   | ]2}t          |t                    o|                    d           dk    V  3dS r   rS   )rW   frames     r   rq   z=test_voice_reuses_an_existing_text_session.<locals>.<genexpr>   sB      __u:eT**Juyy/@/@F/J______r   z,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}any)r_   r   r2   )postr   r   
splitlinesr;   r   r$   r<   r=   r>   rB   rC   rD   r?   r@   rA   r)   r   )r   chat_responser5   r%   r(   rG   r   rJ   rK   r&   rF   rH   s               r   *test_voice_reuses_an_existing_text_sessionr      s   KK 8	=?YKZZM  !&1133    J 
	!	!"T
"T"T	U	U `Y[j**++(
CCCsCCCCCCCCCCCsCCCCCCCCCCCCCsCCCCCsCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC##__X^____s________________s_____s_______________________________	` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `s   "FHH
H)r   )r   r   r   r   )__doc__
__future__r   builtinsrB   _pytest.assertion.rewrite	assertionrewriter=   r   pytestfastapi.testclientr   fixturer   r)   rL   rO   rf   rl   rw   r|   r   r   r   ri   r   r   <module>r      s-    # " " " " "                ) ) ) ) ) )       ,# # #& & &	. 	. 	.& & &7 7 71 1 1
) 
) 
)% % %` ` ` ` `r   