
    (HJjq                     `   d dl Z d dlZd dlmZmZmZ d dlZd dlmZ	 d dl
Zd dlZd dlmZ d dlmZ d dlmZ  ej(                  dd       d d	lmZ d d
lmZ ej2                  d        Z ej2                  d      d        Zd Zd Zd Zd Zd Z d Z! G d d      Z"de#fdZ$d Z%de#defdZ&d Z'd Z(d Z)d  Z*d! Z+d" Z,d# Z-d$ Z.d% Z/d& Z0dAd'Z1dAd(Z2d) Z3d* Z4 G d+ d,      Z5d- Z6	 dBd.Z7d/ Z8d0 Z9d1 Z:d2 Z;d3 Z<d4 Z= G d5 d6      Z> G d7 d8      Z? G d9 d:      Z@d; ZAd< ZBd= ZCd> ZDd? ZEd@ ZFy)C    N)	AsyncMock	MagicMockpatch)RepositoryNotFoundError)read)write	multipartz-python-multipart is required for server tests)reason)
TestClient)appc                       t        t              S N)r   r        e/Users/ahmed/devFolder/claude-voice/.venv/lib/python3.12/site-packages/mlx_audio/tests/test_server.pyclientr      s    c?r   T)autousec               #   ~   K   t        dt              5 } t               | _        |  d d d        y # 1 sw Y   y xY ww)Nzmlx_audio.server.model_provider)new_callable)r   r   r   
load_model)mock_providers    r   mock_model_providerr      s7      
)	
	#,; 	
 
 
s   =1	=:=c                     t        g       |_        | j                  d      }|j                  dk(  sJ |j	                         dg dk(  sJ y )Nreturn_value
/v1/models   list)objectdata)r   get_available_modelsgetstatus_codejsonr   r   responses      r   test_list_models_emptyr'   %   sK    /8b/I,zz,'H3&&&==?<<<<r   c                    t        ddg      |_        | j                  d      }|j                  dk(  sJ |j	                         }|d   dk(  sJ t        |d         d	k(  sJ |d   d
   d   dk(  sJ |d   d   d   dk(  sJ y )Nmodel1model2r   r   r   r   r   r       r   id   )r   r!   r"   r#   r$   len)r   r   r&   r    s       r   test_list_models_with_datar/   -   s    /8)0, zz,'H3&&&==?D>V###tF|!!!<?4 H,,,<?4 H,,,r   c                     | j                  d      }|j                  dk(  sJ |j                         dddk(  sJ |j                  j	                  d       y )N /v1/models?model_name=test_modelr   successz#Model test_model added successfully)statusmessage
test_model)postr#   r$   r   assert_called_once_withr%   s      r   test_add_modelr8   ;   s^    {{=>H3&&&==?8    ""::<Hr   c                     t        d      |_        | j                  d      }|j                  dk(  sJ |j                  j	                  d       y )NTr   r1      r5   r   remove_modeldeleter#   r7   r%   s      r   test_remove_model_successr>   F   sG    '0d'C$}}?@H3&&&$$<<\Jr   c                     t        d      |_        | j                  d      }|j                  dk(  sJ |j	                         ddik(  sJ |j                  j                  d       y )NFr   z(/v1/models?model_name=non_existent_model  detailz$Model 'non_existent_model' not foundnon_existent_model)r   r<   r=   r#   r$   r7   r%   s      r   test_remove_model_not_foundrC   N   sb    '0e'D$}}GHH3&&&==?x)OPPPP$$<<=QRr   c                     t        d      |_        | j                  d      }|j                  dk(  sJ |j                  j	                  d       y )NTr   z)/v1/models?model_name="test_model_quotes"r:   test_model_quotesr;   r%   s      r   %test_remove_model_with_quotes_in_namerF   W   sH    '0d'C$}}HIH3&&&$$<<=PQr   c                       e Zd Zd Zy)MockAudioResultc                      || _         || _        y r   )audiosample_rate)self
audio_datarK   s      r   __init__zMockAudioResult.__init__`   s    
&r   N)__name__
__module____qualname__rN   r   r   r   rH   rH   _   s    'r   rH   
input_textc              +     K   d}d}d}t        j                  d|t        ||z        d      }dt        j                  dt         j                  z  |z  |z        z  }t        |j                  t         j                        |       y w)N>  r-     r   F      ?r+   )nplinspaceintsinpirH   astypefloat32)rR   kwargsrK   duration	frequencytrM   s          r    sync_mock_audio_stream_generatorrb   e   st     KHI
Ax[8%;!<eDArvva"%%i)3a788J
*++BJJ7
EEs   BBc                 
   t               }t        t              |_        t        |      |_        dddd}| j	                  d|      }|j
                  d	k(  sJ |j                  d
   j                         dk(  sJ |j                  d   j                         dk(  sJ |j                  j                  d       |j                  j                          |j                  j                  \  }}|d   |d   k(  sJ |j                  d      |d   k(  sJ 	 t        t        j                  |j                              \  }}|dkD  sJ t!        |      dkD  sJ y # t"        $ r"}	t%        j&                  d|	        Y d }	~	y d }	~	ww xY w)N)wrapsr   test_tts_modelzHello worldalloy)modelinputvoice/v1/audio/speechr$   r   content-type	audio/mp3zcontent-dispositionzattachment; filename=speech.mp3r   rh   ri   z(Failed to read or validate MP3 content: )r   rb   generater   r6   r#   headerslowerassert_any_callassert_called_once	call_argsr"   
audio_readioBytesIOcontentr.   	Exceptionpytestfail)
r   r   mock_tts_modelpayloadr&   argsr^   rM   rK   es
             r   test_tts_speechr   n   s   [N'.NON%.N%K"(=7SG{{-G{<H3&&&N+113{BBB./557,	-	- ""223CD..0!**44LD&7gg&&&&::g''"2222D",RZZ8H8H-I"J
KQ:""" D>qcBCCDs   AE 	F E==F
model_namereturnc                 |    t        j                  dd|        }t        j                  d|      }t        d|      S )zJConstruct a RepositoryNotFoundError shaped like the real HF client raises.GETz"https://huggingface.co/api/models/r@   )requestz&404 Client Error. Repository Not Found)r&   )httpxRequestResponser   )r   r   r&   s      r   _hf_repo_not_foundr      s<    mmE%G
|#TUG~~c73H"
0H r   c                 P   d }t        |      |_        dddd}| j                  d|      }|j                  d	k(  s+J d
|j                   dt	        |j
                         d       |j                  d   j                  d      sJ |j                         }d|v sJ d|d   v sJ y)ao  Regression: bad model ids must surface as 4xx, not 200 OK with empty body.

    Before the pre-flight fix, ``StreamingResponse`` committed the response
    headers (200 OK) before the worker thread tried to load the model, so any
    HuggingFace failure ended up as a clean zero-byte body. The fix loads the
    model synchronously before returning the response.
    c                     t        |       r   r   r   s    r   _raisezDtest_tts_speech_bad_model_returns_404_not_silent_200.<locals>._raise        ,,r   side_effectdoes-not-exist-on-hfhiwavrg   rh   response_formatrj   rk   r@    expected 404 for bad model, got  (body length=)rl   application/jsonrA   N)	r   r   r6   r#   r.   rw   ro   
startswithr$   r   r   r   r|   r&   bodys         r   4test_tts_speech_bad_model_returns_404_not_silent_200r      s    - &/6%B" ( G
 {{-G{<H3& 
*8+?+?*@ AH,,-.a	1& N+667IJJJ==?Dt!T(^333r   c                     d }t        |      |_        dddd}| j                  d|      }|j                  d	k(  sJ |j                  d
   j                  d      sJ |j                         }d|v sJ d|d   v sJ y)zCNon-HF load failures should surface as 500 with a JSON detail body.c                     t        d      )Nzcheckpoint is corrupted)RuntimeErrorr   s    r   r   z8test_tts_speech_load_failure_returns_500.<locals>._raise   s    455r   r   z
some-modelr   r   r   rj   rk   i  rl   r   rA   N)r   r   r6   r#   ro   r   r$   r   s         r   (test_tts_speech_load_failure_returns_500r      s    6 &/6%B"$tNG{{-G{<H3&&&N+667IJJJ==?Dt4>)))r   c                 d   d }t        |      |_        | j                  dddt               dfiddi	      }|j                  d
k(  s+J d|j                   dt        |j                         d       |j                  d   j                  d      sJ |j                         }d|v sJ d|d   v sJ y)zHSame silent-200 hazard applies to the default ndjson streaming response.c                     t        |       r   r   r   s    r   r   z=test_stt_transcriptions_bad_model_returns_404.<locals>._raise   r   r   r   /v1/audio/transcriptionsfiletest.mp3rm   rg   r   filesr    r@   r   r   r   rl   r   rA   N)
r   r   r6    _make_transcription_audio_bufferr#   r.   rw   ro   r   r$   )r   r   r   r&   r   s        r   -test_stt_transcriptions_bad_model_returns_404r      s    - &/6%B"{{"
$D$FTU-.  H 3& 
*8+?+?*@ AH,,-.a	1& N+667IJJJ==?Dt!T(^333r   c                    t               }t        ddi      |_        t        |      |_        d}d}d}t        j                  d|t        ||z        d      }d	t        j                  d
t        j                  z  |z  |z        j                  t        j                        z  }t        j                         }t        |||d       |j                  d       | j                  ddd|dfiddi      }	|	j                  dk(  sJ |	j!                         ddik(  sJ |j                  j#                  d       |j                  j%                          |j                  j&                  d   d   j)                  d      sJ y )NtextzThis is a test transcription.r   rT   r-   rU   r   FrV   r+   mp3formatr   r   r   rm   rg   test_stt_modelr   r   z/tmp/)r   rn   r   rW   rX   rY   rZ   r[   r\   r]   ru   rv   audio_writeseekr6   r#   r$   rq   rr   rs   r   )
r   r   mock_stt_modelrK   r_   r`   ra   rM   bufferr&   s
             r   test_stt_transcriptionsr      sc   [N'=>N &/N%K"KHI
Ax[8%;!<eDArvva"%%i)3a78??

KKJZZ\F
K>
KKN{{"
FK89'(  H 3&&&==?v'FGGGG""223CD..0"",,Q/2==gFFFr   c                  Z   d} d}d}t        j                  d|t        | |z        d      }dt        j                  dt         j                  z  |z  |z        j                  t         j                        z  }t        j                         }t        ||| d	       |j                  d       |S )
z>Build a tiny mp3 buffer suitable as the ``file`` upload field.rT   r-   rU   r   FrV   r+   r   r   )rW   rX   rY   rZ   r[   r\   r]   ru   rv   r   r   )rK   r_   r`   ra   rM   r   s         r   r   r     s    KHI
Ax[8%;!<eDArvva"%%i)3a78??

KKJZZ\F
K>
KKNMr   c                    t               }t        |      |_        t        |      |_        | j                  dddt	               dfid|d      S )	Nr   r   r   r   rm   r   )rg   r   r   )r   rn   r   r6   r   )r   r   mock_returnr   r   s        r   _post_transcriptionr     sX    [N'[AN%.N%K";;"
$D$FTU'OL   r   c                     t        | |ddid      }|j                  dk(  sJ |j                  d   j                  d      sJ |j                  dk(  sJ y)zAresponse_format=text returns plain text body with the transcript.r   Hello world.r   r   rl   z
text/plainN)r   r#   ro   r   r   r%   s      r   ,test_stt_transcriptions_response_format_textr     sd    "	 	H 3&&&N+66|DDD==N***r   c                     t        | |ddid      }|j                  dk(  sJ |j                  d   j                  d      sJ |j	                         ddik(  sJ y)	zDresponse_format=json returns the OpenAI minimal {"text": ...} shape.r   r   r$   r   r   rl   r   Nr   r#   ro   r   r$   r%   s      r   ,test_stt_transcriptions_response_format_jsonr   ,  sl    "	 	H 3&&&N+667IJJJ==?v~6666r   c                 4   ddddddddd	dd
dgd}t        | ||d      }|j                  dk(  sJ |j                  d   j                  d      sJ |j	                         }|d   dk(  sJ |d   dk(  sJ |d   D cg c]  }|d   	 c}dd	gk(  sJ yc c}w )zMresponse_format=verbose_json passes the full model payload through unchanged.r   enr   Hello        rV   )r,   r   startendr-   z world.      ?r   languagesegmentsverbose_jsonr   r   rl   r   r   r   r   Nr   )r   r   full_payloadr&   r   ss         r   4test_stt_transcriptions_response_format_verbose_jsonr   9  s     gC@i#cB
L #&	H 3&&&N+667IJJJ==?D<>)))
t####J/0/!AfI/0Wi4HHHH0s   >Bc                    t               }t        ddi      |_        t        |      |_        | j                  dddt	               dfidd	i
      }|j
                  dk(  sJ |j                  d   j                  d      sJ |j                  j                         D cg c]  }|j                         s| }}|sJ d       ddl}|D ]  }|j                  |        yc c}w )zMWithout response_format, the legacy application/x-ndjson stream is preserved.r   r   r   r   r   r   rm   rg   r   r   r   rl   zapplication/x-ndjsonz!expected at least one ndjson liner   N)r   rn   r   r6   r   r#   ro   r   r   
splitlinesstripr$   loads)r   r   r   r&   linelines_jsons          r   7test_stt_transcriptions_default_format_preserves_ndjsonr   Q  s     [N'fd^DN%.N%K"{{"
$D$FTU'(  H
 3&&&N+667MNNN&mm668I8dDJJLT8EI5555D 	 Js   C!5C!c                     t        j                  d| t        || z        d      }t        j                  dt         j                  z  dz  |z        dz  j                  t         j                        }|S )z?Create int16 audio that reliably triggers VAD speech detection.r   F)endpointr+   i,  ia  )rW   rX   rY   rZ   r[   r\   int16)
duration_srK   ra   rJ   s       r   make_speech_audior   n  sX    
Az3{Z'?#@5QAVVAIOa'(5088BELr   c                 d    t        j                  t        || z        t         j                        S )zFCreate int16 audio of near-zero values that VAD classifies as silence.dtype)rW   zerosrY   r   )r   rK   s     r   make_silence_audior   v  s!    88Cj01BBr   c                      dddd fd
}|S )zNBuild a mock generate function with a ``stream`` parameter that yields deltas.FNstreamr   verbosec                f    |rt              S t        dj                  d D              d d       S )N c              3   2   K   | ]  }t        |        y wr   )str).0ds     r   	<genexpr>z=_make_streaming_generate.<locals>.generate.<locals>.<genexpr>  s     0AQs   r   r   r   )iterr   join)rJ   r   r   r   r^   deltass        r   rn   z*_make_streaming_generate.<locals>.generate~  s3    <0004$
 	
r   r   )r   rn   s   ` r   _make_streaming_generater   {  s     #($ 
 Or   c                      ddd fd
}|S )zEBuild a mock generate without a ``stream`` parameter (legacy models).NF)r   r   c                     t        d d       S )Nr   )r   )rJ   r   r   r^   r   s       r   rn   z._make_non_streaming_generate.<locals>.generate  s    dTDAAr   r   )r   rn   s   ` r   _make_non_streaming_generater     s     %)% B Or   c                       e Zd ZdZd Zy)	MockChunkz3Structured streaming result with a .text attribute.c                     || _         y r   )r   )rL   r   s     r   rN   zMockChunk.__init__  s	    	r   N)rO   rP   rQ   __doc__rN   r   r   r   r   r     s
    =r   r   c                 X     g t        j                          fd       }|_        |S )zHWrap fn to track calls while preserving signature for inspect.signature.c                  :    j                  | |f        | i |S r   )append)r}   r^   callsfns     r   wrapperz_trackable.<locals>.wrapper  s#    dF^$4"6""r   )	functoolsrd   call_args_list)r   r   r   s   ` @r   
_trackabler     s2    E__R# # #GNr   c                 \   t               }t        |      |_        t        |      |_        g }ddd}|r|j	                  |       | j                  d      5 }|j                  |       |j                         d   dk(  sJ t        d      }d	}	t        d
t        |      |	      D ]'  }
|j                  ||
|
|	z    j                                ) |j                  ddi       	 	 |j                  |j                                !# t        $ r Y nw xY w	 ddd       ||fS # 1 sw Y   ||fS xY w)zCConnect WS, send config + 6s speech, stop, and return all messages.r   
test-modelrT   )rg   rK   z!/v1/audio/transcriptions/realtimer3   readyg      @i  r   actionstopN)r   r   rn   r   updatewebsocket_connect	send_jsonreceive_jsonr   ranger.   
send_bytestobytesr   rx   )r   r   generate_fnconfig_extrar   messagesconfigwsspeech
chunk_sizeis              r   _ws_send_audio_and_collectr    s4    [N(5N%.N%K"H#E:Fl#		!	!"E	F"
V *g555 #3'
q#f+z2AMM&Q^4<<>? 3 	h'( 12   ! 
G$ ^##% 
G$ ^##s1   BD#DD	DDDDD+c                    t        g d      }t        | ||      \  }}|D cg c]  }|j                  d      dk(  s| }}|D cg c]  }|j                  d      dk(  s| }}t        |      dk\  s
J d|        t        |      dk\  s
J d|        |D ]  }d|v sJ d|vrJ  |d	   }	d|	v sJ d
|	d   v rd|	d   v sJ d|	v sJ yc c}w c c}w )uC   Streaming model yields string deltas → delta + complete messages.r    world!typedeltacompleter-   Expected delta messages, got:  Expected complete message, got: r   r   world
is_partialNr   r  r"   r.   )
r   r   gen_fnr  _mr   	completesr   r  s
             r   -test_realtime_ws_streaming_model_sends_deltasr'    s   %&>?F,V5H&QKHa ">AQUU6]g%=aF>$DHqf(CHIDv;!H=hZHHy>QM"B8* MM !||Q 
 }HXhv&&7hv6F+FFF8### ?Ds   CCC Cc                 >   t        d      }t        | ||      \  }}|D cg c]  }d|v sd|vs| }}t        |      dk\  s
J d|        |D cg c]  }|j                  dd      r| }}t        |      dk\  s
J d|        |d	   d   dk(  sJ y
c c}w c c}w )u?   Non-streaming model → legacy format messages (no type field).zTranscribed textr   r  r-   #Expected legacy text message, got: r!  Tz)Expected final non-partial message, got: r  N)r   r  r.   r"   )r   r   r#  r  r$  r%  	text_msgsfinals           r   -test_realtime_ws_non_streaming_model_fallbackr,    s    )*<=F,V5H&QKHa %HHq!aHIHy>QP"EhZ PP!C	1|T)BQ	ECu:?RGzRR?9V 2222 I Ds   	BBBB)Bc                    t        d      t        d      g}t        |      }t        | ||      \  }}|D cg c]  }|j                  d      dk(  s| }}|D cg c]  }|j                  d      dk(  s| }}t	        |      dk\  s
J d|        t	        |      dk\  s
J d|        |D 	cg c]  }	|	d   	 }
}	d	j                  |
      }d|v sJ y
c c}w c c}w c c}	w )uG   Streaming model yields objects with .text attribute → delta messages.r   r  r  r  r  r-   r  r  r   N)r   r   r  r"   r.   r   )r   r   chunksr#  r  r$  r%  r   r&  r   delta_textscombineds               r   ,test_realtime_ws_streaming_structured_chunksr1    s     )H"56F%f-F,V5H&QKHa!>AQUU6]g%=aF>$DHqf(CHIDv;!H=hZHHy>QM"B8* MM (..v!1W:vK.ww{#Hh ?D /s   CCC4C.Cc                    t        dg      }t        | ||      \  }}|j                  }t        |j                        dkD  sJ d       |j                  d   d   d   }t        |t        j                        sJ dt        |              y)z2Streaming models receive mx.array, not file paths.testr   zgenerate was never calledzExpected mx.array, got N)	r   r  rn   r.   r   
isinstancemxarrayr  )r   r   r#  r$  r   tracked	first_args          r   test_realtime_ws_mx_array_passr9    s    %vh/F26;NPVWA~ %%Gw%%&*G,GG*&&q)!,	I i*W.Ed9oEV,WW*r   c                     ddddd}t        | ||      \  }}|D cg c]  }|j                  d      dk(  s| }}t        |      dk\  sJ |d   d	   d
k(  sJ yc c}w )zMRegression: models like Parakeet that cast to bfloat16 must receive mx.array.FNr   c                x    |r+| j                  t        j                        }t        dg      S t	        dd d       S )Nokr   )r\   r5  bfloat16r   r   )rJ   r   r   r   r^   r$  s         r   r#  z@test_realtime_ws_mx_array_supports_bfloat16_cast.<locals>.gen_fn  s2    R[[)A<dTDAAr   r  r  r-   r   r   r<  )r  r"   r.   )r   r   r#  r  r$  r%  r&  s          r   0test_realtime_ws_mx_array_supports_bfloat16_castr>    sx     !&e B -V5H&QKHa$DHqf(CHIDy>QQ<4''' Es
   AAc                    t        g d      }t        | ||ddi      \  }}|D cg c]  }|j                  d      dk(  s| }}|D cg c]  }|j                  d      dk(  s| }}t        |      dk(  s
J d	|        t        |      dk(  s
J d
|        |D cg c]  }d|v sd|vs| }}t        |      dk\  s
J d|        yc c}w c c}w c c}w )zPStreaming-capable model with streaming=false config falls back to legacy format.r  	streamingF)r  r  r  r  r   z9Expected no delta messages when streaming disabled, got: z<Expected no complete messages when streaming disabled, got: r   r-   r)  Nr"  )	r   r   r#  r  r$  r%  r   r&  r*  s	            r   ,test_realtime_ws_streaming_disabled_fallbackrA    s   %&>?F,#V;:NKHa
 ">AQUU6]g%=aF>$DHqf(CHIDFqL	B6(KL 	I!R	Ei[QR %HHq!aHIHy>QP"EhZ PP ?D Is)   C C
C$C	C(C-Cc                   >    e Zd ZdZdZd Zd Zd Zd	dZe	d        Z
y)
_FakeStreamingSessionzDMinimal streaming session: emits one delta once the audio is closed.rT   c                      d| _         d| _        y )NF_closed_emittedrL   s    r   rN   z_FakeStreamingSession.__init__;  s    r   c                      y r   r   )rL   sampless     r   feedz_FakeStreamingSession.feed?  s    r   c                     d| _         y )NT)rF  rH  s    r   closez_FakeStreamingSession.closeB  s	    r   c                 J    | j                   r| j                  s
d| _        dgS g S )NTbonjourrE  )rL   max_decode_tokenss     r   stepz_FakeStreamingSession.stepE  s#    << DM;	r   c                 6    | j                   xr | j                  S r   rE  rH  s    r   donez_FakeStreamingSession.doneK  s    ||--r   N)   )rO   rP   rQ   r   input_sample_raterN   rK  rM  rQ  propertyrS  r   r   r   rC  rC  6  s3    N . .r   rC  c                       e Zd ZdddZy)_FakeStreamingModelr   )temperaturec                    t               S r   )rC  )rL   rY  r^   s      r   create_streaming_sessionz,_FakeStreamingModel.create_streaming_sessionQ  s    $&&r   N)rO   rP   rQ   r[  r   r   r   rX  rX  P  s
    69 'r   rX  c                   &    e Zd ZdZd ZddZddZy)_FakeVadModelzDReturns a scripted speech probability per consumed 512-sample frame.c                     || _         y r   )_probs)rL   probss     r   rN   z_FakeVadModel.__init__X  s	    r   c                 
    ddiS )Nr  r   r   )rL   rK   s     r   initial_statez_FakeVadModel.initial_state[  s    Qxr   c                     |d   }|t        | j                        k  r| j                  |   nd}t        j                  |gg      d|dz   ifS )Nr  r   r-   )r.   r_  r5  r6  )rL   chunkstaterK   r  ps         r   rK  z_FakeVadModel.feed^  sI    #J#dkk"22DKKNxx!a!e,,r   NrT   )rO   rP   rQ   r   rN   rb  rK  r   r   r   r]  r]  U  s    N-r   r]  c           
         ddl }ddlm} t        t	                     |_        dgdz  dgdz  z   |j                  d	fd
       | j                  d      5 }|j                         d   dk(  sJ |j                  ddddddddddiid       |j                         }|d   dk(  sJ |d   d   d   d   d   dk(  sJ t        j                  |dz  t        j                        j                         }|j                  d|j                  |      j                         d       g }	 |j                         }	|j!                  |	       |	d   d k(  rn+	 ddd       D 
cg c]  }
|
d   	 }}
d!|v sJ d"|v sJ d#|v sJ |j#                  d!      |j#                  d"      k  sJ |d$   d%   d&k(  sJ y# 1 sw Y   `xY wc c}
w )'u   With server_vad the server detects end-of-speech, emits
    speech_started/stopped, auto-commits and returns the transcript — the
    client never sends input_audio_buffer.commit.r   N)VAD_FRAME_SIZEr   g?rT  r   <   z)mlx_audio.server._load_realtime_vad_modelc                     t              S r   )r]  )namer`  s    r   <lambda>z:test_realtime_ws_server_vad_auto_commits.<locals>.<lambda>q  s
    ]5)r   /v1/realtime?model=fake-sttr  session.createdsession.updaterJ   rh   z	audio/pcmrT   )r  rate
server_vad   )r  silence_duration_ms)r   turn_detectionr  sessionzsession.updatedrw  ru  (   r   zinput_audio_buffer.append)r  rJ   z5conversation.item.input_audio_transcription.completedz!input_audio_buffer.speech_startedz!input_audio_buffer.speech_stoppedzinput_audio_buffer.committedr  
transcriptrO  )base64mlx_audio.realtime_vadri  r   rX  r   setattrr  r	  r  rW   r   r   r  	b64encodedecoder   index)r   r   monkeypatchrz  ri  r  updatedpcmeventsevtr~   typesr`  s               @r   (test_realtime_ws_server_vad_auto_commitsr  d  s+    5%.<O<Q%R"EAI
"E3)
 
	!	!"?	@B (,====
(/:E&J(47:/"
	
  //#v"3333Iw'01AB6J	
 hh~*"((;CCE
3))#.557	
 //#CMM#6{VW	  O 
AR !''1QvYE'.%777.%777)U222;;:;ekk+?    ":l#y000c 
A	@R (s   C?F7$G7G c           	      8   t        t                     |_        | j                  d      5 }|j	                         d   dk(  sJ |j                  dddddd	iiiid
       |j	                         }|d   dk(  sJ d	|d   d   v sJ 	 ddd       y# 1 sw Y   yxY w)uw   semantic_vad is not implemented yet → the server replies with an error
    rather than silently ignoring the request.r   rn  r  ro  rp  rJ   rh   ru  semantic_vadrv  errorr4   N)r   rX  r   r  r	  r  )r   r   r  r  s       r   %test_realtime_ws_rejects_semantic_vadr    s     &/<O<Q%R"		!	!"?	@B (,====
(g(86>:R'ST	
 oo6{g%%%Wi!8888 
A	@	@s   ABBc                  ^    ddl m}   | d      }|j                  du sJ |j                  J y)zRTranscriptionRequest defaults word_timestamps=False, timestamp_granularities=None.r   TranscriptionRequestr  )rg   FNmlx_audio.serverr  word_timestampstimestamp_granularitiesr  reqs     r   3test_transcription_request_word_timestamps_defaultsr    s5    5
\
2C%'''&&...r   c                  h    ddl m}   | ddd      }|j                  du sJ |j                  dk(  sJ y)z2TranscriptionRequest accepts word_timestamps=True.r   r  r  Tword)rg   r  r  Nr  r  s     r   3test_transcription_request_word_timestamps_acceptedr    s@    5
D&C $&&&&&&000r   c                     i fd}t               }||_        t        |      |_        | j                  dddt	               dfiddd	d
      }|j
                  dk(  sJ j                  d      du sJ y)zword_timestamps=true form field reaches stt_model.generate() as a kwarg.

    The STTExecutionAdapter allowlist (_STT_EXTRA_KWARGS) must pass word_timestamps
    through even when it isn't declared in the model's generate() signature.
    c                 2    j                  |       dg ddS )Nhellor   r   )r  )pathr^   captured_kwargss     r   mock_generatezBtest_stt_word_timestamps_passed_to_generate.<locals>.mock_generate  s    v&RTBBr   r   r   r   r   rm   r   r   truerg   r   r  r   r   r  TN)r   rn   r   r6   r   r#   r"   )r   r   r  r   r&   r  s        @r   +test_stt_word_timestamps_passed_to_generater    s     OC [N+N%.N%K"{{"
$D$FTU%-%
  H 3&&&01T999r   c                    ddddddddddd	d
dddd	gdgd}t               }t        |      |_        t        |      |_        | j                  dddt	               dfidddd      }|j
                  dk(  sJ |j                         }|d   dk(  sJ |j                  dg       }t        |      dk(  sJ |d   j                  dg       }t        |      dk(  sJ |d   d   dk(  sJ |d   d   d
k(  sJ y) zPverbose_json response includes words[] from the model when word_timestamps=True.r   r   r   r   r   r   rV   gGz?)r  r   r   probabilityzworld.g\(\?)r,   r   r   r   wordsr   r   r   r   r   rm   r   r   r  r  r   r   r   r   r-   r  r+   r  N)	r   rn   r   r6   r   r#   r$   r"   r.   )r   r   r   r   r&   r   r   r  s           r   7test_stt_word_timestamps_verbose_json_words_passthroughr    sI   
  &$s3tT%CPTU	
L" [N'\BN%.N%K"{{"
$D$FTU%-%
  H 3&&&==?D<>)))xx
B'Hx=AQKOOGR(Eu:??8Fw&&&8Fx'''r   rg  r   )Gr   ru   unittest.mockr   r   r   r   mlx.corecorer5  numpyrW   ry   huggingface_hub.errorsr   mlx_audio.audio_ior   rt   r   r   importorskipfastapi.testclientr   r  r   fixturer   r   r'   r/   r8   r>   rC   rF   rH   r   rb   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r'  r,  r1  r9  r>  rA  rC  rX  r]  r  r  r  r  r  r  r   r   r   <module>r     s    	 5 5     : 1 3   K(W X )      =-IKSR' 'F FD>3 +B 4>*$40GN
	
+
7I0:C
 
 <@$B$03$X( Q2. .4' '
- -A1H92/1:<+(r   